如何在Firebase中编写非规范化数据 [英] How to write denormalized data in Firebase

查看:124
本文介绍了如何在Firebase中编写非规范化数据的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已阅读构建数据上的Firebase文档。数据存储便宜,但用户的时间不是。我们应该优化获取操作,并写在多个地方。那么我可能会存储列表节点和列表索引节点,两个,至少是列表名称。

我使用ES6和承诺在我的JavaScript应用程序来处理异步流程,主要是从firebase中获取ref key之后第一个数据推送。

  let addIndexPromise = new Promise((resolve,reject)=> {
let newRef (newItem);
resolve(newRef.key()); //为了简洁,忽略reject()
});
addIndexPromise.then(key => {
ref.child('list')。child(key).set(newItem);
});

如何确保数据在所有地方保持同步,知道我的应用程序只运行在客户端上吗?

为了完整性检查,我在我的承诺中设置了setTimeout,并在解决之前关闭浏览器,实际上我的数据库不再与额外的索引保存,没有相应的列表



任何建议?

解决方案

好的问题。我知道三种方法,我将在下面列出。



我将稍微改变一些例子,主要是因为它允许我使用更具体的在说明的条款。



说我们有一个聊天应用程序,在那里我们存储两个实体:消息和用户。在显示消息的屏幕中,我们也显示用户的名字。因此,为了尽量减少读取次数,我们也存储了每个聊天消息的用户名。

  users 
所以:209103
名称:Frank van Puffelen
位置:旧金山,加州
questionCount:12
so:3648524
名称:legolandbridge
位置:伦敦,布拉格,巴塞罗那
questionCount:4
消息
-Jabhsay3487
消息:如何在Firebase中编写非规范化数据
用户:so:3648524
用户名:legolandbridge
-Jabhsay3591
留言:好问题。
user:so:209103
用户名:Frank van Puffelen
-Jabhsay3595
消息:我知道三种方法,我将在下面列出。
user:so:209103
username:Frank van Puffelen

我们将用户配置文件的主要副本存储在 users 节点中。在消息中我们存储 uid (so:209103等等:3648524),以便我们可以查找用户。但是,我们也 存储用户的名字,所以当我们想要显示一个消息列表时,我们不必为每个用户查看。



所以,现在当我进入聊天服务的个人资料页面并将我的名字从Frank van Puffelen改为puf时,会发生什么情况。



执行事务更新是最初大多数开发人员可能想到的事情。我们总是希望消息中的用户名与相应配置文件中的名称匹配。

使用多路径写入(在20150925上添加)



JavaScript)和2.4(对于Android和iOS),您可以通过使用单个多路径更新来轻松实现原子更新:

  function renameUser(ref,uid,name){
var updates = {}; //所有要更新的路径及其新值
updates ['users /'+ uid +'/ name'] = name;
var query = ref.child('messages')。orderByChild('user')。equalTo(uid);
query.once('value',function(snapshot){
snapshot.forEach(function(messageSnapshot){
updates ['messages /'+ messageSnapshot.key()+'/ username '] = name;
})
ref.update(updates);
});



$ b $ p
$ b

这会将一个更新命令发送到Firebase,以更新其用户名配置文件和每个消息中。

以前的原子方法

所以当用户在其个人资料中更改名称

  var ref = new Firebase 'https://mychat.firebaseio.com/'); 
