بسیاری از برنامههای بلادرنگ، اسنادی دارند که به عنوان شمارنده عمل میکنند. برای مثال، ممکن است «لایکها»ی یک پست یا «موارد دلخواه» یک آیتم خاص را بشمارید.
در Cloud Firestore ، نمیتوانید یک سند واحد را با سرعت نامحدود بهروزرسانی کنید. اگر یک شمارنده بر اساس یک سند واحد داشته باشید و افزایشهای کافی و مکرر به آن اعمال کنید، در نهایت شاهد اختلاف بر سر بهروزرسانیهای سند خواهید بود. به بخش بهروزرسانیهای یک سند واحد مراجعه کنید.
راه حل: شمارندههای توزیعشده
برای پشتیبانی از بهروزرسانیهای مکرر شمارنده، یک شمارنده توزیعشده ایجاد کنید. هر شمارنده یک سند با زیرمجموعهای از «شاردها» است و مقدار شمارنده مجموع مقدار شاردها است.
توان عملیاتی نوشتن با تعداد شاردها به صورت خطی افزایش مییابد، بنابراین یک شمارنده توزیعشده با ۱۰ شارد میتواند ۱۰ برابر یک شمارنده سنتی، نوشتن انجام دهد.
وب
// 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
// 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
نود جی اس
قابل اجرا نیست، به قطعه کد افزایش شمارنده در زیر مراجعه کنید.
برو
پی اچ پی
قابل اجرا نیست، به قطعه کد مقداردهی اولیه شمارنده در زیر مراجعه کنید.
سی شارپ
کد زیر یک شمارنده توزیعشده را مقداردهی اولیه میکند:
وب
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) async { do { try await ref.setData(["numShards": numShards]) for i in 0...numShards { try await ref.collection("shards").document(String(i)).setData(["count": 0]) } } catch { // ... } }
هدف-سی
- (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
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
نود جی اس
قابل اجرا نیست، به قطعه کد افزایش شمارنده در زیر مراجعه کنید.
برو
پی اچ پی
سی شارپ
روبی
برای افزایش شمارنده، یک Shard تصادفی انتخاب کنید و تعداد را افزایش دهید:
وب
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
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
نود جی اس
برو
پی اچ پی
سی شارپ
روبی
برای بدست آوردن تعداد کل، تمام Shardها را جستجو کنید و فیلدهای 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) async { do { let querySnapshot = try await ref.collection("shards").getDocuments() var totalCount = 0 for document in querySnapshot.documents { let count = document.data()["count"] as! Int totalCount += count } print("Total count is \(totalCount)") } catch { // handle error } }
هدف-سی
- (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
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
نود جی اس
برو
پی اچ پی
سی شارپ
روبی
محدودیتها
راهکار نشان داده شده در بالا، روشی مقیاسپذیر برای ایجاد شمارندههای مشترک در Cloud Firestore است، اما باید از محدودیتهای زیر آگاه باشید:
- تعداد قطعات - تعداد قطعات، عملکرد شمارنده توزیعشده را کنترل میکند. با تعداد قطعات بسیار کم، ممکن است برخی از تراکنشها قبل از موفقیت مجبور به تلاش مجدد شوند که این امر باعث کند شدن نوشتن میشود. با تعداد قطعات بسیار زیاد، خواندن کندتر و گرانتر میشود. میتوانید با نگه داشتن مجموع شمارنده در یک سند جمعبندی جداگانه که با آهنگ کندتری بهروزرسانی میشود و وادار کردن کلاینتها به خواندن از آن سند برای دریافت مجموع، هزینه خواندن را جبران کنید. مزیت این است که کلاینتها باید منتظر بهروزرسانی سند جمعبندی بمانند، به جای اینکه مجموع را با خواندن تمام قطعات بلافاصله پس از هر بهروزرسانی محاسبه کنند.
- هزینه - هزینه خواندن مقدار شمارنده به صورت خطی با تعداد Shardها افزایش مییابد، زیرا کل زیرمجموعه Shardها باید بارگذاری شوند.