לאפליקציות רבות בזמן אמת יש מסמכים הפועלים כמונים. לדוגמה, תוכל לספור 'אהבתי' לפוסט, או 'מועדפים' של פריט ספציפי.
ב-Cloud Firestore, לא ניתן לעדכן מסמך אחד בתעריף בלתי מוגבל. אם יש לך מונה המבוסס על מסמך בודד ותכופות מספיק תכופות אליו, בסופו של דבר תראה ויכוח על העדכונים למסמך. ראה עדכונים למסמך בודד .
פתרון: מונים מבוזרים
כדי לתמוך בעדכוני מונה תכופים יותר, צור מונה מבוזר. כל מונה הוא מסמך עם תת-אוסף של "רסיסים", וערך המונה הוא סכום הערך של הרסיסים.
תפוקת הכתיבה עולה באופן ליניארי עם מספר הרסיסים, כך שמונה מבוזר עם 10 רסיסים יכול להתמודד עם פי 10 כתיבה כמו מונה מסורתי.
אינטרנט
// counters/${ID}
{
"num_shards": NUM_SHARDS,
"shards": [subcollection]
}
// counters/${ID}/shards/${NUM}
{
"count": 123
}
מָהִיר
// counters/${ID} struct Counter { let numShards: Int init(numShards: Int) { self.numShards = numShards } } // counters/${ID}/shards/${NUM} struct Shard { let count: Int init(count: Int) { self.count = count } }
Objective-C
// counters/${ID} @interface FIRCounter : NSObject @property (nonatomic, readonly) NSInteger shardCount; @end @implementation FIRCounter - (instancetype)initWithShardCount:(NSInteger)shardCount { self = [super init]; if (self != nil) { _shardCount = shardCount; } return self; } @end // counters/${ID}/shards/${NUM} @interface FIRShard : NSObject @property (nonatomic, readonly) NSInteger count; @end @implementation FIRShard - (instancetype)initWithCount:(NSInteger)count { self = [super init]; if (self != nil) { _count = count; } return self; } @end
Kotlin+KTX
// counters/${ID} data class Counter(var numShards: Int) // counters/${ID}/shards/${NUM} data class Shard(var count: Int)
Java
// counters/${ID} public class Counter { int numShards; public Counter(int numShards) { this.numShards = numShards; } } // counters/${ID}/shards/${NUM} public class Shard { int count; public Shard(int count) { this.count = count; } }
פִּיתוֹן
Python
Node.js
לא ישים, ראה את קטע תוספת המונה למטה.
ללכת
PHP
לא ישים, ראה את קטע האתחול נגדי למטה.
C#
הקוד הבא מאתחל מונה מבוזר:
אינטרנט
function createCounter(ref, num_shards) { var batch = db.batch(); // Initialize the counter document batch.set(ref, { num_shards: num_shards }); // Initialize each shard with count=0 for (let i = 0; i < num_shards; i++) { const shardRef = ref.collection('shards').doc(i.toString()); batch.set(shardRef, { count: 0 }); } // Commit the write batch return batch.commit(); }
מָהִיר
func createCounter(ref: DocumentReference, numShards: Int) { ref.setData(["numShards": numShards]){ (err) in for i in 0...numShards { ref.collection("shards").document(String(i)).setData(["count": 0]) } } }
Objective-C
- (void)createCounterAtReference:(FIRDocumentReference *)reference shardCount:(NSInteger)shardCount { [reference setData:@{ @"numShards": @(shardCount) } completion:^(NSError * _Nullable error) { for (NSInteger i = 0; i < shardCount; i++) { NSString *shardName = [NSString stringWithFormat:@"%ld", (long)shardCount]; [[[reference collectionWithPath:@"shards"] documentWithPath:shardName] setData:@{ @"count": @(0) }]; } }]; }
Kotlin+KTX
fun createCounter(ref: DocumentReference, numShards: Int): Task<Void> { // Initialize the counter document, then initialize each shard. return ref.set(Counter(numShards)) .continueWithTask { task -> if (!task.isSuccessful) { throw task.exception!! } val tasks = arrayListOf<Task<Void>>() // Initialize each shard with count=0 for (i in 0 until numShards) { val makeShard = ref.collection("shards") .document(i.toString()) .set(Shard(0)) tasks.add(makeShard) } Tasks.whenAll(tasks) } }
Java
public Task<Void> createCounter(final DocumentReference ref, final int numShards) { // Initialize the counter document, then initialize each shard. return ref.set(new Counter(numShards)) .continueWithTask(new Continuation<Void, Task<Void>>() { @Override public Task<Void> then(@NonNull Task<Void> task) throws Exception { if (!task.isSuccessful()) { throw task.getException(); } List<Task<Void>> tasks = new ArrayList<>(); // Initialize each shard with count=0 for (int i = 0; i < numShards; i++) { Task<Void> makeShard = ref.collection("shards") .document(String.valueOf(i)) .set(new Shard(0)); tasks.add(makeShard); } return Tasks.whenAll(tasks); } }); }
פִּיתוֹן
Python
Node.js
לא ישים, ראה את קטע תוספת המונה למטה.
ללכת
PHP
C#
אוֹדֶם
כדי להגדיל את המונה, בחר רסיס אקראי והגדל את הספירה:
אינטרנט
function incrementCounter(ref, num_shards) { // Select a shard of the counter at random const shard_id = Math.floor(Math.random() * num_shards).toString(); const shard_ref = ref.collection('shards').doc(shard_id); // Update count return shard_ref.update("count", firebase.firestore.FieldValue.increment(1)); }
מָהִיר
func incrementCounter(ref: DocumentReference, numShards: Int) { // Select a shard of the counter at random let shardId = Int(arc4random_uniform(UInt32(numShards))) let shardRef = ref.collection("shards").document(String(shardId)) shardRef.updateData([ "count": FieldValue.increment(Int64(1)) ]) }
Objective-C
- (void)incrementCounterAtReference:(FIRDocumentReference *)reference shardCount:(NSInteger)shardCount { // Select a shard of the counter at random NSInteger shardID = (NSInteger)arc4random_uniform((uint32_t)shardCount); NSString *shardName = [NSString stringWithFormat:@"%ld", (long)shardID]; FIRDocumentReference *shardReference = [[reference collectionWithPath:@"shards"] documentWithPath:shardName]; [shardReference updateData:@{ @"count": [FIRFieldValue fieldValueForIntegerIncrement:1] }]; }
Kotlin+KTX
fun incrementCounter(ref: DocumentReference, numShards: Int): Task<Void> { val shardId = Math.floor(Math.random() * numShards).toInt() val shardRef = ref.collection("shards").document(shardId.toString()) return shardRef.update("count", FieldValue.increment(1)) }
Java
public Task<Void> incrementCounter(final DocumentReference ref, final int numShards) { int shardId = (int) Math.floor(Math.random() * numShards); DocumentReference shardRef = ref.collection("shards").document(String.valueOf(shardId)); return shardRef.update("count", FieldValue.increment(1)); }
פִּיתוֹן
Python
Node.js
ללכת
PHP
C#
אוֹדֶם
כדי לקבל את הספירה הכוללת, בצע שאילתה עבור כל הרסיסים וסכם את שדות count
שלהם:
אינטרנט
function getCount(ref) { // Sum the count of each shard in the subcollection return ref.collection('shards').get().then((snapshot) => { let total_count = 0; snapshot.forEach((doc) => { total_count += doc.data().count; }); return total_count; }); }
מָהִיר
func getCount(ref: DocumentReference) { ref.collection("shards").getDocuments() { (querySnapshot, err) in var totalCount = 0 if err != nil { // Error getting shards // ... } else { for document in querySnapshot!.documents { let count = document.data()["count"] as! Int totalCount += count } } print("Total count is \(totalCount)") } }
Objective-C
- (void)getCountWithReference:(FIRDocumentReference *)reference { [[reference collectionWithPath:@"shards"] getDocumentsWithCompletion:^(FIRQuerySnapshot *snapshot, NSError *error) { NSInteger totalCount = 0; if (error != nil) { // Error getting shards // ... } else { for (FIRDocumentSnapshot *document in snapshot.documents) { NSInteger count = [document[@"count"] integerValue]; totalCount += count; } NSLog(@"Total count is %ld", (long)totalCount); } }]; }
Kotlin+KTX
fun getCount(ref: DocumentReference): Task<Int> { // Sum the count of each shard in the subcollection return ref.collection("shards").get() .continueWith { task -> var count = 0 for (snap in task.result!!) { val shard = snap.toObject<Shard>() count += shard.count } count } }
Java
public Task<Integer> getCount(final DocumentReference ref) { // Sum the count of each shard in the subcollection return ref.collection("shards").get() .continueWith(new Continuation<QuerySnapshot, Integer>() { @Override public Integer then(@NonNull Task<QuerySnapshot> task) throws Exception { int count = 0; for (DocumentSnapshot snap : task.getResult()) { Shard shard = snap.toObject(Shard.class); count += shard.count; } return count; } }); }
פִּיתוֹן
Python
Node.js
ללכת
PHP
C#
אוֹדֶם
מגבלות
הפתרון המוצג לעיל הוא דרך ניתנת להרחבה ליצירת מונים משותפים ב-Cloud Firestore, אך עליך להיות מודע למגבלות הבאות:
- ספירת רסיסים - מספר הרסיסים שולט בביצועי המונה המופץ. עם מעט מדי רסיסים, ייתכן שחלק מהעסקאות יצטרכו לנסות שוב לפני שיצליחו, מה שיאט את הכתיבה. עם יותר מדי רסיסים, הקריאה נעשית איטית יותר ויקרה יותר. אתה יכול לקזז את הוצאות הקריאה על ידי שמירת סך המונה במסמך סיכום נפרד שמתעדכן בקצב איטי יותר, וקבלת לקוחות לקרוא ממסמך זה כדי לקבל את הסכום הכולל. הפשרה היא שלקוחות יצטרכו להמתין לעדכון מסמך ה-Roll-up, במקום לחשב את הסכום הכולל על ידי קריאת כל הרסיסים מיד לאחר כל עדכון.
- עלות - העלות של קריאת ערך מונה עולה באופן ליניארי עם מספר הרסיסים, כי יש לטעון את כל תת-אוסף הרסיסים.