แอปแบบเรียลไทม์จำนวนมากมีเอกสารที่ทำหน้าที่เป็นตัวนับ เช่น คุณอาจนับ "การกดชอบ" ในโพสต์ หรือ "รายการโปรด" ของรายการหนึ่งๆ
ใน Cloud Firestore คุณจะอัปเดตเอกสารรายการเดียวในอัตราไม่จำกัดไม่ได้ หากคุณมีตัวนับที่อิงตามเอกสารเดียวและมีการเพิ่มขึ้นบ่อยพอ คุณจะเห็นการแย่งกันอัปเดตเอกสารในที่สุด โปรดดูการอัปเดตเอกสารเดียว
โซลูชัน: ตัวนับแบบกระจาย
หากต้องการรองรับการอัปเดตตัวนับบ่อยขึ้น ให้สร้างตัวนับแบบกระจาย ตัวนับแต่ละรายการคือเอกสารที่มีคอลเล็กชันย่อยของ "กลุ่ม" และค่าของตัวนับคือผลรวมของค่าของกลุ่ม
อัตราการส่งข้อมูลการเขียนจะเพิ่มขึ้นตามจำนวนกลุ่มย่อย ดังนั้นตัวนับแบบกระจายที่มีกลุ่มย่อย 10 กลุ่มจะจัดการการเขียนได้มากกว่าตัวนับแบบดั้งเดิม 10 เท่า
เว็บ
// 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
ไม่เกี่ยวข้อง โปรดดูข้อมูลโค้ดการเพิ่มขึ้นของตัวนับด้านล่าง
Go
PHP
ไม่เกี่ยวข้อง โปรดดูข้อมูลโค้ดการเริ่มต้นตัวนับด้านล่าง
C#
โค้ดต่อไปนี้จะเริ่มต้นตัวนับแบบกระจาย
เว็บ
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
ไม่เกี่ยวข้อง โปรดดูข้อมูลโค้ดการเพิ่มขึ้นของตัวนับด้านล่าง
Go
PHP
C#
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+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
หากต้องการดูจํานวนทั้งหมด ให้ค้นหาข้อมูลในข้อมูลพร็อพเพอร์ตี้ทั้งหมดและรวมช่อง 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+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
ข้อจำกัด
โซลูชันที่แสดงด้านบนเป็นวิธีที่ปรับขนาดได้ในการสร้างตัวนับที่แชร์ใน Cloud Firestore แต่คุณควรทราบข้อจํากัดต่อไปนี้
- จํานวนชาร์ด - จํานวนชาร์ดจะควบคุมประสิทธิภาพของตัวนับแบบกระจาย หากมีข้อมูลพร็อพเพอร์ตี้ Shard น้อยเกินไป ธุรกรรมบางอย่างอาจต้องลองอีกครั้งจึงจะสำเร็จ ซึ่งจะทำให้การเขียนช้าลง การมีกลุ่มย่อยมากเกินไปจะทำให้การอ่านช้าลงและค่าใช้จ่ายสูงขึ้น คุณสามารถหักลบค่าใช้จ่ายในการอ่านได้โดยเก็บยอดรวมของเคาน์เตอร์ไว้ในเอกสารสรุปแยกต่างหาก ซึ่งมีการอัปเดตเป็นระยะเวลาที่ช้าลง และให้ลูกค้าอ่านจากเอกสารนั้นเพื่อดูยอดรวม ข้อเสียคือ ลูกค้าจะต้องรอให้เอกสารรวมข้อมูลได้รับการอัปเดตแทนที่จะคํานวณผลรวมโดยการอ่านข้อมูลทั้งหมดของกลุ่มทันทีหลังจากการอัปเดต
- ต้นทุน - ต้นทุนในการอ่านค่าตัวนับจะเพิ่มขึ้นตามจำนวนกลุ่มเนื่องจากต้องโหลดคอลเล็กชันย่อยของกลุ่มทั้งหมด