var uid =so:209103;
var nameInProfileRef = ref.child('users')。child(uid).child('name');
nameInProfileRef.transaction函数(currentName){
返回puf;
},函数(error,committed,snapshot){
if(error){
console.log('Transaction failed abnormally!',error);
} else if(!committed){
console.log('Transaction by aborted by the code。');
} else {
console.log('配置文件中名称已更新,现在在消息中更新它');
var query = ref.child('messages')。orderByChild('user')。equalTo(uid );
query.on('child_added',function(messageSnapshot){
messageSnapshot.ref()。update({username:puf});
});

console.log(Wilma's data:,snapshot.val());
},false / *不在本地应用更改* /);

非常有趣,聪明的读者会注意到我在处理邮件时会作弊。首先作弊的是我从来没有为监听器调用 off ,但是我也不使用事务。

如果我们想要安全地从客户端执行这种操作,我们需要:


  1. 确保两个地方的名字的安全规则比赛。但是规则需要有足够的灵活性让他们在改变名字时暂时改变。所以这变成了一个非常痛苦的两阶段提交方案。


    1. 将所有用户名字段更改为 so:209103 null (一些魔术值)
    2. 更改名称用户 so:209103 改为'puf'

    3. 更改用户名 so的每个消息中:209103 null puf
    4. 该查询需要两个条件的,Firebase查询不支持这两个条件。所以我们最终会得到一个额外的属性 uid_plus_name (值 so:209103_puf ),我们可以查询。


    5. 客户端代码可以事务处理所有这些转换。

    这种方法让我头痛。通常这意味着我做错了什么。但即使这是正确的方法,头痛,我更可能犯错误编码。所以我更愿意寻找一个更简单的解决方案。
    $ b $ h1最终一致性
    $ b $ p 更新(20150925) / strong>:Firebase发布了一项功能,允许将原子写入多个路径。这与下面的方法类似,但只用一个命令。

    第二种方法取决于将用户操作(我想将我的名字更改为'puf')从(我们需要更新配置文件中的名称:209103以及每个包含 user = so:209103 的消息)。



    我会在一个服务器上运行的脚本中处理重命名,主要的方法是这样的:

    $ $ $ ($ name);
    var.child('users')。child(uid).update({name:name});
    var orderByChild('user')。equalTo(uid);
    query.once('value',function(snapshot){
    snapshot.forEach(function( messageSnapshot){
    messageSnapshot.update({username:name});
    })
    });
    }

我再次在这里使用一些快捷方式,比如使用一次('value'一个坏主意以获得Firebase的最佳性能)。但是总体而言,这种方法更简单,代价是不能同时更新所有数据。但最终消息将全部更新,以匹配新的价值。



不关心



第三种方法是最简单的是:在许多情况下,你根本不需要更新重复的数据。在我们这里使用的例子中,可以说每条消息都记录了当时使用的名称。直到现在我还没有改变我的名字,所以旧信息显示我当时使用的名字是有道理的。这在二手数据本质上是交易性的许多情况下适用。它不适用于任何地方,但适用于不关心是最简单的方法。
$ b

总结



虽然以上只是对如何解决这个问题的广泛描述,而且它们绝对不是完整的,但我发现每次需要重复数据时,都会回到这些基本方法之一。 p>

I've read the Firebase docs on Stucturing Data. Data storage is cheap, but the user's time is not. We should optimize for get operations, and write in multiple places.

So then I might store a list node and a list-index node, with some duplicated data between the two, at very least the list name.

I'm using ES6 and promises in my javascript app to handle the async flow, mainly of fetching a ref key from firebase after the first data push.

let addIndexPromise = new Promise( (resolve, reject) => {
    let newRef = ref.child('list-index').push(newItem);
    resolve( newRef.key()); // ignore reject() for brevity
});
addIndexPromise.then( key => {
   ref.child('list').child(key).set(newItem);
 });

How do I make sure the data stays in sync in all places, knowing my app runs only on the client?

For sanity check, I set a setTimeout in my promise and shut my browser before it resolved, and indeed my database was no longer consistent, with an extra index saved without a corresponding list.

Any advice?

解决方案

Great question. I know of three approaches to this, which I'll list below.

I'll take a slightly different example for this, mostly because it allows me to use more concrete terms in the explanation.

Say we have a chat application, where we store two entities: messages and users. In the screen where we show the messages, we also show the name of the user. So to minimize the number of reads, we store the name of the user with each chat message too.

users
  so:209103
    name: "Frank van Puffelen"
    location: "San Francisco, CA"
    questionCount: 12
  so:3648524
    name: "legolandbridge"
    location: "London, Prague, Barcelona"
    questionCount: 4
messages
  -Jabhsay3487
    message: "How to write denormalized data in Firebase"
    user: so:3648524
    username: "legolandbridge"
  -Jabhsay3591
    message: "Great question."
    user: so:209103
    username: "Frank van Puffelen"
  -Jabhsay3595
    message: "I know of three approaches, which I'll list below."
    user: so:209103
    username: "Frank van Puffelen"

So we store the primary copy of the user's profile in the users node. In the message we store the uid (so:209103 and so:3648524) so that we can look up the user. But we also store the user's name in the messages, so that we don't have to look this up for each user when we want to display a list of messages.

