Skip to content

Commit 6ab0195

Browse files
authored
Minimize rereading of batches in FSTLocalDocumentsView documentsMatchingCollectionQuery (#1533)
Previously in `documentsMatchingCollectionQuery`, write batches were read three times; in fact, all relevant batches will always be contained inside `allMutationBatchesAffectingQuery` (which is also more efficient); the other two calls were redundant.
1 parent e9b5695 commit 6ab0195

File tree

2 files changed

+36
-73
lines changed

2 files changed

+36
-73
lines changed

Firestore/Source/Local/FSTLocalDocumentsView.mm

Lines changed: 26 additions & 73 deletions
Original file line numberDiff line numberDiff line change
@@ -70,8 +70,12 @@ - (nullable FSTMaybeDocument *)documentForKey:(const DocumentKey &)key {
7070
// Internal version of documentForKey: which allows reusing `batches`.
7171
- (nullable FSTMaybeDocument *)documentForKey:(const DocumentKey &)key
7272
inBatches:(NSArray<FSTMutationBatch *> *)batches {
73-
FSTMaybeDocument *_Nullable remoteDoc = [self.remoteDocumentCache entryForKey:key];
74-
return [self localDocument:remoteDoc key:key inBatches:batches];
73+
FSTMaybeDocument *_Nullable document = [self.remoteDocumentCache entryForKey:key];
74+
for (FSTMutationBatch *batch in batches) {
75+
document = [batch applyTo:document documentKey:key];
76+
}
77+
78+
return document;
7579
}
7680

7781
- (FSTMaybeDocumentDictionary *)documentsForKeys:(const DocumentKeySet &)keys {
@@ -110,37 +114,34 @@ - (FSTDocumentDictionary *)documentsMatchingDocumentQuery:(const ResourcePath &)
110114
}
111115

112116
- (FSTDocumentDictionary *)documentsMatchingCollectionQuery:(FSTQuery *)query {
113-
// Query the remote documents and overlay mutations.
114-
// TODO(mikelehen): There may be significant overlap between the mutations affecting these
115-
// remote documents and the allMutationBatchesAffectingQuery mutations. Consider optimizing.
116117
__block FSTDocumentDictionary *results = [self.remoteDocumentCache documentsMatchingQuery:query];
117-
results = [self localDocuments:results];
118-
119-
// Now use the mutation queue to discover any other documents that may match the query after
120-
// applying mutations.
121-
DocumentKeySet matchingKeys;
122-
NSArray<FSTMutationBatch *> *matchingMutationBatches =
118+
// Get locally persisted mutation batches.
119+
NSArray<FSTMutationBatch *> *matchingBatches =
123120
[self.mutationQueue allMutationBatchesAffectingQuery:query];
124-
for (FSTMutationBatch *batch in matchingMutationBatches) {
121+
122+
for (FSTMutationBatch *batch in matchingBatches) {
125123
for (FSTMutation *mutation in batch.mutations) {
126-
// TODO(mikelehen): PERF: Check if this mutation actually affects the query to reduce work.
124+
// Only process documents belonging to the collection.
125+
if (!query.path.IsImmediateParentOf(mutation.key.path())) {
126+
continue;
127+
}
127128

128-
// If the key is already in the results, we can skip it.
129-
if (![results containsKey:mutation.key]) {
130-
matchingKeys = matchingKeys.insert(mutation.key);
129+
FSTDocumentKey *key = static_cast<FSTDocumentKey *>(mutation.key);
130+
// baseDoc may be nil for the documents that weren't yet written to the backend.
131+
FSTMaybeDocument *baseDoc = results[key];
132+
FSTMaybeDocument *mutatedDoc =
133+
[mutation applyTo:baseDoc baseDocument:baseDoc localWriteTime:batch.localWriteTime];
134+
135+
if ([mutatedDoc isKindOfClass:[FSTDeletedDocument class]]) {
136+
results = [results dictionaryByRemovingObjectForKey:key];
137+
} else if ([mutatedDoc isKindOfClass:[FSTDocument class]]) {
138+
results = [results dictionaryBySettingObject:(FSTDocument *)mutatedDoc forKey:key];
139+
} else {
140+
HARD_FAIL("Unknown document: %s", mutatedDoc);
131141
}
132142
}
133143
}
134144

135-
// Now add in results for the matchingKeys.
136-
FSTMaybeDocumentDictionary *matchingKeysDocs = [self documentsForKeys:matchingKeys];
137-
[matchingKeysDocs
138-
enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key, FSTMaybeDocument *doc, BOOL *stop) {
139-
if ([doc isKindOfClass:[FSTDocument class]]) {
140-
results = [results dictionaryBySettingObject:(FSTDocument *)doc forKey:key];
141-
}
142-
}];
143-
144145
// Finally, filter out any documents that don't actually match the query. Note that the extra
145146
// reference here prevents ARC from deallocating the initial unfiltered results while we're
146147
// enumerating them.
@@ -155,54 +156,6 @@ - (FSTDocumentDictionary *)documentsMatchingCollectionQuery:(FSTQuery *)query {
155156
return results;
156157
}
157158

158-
/**
159-
* Takes a remote document and applies local mutations to generate the local view of the
160-
* document.
161-
*
162-
* @param document The base remote document to apply mutations to.
163-
* @param documentKey The key of the document (necessary when remoteDocument is nil).
164-
*/
165-
- (nullable FSTMaybeDocument *)localDocument:(nullable FSTMaybeDocument *)document
166-
key:(const DocumentKey &)documentKey
167-
inBatches:(NSArray<FSTMutationBatch *> *)batches {
168-
for (FSTMutationBatch *batch in batches) {
169-
document = [batch applyTo:document documentKey:documentKey];
170-
}
171-
172-
return document;
173-
}
174-
175-
/**
176-
* Takes a set of remote documents and applies local mutations to generate the local view of
177-
* the documents.
178-
*
179-
* @param documents The base remote documents to apply mutations to.
180-
* @return The local view of the documents.
181-
*/
182-
- (FSTDocumentDictionary *)localDocuments:(FSTDocumentDictionary *)documents {
183-
__block DocumentKeySet keySet;
184-
[documents
185-
enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key, FSTDocument *doc, BOOL *stop) {
186-
keySet = keySet.insert(doc.key);
187-
}];
188-
NSArray<FSTMutationBatch *> *batches =
189-
[self.mutationQueue allMutationBatchesAffectingDocumentKeys:keySet];
190-
191-
__block FSTDocumentDictionary *result = documents;
192-
[documents enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key, FSTDocument *remoteDocument,
193-
BOOL *stop) {
194-
FSTMaybeDocument *mutatedDoc = [self localDocument:remoteDocument key:key inBatches:batches];
195-
if ([mutatedDoc isKindOfClass:[FSTDeletedDocument class]]) {
196-
result = [result dictionaryByRemovingObjectForKey:key];
197-
} else if ([mutatedDoc isKindOfClass:[FSTDocument class]]) {
198-
result = [result dictionaryBySettingObject:(FSTDocument *)mutatedDoc forKey:key];
199-
} else {
200-
HARD_FAIL("Unknown document: %s", mutatedDoc);
201-
}
202-
}];
203-
return result;
204-
}
205-
206159
@end
207160

208161
NS_ASSUME_NONNULL_END

Firestore/core/src/firebase/firestore/model/base_path.h

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -139,6 +139,16 @@ class BasePath {
139139
return size() <= rhs.size() && std::equal(begin(), end(), rhs.begin());
140140
}
141141

142+
/**
143+
* Returns true if the given argument is a direct child of this path.
144+
*
145+
* Empty path is a parent of any path that consists of a single segment.
146+
*/
147+
bool IsImmediateParentOf(const T& potential_child) const {
148+
return size() + 1 == potential_child.size() &&
149+
std::equal(begin(), end(), potential_child.begin());
150+
}
151+
142152
bool operator==(const BasePath& rhs) const {
143153
return segments_ == rhs.segments_;
144154
}

0 commit comments

Comments
 (0)