Skip to content

Commit f7cf0bc

Browse files
Parse Overlays in background
1 parent d1fc101 commit f7cf0bc

File tree

8 files changed

+213
-88
lines changed

8 files changed

+213
-88
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: 24 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,21 @@ 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());
134+
136135
// Recalculate an overlay if the document's existence state is changed due to a remote
137136
// event *and* the overlay is a PatchMutation. This is because document existence state
138137
// can change if some patch mutation's preconditions are met.
@@ -290,11 +289,26 @@ LocalDocumentsResult getNextDocuments(String collectionGroup, IndexOffset offset
290289
largestBatchId = Math.max(largestBatchId, overlay.getLargestBatchId());
291290
}
292291

292+
populateOverlays(overlays, docs.keySet());
293293
ImmutableSortedMap<DocumentKey, Document> localDocs =
294294
computeViews(docs, overlays, Collections.emptySet());
295295
return new LocalDocumentsResult(largestBatchId, localDocs);
296296
}
297297

298+
/**
299+
* Fetches the overlays for {@code keys} and adds them to provided overlay map if the map does not
300+
* already contain an entry for the given key.
301+
*/
302+
private void populateOverlays(Map<DocumentKey, Overlay> overlays, Set<DocumentKey> keys) {
303+
SortedSet<DocumentKey> missingOverlays = new TreeSet<>();
304+
for (DocumentKey key : keys) {
305+
if (!overlays.containsKey(key)) {
306+
missingOverlays.add(key);
307+
}
308+
}
309+
overlays.putAll(documentOverlayCache.getOverlays(missingOverlays));
310+
}
311+
298312
private ImmutableSortedMap<DocumentKey, Document> getDocumentsMatchingCollectionQuery(
299313
Query query, IndexOffset offset) {
300314
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: 89 additions & 29 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,47 @@ 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+
ResourcePath currentCollectionPath = ResourcePath.EMPTY;
66+
List<Object> currentDocumentIds = new ArrayList<>();
67+
for (DocumentKey key : keys) {
68+
if (!currentCollectionPath.equals(key.getCollectionPath())) {
69+
processSingleCollection(result, currentCollectionPath, currentDocumentIds);
70+
currentDocumentIds = new ArrayList<>();
71+
}
72+
currentCollectionPath = key.getCollectionPath();
73+
currentDocumentIds.add(key.getDocumentId());
74+
}
75+
76+
processSingleCollection(result, currentCollectionPath, currentDocumentIds);
77+
return result;
78+
}
79+
80+
/** Reads the overlays for the documents in a single collection. */
81+
private void processSingleCollection(
82+
Map<DocumentKey, Overlay> result, ResourcePath collectionPath, List<Object> documentIds) {
83+
SQLitePersistence.LongQuery longQuery =
84+
new SQLitePersistence.LongQuery(
85+
db,
86+
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
87+
+ "WHERE uid = ? AND collection_path = ? AND document_id IN (",
88+
Arrays.asList(uid, EncodedPath.encode(collectionPath)),
89+
documentIds,
90+
")");
91+
92+
BackgroundQueue backgroundQueue = new BackgroundQueue();
93+
while (longQuery.hasMoreSubqueries()) {
94+
longQuery
95+
.performNextSubquery()
96+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
97+
}
98+
backgroundQueue.drain();
5199
}
52100

53101
private void saveOverlay(int largestBatchId, DocumentKey key, @Nullable Mutation mutation) {
@@ -83,46 +131,45 @@ public void removeOverlaysForBatchId(int batchId) {
83131

84132
@Override
85133
public Map<DocumentKey, Overlay> getOverlays(ResourcePath collection, int sinceBatchId) {
86-
String collectionPath = EncodedPath.encode(collection);
87-
88134
Map<DocumentKey, Overlay> result = new HashMap<>();
135+
BackgroundQueue backgroundQueue = new BackgroundQueue();
89136
db.query(
90137
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
91138
+ "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-
139+
.binding(uid, EncodedPath.encode(collection), sinceBatchId)
140+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
141+
backgroundQueue.drain();
99142
return result;
100143
}
101144

102145
@Override
103146
public Map<DocumentKey, Overlay> getOverlays(
104147
String collectionGroup, int sinceBatchId, int count) {
105148
Map<DocumentKey, Overlay> result = new HashMap<>();
106-
Overlay[] lastOverlay = new Overlay[] {null};
149+
String[] lastCollectionPath = new String[] {null};
150+
String[] lastDocumentPath = new String[] {null};
151+
int[] lastLargestBatchId = new int[] {0};
107152

153+
BackgroundQueue backgroundQueue = new BackgroundQueue();
108154
db.query(
109-
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
155+
"SELECT overlay_mutation, largest_batch_id, collection_path, document_id "
156+
+ " FROM document_overlays "
110157
+ "WHERE uid = ? AND collection_group = ? AND largest_batch_id > ? "
111158
+ "ORDER BY largest_batch_id, collection_path, document_id LIMIT ?")
112159
.binding(uid, collectionGroup, sinceBatchId, count)
113160
.forEach(
114161
row -> {
115-
lastOverlay[0] = decodeOverlay(row);
116-
result.put(lastOverlay[0].getKey(), lastOverlay[0]);
162+
lastLargestBatchId[0] = row.getInt(1);
163+
lastCollectionPath[0] = row.getString(2);
164+
lastDocumentPath[0] = row.getString(3);
165+
processOverlaysInBackground(backgroundQueue, result, row);
117166
});
118167

119-
if (lastOverlay[0] == null) {
168+
if (lastCollectionPath[0] == null) {
120169
return result;
121170
}
122171

123172
// Finish batch
124-
DocumentKey key = lastOverlay[0].getKey();
125-
String encodedCollectionPath = EncodedPath.encode(key.getCollectionPath());
126173
db.query(
127174
"SELECT overlay_mutation, largest_batch_id FROM document_overlays "
128175
+ "WHERE uid = ? AND collection_group = ? "
@@ -131,23 +178,36 @@ public Map<DocumentKey, Overlay> getOverlays(
131178
.binding(
132179
uid,
133180
collectionGroup,
134-
encodedCollectionPath,
135-
encodedCollectionPath,
136-
key.getDocumentId(),
137-
lastOverlay[0].getLargestBatchId())
138-
.forEach(
139-
row -> {
140-
Overlay overlay = decodeOverlay(row);
141-
result.put(overlay.getKey(), overlay);
142-
});
181+
lastCollectionPath[0],
182+
lastCollectionPath[0],
183+
lastDocumentPath[0],
184+
lastLargestBatchId[0])
185+
.forEach(row -> processOverlaysInBackground(backgroundQueue, result, row));
143186

187+
backgroundQueue.drain();
144188
return result;
145189
}
146190

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

0 commit comments

Comments
 (0)