Skip to content

Commit ff95ffc

Browse files
authored
In FSTLocalDocumentsView, search for all batches affecting a set of keys in one go (#1505)
This uses the newly-added allMutationBatchesAffectingDocumentKeys to find/deserialize all such batches in one go and then reuse the batches while processing a set of keys.
1 parent 6d6ed82 commit ff95ffc

File tree

3 files changed

+85
-12
lines changed

3 files changed

+85
-12
lines changed

Firestore/Example/Tests/Integration/API/FIRWriteBatchTests.mm

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
#import <FirebaseFirestore/FirebaseFirestore.h>
1818

1919
#import <XCTest/XCTest.h>
20+
#include <mach/mach.h>
21+
22+
#include <cstdint>
2023

2124
#import "Firestore/Example/Tests/Util/FSTEventAccumulator.h"
2225
#import "Firestore/Example/Tests/Util/FSTIntegrationTestCase.h"
@@ -323,4 +326,54 @@ - (void)testUpdateNestedFields {
323326
[self awaitExpectations];
324327
}
325328

329+
// Returns how much memory the test application is currently using, in megabytes (fractional part is
330+
// truncated), or -1 if the OS call fails.
331+
// TODO(varconst): move the helper function and the test into a new test target for performance
332+
// testing.
333+
int64_t GetCurrentMemoryUsedInMb() {
334+
mach_task_basic_info taskInfo;
335+
mach_msg_type_number_t taskInfoSize = MACH_TASK_BASIC_INFO_COUNT;
336+
const auto errorCode =
337+
task_info(mach_task_self(), MACH_TASK_BASIC_INFO, (task_info_t)&taskInfo, &taskInfoSize);
338+
if (errorCode == KERN_SUCCESS) {
339+
const int bytesInMegabyte = 1000 * 1000;
340+
return taskInfo.resident_size / bytesInMegabyte;
341+
}
342+
return -1;
343+
}
344+
345+
- (void)testReasonableMemoryUsageForLotsOfMutations {
346+
XCTestExpectation *expectation =
347+
[self expectationWithDescription:@"testReasonableMemoryUsageForLotsOfMutations"];
348+
349+
FIRDocumentReference *mainDoc = [self documentRef];
350+
FIRWriteBatch *batch = [mainDoc.firestore batch];
351+
352+
// >= 500 mutations will be rejected, so use 500-1 mutations
353+
for (int i = 0; i != 500 - 1; ++i) {
354+
FIRDocumentReference *nestedDoc = [[mainDoc collectionWithPath:@"nested"] documentWithAutoID];
355+
// The exact data doesn't matter; what is important is the large number of mutations.
356+
[batch setData:@{
357+
@"a" : @"foo",
358+
@"b" : @"bar",
359+
}
360+
forDocument:nestedDoc];
361+
}
362+
363+
const int64_t memoryUsedBeforeCommitMb = GetCurrentMemoryUsedInMb();
364+
XCTAssertNotEqual(memoryUsedBeforeCommitMb, -1);
365+
[batch commitWithCompletion:^(NSError *_Nullable error) {
366+
XCTAssertNil(error);
367+
const int64_t memoryUsedAfterCommitMb = GetCurrentMemoryUsedInMb();
368+
XCTAssertNotEqual(memoryUsedAfterCommitMb, -1);
369+
const int64_t memoryDeltaMb = memoryUsedAfterCommitMb - memoryUsedBeforeCommitMb;
370+
// This by its nature cannot be a precise value. In debug mode, the increase on simulator
371+
// seems to be around 90 MB. A regression would be on the scale of 500Mb.
372+
XCTAssertLessThan(memoryDeltaMb, 150);
373+
374+
[expectation fulfill];
375+
}];
376+
[self awaitExpectations];
377+
}
378+
326379
@end

Firestore/Source/Local/FSTLocalDocumentsView.mm

Lines changed: 31 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -62,21 +62,32 @@ - (instancetype)initWithRemoteDocumentCache:(id<FSTRemoteDocumentCache>)remoteDo
6262
}
6363

