Skip to content

Commit 1a48965

Browse files
Decode Overlays in background
1 parent 149529a commit 1a48965

File tree

8 files changed

+214
-89
lines changed

8 files changed

+214
-89
lines changed

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

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@
2020
import com.google.firebase.firestore.model.mutation.Mutation;
2121
import com.google.firebase.firestore.model.mutation.Overlay;
2222
import java.util.Map;
23+
import java.util.SortedSet;
2324

2425
/**
2526
* Provides methods to read and write document overlays.
@@ -38,6 +39,12 @@ public interface DocumentOverlayCache {
3839
@Nullable
3940
Overlay getOverlay(DocumentKey key);
4041

42+
/**
43+
* Gets the saved overlay mutation for the given document keys. Skips keys for which there are no
44+
* overlays.
45+
*/
46+
Map<DocumentKey, Overlay> getOverlays(SortedSet<DocumentKey> keys);
47+
4148
/**
4249
* Saves the given document key to mutation map to persistence as overlays. All overlays will have
4350
* their largest batch id set to {@code largestBatchId}.

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

Lines changed: 23 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,9 @@
3939
import java.util.List;
4040
import java.util.Map;
4141
import java.util.Set;
42+
import java.util.SortedSet;
4243
import java.util.TreeMap;
44+
import java.util.TreeSet;
4345

4446
/**
4547
* A readonly view of the local state of all documents we're tracking (i.e. we have a cached version
@@ -115,24 +117,20 @@ ImmutableSortedMap<DocumentKey, Document> getDocuments(Iterable<DocumentKey> key
115117
*/
116118
ImmutableSortedMap<DocumentKey, Document> getLocalViewOfDocuments(
117119
Map<DocumentKey, MutableDocument> docs, Set<DocumentKey> existenceStateChanged) {
118-
return computeViews(docs, Collections.emptyMap(), existenceStateChanged);
120+
Map<DocumentKey, Overlay> overlays = new HashMap<>();
121+
populateOverlays(overlays, docs.keySet());
122+
return computeViews(docs, overlays, existenceStateChanged);
119123
}
120124

