13
votes

I have seen an increment counter with Cloud Functions referencing Realtime Database, but not Firebase Firestore yet.

I have a cloud function that listens for new documents:

exports.addToChainCount = functions.firestore
    .document('chains/{name}')
    .onCreate((snap, context) => {

    // Initialize document
    var chainCounterRef = db.collection('counters').doc('chains');

    var transaction = db.runTransaction(t => {
        return t.get(chainCounterRef).then(doc => {
            // Add to the chain count
            var newCount = doc.data().count + 1;
            t.update(chainCounterRef, { count: newCount });
        });
    }).then(result => {
        console.log('Transaction success!');
    }).catch(err => {
        console.log('Transaction failure:', err);
    });
    return true;
});

I'm attempting the above transaction, but when I run firebase deploy in terminal I get this error:

error Each then() should return a value or throw promise/always-return functions predeploy error: Command terminated with non-zero exit code1

This is my first attempt at anything node.js, and I'm not sure I've written this right.

3
There is a whole page of documentation describing sharded counters with Firestore. The reason why you need to shard in many production cases is because Firestore documents can only process one write per second under load. Anything more under load and you will lose writes, and your count will become inaccurate. firebase.google.com/docs/firestore/solutions/countersDoug Stevenson
@DougStevenson Thanks, but I want to increment my counter using cloud functions. Are you suggesting this is not recommended?Austin Berenyi
It doesn't matter where you write your documents. The limit stands.Doug Stevenson
@DougStevenson Is there any way to control the rate at which cloud functions are triggered by new documents? If so, it would be possible to update a counter using a queuing system as described in the original post. It would occasionally get backed up, but the rate-limiting would prevent Firestore from getting overloaded.Derrick Miller
@DerrickMiller What you're asking is far more complex than could be addressed in a SO comment. Such as thing is not built into the product.Doug Stevenson

3 Answers

34
votes

There is now a much simpler way to increment/decrement a field in a document: FieldValue.increment(). Your sample would be something like this:

var chainCounterRef = db.collection('counters').doc('chains');
chainCounterRef.update({ count: FieldValue.increment(1) });

See:

1
votes

If you want to safely increment a number in a document, you can use a transaction. The following code is taken directly from the linked page. It adds one to a field called population in a document /cities/SF after giving it some initial values:

// Initialize document
var cityRef = db.collection('cities').doc('SF');
var setCity = cityRef.set({
  name: 'San Francisco',
  state: 'CA',
  country: 'USA',
  capital: false,
  population: 860000
});

var transaction = db.runTransaction(t => {
  return t.get(cityRef)
      .then(doc => {
        // Add one person to the city population
        var newPopulation = doc.data().population + 1;
        t.update(cityRef, { population: newPopulation });
      });
}).then(result => {
  console.log('Transaction success!');
}).catch(err => {
  console.log('Transaction failure:', err);
});

Bear in mind that Firestore is limited to one write per second under sustained load, so if you're going to be writing a lot, you will need to use a sharded counter instead.

0
votes

Here's one potential solution. You could record the votes using sharding, but then use cloud functions to aggregate the shard totals and update your counter. That should reduce the number of document reads and therefore your Firestore bill.

Tutorial here: https://angularfirebase.com/lessons/firestore-cloud-functions-data-aggregation/