So now what happens when I go to the Profile page on the chat service and change my name from "Frank van Puffelen" to just "puf".

Transactional update

Performing a transactional update is the one that probably pops to mind of most developers initially. We always want the username in messages to match the name in the corresponding profile.

Using multipath writes (added on 20150925)

Since Firebase 2.3 (for JavaScript) and 2.4 (for Android and iOS), you can achieve atomic updates quite easily by using a single multi-path update:

function renameUser(ref, uid, name) {
  var updates = {}; // all paths to be updated and their new values
  updates['users/'+uid+'/name'] = name;
  var query = ref.child('messages').orderByChild('user').equalTo(uid);
  query.once('value', function(snapshot) {
    snapshot.forEach(function(messageSnapshot) {
      updates['messages/'+messageSnapshot.key()+'/username'] = name;
    })
    ref.update(updates);
  });
}

This will send a single update command to Firebase that updates the user's name in their profile and in each message.

Previous atomic approach

So when the user change's the name in their profile:

var ref = new Firebase('https://mychat.firebaseio.com/');
var uid = "so:209103";
var nameInProfileRef = ref.child('users').child(uid).child('name');
nameInProfileRef.transaction(function(currentName) {
  return "puf";
}, function(error, committed, snapshot) {
  if (error) { 
    console.log('Transaction failed abnormally!', error);
  } else if (!committed) {
    console.log('Transaction aborted by our code.');
  } else {
    console.log('Name updated in profile, now update it in the messages');
    var query = ref.child('messages').orderByChild('user').equalTo(uid);
    query.on('child_added', function(messageSnapshot) {
      messageSnapshot.ref().update({ username: "puf" });
    });
  }
  console.log("Wilma's data: ", snapshot.val());
}, false /* don't apply the change locally */);

Pretty involved and the astute reader will notice that I cheat in the handling of the messages. First cheat is that I never call off for the listener, but I also don't use a transaction.

If we want to securely do this type of operation from the client, we'd need:

  1. security rules that ensure the names in both places match. But the rules need to allow enough flexibility for them to temporarily be different while we're changing the name. So this turns into a pretty painful two-phase commit scheme.

    1. change all username fields for messages by so:209103 to null (some magic value)
    2. change the name of user so:209103 to 'puf'
    3. change the username in every message by so:209103 that is null to puf.
    4. that query requires an and of two conditions, which Firebase queries don't support. So we'll end up with an extra property uid_plus_name (with value so:209103_puf) that we can query on.

  2. client-side code that handles all these transitions transactionally.

This type of approach makes my head hurt. And usually that means that I'm doing something wrong. But even if it's the right approach, with a head that hurts I'm way more likely to make coding mistakes. So I prefer to look for a simpler solution.

Eventual consistency

Update (20150925): Firebase released a feature to allow atomic writes to multiple paths. This works similar to approach below, but with a single command. See the updated section above to read how this works.

The second approach depends on splitting the user action ("I want to change my name to 'puf'") from the implications of that action ("We need to update the name in profile so:209103 and in every message that has user = so:209103).

I'd handle the rename in a script that we run on a server. The main method would be something like this:

function renameUser(ref, uid, name) {
  ref.child('users').child(uid).update({ name: name });
  var query = ref.child('messages').orderByChild('user').equalTo(uid);
  query.once('value', function(snapshot) {
    snapshot.forEach(function(messageSnapshot) {
      messageSnapshot.update({ username: name });
    })
  });
}

Once again I take a few shortcuts here, such as using once('value' (which is in general a bad idea for optimal performance with Firebase). But overall the approach is simpler, at the cost of not having all data completely updated at the same time. But eventually the messages will all be updated to match the new value.

Not caring

The third approach is the simplest of all: in many cases you don't really have to update the duplicated data at all. In the example we've used here, you could say that each message recorded the name as I used it at that time. I didn't change my name until just now, so it makes sense that older messages show the name I used at that time. This applies in many cases where the secondary data is transactional in nature. It doesn't apply everywhere of course, but where it applies "not caring" is the simplest approach of all.

Summary

While the above are just broad descriptions of how you could solve this problem and they are definitely not complete, I find that each time I need to fan out duplicate data it comes back to one of these basic approaches.

这篇关于如何在Firebase中编写非规范化数据的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