121-
/**
122-
* Computes the local view for doc, applying overlays from both {@code memoizedOverlays} and the
123-
* overlay cache.
124-
*/
125+
/*Computes the local view for doc */
125126
private ImmutableSortedMap<DocumentKey, Document> computeViews(
126127
Map<DocumentKey, MutableDocument> docs,
127-
Map<DocumentKey, Overlay> memoizedOverlays,
128+
Map<DocumentKey, Overlay> overlays,
128129
Set<DocumentKey> existenceStateChanged) {
129130
ImmutableSortedMap<DocumentKey, Document> results = emptyDocumentMap();
130131
Map<DocumentKey, MutableDocument> recalculateDocuments = new HashMap<>();
131132
for (MutableDocument doc : docs.values()) {
132-
Overlay overlay =
133-
memoizedOverlays.containsKey(doc.getKey())
134-
? memoizedOverlays.get(doc.getKey())
135-
: documentOverlayCache.getOverlay(doc.getKey());
133+
Overlay overlay = overlays.get(doc.getKey());
136134
// Recalculate an overlay if the document's existence state is changed due to a remote
137135
// event *and* the overlay is a PatchMutation. This is because document existence state
138136
// can change if some patch mutation's preconditions are met.
@@ -290,11 +288,26 @@ LocalDocumentsResult getNextDocuments(String collectionGroup, IndexOffset offset
290288
largestBatchId = Math.max(largestBatchId, overlay.getLargestBatchId());
291289
}
292290

291+
populateOverlays(overlays, docs.keySet());
293292
ImmutableSortedMap<DocumentKey, Document> localDocs =
294293
computeViews(docs, overlays, Collections.emptySet());
295294
return new LocalDocumentsResult(largestBatchId, localDocs);
296295
}
297296

297+
/**
298+
* Fetches the overlays for {@code keys} and adds them to provided overlay map if the map does not
299+
* already contain an entry for the given key.
300+
*/
301+
private void populateOverlays(Map<DocumentKey, Overlay> overlays, Set<DocumentKey> keys) {
302+
SortedSet<DocumentKey> missingOverlays = new TreeSet<>();
303+
for (DocumentKey key : keys) {
304+
if (!overlays.containsKey(key)) {
305+
missingOverlays.add(key);
306+
}
307+
}
308+
overlays.putAll(documentOverlayCache.getOverlays(missingOverlays));
309+
}
310+
298311
private ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingCollectionQuery(
299312
Query query, IndexOffset offset) {
300313
Map<DocumentKey, MutableDocument> remoteDocuments =

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

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@
2424
import java.util.Map;
2525
import java.util.Set;
2626
import java.util.SortedMap;
27+
import java.util.SortedSet;
2728
import java.util.TreeMap;
2829

2930
public class MemoryDocumentOverlayCache implements DocumentOverlayCache {
@@ -38,6 +39,17 @@ public Overlay getOverlay(DocumentKey key) {
3839
return overlays.get(key);
3940
}
4041

42+
public Map<DocumentKey, Overlay> getOverlays(SortedSet<DocumentKey> keys) {
43+
Map<DocumentKey, Overlay> result = new HashMap<>();
44+
for (DocumentKey key : keys) {
45+
Overlay overlay = overlays.get(key);
46+
if (overlay != null) {
47+
result.put(key, overlay);
48+
}
49+
}
50+
return result;
51+
}
52+
4153
private void saveOverlay(int largestBatchId, @Nullable Mutation mutation) {
4254
if (mutation == null) {
4355
return;

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

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,24 @@
1616

1717
import static com.google.firebase.firestore.util.Assert.fail;
1818

19+
import android.database.Cursor;
1920
import androidx.annotation.Nullable;
2021
import com.google.firebase.firestore.auth.User;
2122
import com.google.firebase.firestore.model.DocumentKey;
2223
import com.google.firebase.firestore.model.ResourcePath;
2324
import com.google.firebase.firestore.model.mutation.Mutation;
2425
import com.google.firebase.firestore.model.mutation.Overlay;
26+
import com.google.firebase.firestore.util.BackgroundQueue;
27+
import com.google.firebase.firestore.util.Executors;
2528
import com.google.firestore.v1.Write;
2629
import com.google.protobuf.InvalidProtocolBufferException;
30+
import java.util.ArrayList;
31+
import java.util.Arrays;
2732
import java.util.HashMap;
33+
import java.util.List;
2834
import java.util.Map;
35+
import java.util.SortedSet;
36+
import java.util.concurrent.Executor;
2937

3038
public class SQLiteDocumentOverlayCache implements DocumentOverlayCache {
3139
private final SQLitePersistence db;
@@ -47,7 +55,49 @@ public Overlay getOverlay(DocumentKey key) {
4755
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
4856
+ "WHERE uid = ? AND collection_path = ? AND document_id = ?")
4957
.binding(uid, collectionPath, documentId)
50-
.firstValue(this::decodeOverlay);
58+
.firstValue(row -> this.decodeOverlay(row.getBlob(0), row.getInt(1)));
59+
}
60+
61+
@Override
62+
public Map<DocumentKey, Overlay> getOverlays(SortedSet<DocumentKey> keys) {
63+
Map<DocumentKey, Overlay> result = new HashMap<>();
64+
65+
BackgroundQueue backgroundQueue = new BackgroundQueue();
66+
ResourcePath currentCollectionPath = ResourcePath.EMPTY;
67+
List<Object> currentDocumentIds = new ArrayList<>();
68+
for (DocumentKey key : keys) {
69+
if (!currentCollectionPath.equals(key.getCollectionPath())) {
70+
processSingleCollection(result, backgroundQueue, currentCollectionPath, currentDocumentIds);
71+
currentDocumentIds = new ArrayList<>();
72+
}
73+
currentCollectionPath = key.getCollectionPath();
74+
currentDocumentIds.add(key.getDocumentId());
75+
}
76+
77+
processSingleCollection(result, backgroundQueue, currentCollectionPath, currentDocumentIds);
78+
backgroundQueue.drain();
79+
return result;
80+
}
81+
82+
/** Reads the overlays for the documents in a single collection. */
83+
private void processSingleCollection(
84+
Map<DocumentKey, Overlay> result,
85+
BackgroundQueue backgroundQueue,
86+
ResourcePath collectionPath,
87+
List<Object> documentIds) {
88+
SQLitePersistence.LongQuery longQuery =
89+
new SQLitePersistence.LongQuery(
90+
db,
91+
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
92+
+ "WHERE uid = ? AND collection_path = ? AND document_id IN (",
93+
Arrays.asList(uid, EncodedPath.encode(collectionPath)),
94+
documentIds,
95+
")");
96+
while (longQuery.hasMoreSubqueries()) {
97+
longQuery
98+
.performNextSubquery()
99+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
100+
}
51101
}
52102

53103
private void saveOverlay(int largestBatchId, DocumentKey key, @Nullable Mutation mutation) {
@@ -83,49 +133,48 @@ public void removeOverlaysForBatchId(int batchId) {
83133

84134
@Override
85135
public Map<DocumentKey, Overlay> getOverlays(ResourcePath collection, int sinceBatchId) {
86-
String collectionPath = EncodedPath.encode(collection);
87-
88136
Map<DocumentKey, Overlay> result = new HashMap<>();
137+
BackgroundQueue backgroundQueue = new BackgroundQueue();
89138
db.query(
90139
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
91140
+ "WHERE uid = ? AND collection_path = ? AND largest_batch_id > ?")
92-
.binding(uid, collectionPath, sinceBatchId)
93-
.forEach(
94-
row -> {
95-
Overlay overlay = decodeOverlay(row);
96-
result.put(overlay.getKey(), overlay);
97-
});
98-
141+
.binding(uid, EncodedPath.encode(collection), sinceBatchId)
142+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
143+
backgroundQueue.drain();
99144
return result;
100145
}
101146

102147
@Override
103148
public Map<DocumentKey, Overlay> getOverlays(
104149
String collectionGroup, int sinceBatchId, int count) {
105150
Map<DocumentKey, Overlay> result = new HashMap<>();
106-
Overlay[] lastOverlay = new Overlay[] {null};
151+
String[] lastCollectionPath = new String[] {null};
152+
String[] lastDocumentPath = new String[] {null};
153+
int[] lastLargestBatchId = new int[] {0};
107154

155+
BackgroundQueue backgroundQueue = new BackgroundQueue();
108156
db.query(
109-
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
157+
"SELECT overlay_mutation, largest_batch_id, collection_path, document_id "
158+
+ " FROM document_overlays "
110159
+ "WHERE uid = ? AND collection_group = ? AND largest_batch_id > ? "
111160
+ "ORDER BY largest_batch_id, collection_path, document_id LIMIT ?")
112161
.binding(uid, collectionGroup, sinceBatchId, count)
113162
.forEach(
114163
row -> {
115-
lastOverlay[0] = decodeOverlay(row);
116-
result.put(lastOverlay[0].getKey(), lastOverlay[0]);
164+
lastLargestBatchId[0] = row.getInt(1);
165+
lastCollectionPath[0] = row.getString(2);
166+
lastDocumentPath[0] = row.getString(3);
167+
processOverlaysInBackground(backgroundQueue, result, row);
117168
});
118169

119-
if (lastOverlay[0] == null) {
170+
if (lastCollectionPath[0] == null) {
120171
return result;
121172
}
122173

123174
// This function should not return partial batch overlays, even if the number of overlays in the
124175
// result set exceeds the given `count` argument. Since the `LIMIT` in the above query might
125176
// result in a partial batch, the following query appends any remaining overlays for the last
126177
// batch.
127-
DocumentKey key = lastOverlay[0].getKey();
128-
String encodedCollectionPath = EncodedPath.encode(key.getCollectionPath());
129178
db.query(
130179
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
131180
+ "WHERE uid = ? AND collection_group = ? "
@@ -134,23 +183,35 @@ public Map<DocumentKey, Overlay> getOverlays(
134183
.binding(
135184
uid,
136185
collectionGroup,
137-
encodedCollectionPath,
138-
encodedCollectionPath,
139-
key.getDocumentId(),
140-
lastOverlay[0].getLargestBatchId())
141-
.forEach(
142-
row -> {
143-
Overlay overlay = decodeOverlay(row);
144-
result.put(overlay.getKey(), overlay);
145-
});
146-
186+
lastCollectionPath[0],
187+
lastCollectionPath[0],
188+
lastDocumentPath[0],
189+
lastLargestBatchId[0])
190+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
191+
backgroundQueue.drain();
147192
return result;
148193
}
149194

150-
private Overlay decodeOverlay(android.database.Cursor row) {
195+
private void processOverlaysInBackground(
196+
BackgroundQueue backgroundQueue, Map<DocumentKey, Overlay> results, Cursor row) {
197+
byte[] rawMutation = row.getBlob(0);
198+
int largestBatchId = row.getInt(1);
199+
200+
// Since scheduling background tasks incurs overhead, we only dispatch to a
201+
// background thread if there are still some documents remaining.
202+
Executor executor = row.isLast() ? Executors.DIRECT_EXECUTOR : backgroundQueue;
203+
executor.execute(
204+
() -> {
205+
Overlay document = decodeOverlay(rawMutation, largestBatchId);
206+
synchronized (results) {
207+
results.put(document.getKey(), document);
208+
}
209+
});
210+
}
211+
212+
private Overlay decodeOverlay(byte[] rawMutation, int largestBatchId) {
151213
try {
152-
Write write = Write.parseFrom(row.getBlob(0));
153-
int largestBatchId = row.getInt(1);
214+
Write write = Write.parseFrom(rawMutation);
154215
Mutation mutation = serializer.decodeMutation(write);
155216
return Overlay.create(largestBatchId, mutation);
156217
} catch (InvalidProtocolBufferException e) {

0 commit comments

Comments
 (0)