The Three Firestore Counters
7 min read

Cloud Firestore has many options to handle your noSQL counter needs in 2023. Here is every problem and solution I could think of. Firestore Counters Solutions are still constrained by Firestore limits.

Counter Methods

1. Count Aggregation

const coll = collection(db, "cities");
const snapshot = await getCountFromServer(coll);
console.log('count: ', snapshot.data().count);

You can count the results from any document collection, or any acceptable Firestore query. Remember, you are charged 1 document read for every 1000 documents the query has to count.

2. Batch Update + Firestore Rules

const batch = writeBatch(db);
const newDoc = ;
const countDoc = doc(db, "counter-doc-path");

// add a new document to the collection
batch.set(
  doc(db, "new-doc-path"),
  { new_data: "will-be-here" }
);

// increase counter count in counter document
batch.update(
  doc(db, "counter-doc-path"),
  { postCount: increment(1) }
);
await batch.commit();

This will require you to update your counter document at the same time you add a new document. You need to handle deleting a document in the same way. The counter gets decremented at the same time a document is deleted.

I wrote a package to handle this automatically. See j-firebase.

Security

This can get a little complicated. However, basically you want to make sure you have two sets of rules.

  1. On the new document collection, you need to make sure when there is a create / delete document, there is also a corresponding counter increment / decrement on the counter document.
  2. On the counter document, you need to make sure when the counter is incremented / decremented, there is a document create / delete on the corresponding document collection.

These rules should prevent someone messing with the counter, or adding / deleting a document without a corresponding counter. It should be worth noting, that you may have SEVERAL document reads just on creating a document. Reading documents in Firestore Rules, are still counted as reads.

Reactions

If you have reactions (votes, likes, thumbs-up), you will still be following this pattern. When you create a document like posts/{postID}/likes/{userId}, you also want to increment / decrement the posts count, likesCount, on the post document at posts/{postID}.

This gives you an advantage over the count() aggregation, as you will be able to sort your posts by likesCount, which is impossible without pre-saving this value.

3. Firestore Function Trigger

This is at the bottom of the list, but technically an option. The good thing about this option, is you never have to worry about rules or extra frontend code.

exports.myFunction = functions.firestore
    .document('posts/{docId}')
    .onWrite(async (change: any, context: any): Promise<any> => {

// update counter

});

Here, you can update a counter automatically every time a document is added / removed from a collection. If it is a create document, increment the counter. If it is a delete document, decrement the counter.

Accuracy

However, and a BIG HOWEVER, Firestore Functions need to be idempotent functions. This basically means the functions could run more than once and handle those cases. Although rare, this could occur with retries or different servers. This does not guarantee our counters to be correct in all situations. The way around this is to get the function event ID and save it to another collection, usually as the document ID. If that event ID exists, do not increment / decrement the counter. You should generally save the date with the events so that you can purge old event documents later. You can look at the source code to my old adv-firestore-functions repo for ideas on this if you absolutely need this method.

Sharding

It should also be noted, that Firestore Functions are also needed to cover cases where one documents needs to increment its counter at a high velocities. Sharding must be used to cover these high volume writing cases.

There are use cases for all three methods, but you should be fine in most cases to stick with the count() aggregation function.

J

cloud-firestore
firebase
indexing
data-modeling


Related Posts

15 min read


2 min read


6 min read


© 2023 Code.Build