Skip to content

Commit 1986969

Browse files
Only fetch 50 documents per backfill (#3193)
1 parent 110b867 commit 1986969

22 files changed

+284
-113
lines changed

firebase-firestore/CHANGELOG.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ by opting into a release at
33
[go/firebase-android-release](http:go/firebase-android-release) (Googlers only).
44

55
# 24.1.0
6-
- [changed] Performance optimization for offline usage.
7-
- [changed] Improved performance for queries with collections that contain
6+
- [changed] Improved performance for databases that contain many document
7+
updates that have not yet been synced with the backend.
8+
- [changed] Improved performance for queries against collections that contain
89
subcollections.
910

1011
# 24.0.0

firebase-firestore/src/main/java/com/google/firebase/firestore/local/IndexBackfiller.java

Lines changed: 22 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -19,19 +19,15 @@
1919
import androidx.annotation.Nullable;
2020
import androidx.annotation.VisibleForTesting;
2121
import com.google.firebase.database.collection.ImmutableSortedMap;
22-
import com.google.firebase.firestore.core.Query;
2322
import com.google.firebase.firestore.model.Document;
2423
import com.google.firebase.firestore.model.DocumentKey;
2524
import com.google.firebase.firestore.model.FieldIndex;
2625
import com.google.firebase.firestore.model.FieldIndex.IndexOffset;
27-
import com.google.firebase.firestore.model.ResourcePath;
2826
import com.google.firebase.firestore.util.AsyncQueue;
2927
import com.google.firebase.firestore.util.Logger;
30-
import java.util.ArrayList;
3128
import java.util.Collection;
32-
import java.util.Collections;
3329
import java.util.HashSet;
34-
import java.util.List;
30+
import java.util.Iterator;
3531
import java.util.Map;
3632
import java.util.Set;
3733
import java.util.concurrent.TimeUnit;
@@ -138,22 +134,18 @@ private int writeIndexEntries(LocalDocumentsView localDocumentsView) {
138134
/** Writes entries for the fetched field indexes. */
139135
private int writeEntriesForCollectionGroup(
140136
LocalDocumentsView localDocumentsView, String collectionGroup, int entriesRemainingUnderCap) {
141-
Query query = new Query(ResourcePath.EMPTY, collectionGroup);
137+
// TODO(indexing): Support mutation batch Ids when sorting and writing indexes.
142138

143139
// Use the earliest offset of all field indexes to query the local cache.
144140
IndexOffset existingOffset = getExistingOffset(indexManager.getFieldIndexes(collectionGroup));
145-
146-
// TODO(indexing): Use limit queries to only fetch the required number of entries.
147-
// TODO(indexing): Support mutation batch Ids when sorting and writing indexes.
148141
ImmutableSortedMap<DocumentKey, Document> documents =
149-
localDocumentsView.getDocumentsMatchingQuery(query, existingOffset);
150-
151-
List<Document> oldestDocuments = getOldestDocuments(documents, entriesRemainingUnderCap);
152-
indexManager.updateIndexEntries(oldestDocuments);
142+
localDocumentsView.getDocuments(collectionGroup, existingOffset, entriesRemainingUnderCap);
143+
indexManager.updateIndexEntries(documents);
153144

154-
IndexOffset newOffset = getNewOffset(oldestDocuments, existingOffset);
145+
IndexOffset newOffset = getNewOffset(documents, existingOffset);
155146
indexManager.updateCollectionGroup(collectionGroup, newOffset);
156-
return oldestDocuments.size();
147+
148+
return documents.size();
157149
}
158150

159151
/** Returns the lowest offset for the provided index group. */
@@ -168,37 +160,22 @@ private IndexOffset getExistingOffset(Collection<FieldIndex> fieldIndexes) {
168160
return lowestOffset == null ? IndexOffset.NONE : lowestOffset;
169161
}
170162

171-
/**
172-
* Returns the offset for the index based on the newly indexed documents.
173-
*
174-
* @param documents a list of documents sorted by read time and key (ascending)
175-
* @param currentOffset the current offset of the index group
176-
*/
177-
private IndexOffset getNewOffset(List<Document> documents, IndexOffset currentOffset) {
178-
IndexOffset latestOffset =
179-
documents.isEmpty()
180-
? IndexOffset.create(remoteDocumentCache.getLatestReadTime())
181-
: IndexOffset.create(
182-
documents.get(documents.size() - 1).getReadTime(),
183-
documents.get(documents.size() - 1).getKey());
184-
// Make sure the index does not go back in time
185-
latestOffset = latestOffset.compareTo(currentOffset) > 0 ? latestOffset : currentOffset;
186-
return latestOffset;
187-
}
188-
189-
/** Returns up to {@code count} documents sorted by read time and key. */
190-
private List<Document> getOldestDocuments(
191-
ImmutableSortedMap<DocumentKey, Document> documents, int count) {
192-
List<Document> oldestDocuments = new ArrayList<>();
193-
for (Map.Entry<DocumentKey, Document> entry : documents) {
194-
oldestDocuments.add(entry.getValue());
163+
/** Returns the offset for the index based on the newly indexed documents. */
164+
private IndexOffset getNewOffset(
165+
ImmutableSortedMap<DocumentKey, Document> documents, IndexOffset currentOffset) {
166+
if (documents.isEmpty()) {
167+
return IndexOffset.create(remoteDocumentCache.getLatestReadTime());
168+
} else {
169+
IndexOffset latestOffset = currentOffset;
170+
Iterator<Map.Entry<DocumentKey, Document>> it = documents.iterator();
171+
while (it.hasNext()) {
172+
IndexOffset newOffset = IndexOffset.fromDocument(it.next().getValue());
173+
if (newOffset.compareTo(latestOffset) > 0) {
174+
latestOffset = newOffset;
175+
}
176+
}
177+
return latestOffset;
195178
}
196-
Collections.sort(
197-
oldestDocuments,
198-
(l, r) ->
199-
IndexOffset.create(l.getReadTime(), l.getKey())
200-
.compareTo(IndexOffset.create(r.getReadTime(), r.getKey())));
201-
return oldestDocuments.subList(0, Math.min(count, oldestDocuments.size()));
202179
}
203180

204181
@VisibleForTesting

firebase-firestore/src/main/java/com/google/firebase/firestore/local/IndexManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
package com.google.firebase.firestore.local;
1616

1717
import androidx.annotation.Nullable;
18+
import com.google.firebase.database.collection.ImmutableSortedMap;
1819
import com.google.firebase.firestore.core.Target;
1920
import com.google.firebase.firestore.model.Document;
2021
import com.google.firebase.firestore.model.DocumentKey;
@@ -97,5 +98,5 @@ public interface IndexManager {
9798
void updateCollectionGroup(String collectionGroup, FieldIndex.IndexOffset offset);
9899

99100
/** Updates the index entries for the provided documents. */
100-
void updateIndexEntries(Collection<Document> documents);
101+
void updateIndexEntries(ImmutableSortedMap<DocumentKey, Document> documents);
101102
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/LocalDocumentsView.java

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -191,10 +191,13 @@ void recalculateAndSaveOverlays(Set<DocumentKey> documentKeys) {
191191
recalculateAndSaveOverlays(docs);
192192
}
193193

194-
// TODO: The Querying implementation here should move 100% to the query engines.
195-
// Instead, we should just provide a getCollectionDocuments() method here that return all the
196-
// documents in a given collection so that query engine can do that and then filter in
197-
// memory.
194+
/** Gets the local view of the next {@code count} documents based on their read time. */
195+
ImmutableSortedMap<DocumentKey, Document> getDocuments(
196+
String collectionGroup, IndexOffset offset, int count) {
197+
Map<DocumentKey, MutableDocument> docs =
198+
remoteDocumentCache.getAll(collectionGroup, offset, count);
199+
return getLocalViewOfDocuments(docs, new HashSet<>());
200+
}
198201

199202
/**
200203
* Performs a query against the local view of all documents.

firebase-firestore/src/main/java/com/google/firebase/firestore/local/MemoryIndexManager.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
import static com.google.firebase.firestore.util.Assert.hardAssert;
1717

1818
import androidx.annotation.Nullable;
19+
import com.google.firebase.database.collection.ImmutableSortedMap;
1920
import com.google.firebase.firestore.core.Target;
2021
import com.google.firebase.firestore.model.Document;
2122
import com.google.firebase.firestore.model.DocumentKey;
@@ -98,7 +99,7 @@ public Collection<FieldIndex> getFieldIndexes() {
9899
}
99100

100101
@Override
101-
public void updateIndexEntries(Collection<Document> documents) {
102+
public void updateIndexEntries(ImmutableSortedMap<DocumentKey, Document> documents) {
102103
// Field indices are not supported with memory persistence.
103104
}
104105

firebase-firestore/src/main/java/com/google/firebase/firestore/local/MemoryRemoteDocumentCache.java

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,13 @@ public Map<DocumentKey, MutableDocument> getAll(Iterable<DocumentKey> keys) {
8181
return result;
8282
}
8383

84+
@Override
85+
public Map<DocumentKey, MutableDocument> getAll(
86+
String collectionGroup, IndexOffset offset, int limit) {
87+
// This method should only be called from the IndexBackfiller if SQLite is enabled.
88+
throw new UnsupportedOperationException("getAll(String, IndexOffset, int) is not supported.");
89+
}
90+
8491
@Override
8592
public ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQuery(
8693
Query query, IndexOffset offset) {
@@ -108,7 +115,7 @@ public ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQ
108115
continue;
109116
}
110117

111-
if (IndexOffset.create(doc.getReadTime(), doc.getKey()).compareTo(offset) <= 0) {
118+
if (IndexOffset.fromDocument(doc).compareTo(offset) <= 0) {
112119
continue;
113120
}
114121

firebase-firestore/src/main/java/com/google/firebase/firestore/local/RemoteDocumentCache.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
import com.google.firebase.database.collection.ImmutableSortedMap;
1818
import com.google.firebase.firestore.core.Query;
1919
import com.google.firebase.firestore.model.DocumentKey;
20-
import com.google.firebase.firestore.model.FieldIndex;
20+
import com.google.firebase.firestore.model.FieldIndex.IndexOffset;
2121
import com.google.firebase.firestore.model.MutableDocument;
2222
import com.google.firebase.firestore.model.SnapshotVersion;
2323
import java.util.Map;
@@ -65,6 +65,17 @@ interface RemoteDocumentCache {
6565
*/
6666
Map<DocumentKey, MutableDocument> getAll(Iterable<DocumentKey> documentKeys);
6767

68+
/**
69+
* Looks up the next {@code limit} documents for a collection group based on the provided offset.
70+
* The ordering is based on the document's read time and key.
71+
*
72+
* @param collectionGroup The collection group to scan.
73+
* @param offset The offset to start the scan at.
74+
* @param limit The maximum number of results to return.
75+
* @return A map with next set of documents.
76+
*/
77+
Map<DocumentKey, MutableDocument> getAll(String collectionGroup, IndexOffset offset, int limit);
78+
6879
/**
6980
* Executes a query against the cached Document entries
7081
*
@@ -78,7 +89,7 @@ interface RemoteDocumentCache {
7889
* @return The set of matching documents.
7990
*/
8091
ImmutableSortedMap<DocumentKey, MutableDocument> getAllDocumentsMatchingQuery(
81-
Query query, FieldIndex.IndexOffset offset);
92+
Query query, IndexOffset offset);
8293

8394
/** Returns the latest read time of any document in the cache. */
8495
SnapshotVersion getLatestReadTime();

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLiteIndexManager.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323

2424
import androidx.annotation.Nullable;
2525
import com.google.firebase.Timestamp;
26+
import com.google.firebase.database.collection.ImmutableSortedMap;
2627
import com.google.firebase.firestore.auth.User;
2728
import com.google.firebase.firestore.core.Bound;
2829
import com.google.firebase.firestore.core.FieldFilter;
@@ -222,16 +223,15 @@ public void deleteFieldIndex(FieldIndex index) {
222223
}
223224

224225
@Override
225-
public void updateIndexEntries(Collection<Document> documents) {
226+
public void updateIndexEntries(ImmutableSortedMap<DocumentKey, Document> documents) {
226227
hardAssert(started, "IndexManager not started");
227-
for (Document document : documents) {
228-
Collection<FieldIndex> fieldIndexes = getFieldIndexes(document.getKey().getCollectionGroup());
228+
for (Map.Entry<DocumentKey, Document> entry : documents) {
229+
Collection<FieldIndex> fieldIndexes = getFieldIndexes(entry.getKey().getCollectionGroup());
229230
for (FieldIndex fieldIndex : fieldIndexes) {
230-
SortedSet<IndexEntry> existingEntries =
231-
getExistingIndexEntries(document.getKey(), fieldIndex);
232-
SortedSet<IndexEntry> newEntries = computeIndexEntries(document, fieldIndex);
231+
SortedSet<IndexEntry> existingEntries = getExistingIndexEntries(entry.getKey(), fieldIndex);
232+
SortedSet<IndexEntry> newEntries = computeIndexEntries(entry.getValue(), fieldIndex);
233233
if (!existingEntries.equals(newEntries)) {
234-
updateEntries(document, existingEntries, newEntries);
234+
updateEntries(entry.getValue(), existingEntries, newEntries);
235235
}
236236
}
237237
}

firebase-firestore/src/main/java/com/google/firebase/firestore/local/SQLitePersistence.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,10 @@
5454
* helper routines that make dealing with SQLite much more pleasant.
5555
*/
5656
public final class SQLitePersistence extends Persistence {
57+
/**
58+
* The maximum number of bind args for a single statement. Set to 900 instead of 999 for safety.
59+
*/
60+
public static final int MAX_ARGS = 900;
5761

5862
/**
5963
* Creates the database name that is used to identify the database to be used with a Firestore

0 commit comments

Comments
 (0)