Birçok gerçek zamanlı uygulama, sayaç görevi gören dokümanlara sahiptir. Örneğin, bir gönderideki "beğenileri" veya belirli bir öğenin "favorilerini" sayabilirsiniz.
Cloud Firestore'da tek bir dokümanı sınırsız sayıda güncelleyemezsiniz. Tek bir belgeye dayalı bir sayacınız varsa ve bu sayaçta yeterince sık artış varsa dokümandaki güncellemelerle ilgili çekişmeler görürsünüz. Tek bir dokümanda yapılan güncellemeler başlıklı makaleyi inceleyin.
Çözüm: Dağıtılmış sayaçlar
Daha sık karşı güncellemeleri desteklemek için dağıtılmış bir sayaç oluşturun. Her sayaç, "parçalardan" oluşan bir alt koleksiyona sahip bir belgedir. Sayacın değeri, parçaların değerlerinin toplamıdır.
Yazma işleme hızı, parça sayısıyla birlikte doğrusal olarak artar. Bu nedenle, 10 parçalı dağıtılmış bir sayaç, geleneksel bir sayaca göre 10 kat daha fazla yazma işlemi yapabilir.
Web
// 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+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
Python
Node.js
Geçerli değil. Aşağıdaki sayaç artırma snippet'ine bakın.
Go
PHP
Geçerli değil. Aşağıdaki karşı başlatma snippet'ine bakın.
C#
Aşağıdaki kod, dağıtılmış bir sayacı başlatır:
Web
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+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
Python
Node.js
Geçerli değil. Aşağıdaki sayaç artırma snippet'ine bakın.
Go
PHP
C#
Ruby
Sayacı artırmak için rastgele bir kırık seçin ve sayıyı artırın:
Web
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+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
Python
Node.js
Go
PHP
C#
Ruby
Toplam sayıyı elde etmek için tüm kırıkları sorgulayın ve count
alanlarını toplayın:
Web
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+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
Python
Node.js
Go
PHP
C#
Ruby
Sınırlamalar
Yukarıda gösterilen çözüm, Cloud Firestore'da paylaşılan sayaçlar oluşturmanın ölçeklenebilir bir yoludur ancak aşağıdaki sınırlamaları dikkate almanız gerekir:
- Parça sayısı: Kırık sayısı, dağıtılan sayacın performansını kontrol eder. Çok az parçayla bazı işlemlerin başarılı olmadan önce yeniden denenmesi gerekebilir ve bu da yazmaların yavaşlamasına neden olur. Çok fazla parça kullanıldığında, okuma işlemleri yavaş ve pahalı hale gelir. Sayaç toplamını daha yavaş bir tempoda güncellenen ayrı bir toplayıcı dokümanda tutarak ve toplamı almak için istemcilerin bu belgeyi okumasını sağlayarak okuma masraflarını dengeleyebilirsiniz. Bunun karşılığında müşteriler, herhangi bir güncellemeden hemen sonra tüm kırıkları okuyarak toplamı hesaplamak yerine toplayıcı dokümanın güncellenmesini beklemek zorunda kalır.
- Maliyet: Parça alt koleksiyonunun tamamının yüklenmesi gerekeceğinden, sayaç değerini okuma maliyeti, parça sayısıyla birlikte doğrusal olarak artar.