بسیاری از برنامههای همزمان اسنادی دارند که بهعنوان شمارنده عمل میکنند. برای مثال، ممکن است «پسندیدنهای» یک پست یا «موارد دلخواه» یک مورد خاص را بشمارید.
در Cloud Firestore، نمیتوانید یک سند را با نرخ نامحدود بهروز کنید. اگر شمارندهای براساس یک سند داشته باشید و بهاندازه کافی افزایش مکرر در آن داشته باشید، درنهایت شاهد رقابت در بهروزرسانیهای سند خواهید بود. بهروزرسانیهای یک سند را ببینید.
راهحل: شمارندههای توزیعشده
برای پشتیبانی از بهروزرسانیهای مکرر شمارنده، شمارنده توزیعشده ایجاد کنید. هر شمارنده سندی با زیرمجموعهای از «تکهها» است و مقدار شمارنده مجموع مقدار تکهها است.
توان عملیاتی نوشتن بهصورت خطی با تعداد بخشها افزایش مییابد، بنابراین یک شمارنده توزیعشده با ۱۰ بخش میتواند ۱۰ برابر بیشتر از یک شمارنده سنتی نوشتن را مدیریت کند.
وب
// counters/${ID}
{
"num_shards": NUM_SHARDS,
"shards": [subcollection]
}
// counters/${ID}/shards/${NUM}
{
"count": 123
}
Swift
// 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
// 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
قابلاجرا نیست، تکهکد مقداردهی اولیه شمارنده را در زیر ببینید.
سی شارپ
کد زیر یک شمارنده توزیعشده را مقداردهی اولیه میکند:
وب
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(); }
Swift
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 { // ... } }
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
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
سی شارپ
Ruby
برای افزایش شمارنده، یک قطعه تصادفی را انتخاب کنید و تعداد را افزایش دهید:
وب
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)); }
Swift
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
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
سی شارپ
Ruby
برای دریافت تعداد کل، همه قطعات را پُرسمان کنید و فیلدهای 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; }); }
Swift
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 } }
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
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
سی شارپ
Ruby
محدودیتها
راهحل نشاندادهشده در بالا روشی مقیاسپذیر برای ایجاد شمارندههای مشترک در Cloud Firestore است، اما باید از محدودیتهای زیر آگاه باشید:
- تعداد شارد - تعداد شاردها عملکرد شمارنده توزیعشده را کنترل میکند. اگر تعداد قطعهها خیلی کم باشد، ممکن است برخی تراکنشها قبلاز موفقیت مجبور به تکرار شوند که این امر سرعت نوشتن را کاهش میدهد. با تعداد زیادی تکه، خواندن کندتر و گرانتر میشود. میتوانید با نگه داشتن مجموع شمارنده در سند خلاصهسازی جداگانهای که با آهنگ کندتری بهروز میشود، هزینه خواندن را جبران کنید و از مشتریان بخواهید برای دریافت مجموع، از آن سند بخوانند. مبادله این است که کارخواهها باید منتظر بهروز شدن سند خلاصهسازی باشند، بهجای اینکه مجموع را با خواندن همه قطعهها بلافاصله پساز هر بهروزرسانی محاسبه کنند.
- هزینه - هزینه خواندن مقدار شمارنده بهصورت خطی با تعداد قطعات افزایش مییابد، زیرا کل زیرمجموعه قطعات باید بارگیری شود.