Stucturing Data に関するFirebaseのドキュメントを読みました。データストレージは安価ですが、ユーザーの時間はそうではありません。 get操作を最適化し、複数の場所に書き込む必要があります。
したがって、listノードとlist-indexノードを格納する可能性があります、少なくともリスト名で、2つの間にいくつかの重複データがあります。
私はES6を使用しており、JavaScriptアプリで非同期フローを処理することを約束しています。主に、最初のデータプッシュ後にFirebaseからrefキーをフェッチします。
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);
});
アプリがクライアントでのみ実行されることを確認しながら、データがすべての場所で同期されていることを確認するにはどうすればよいですか?
健全性チェックのために、promiseにsetTimeoutを設定し、解決する前にブラウザーをシャットダウンしました。実際、データベースの整合性が失われ、対応するリストなしで追加のインデックスが保存されました。
何かアドバイス?
素晴らしい質問です。私はこれに対する3つのアプローチを知っています。それを以下にリストします。
これについては少し異なる例を取り上げます。これは主に、説明でより具体的な用語を使用できるためです。
メッセージとユーザーの2つのエンティティを格納するチャットアプリケーションがあるとします。メッセージを表示する画面には、ユーザーの名前も表示されます。そのため、読み取り回数を最小限に抑えるために、チャットメッセージごとにユーザーの名前も保存します。
_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"
_
そのため、ユーザーのプロファイルのプライマリコピーをusers
ノードに保存します。メッセージには、ユーザーを検索できるようにuid
(so:209103など:3648524)を格納します。ただし、alsoはメッセージにユーザーの名前を格納するため、リストを表示するときにユーザーごとにこれを検索する必要はありません。メッセージの。
では、チャットサービスのプロフィールページに移動して、名前を「FrankvanPuffelen」から「puf」に変更するとどうなりますか。
トランザクション更新の実行は、おそらく最初はほとんどの開発者の頭に浮かぶものです。メッセージのusername
は、対応するプロファイルのname
と常に一致する必要があります。
マルチパス書き込みを使用(20150925に追加)
Firebase 2.3(JavaScriptの場合)および2.4(AndroidおよびiOSの場合))以降、単一のマルチパス更新を使用することで、アトミック更新を非常に簡単に実現できます。
_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);
});
}
_
これにより、プロファイルと各メッセージでユーザーの名前を更新する単一の更新コマンドがFirebaseに送信されます。
以前のアトミックアプローチ
したがって、ユーザーがプロファイルのname
を変更すると、次のようになります。
_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 */);
_
かなり関与していて、賢明な読者は、私がメッセージの処理をだましていることに気付くでしょう。最初のチートは、リスナーに対してoff
を呼び出さないことですが、トランザクションも使用しません。
このタイプの操作をクライアントから安全に実行するには、次のものが必要です。
so:209103
_によるメッセージのすべてのusername
フィールドをnull
(魔法の値)に変更しますso:209103
_のname
を「puf」に変更しますusername
を_so:209103
_、つまりnull
からpuf
に変更します。and
が必要ですが、Firebaseクエリではサポートされていません。したがって、クエリを実行できる追加のプロパティ_uid_plus_name
_(値は_so:209103_puf
_)になります。このタイプのアプローチは私の頭を傷つけます。そして通常、それは私が何か間違ったことをしていることを意味します。しかし、それが正しいアプローチであったとしても、頭が痛いので、コーディングの間違いを犯す可能性がはるかに高くなります。だから私はもっと簡単な解決策を探すことを好みます。
更新(20150925):Firebaseは、複数のパスへのアトミック書き込みを許可する機能をリリースしました。これは以下のアプローチと同様に機能しますが、コマンドは1つです。これがどのように機能するかを読むには、上記の更新されたセクションを参照してください。
2番目のアプローチは、ユーザーアクション(「名前を「puf」に変更したい」)をそのアクションの意味(「プロファイルso:209103および_user = so:209103
_)。
サーバーで実行するスクリプトで名前の変更を処理します。主な方法は次のようになります。
_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('value'
_を使用するなど、いくつかのショートカットを使用します(これは、Firebaseで最適なパフォーマンスを実現するには一般的に悪い考えです)。ただし、全体的なアプローチは単純ですが、すべてのデータが同時に完全に更新されるわけではありません。ただし、最終的には、メッセージはすべて新しい値に一致するように更新されます。
3番目のアプローチは、すべての中で最も単純です。多くの場合、複製されたデータを実際に更新する必要はまったくありません。ここで使用した例では、各メッセージに、その時点で使用した名前が記録されていると言えます。私は今まで名前を変更していなかったので、古いメッセージにそのときに使用した名前が表示されているのは理にかなっています。これは、二次データが本質的にトランザクションである多くの場合に当てはまります。もちろんどこにでも当てはまるわけではありませんが、「気にしない」というのが最も簡単なアプローチです。
上記はこの問題を解決する方法の大まかな説明であり、完全ではありませんが、重複データをファンアウトする必要があるたびに、これらの基本的なアプローチの1つに戻ることがわかります。
Franksのすばらしい回答に追加するために、一連の Firebase Cloud Functions を使用して結果整合性アプローチを実装しました。関数は、プライマリ値(ユーザー名など)が変更されるたびにトリガーされ、非正規化されたフィールドに変更を伝播します。
トランザクションほど高速ではありませんが、多くの場合、高速である必要はありません。