21

I'm using Cloud Firestore and have a collection of documents. For each document in the collection I would like to update one of the fields.

Using a transaction to perform the update would be inefficient because I do not need to read any of the data while updating it.

Batch updates seem like the right direction, however the docs do not include examples of updating multiple docs at once. See here: Batched Writes

Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
Trey Granderson
  • 670
  • 3
  • 8
  • 19

4 Answers4

27

If you have used Firebase database, writing to completely single separate locations atomically was not possible, that's why you would have to use batch writes, which means that either all of the operations succeed, or none of them are applied.

Regarding Firestore, all operations are now atomically processed. However, you can execute multiple write operations as a single batch that contains any combination of set(), update(), or delete() operations. A batch of writes completes atomically and can write to multiple documents.

This a simple example regarding a batch operation for write, update and delete operation.

WriteBatch batch = db.batch();

DocumentReference johnRef = db.collection("users").document("John");
batch.set(johnRef, new User());

DocumentReference maryRef = db.collection("users").document("Mary");
batch.update(maryRef, "Anna", 20); //Update name and age

DocumentReference alexRef = db.collection("users").document("Alex");
batch.delete(alexRef);

batch.commit().addOnCompleteListener(new OnCompleteListener<Void>() {
    @Override
    public void onComplete(@NonNull Task<Void> task) {
        // ...
    }
});

Calling commit() method on the batch object means that you commit the entire batch.

Community
  • 1
  • 1
Alex Mamo
  • 130,605
  • 17
  • 163
  • 193
  • 1
    Thanks for the speedy reply however my question is on how to perform a bulk updates on every document in a collection. This answer reiterates what is stated in the link to the docs that I provided which performs individual writes to individual documents. – Trey Granderson Oct 20 '17 at 12:41
  • It's very simple. Get all those documents from the collection and using the model above, updated those documents with the desired data. – Alex Mamo Oct 20 '17 at 12:44
  • 18
    So in order to increase some count in a bunch of documents that match a query, you must read every single document into memory, then write each back? With MySQL you've been able to [update directly into the database](https://dev.mysql.com/doc/refman/8.0/en/update.html) since 1995, [MongoDB supports multi-document updates too](https://docs.mongodb.com/manual/reference/method/db.collection.update/#example-update-specific-fields) - but Firestore doesn't? Much progress, amaze database, very wow! – Dan Dascalescu Feb 23 '18 at 11:38
  • 1
    @DanDascalescu No, you are writing directly. Why are you saying that you need to read it first? – Alex Mamo Feb 23 '18 at 13:08
  • When you write with a batch operation, does each document written count as a write operation (for pricing purposes) or is the entire batch a single write? – Charlie Martin Aug 04 '18 at 18:11
  • 1
    @CharlieMartin Yes, each document written count as a write operation. – Alex Mamo Aug 05 '18 at 07:47
  • @DanDascalescu no, you are not reading these documents, you are creating a reference to the documents so you are updating directly by referencing the document similar to MySQL without having to read the document itself. As you can see in the code these are all "DocumentReference"s, not Documents. – Tolga Açış Apr 02 '20 at 14:57
  • I think it's the time to go back and work with mongodb again, Firebase are surprising me with it's huge limits and over priced service. – Ahmed Saeed Jul 03 '20 at 03:01
  • 1
    @AhmedSaeed Why would you say that? If you have hard times with some practices, post a new question here on StackOverflow, so I and other Firebase developers can take a look at it. – Alex Mamo Jul 03 '20 at 08:55
  • How dare you create a product with limitations – geg Mar 31 '21 at 17:49
  • @DanDascalescu now you can use `firestore.firestore.FieldValue.increment(1)` as stated here: https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes – arielhasidim Sep 25 '21 at 13:47
  • What if you want to get DocumentReferences from a query? like using `where` query on the collection reference? You need to call `get()` right and read all the documents before we can perform any operations? – NullByte08 Feb 16 '23 at 07:01
  • @NullByte08 As soon as you called get(), it means that you have already performed the read operation. – Alex Mamo Feb 16 '23 at 07:03
  • @AlexMamo That's what I am saying, How do I do batch updates on a list of documents that match a query? It's not possible without reading the docs currently afaik i.e. using `get()` – NullByte08 Feb 16 '23 at 07:20
  • I'm not sure I fully understand the question, so please post a new question, here on StackOverflow, using its own [MCVE](https://stackoverflow.com/help/mcve) and a screenshot of your database, so I and other Firebase developers can help you. – Alex Mamo Feb 16 '23 at 09:14
