Birçok gerçek zamanlı uygulamada sayaç görevi gören belgeler bulunur. Örneğin, bir gönderideki "beğenileri" veya belirli bir öğenin "favorilerini" sayabilirsiniz.
Cloud Firestore'da, tek bir belgeyi saniyede yalnızca bir kez güncelleyebilirsiniz; bu, bazı yüksek trafikli uygulamalar için çok düşük olabilir.
Çözüm: Dağıtılmış sayaçlar
Daha sık sayaç güncellemelerini desteklemek için dağıtılmış bir sayaç oluşturun. Her sayaç, "parçalar" alt koleksiyonuna sahip bir belgedir ve sayacın değeri, parçaların değerlerinin toplamıdır.
Yazma verimi, parça sayısıyla doğrusal olarak artar, bu nedenle 10 parçalı dağıtılmış bir sayaç, geleneksel bir sayaçtan 10 kat daha fazla yazma işleyebilir.
ağ
// counters/${ID}
{
"num_shards": NUM_SHARDS,
"shards": [subcollection]
}
// counters/${ID}/shards/${NUM}
{
"count": 123
}
Süratli
// 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 } }
Amaç-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
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; } }
Kotlin+KTX
// counters/${ID} data class Counter(var numShards: Int) // counters/${ID}/shards/${NUM} data class Shard(var count: Int)
piton
Python
Node.js
Uygulanamaz, aşağıdaki sayaç artışı snippet'ine bakın.
Gitmek
PHP
Uygulanamaz, aşağıdaki sayaç başlatma snippet'ine bakın.
C#
/// <summary> /// Shard is a document that contains the count. /// </summary> [FirestoreData] public class Shard { [FirestoreProperty(name: "count")] public int Count { get; set; } }
Aşağıdaki kod, dağıtılmış bir sayacı başlatır:
ağ
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(); }
Süratli
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]) } } }
Amaç-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) }]; } }]; }
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); } }); }
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) } }
piton
Python
Node.js
Uygulanamaz, aşağıdaki sayaç artışı snippet'ine bakın.
Gitmek
PHP
$numShards = 10; $ref = $db->collection('samples/php/distributedCounters'); for ($i = 0; $i < $numShards; $i++) { $doc = $ref->document($i); $doc->set(['Cnt' => 0]); }
C#
/// <summary> /// Create a given number of shards as a /// subcollection of specified document. /// </summary> /// <param name="docRef">The document reference <see cref="DocumentReference"/></param> private static async Task CreateCounterAsync(DocumentReference docRef, int numOfShards) { CollectionReference colRef = docRef.Collection("shards"); var tasks = new List<Task>(); // Initialize each shard with Count=0 for (var i = 0; i < numOfShards; i++) { tasks.Add(colRef.Document(i.ToString()).SetAsync(new Shard() { Count = 0 })); } await Task.WhenAll(tasks); }
yakut
Sayacı artırmak için rastgele bir parça seçin ve sayımı artırın:
ağ
function incrementCounter(db, 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)); }
Süratli
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)) ]) }
Amaç-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] }]; }
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)); }
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)) }
piton
Python
Node.js
Gitmek
PHP
$ref = $db->collection('samples/php/distributedCounters'); $numShards = 0; $docCollection = $ref->documents(); foreach ($docCollection as $doc) { $numShards++; } $shardIdx = random_int(0, $numShards - 1); $doc = $ref->document($shardIdx); $doc->update([ ['path' => 'Cnt', 'value' => FieldValue::increment(1)] ]);
C#
/// <summary> /// Increment a randomly picked shard by 1. /// </summary> /// <param name="docRef">The document reference <see cref="DocumentReference"/></param> /// <returns>The <see cref="Task"/></returns> private static async Task IncrementCounterAsync(DocumentReference docRef, int numOfShards) { int documentId; lock (s_randLock) { documentId = s_rand.Next(numOfShards); } var shardRef = docRef.Collection("shards").Document(documentId.ToString()); await shardRef.UpdateAsync("count", FieldValue.Increment(1)); }
yakut
Toplam sayımı almak için tüm parçaları sorgulayın ve count
alanlarını toplayın:
ağ
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; }); }
Süratli
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)") } }
Amaç-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); } }]; }
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; } }); }
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 } }
piton
Python
Node.js
Gitmek
PHP
$result = 0; $docCollection = $db->collection('samples/php/distributedCounters')->documents(); foreach ($docCollection as $doc) { $result += $doc->data()['Cnt']; }
C#
/// <summary> /// Get total count across all shards. /// </summary> /// <param name="docRef">The document reference <see cref="DocumentReference"/></param> /// <returns>The <see cref="int"/></returns> private static async Task<int> GetCountAsync(DocumentReference docRef) { var snapshotList = await docRef.Collection("shards").GetSnapshotAsync(); return snapshotList.Sum(shard => shard.GetValue<int>("count")); }
yakut
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ın farkında olmalısınız:
- Parça sayısı - Parça 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, bu da yazma işlemlerini yavaşlatır. Çok fazla parça ile okumalar daha yavaş ve daha pahalı hale gelir. Sayaç toplamını daha yavaş bir kadansta (örneğin saniyede bir) güncellenen ayrı bir özet belgede tutarak ve toplamı almak için istemcilerin bu belgeden okumasını sağlayarak okuma masrafını dengeleyebilirsiniz. Buradaki ödün, müşterilerin herhangi bir güncellemeden hemen sonra tüm parçaları okuyarak toplamı hesaplamak yerine, özet belgenin güncellenmesini beklemek zorunda kalacak olmasıdır.
- Maliyet - Bir sayaç değeri okuma maliyeti, tüm parçalar alt koleksiyonunun yüklenmesi gerektiğinden, parça sayısıyla doğrusal olarak artar.