6464
- (nullable FSTMaybeDocument *)documentForKey:(const DocumentKey &)key {
65+
NSArray<FSTMutationBatch *> *batches =
66+
[self.mutationQueue allMutationBatchesAffectingDocumentKey:key];
67+
return [self documentForKey:key inBatches:batches];
68+
}
69+
70+
// Internal version of documentForKey: which allows reusing `batches`.
71+
- (nullable FSTMaybeDocument *)documentForKey:(const DocumentKey &)key
72+
inBatches:(NSArray<FSTMutationBatch *> *)batches {
6573
FSTMaybeDocument *_Nullable remoteDoc = [self.remoteDocumentCache entryForKey:key];
66-
return [self localDocument:remoteDoc key:key];
74+
return [self localDocument:remoteDoc key:key inBatches:batches];
6775
}
6876

6977
- (FSTMaybeDocumentDictionary *)documentsForKeys:(const DocumentKeySet &)keys {
7078
FSTMaybeDocumentDictionary *results = [FSTMaybeDocumentDictionary maybeDocumentDictionary];
79+
NSArray<FSTMutationBatch *> *batches =
80+
[self.mutationQueue allMutationBatchesAffectingDocumentKeys:keys];
7181
for (const DocumentKey &key : keys) {
7282
// TODO(mikelehen): PERF: Consider fetching all remote documents at once rather than one-by-one.
73-
FSTMaybeDocument *maybeDoc = [self documentForKey:key];
83+
FSTMaybeDocument *maybeDoc = [self documentForKey:key inBatches:batches];
7484
// TODO(http://b/32275378): Don't conflate missing / deleted.
7585
if (!maybeDoc) {
7686
maybeDoc = [FSTDeletedDocument documentWithKey:key version:SnapshotVersion::None()];
7787
}
7888
results = [results dictionaryBySettingObject:maybeDoc forKey:key];
7989
}
90+
8091
return results;
8192
}
8293

@@ -122,12 +133,13 @@ - (FSTDocumentDictionary *)documentsMatchingCollectionQuery:(FSTQuery *)query {
122133
}
123134

124135
// Now add in results for the matchingKeys.
125-
for (const DocumentKey &key : matchingKeys) {
126-
FSTMaybeDocument *doc = [self documentForKey:key];
127-
if ([doc isKindOfClass:[FSTDocument class]]) {
128-
results = [results dictionaryBySettingObject:(FSTDocument *)doc forKey:key];
129-
}
130-
}
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+
}];
131143

132144
// Finally, filter out any documents that don't actually match the query. Note that the extra
133145
// reference here prevents ARC from deallocating the initial unfiltered results while we're
@@ -151,9 +163,8 @@ - (FSTDocumentDictionary *)documentsMatchingCollectionQuery:(FSTQuery *)query {
151163
* @param documentKey The key of the document (necessary when remoteDocument is nil).
152164
*/
153165
- (nullable FSTMaybeDocument *)localDocument:(nullable FSTMaybeDocument *)document
154-
key:(const DocumentKey &)documentKey {
155-
NSArray<FSTMutationBatch *> *batches =
156-
[self.mutationQueue allMutationBatchesAffectingDocumentKey:documentKey];
166+
key:(const DocumentKey &)documentKey
167+
inBatches:(NSArray<FSTMutationBatch *> *)batches {
157168
for (FSTMutationBatch *batch in batches) {
158169
document = [batch applyTo:document documentKey:documentKey];
159170
}
@@ -169,10 +180,18 @@ - (nullable FSTMaybeDocument *)localDocument:(nullable FSTMaybeDocument *)docume
169180
* @return The local view of the documents.
170181
*/
171182
- (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+
172191
__block FSTDocumentDictionary *result = documents;
173192
[documents enumerateKeysAndObjectsUsingBlock:^(FSTDocumentKey *key, FSTDocument *remoteDocument,
174193
BOOL *stop) {
175-
FSTMaybeDocument *mutatedDoc = [self localDocument:remoteDocument key:key];
194+
FSTMaybeDocument *mutatedDoc = [self localDocument:remoteDocument key:key inBatches:batches];
176195
if ([mutatedDoc isKindOfClass:[FSTDeletedDocument class]]) {
177196
result = [result dictionaryByRemovingObjectForKey:key];
178197
} else if ([mutatedDoc isKindOfClass:[FSTDocument class]]) {

scripts/cpplint.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -416,6 +416,7 @@
416416

417417
_C_SYSTEM_DIRECTORIES = frozenset([
418418
'libkern',
419+
'mach',
419420
'sys',
420421
])
421422

0 commit comments

Comments
 (0)