Stay organized with collectionsSave and categorize content based on your preferences.
Many realtime apps have documents that act as counters. For example, you might
count 'likes' on a post, or 'favorites' of a specific item.
InCloud Firestore, you can't update a single document at an unlimited rate. If you have a counter based on single document and frequent enough increments to it you will eventually see contention on the updates to the document. SeeUpdates to a single document.
Solution: Distributed counters
To support more frequent counter updates, create a distributed counter.
Each counter is a document with a subcollection of "shards," and the value of
the counter is the sum of the value of the shards.
Write throughput increases linearly with the number of shards, so a distributed
counter with 10 shards can handle 10x as many writes as a traditional counter.
importrandomfromgoogle.cloudimportfirestoreclassShard:"""A shard is a distributed counter. Each shard can support being incrementedonce per second. Multiple shards are needed within a Counter to allowmore frequent incrementing."""def__init__(self):self._count=0defto_dict(self):return{"count":self._count}classCounter:"""A counter stores a collection of shards which aresummed to return a total count. This allows for morefrequent incrementing than a single document."""def__init__(self,num_shards):self._num_shards=num_shards
importrandomfromgoogle.cloudimportfirestoreclassShard:"""A shard is a distributed counter. Each shard can support being incrementedonce per second. Multiple shards are needed within a Counter to allowmore frequent incrementing."""def__init__(self):self._count=0defto_dict(self):return{"count":self._count}classCounter:"""A counter stores a collection of shards which aresummed to return a total count. This allows for morefrequent incrementing than a single document."""def__init__(self,num_shards):self._num_shards=num_shards
Not applicable, see the counter increment snippet below.
Go
import("context""fmt""math/rand""strconv""cloud.google.com/go/firestore""google.golang.org/api/iterator")// Counter is a collection of documents (shards)// to realize counter with high frequency.typeCounterstruct{numShardsint}// Shard is a single counter, which is used in a group// of other shards within Counter.typeShardstruct{Countint}
Not applicable, see the counter initialization snippet below.
C#
/// <summary>/// Shard is a document that contains the count./// </summary>[FirestoreData]publicclassShard{[FirestoreProperty(name: "count")]publicintCount{get;set;}}
The following code initializes a distributed counter:
Web
functioncreateCounter(ref,num_shards){varbatch=db.batch();// Initialize the counter documentbatch.set(ref,{num_shards:num_shards});// Initialize each shard with count=0for(leti=0;i<num_shards;i++){constshardRef=ref.collection('shards').doc(i.toString());batch.set(shardRef,{count:0});}// Commit the write batchreturnbatch.commit();}
funcreateCounter(ref:DocumentReference,numShards:Int):Task<Void>{// Initialize the counter document, then initialize each shard.returnref.set(Counter(numShards)).continueWithTask{task->if(!task.isSuccessful){throwtask.exception!!}valtasks=arrayListOf<Task<Void>>()// Initialize each shard with count=0for(iin0untilnumShards){valmakeShard=ref.collection("shards").document(i.toString()).set(Shard(0))tasks.add(makeShard)}Tasks.whenAll(tasks)}}
publicTask<Void>createCounter(finalDocumentReferenceref,finalintnumShards){// Initialize the counter document, then initialize each shard.returnref.set(newCounter(numShards)).continueWithTask(newContinuation<Void,Task<Void>>(){@OverridepublicTask<Void>then(@NonNullTask<Void>task)throwsException{if(!task.isSuccessful()){throwtask.getException();}List<Task<Void>>tasks=newArrayList<>();// Initialize each shard with count=0for(inti=0;i<numShards;i++){Task<Void>makeShard=ref.collection("shards").document(String.valueOf(i)).set(newShard(0));tasks.add(makeShard);}returnTasks.whenAll(tasks);}});}
definit_counter(self,doc_ref):"""Create a given number of shards assubcollection of specified document."""col_ref=doc_ref.collection("shards")# Initialize each shard with count=0fornuminrange(self._num_shards):shard=Shard()col_ref.document(str(num)).set(shard.to_dict())
asyncdefinit_counter(self,doc_ref):"""Create a given number of shards assubcollection of specified document."""col_ref=doc_ref.collection("shards")# Initialize each shard with count=0fornuminrange(self._num_shards):shard=Shard()awaitcol_ref.document(str(num)).set(shard.to_dict())
Not applicable, see the counter increment snippet below.
Go
// initCounter creates a given number of shards as// subcollection of specified document.func(c*Counter)initCounter(ctxcontext.Context,docRef*firestore.DocumentRef)error{colRef:=docRef.Collection("shards")// Initialize each shard with count=0fornum:=0;num<c.numShards;num++{shard:=Shard{0}if_,err:=colRef.Doc(strconv.Itoa(num)).Set(ctx,shard);err!=nil{returnfmt.Errorf("Set: %w",err)}}returnnil}
/// <summary>/// Create a given number of shards as a/// subcollection of specified document./// </summary>/// <param name="docRef">The document reference <see cref="DocumentReference"/></param>privatestaticasyncTaskCreateCounterAsync(DocumentReferencedocRef,intnumOfShards){CollectionReferencecolRef=docRef.Collection("shards");vartasks=newList<Task>();// Initialize each shard with Count=0for(vari=0;i<numOfShards;i++){tasks.Add(colRef.Document(i.ToString()).SetAsync(newShard(){Count=0}));}awaitTask.WhenAll(tasks);}
To increment the counter, choose a random shard and increment
the count:
Web
functionincrementCounter(ref,num_shards){// Select a shard of the counter at randomconstshard_id=Math.floor(Math.random()*num_shards).toString();constshard_ref=ref.collection('shards').doc(shard_id);// Update countreturnshard_ref.update("count",firebase.firestore.FieldValue.increment(1));}
Note:This product is not available on watchOS and App Clip targets.
funcincrementCounter(ref:DocumentReference,numShards:Int){// Select a shard of the counter at randomletshardId=Int(arc4random_uniform(UInt32(numShards)))letshardRef=ref.collection("shards").document(String(shardId))shardRef.updateData(["count":FieldValue.increment(Int64(1))])}
Note:This product is not available on watchOS and App Clip targets.
-(void)incrementCounterAtReference:(FIRDocumentReference*)referenceshardCount:(NSInteger)shardCount{// Select a shard of the counter at randomNSIntegershardID=(NSInteger)arc4random_uniform((uint32_t)shardCount);NSString*shardName=[NSStringstringWithFormat:@"%ld",(long)shardID];FIRDocumentReference*shardReference=[[referencecollectionWithPath:@"shards"]documentWithPath:shardName];[shardReferenceupdateData:@{@"count":[FIRFieldValuefieldValueForIntegerIncrement:1]}];}
defincrement_counter(self,doc_ref):"""Increment a randomly picked shard."""doc_id=random.randint(0,self._num_shards-1)shard_ref=doc_ref.collection("shards").document(str(doc_id))returnshard_ref.update({"count":firestore.Increment(1)})
asyncdefincrement_counter(self,doc_ref):"""Increment a randomly picked shard."""doc_id=random.randint(0,self._num_shards-1)shard_ref=doc_ref.collection("shards").document(str(doc_id))returnawaitshard_ref.update({"count":firestore.Increment(1)})
# project_id = "Your Google Cloud Project ID"# num_shards = "Number of shards for distributed counter"# collection_path = "shards"require"google/cloud/firestore"firestore=Google::Cloud::Firestore.newproject_id:project_id# Select a shard of the counter at randomshard_id=rand0...num_shardsshard_ref=firestore.doc"#{collection_path}/#{shard_id}"# increment countershard_ref.update({count:firestore.field_increment(1)})puts"Counter incremented."
To get the total count, query for all shards and sum theircountfields:
Web
functiongetCount(ref){// Sum the count of each shard in the subcollectionreturnref.collection('shards').get().then((snapshot)=>{lettotal_count=0;snapshot.forEach((doc)=>{total_count+=doc.data().count;});returntotal_count;});}
fungetCount(ref:DocumentReference):Task<Int>{// Sum the count of each shard in the subcollectionreturnref.collection("shards").get().continueWith{task->varcount=0for(snapintask.result!!){valshard=snap.toObject<Shard>()count+=shard.count}count}}
publicTask<Integer>getCount(finalDocumentReferenceref){// Sum the count of each shard in the subcollectionreturnref.collection("shards").get().continueWith(newContinuation<QuerySnapshot,Integer>(){@OverridepublicIntegerthen(@NonNullTask<QuerySnapshot>task)throwsException{intcount=0;for(DocumentSnapshotsnap:task.getResult()){Shardshard=snap.toObject(Shard.class);count+=shard.count;}returncount;}});}
defget_count(self,doc_ref):"""Return a total count across all shards."""total=0shards=doc_ref.collection("shards").list_documents()forshardinshards:total+=shard.get().to_dict().get("count",0)returntotal
asyncdefget_count(self,doc_ref):"""Return a total count across all shards."""total=0shards=doc_ref.collection("shards").list_documents()asyncforshardinshards:total+=(awaitshard.get()).to_dict().get("count",0)returntotal
// getCount returns a total count across all shards.func(c*Counter)getCount(ctxcontext.Context,docRef*firestore.DocumentRef)(int64,error){vartotalint64shards:=docRef.Collection("shards").Documents(ctx)for{doc,err:=shards.Next()iferr==iterator.Done{break}iferr!=nil{return0,fmt.Errorf("Next: %w",err)}vTotal:=doc.Data()["Count"]shardCount,ok:=vTotal.(int64)if!ok{return0,fmt.Errorf("firestore: invalid dataType %T, want int64",vTotal)}total+=shardCount}returntotal,nil}
/// <summary>/// Get total count across all shards./// </summary>/// <param name="docRef">The document reference <see cref="DocumentReference"/></param>/// <returns>The <see cref="int"/></returns>privatestaticasyncTask<int>GetCountAsync(DocumentReferencedocRef){varsnapshotList=awaitdocRef.Collection("shards").GetSnapshotAsync();returnsnapshotList.Sum(shard=>shard.GetValue<int>("count"));}
The solution shown above is a scalable way to create shared counters inCloud Firestore, but you should be aware of the following limitations:
Shard count- The number of shards controls the performance of the
distributed counter. With too few shards, some transactions
may have to retry before succeeding, which will slow writes. With too many
shards, reads become slower and more expensive. You can offset the
read-expense by keeping the counter total in a separate roll-up document
which is updated at a slower cadence, and having
clients read from that document to get the total. The tradeoff is that
clients will have to wait for the roll-up document to be updated, instead
of computing the total by reading all of the shards immediately after any
update.
Cost- The cost of reading a counter value increases linearly with the
number of shards, because the entire shards subcollection must be loaded.
[[["Easy to understand","easyToUnderstand","thumb-up"],["Solved my problem","solvedMyProblem","thumb-up"],["Other","otherUp","thumb-up"]],[["Missing the information I need","missingTheInformationINeed","thumb-down"],["Too complicated / too many steps","tooComplicatedTooManySteps","thumb-down"],["Out of date","outOfDate","thumb-down"],["Samples / code issue","samplesCodeIssue","thumb-down"],["Other","otherDown","thumb-down"]],["Last updated 2025-09-04 UTC."],[],[],null,[]]