2

I was looking for a solution, found none, so I made this one, if anyone's interested.

public boolean bulkUpdate() {
  try {
    // see https://firebase.google.com/docs/firestore/quotas#writes_and_transactions
    int writeBatchLimit = 500;
    int totalUpdates = 0;

    while (totalUpdates % writeBatchLimit == 0) {
      WriteBatch writeBatch = this.firestoreDB.batch();

      List<QueryDocumentSnapshot> documentsInBatch =
          this.firestoreDB.collection("animals")
              .whereEqualTo("species", "cat")
              .limit(writeBatchLimit)
              .get()
              .get()
              .getDocuments();

      if (documentsInBatch.isEmpty()) {
        break;
      }

      documentsInBatch.forEach(
          document -> writeBatch.update(document.getReference(), "hasTail", true));

      writeBatch.commit().get();

      totalUpdates += documentsInBatch.size();
    }

    System.out.println("Number of updates: " + totalUpdates);

  } catch (Exception e) {
    return false;
  }
  return true;
}
1

in SQL you can do

UPDATE table_name
SET column1 = value1, column2 = value2, ...
WHERE condition;

The WHERE is optional - so you can set all fields to a specific value or all fields for a selection of rows but you don't need to first obtain a reference to the rows.

For example if content needs review you could set a flag in a review column for all rows.

In firestore I don't think there is a way to write to a collection.

https://firebase.google.com/docs/reference/js/firebase.firestore.CollectionReference

All the collection methods are ways to add a document, get a doc reference - or filter a search for documents.

So as far as I can see there is no way to update a set of documents in firestore without first obtaining references to them.

Batch writes speeds this up as you can update 500 documents at a time.

Sean Burlington
  • 873
  • 10
  • 13
  • What if the collection has more than 500 documents? In my case, I'm using a cloud function to do this but I'm not sure what's the best way to handle cases where there's a lot of documents. – Charles de Dreuille Jan 27 '21 at 13:56
  • https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes You can do multiple batches. If you don't batch you send a response to the server for every single record. With batches you can send up to 500 to the server at a time which is a lot faster. Just count how may records you are processing and do a batch.commit() every 500 – Sean Burlington Jan 28 '21 at 14:12
1

Here's how I did bulk update in Node.js (case: update all documents in users collection to add a new field country):

function updateCountry(countryName) {

  let lastUserUpdated = null;
  let updatedUsersCount = 0;

  const usersRef = firestore.collection("users");
  const batchSize = 500;

  while (true) {

    const batch = firestore.batch();
    const usersListRef = usersRef
      .orderBy("userId") // To start after a particular user, there should be some order, hence orderBy is necessary
      .startAfter(lastUserUpdated)
      .limit(batchSize);
    const usersList = await usersListRef.get();

    if (usersList.size === 0) { // When all the users have been traversed, we can exit from the loop
      break;
    }
    usersList.forEach((user) => {
      lastUserUpdated = user;
      batch.update(user.ref, { country: countryName }); // Here, the update will be added to the batch, but won't be executed now
      updatedUsersCount++;
    });
    await batch.commit(); // At this point, everything added to the batch will be executed together
    console.log(`Updated ${updatedUsersCount} users.`);
  }
}
Daniel James
  • 381
  • 1
  • 5
  • 15