-
Notifications
You must be signed in to change notification settings - Fork 619
Performance optimizations to speed up reading large collections #123
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from 11 commits
876e4eb
cfa2338
999d655
7fefc65
8b2af95
3585c3a
2438147
2f47171
4b02f9b
3e005bd
284e08b
56e1681
323d697
7ad6899
de3486c
44682ab
d5357d3
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -69,20 +69,46 @@ private MaybeDocument getDocument(DocumentKey key, List<MutationBatch> inBatches | |
return document; | ||
} | ||
|
||
// Returns the view of the given {@code docs} as they would appear after applying all mutations in | ||
// the given {@code batches}. | ||
private Map<DocumentKey, MaybeDocument> applyLocalMutationsToDocuments( | ||
Map<DocumentKey, MaybeDocument> docs, List<MutationBatch> batches) { | ||
for (Map.Entry<DocumentKey, MaybeDocument> base : docs.entrySet()) { | ||
MaybeDocument localView = base.getValue(); | ||
for (MutationBatch batch : batches) { | ||
localView = batch.applyToLocalView(base.getKey(), localView); | ||
} | ||
base.setValue(localView); | ||
} | ||
|
||
return docs; | ||
} | ||
|
||
/** | ||
* Gets the local view of the documents identified by {@code keys}. | ||
* | ||
* <p>If we don't have cached state for a document in {@code keys}, a NoDocument will be stored | ||
* for that key in the resulting set. | ||
*/ | ||
ImmutableSortedMap<DocumentKey, MaybeDocument> getDocuments(Iterable<DocumentKey> keys) { | ||
Map<DocumentKey, MaybeDocument> docs = remoteDocumentCache.getAll(keys); | ||
return getDocuments(docs); | ||
} | ||
|
||
/** | ||
* Similar to {@code #getDocuments}, but creates the local view from the given {@code baseDocs} | ||
* without retrieving documents from the local store. | ||
*/ | ||
ImmutableSortedMap<DocumentKey, MaybeDocument> getDocuments( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I feel like this method should have a different name (passing documents to a method called So maybe There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I agree. I tentatively renamed the function to |
||
Map<DocumentKey, MaybeDocument> baseDocs) { | ||
ImmutableSortedMap<DocumentKey, MaybeDocument> results = emptyMaybeDocumentMap(); | ||
|
||
List<MutationBatch> batches = mutationQueue.getAllMutationBatchesAffectingDocumentKeys(keys); | ||
for (DocumentKey key : keys) { | ||
// TODO: PERF: Consider fetching all remote documents at once rather than | ||
// one-by-one. | ||
MaybeDocument maybeDoc = getDocument(key, batches); | ||
List<MutationBatch> batches = | ||
mutationQueue.getAllMutationBatchesAffectingDocumentKeys(baseDocs.keySet()); | ||
Map<DocumentKey, MaybeDocument> docs = applyLocalMutationsToDocuments(baseDocs, batches); | ||
for (Map.Entry<DocumentKey, MaybeDocument> entry : docs.entrySet()) { | ||
DocumentKey key = entry.getKey(); | ||
MaybeDocument maybeDoc = entry.getValue(); | ||
// TODO: Don't conflate missing / deleted. | ||
if (maybeDoc == null) { | ||
maybeDoc = new NoDocument(key, SnapshotVersion.NONE, /*hasCommittedMutations=*/ false); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -48,13 +48,19 @@ public LocalSerializer(RemoteSerializer rpcSerializer) { | |
com.google.firebase.firestore.proto.MaybeDocument encodeMaybeDocument(MaybeDocument document) { | ||
com.google.firebase.firestore.proto.MaybeDocument.Builder builder = | ||
com.google.firebase.firestore.proto.MaybeDocument.newBuilder(); | ||
|
||
if (document instanceof NoDocument) { | ||
NoDocument noDocument = (NoDocument) document; | ||
builder.setNoDocument(encodeNoDocument(noDocument)); | ||
builder.setHasCommittedMutations(noDocument.hasCommittedMutations()); | ||
} else if (document instanceof Document) { | ||
Document existingDocument = (Document) document; | ||
builder.setDocument(encodeDocument(existingDocument)); | ||
// Use the memoized encoded form if it exists. | ||
if (existingDocument.getProto() != null) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimization 1 ("Avoid encode"): avoid serializing a |
||
builder.setDocument(existingDocument.getProto()); | ||
} else { | ||
builder.setDocument(encodeDocument(existingDocument)); | ||
} | ||
builder.setHasCommittedMutations(existingDocument.hasCommittedMutations()); | ||
} else if (document instanceof UnknownDocument) { | ||
builder.setUnknownDocument(encodeUnknownDocument((UnknownDocument) document)); | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ | |
import com.google.firebase.firestore.remote.TargetChange; | ||
import com.google.firebase.firestore.util.Logger; | ||
import com.google.protobuf.ByteString; | ||
import java.util.HashMap; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.Map; | ||
|
@@ -329,14 +330,19 @@ public ImmutableSortedMap<DocumentKey, MaybeDocument> applyRemoteEvent(RemoteEve | |
} | ||
} | ||
|
||
Set<DocumentKey> changedDocKeys = new HashSet<>(); | ||
Map<DocumentKey, MaybeDocument> changedDocs = new HashMap<>(); | ||
Map<DocumentKey, MaybeDocument> documentUpdates = remoteEvent.getDocumentUpdates(); | ||
Set<DocumentKey> limboDocuments = remoteEvent.getResolvedLimboDocuments(); | ||
// Each loop iteration only affects its "own" doc, so it's safe to get all the remote | ||
mikelehen marked this conversation as resolved.
Show resolved
Hide resolved
|
||
// documents in advance in a single call. | ||
Map<DocumentKey, MaybeDocument> existingDocs = | ||
remoteDocuments.getAll(documentUpdates.keySet()); | ||
|
||
for (Entry<DocumentKey, MaybeDocument> entry : documentUpdates.entrySet()) { | ||
DocumentKey key = entry.getKey(); | ||
MaybeDocument doc = entry.getValue(); | ||
changedDocKeys.add(key); | ||
MaybeDocument existingDoc = remoteDocuments.get(key); | ||
MaybeDocument existingDoc = existingDocs.get(key); | ||
|
||
// If a document update isn't authoritative, make sure we don't | ||
// apply an old document version to the remote cache. We make an | ||
// exception for SnapshotVersion.MIN which can happen for | ||
|
@@ -347,6 +353,7 @@ public ImmutableSortedMap<DocumentKey, MaybeDocument> applyRemoteEvent(RemoteEve | |
|| (authoritativeUpdates.contains(doc.getKey()) && !existingDoc.hasPendingWrites()) | ||
|| doc.getVersion().compareTo(existingDoc.getVersion()) >= 0) { | ||
remoteDocuments.add(doc); | ||
changedDocs.put(key, doc); | ||
} else { | ||
Logger.debug( | ||
"LocalStore", | ||
|
@@ -355,6 +362,7 @@ public ImmutableSortedMap<DocumentKey, MaybeDocument> applyRemoteEvent(RemoteEve | |
key, | ||
existingDoc.getVersion(), | ||
doc.getVersion()); | ||
changedDocs.put(key, existingDoc); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It seems like this probably isn't necessary (nothing changed). WDYT? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, the previous behavior was to add the key to There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Hmm, logically this seems unnecessary. However, the previous behavior added the key to The tests pass either way, but I'm not sure we cover the situation when the server sends outdated docs. All in all, I'm a little wary about this change... What do you think? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Does https://github.com/firebase/firebase-js-sdk/blob/master/packages/firestore/test/unit/specs/listen_spec.test.ts#L228 cover this? In general the best way to see if we have a test covering a behavior is to set a breakpoint and then check if we hit it under the debugger. I applaud your skeptical approach to changing existing behavior, but it's also worth considering that a lot of this code was written in a hurry or has evolved organically over time. For example, we only learned that Watch even had this kind of behavior after we observed it in a bug bash. The code to defend against this was added later and it's likely we just didn't adjust the initial change computation. In any case, our approach in the LocalStore has always been to err on the side of over-notifying because the view code is ultimately responsible for computing what has changed. This likely has no visible effect precisely because the view is discarding updates that don't net any changes, but that doesn't invalidate the logic behind changing this. We just need to avoid under-notifying--that's something for which the view can't compensate. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm pretty sure it's safe. Since we're not updating the cached doc, it logically makes sense not to include it in the changed docs. The result of this method is used to update our Views, and again, since we kept the existing doc, no update should be needed. And this code is exercised by the "Listens: Individual documents cannot revert" spec test. So I'd feel comfortable removing it. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
} | ||
|
||
if (limboDocuments.contains(key)) { | ||
|
@@ -376,7 +384,7 @@ public ImmutableSortedMap<DocumentKey, MaybeDocument> applyRemoteEvent(RemoteEve | |
queryCache.setLastRemoteSnapshotVersion(remoteVersion); | ||
} | ||
|
||
return localDocuments.getDocuments(changedDocKeys); | ||
return localDocuments.getDocuments(changedDocs); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Optimization 3 ("No double get"): avoid retrieving the documents from local database again, we already have them in this function; just apply the pending write batches to them. |
||
}); | ||
} | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -23,6 +23,7 @@ | |
import com.google.firebase.firestore.model.DocumentKey; | ||
import com.google.firebase.firestore.model.MaybeDocument; | ||
import com.google.firebase.firestore.model.ResourcePath; | ||
import java.util.HashMap; | ||
import java.util.Iterator; | ||
import java.util.Map; | ||
import javax.annotation.Nullable; | ||
|
@@ -53,6 +54,21 @@ public MaybeDocument get(DocumentKey key) { | |
return docs.get(key); | ||
} | ||
|
||
@Override | ||
public Map<DocumentKey, MaybeDocument> getAll(Iterable<DocumentKey> keys) { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. One of the reasons I'm returning a |
||
Map<DocumentKey, MaybeDocument> result = new HashMap<>(); | ||
|
||
Iterator<DocumentKey> iter = keys.iterator(); | ||
while (iter.hasNext()) { | ||
DocumentKey key = iter.next(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done. |
||
// Make sure each key has a corresponding entry, which is null in case the document is not | ||
// found. | ||
result.put(key, get(key)); | ||
} | ||
|
||
return result; | ||
} | ||
|
||
@Override | ||
public ImmutableSortedMap<DocumentKey, Document> getAllDocumentsMatchingQuery(Query query) { | ||
ImmutableSortedMap<DocumentKey, Document> result = emptyDocumentMap(); | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the significant refactoring in this file.