firebase / firebase/firebase-ios-sdk

Firestore batch deletes emit a snapshot per delete

Open
#13,883 11 comments 0 reactions 1 assignee Claimed by @MarkDuckworth View on GitHub
api: firestore needs-attention
Dominant language
C++
Stars
6.7k
Forks
1.8k
Avg merge
2d 14h
Merged PRs (30d)
72

Description

### Description

Batch deletes cause the firestore ios client to emit a snapshot for every individual delete, which creates an exponential (edit: N + N-1 + N-2, [triangular](https://en.wikipedia.org/wiki/Triangular_number)?) amount work decoding every document in each snapshot.

I would expect to get a single snapshot for the single batch delete. [Firebase's documentation](https://firebase.google.com/docs/firestore/manage-data/transactions#batched-writes) says "A batch of writes completes atomically and can write to multiple documents". The documentation seems to recommend batch writes over transactions for cases where you're not reading: "Like transactions, batched writes are atomic. Unlike transactions, batched writes do not need to ensure that read documents remain un-modified which leads to fewer failure cases."

I'd also expect that if multiple changes happen in the background, the next time the ios client connects, it emits a single snapshot with that complete set of changes, not one snapshot per change.

### Reproducing the issue

Write 500 documents to a Firestore collection using Firestore's batch API with an admin SDK.
Observe the collection in a snapshot listener on iOS.
Receive a single snapshot update from network with 500 documents (WAI)

Then, using the same batch API, delete all 500 visits, roughly:
```
all_documents = list(collection.list_documents())
batch = db.batch()
for doc in all_documents:
batch.delete(doc)
batch.commit()
```

On the client, add a snapshot listener to the collection:
```
let options = SnapshotListenOptions().withIncludeMetadataChanges(includeMetadataChanges)
db.collectionGroup("collection")
.whereField(...)
.order(by: "soc_date", descending: true)
.addSnapshotListener(options: options) { querySnapshot, error in
Log("Got snapshot, from cache: \(snapshot?.metadata.isFromCache ?? "nil"));
}
```

Receive separate snapshots marked "from cache" for every single delete:
```
Got snapshot, from cache: 1, documents: 500
Got snapshot, from cache: 1, documents: 499
Got snapshot, from cache: 1, documents: 498
Got snapshot, from cache: 1, documents: 497
Got snapshot, from cache: 1, documents: 496
...
```

If you do this with a collection of 5000 items and you decode every item in each snapshot on the client, you end up decoding ~12 million documents during this process (5000 + 4999 + 4998 etc). Firebase also seems to emit the snapshots quite slowly (< 1 per second at the start). This is pretty catastrophic for app performance and users' battery life.

I also see this behavior for Android FWIW.

### Firebase SDK Version

10.24.0

### Xcode Version

15.0.1

### Installation Method

Swift Package Manager

### Firebase Product(s)

Firestore

### Targeted Platforms

iOS

### Relevant Log Output

_No response_

### If using Swift Package Manager, the project's Package.resolved

Expand Package.resolved snippet

```json

Replace this line with the contents of your Package.resolved.

```

### If using CocoaPods, the project's Podfile.lock

Expand Podfile.lock snippet

```yml

Replace this line with the contents of your Podfile.lock!

```

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.