تحتوي العديد من تطبيقات الوقت الفعلي على مستندات تعمل كعدادات. على سبيل المثال، يمكنك حساب "الإعجابات" على منشور ما، أو "المفضلة" لعنصر معين.
في 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 } }
ج موضوعية
// 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
لا ينطبق، راجع مقتطف زيادة العداد أدناه.
يذهب
بي أتش بي
لا ينطبق، راجع مقتطف تهيئة العداد أدناه.
ج#
الكود التالي يقوم بتهيئة العداد الموزع:
ويب
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]) } } }
ج موضوعية
- (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
لا ينطبق، راجع مقتطف زيادة العداد أدناه.
يذهب
بي أتش بي
ج#
روبي
لزيادة العداد، اختر قطعة عشوائية وقم بزيادة العدد:
ويب
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)) ]) }
ج موضوعية
- (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
يذهب
بي أتش بي
ج#
روبي
للحصول على العدد الإجمالي، قم بالاستعلام عن كافة الأجزاء وقم بجمع حقول 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)") } }
ج موضوعية
- (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
يذهب
بي أتش بي
ج#
روبي
محددات
الحل الموضح أعلاه هو وسيلة قابلة للتطوير لإنشاء عدادات مشتركة في Cloud Firestore، ولكن يجب أن تكون على دراية بالقيود التالية:
- عدد الأجزاء - يتحكم عدد الأجزاء في أداء العداد الموزع. مع وجود عدد قليل جدًا من القطع، قد تضطر بعض المعاملات إلى إعادة المحاولة قبل النجاح، مما سيؤدي إلى إبطاء عملية الكتابة. ومع كثرة الشظايا، تصبح عمليات القراءة أبطأ وأكثر تكلفة. يمكنك تعويض نفقات القراءة عن طريق الاحتفاظ بإجمالي العداد في مستند إجمالي منفصل يتم تحديثه بإيقاع أبطأ، وجعل العملاء يقرؤون من هذا المستند للحصول على الإجمالي. وتتمثل المقايضة في أنه سيتعين على العملاء الانتظار حتى يتم تحديث المستند الإجمالي، بدلاً من حساب الإجمالي عن طريق قراءة جميع الأجزاء مباشرة بعد أي تحديث.
- التكلفة - تزيد تكلفة قراءة قيمة العداد خطيًا مع عدد الأجزاء، لأنه يجب تحميل مجموعة الأجزاء الفرعية بأكملها.