Skip to content

Port performance optimizations to speed up reading large collections from Android #1433

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

Merged
merged 30 commits into from
Dec 22, 2018
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
3da020e
1
var-const Dec 14, 2018
9717fef
Compiles
var-const Dec 14, 2018
204443e
Compiles, pt 2
var-const Dec 14, 2018
c56820b
Compiles, pt 3m
var-const Dec 14, 2018
f3174ca
[AUTOMATED]: Prettier Code Styling
var-const Dec 14, 2018
36c53d3
Some fixes
var-const Dec 14, 2018
942b0fa
Very hacky version works
var-const Dec 15, 2018
7767b60
Most unit tests pass
var-const Dec 17, 2018
a1ad6d2
[AUTOMATED]: Prettier Code Styling
var-const Dec 17, 2018
cc29731
Undo temp/accidental changes
var-const Dec 17, 2018
307e0b3
applyRemoteEvent, sized entries
var-const Dec 18, 2018
62c627b
Fix failing tests
var-const Dec 18, 2018
8f293eb
Serializer
var-const Dec 18, 2018
4e23f3b
[AUTOMATED]: Prettier Code Styling
var-const Dec 18, 2018
fb751dd
small cleanup
var-const Dec 18, 2018
431f618
Fix accidental
var-const Dec 18, 2018
1681f3d
Comment
var-const Dec 18, 2018
55c7ff3
Review feedback 1, test
var-const Dec 19, 2018
5afa305
[AUTOMATED]: Prettier Code Styling
var-const Dec 19, 2018
56eefbc
Review feedback 1
var-const Dec 20, 2018
5e506fa
Review feedback 2
var-const Dec 20, 2018
7b11cec
[AUTOMATED]: Prettier Code Styling
var-const Dec 20, 2018
1a32b22
Review feedback 3
var-const Dec 21, 2018
17a69a6
Review feedback
var-const Dec 22, 2018
4c270c7
Appease linter
var-const Dec 22, 2018
512667e
Fix node tests
var-const Dec 22, 2018
6cb4bfb
Appease linter 2
var-const Dec 22, 2018
77bf92e
[AUTOMATED]: Prettier Code Styling
var-const Dec 22, 2018
e7b8c8e
Comment
var-const Dec 22, 2018
a0d25f5
Merge branch 'master' into varconst/port-android-1000-reads-3
var-const Dec 22, 2018
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion packages/firestore/src/local/indexeddb_mutation_queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import { BATCHID_UNKNOWN, MutationBatch } from '../model/mutation_batch';
import { ResourcePath } from '../model/path';
import { assert, fail } from '../util/assert';
import { primitiveComparator } from '../util/misc';
import { SortedMap } from '../util/sorted_map';
import { SortedSet } from '../util/sorted_set';

import * as EncodedResourcePath from './encoded_resource_path';
Expand All @@ -46,6 +47,8 @@ import { PersistenceTransaction, ReferenceDelegate } from './persistence';
import { PersistencePromise } from './persistence_promise';
import { SimpleDbStore, SimpleDbTransaction } from './simple_db';

import { AnyJs } from '../../src/util/misc';

/** A mutation queue for a specific user, backed by IndexedDB. */
export class IndexedDbMutationQueue implements MutationQueue {
/**
Expand Down Expand Up @@ -325,7 +328,7 @@ export class IndexedDbMutationQueue implements MutationQueue {

getAllMutationBatchesAffectingDocumentKeys(
transaction: PersistenceTransaction,
documentKeys: DocumentKeySet
documentKeys: SortedMap<DocumentKey, AnyJs>
): PersistencePromise<MutationBatch[]> {
let uniqueBatchIDs = new SortedSet<BatchId>(primitiveComparator);

Expand Down
102 changes: 70 additions & 32 deletions packages/firestore/src/local/indexeddb_remote_document_cache.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,16 +20,16 @@ import {
documentKeySet,
DocumentMap,
documentMap,
DocumentSizeEntry,
DocumentSizeEntries,
nullableMaybeDocumentMap,
NullableMaybeDocumentMap,
DocumentSizeEntry,
MaybeDocumentMap,
maybeDocumentMap
maybeDocumentMap,
nullableMaybeDocumentMap,
NullableMaybeDocumentMap
} from '../model/collections';
import { Document, MaybeDocument, NoDocument } from '../model/document';
import { SortedMap } from '../util/sorted_map';
import { DocumentKey } from '../model/document_key';
import { SortedMap } from '../util/sorted_map';

import { SnapshotVersion } from '../core/snapshot_version';
import { assert, fail } from '../util/assert';
Expand Down Expand Up @@ -187,9 +187,21 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
transaction: PersistenceTransaction,
documentKeys: DocumentKeySet
): PersistencePromise<NullableMaybeDocumentMap> {
return this.getSizedEntries(transaction, documentKeys).next(
result => result.maybeDocuments
);
let results = nullableMaybeDocumentMap();
return this.forEachDbEntry(
transaction,
documentKeys,
(key, dbRemoteDoc) => {
if (dbRemoteDoc) {
results = results.insert(
key,
this.serializer.fromDbRemoteDocument(dbRemoteDoc)
);
} else {
results = results.insert(key, null);
}
}
).next(() => results);
}

/**
Expand All @@ -206,46 +218,72 @@ export class IndexedDbRemoteDocumentCache implements RemoteDocumentCache {
): PersistencePromise<DocumentSizeEntries> {
let results = nullableMaybeDocumentMap();
let sizeMap = new SortedMap<DocumentKey, number>(DocumentKey.comparator);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry, now that getEntries() is no longer a wrapper around getSizedEntries(), can we drop sizeMap and have this be new SortedMap<DocumentKey, DocumentSizeEntry|null> ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

First, I don't feel strongly about this. The reason I set it up that way is so that getEntries in RemoteDocumentChangeBuffer can return the MaybeDocumentMap directly. If this were a sorted map of DocumentSizeEntrys, then getEntries would have to create a new MaybeDocumentMap and fill it with just MaybeDocuments.

It's probably not a big deal, so if you think code clarity is more important here, I'll do the change.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, sorry! I was confused. I thought it was just so the old getEntries() implementation could return the MaybeDocumentMap directly. But I see now that RemoteDocumentChangeBuffer.getEntries() ends up calling getSizedEntries() and using the sizes and also passing the MaybeDocumentMap straight through. So it needs both, and the way it's structured right now makes sense.

So nevermind. Please keep it the way it is.

return this.forEachDbEntry(
transaction,
documentKeys,
(key, dbRemoteDoc) => {
if (dbRemoteDoc) {
results = results.insert(
key,
this.serializer.fromDbRemoteDocument(dbRemoteDoc)
);
sizeMap = sizeMap.insert(key, dbDocumentSize(dbRemoteDoc));
} else {
results = results.insert(key, null);
sizeMap = sizeMap.insert(key, 0);
}
}
).next(() => {
return { maybeDocuments: results, sizeMap };
});
}

forEachDbEntry(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

private please

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

transaction: PersistenceTransaction,
documentKeys: DocumentKeySet,
callback: (key: DocumentKey, doc: DbRemoteDocument | null) => void
): PersistencePromise<void> {
if (documentKeys.isEmpty()) {
return PersistencePromise.resolve({ maybeDocuments: results, sizeMap });
return PersistencePromise.resolve();
}

const range = IDBKeyRange.bound(
documentKeys.first()!.path.toArray(),
documentKeys.last()!.path.toArray()
);
let key = documentKeys.first();
const keyIter = documentKeys.getIterator();
let nextKey: DocumentKey | null = keyIter.getNext();

return remoteDocumentsStore(transaction)
.iterate({ range }, (potentialKeyRaw, dbRemoteDoc, control) => {
const potentialKey = DocumentKey.fromSegments(potentialKeyRaw);
while (DocumentKey.comparator(key!, potentialKey) != 1) {
if (key!.isEqual(potentialKey)) {
results = results.insert(
key!,
this.serializer.fromDbRemoteDocument(dbRemoteDoc)
);
sizeMap = sizeMap.insert(key!, dbDocumentSize(dbRemoteDoc));
} else {
results = results.insert(key!, null);
sizeMap = sizeMap.insert(key!, 0);
}

key = documentKeys.firstAfter(key!);
if (!key) {
control.done();
return;
}
control.skip(key!.path.toArray());
// Go through keys not found in cache.
while (nextKey && DocumentKey.comparator(nextKey!, potentialKey) < 0) {
callback(nextKey!, null);
nextKey = keyIter.getNext();
}

if (nextKey && nextKey!.isEqual(potentialKey)) {
// Key found in cache.
callback(nextKey!, dbRemoteDoc);
nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
}

// Skip to the next key (if there is one).
if (nextKey) {
control.skip(nextKey!.path.toArray());
} else {
control.done();
}
})
.next(() => {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe add a comment.

// The rest of the keys must not be in the cache.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done (with minor addition).

while (key) {
results = results.insert(key, null);
sizeMap = sizeMap.insert(key, 0);
key = documentKeys.firstAfter(key!);
// The rest of the keys are not in the cache. One case where `iterate`
// above won't go through them is when the cache is empty.
while (nextKey) {
callback(nextKey!, null);
nextKey = keyIter.hasNext() ? keyIter.getNext() : null;
}
return { maybeDocuments: results, sizeMap };
});
}

Expand Down
33 changes: 14 additions & 19 deletions packages/firestore/src/local/local_documents_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -83,17 +83,15 @@ export class LocalDocumentsView {
transaction: PersistenceTransaction,
docs: NullableMaybeDocumentMap,
batches: MutationBatch[]
): PersistencePromise<NullableMaybeDocumentMap> {
): NullableMaybeDocumentMap {
let results = nullableMaybeDocumentMap();
return new PersistencePromise<NullableMaybeDocumentMap>(resolve => {
docs.forEach((key, localView) => {
for (const batch of batches) {
localView = batch.applyToLocalView(key, localView);
}
results = results.insert(key, localView);
});
resolve(results);
docs.forEach((key, localView) => {
for (const batch of batches) {
localView = batch.applyToLocalView(key, localView);
}
results = results.insert(key, localView);
});
return results;
}

/**
Expand All @@ -119,17 +117,14 @@ export class LocalDocumentsView {
transaction: PersistenceTransaction,
baseDocs: NullableMaybeDocumentMap
): PersistencePromise<MaybeDocumentMap> {
let allKeys = documentKeySet();
baseDocs.forEach(key => {
allKeys = allKeys.add(key);
});

return this.mutationQueue
.getAllMutationBatchesAffectingDocumentKeys(transaction, allKeys)
.next(batches =>
this.applyLocalMutationsToDocuments(transaction, baseDocs, batches)
)
.next(docs => {
.getAllMutationBatchesAffectingDocumentKeys(transaction, baseDocs)
.next(batches => {
const docs = this.applyLocalMutationsToDocuments(
transaction,
baseDocs,
batches
);
let results = maybeDocumentMap();
docs.forEach((key, maybeDoc) => {
// TODO(http://b/32275378): Don't conflate missing / deleted.
Expand Down
9 changes: 3 additions & 6 deletions packages/firestore/src/local/local_serializer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,12 +71,9 @@ export class LocalSerializer {
/** Encodes a document for storage locally. */
toDbRemoteDocument(maybeDoc: MaybeDocument): DbRemoteDocument {
if (maybeDoc instanceof Document) {
let doc: api.Document;
if (maybeDoc.proto) {
doc = maybeDoc.proto;
} else {
doc = this.remoteSerializer.toDocument(maybeDoc);
}
const doc = maybeDoc.proto
? maybeDoc.proto
: this.remoteSerializer.toDocument(maybeDoc);
const hasCommittedMutations = maybeDoc.hasCommittedMutations;
return new DbRemoteDocument(
/* unknownDocument= */ null,
Expand Down
5 changes: 4 additions & 1 deletion packages/firestore/src/local/memory_mutation_queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,13 +24,16 @@ import { BATCHID_UNKNOWN, MutationBatch } from '../model/mutation_batch';
import { emptyByteString } from '../platform/platform';
import { assert } from '../util/assert';
import { primitiveComparator } from '../util/misc';
import { SortedMap } from '../util/sorted_map';
import { SortedSet } from '../util/sorted_set';

import { MutationQueue } from './mutation_queue';
import { PersistenceTransaction, ReferenceDelegate } from './persistence';
import { PersistencePromise } from './persistence_promise';
import { DocReference } from './reference_set';

import { AnyJs } from '../../src/util/misc';

export class MemoryMutationQueue implements MutationQueue {
/**
* The set of all mutations that have been sent but not yet been applied to
Expand Down Expand Up @@ -203,7 +206,7 @@ export class MemoryMutationQueue implements MutationQueue {

getAllMutationBatchesAffectingDocumentKeys(
transaction: PersistenceTransaction,
documentKeys: DocumentKeySet
documentKeys: SortedMap<DocumentKey, AnyJs>
): PersistencePromise<MutationBatch[]> {
let uniqueBatchIDs = new SortedSet<number>(primitiveComparator);

Expand Down
5 changes: 4 additions & 1 deletion packages/firestore/src/local/mutation_queue.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,13 @@ import { DocumentKeySet } from '../model/collections';
import { DocumentKey } from '../model/document_key';
import { Mutation } from '../model/mutation';
import { MutationBatch } from '../model/mutation_batch';
import { SortedMap } from '../util/sorted_map';

import { PersistenceTransaction } from './persistence';
import { PersistencePromise } from './persistence_promise';

import { AnyJs } from '../../src/util/misc';

/** A queue of mutations to apply to the remote store. */
export interface MutationQueue {
/** Returns true if this queue contains no mutation batches. */
Expand Down Expand Up @@ -133,7 +136,7 @@ export interface MutationQueue {
// TODO(mcg): This should really return an enumerator
getAllMutationBatchesAffectingDocumentKeys(
transaction: PersistenceTransaction,
documentKeys: DocumentKeySet
documentKeys: SortedMap<DocumentKey, AnyJs>
): PersistencePromise<MutationBatch[]>;

/**
Expand Down
17 changes: 9 additions & 8 deletions packages/firestore/src/local/remote_document_change_buffer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -108,8 +108,7 @@ export abstract class RemoteDocumentChangeBuffer {
}

/**
* Looks up several entries in the cache.
* checked, and if no buffered change applies, this will forward to
* Looks up several entries in the cache, orwarding to
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

forwarding :)

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

* `RemoteDocumentCache.getEntry()`.
*
* @param transaction The transaction in which to perform any persistence
Expand All @@ -127,12 +126,14 @@ export abstract class RemoteDocumentChangeBuffer {

// Record the size of everything we load from the cache so we can compute
// a delta later.
return this.getAllFromCache(transaction, documentKeys).next(getResult => {
getResult.sizeMap.forEach((documentKey, size) => {
this.documentSizes.set(documentKey, size);
});
return getResult.maybeDocuments;
});
return this.getAllFromCache(transaction, documentKeys).next(
({ maybeDocuments, sizeMap }) => {
sizeMap.forEach((documentKey, size) => {
this.documentSizes.set(documentKey, size);
});
return maybeDocuments;
}
);
}

/**
Expand Down
33 changes: 20 additions & 13 deletions packages/firestore/src/util/sorted_set.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,25 +97,20 @@ export class SortedSet<T> {
}
}

/** Finds the least element greater than `elem`. */
firstAfter(elem: T): T | null {
const iter = this.data.getIteratorFrom(elem);
if (!iter.hasNext()) {
return null;
}
if (this.comparator(iter.peek()!.key, elem) == 0) {
// Skip
iter.getNext();
}
return iter.hasNext() ? iter.getNext().key : null;
}

/** Finds the least element greater than or equal to `elem`. */
firstAfterOrEqual(elem: T): T | null {
const iter = this.data.getIteratorFrom(elem);
return iter.hasNext() ? iter.getNext().key : null;
}

getIterator(): SortedSetIterator<T> {
return new SortedSetIterator<T>(this.data.getIterator());
}

getIteratorFrom(key: T): SortedSetIterator<T> {
return new SortedSetIterator<T>(this.data.getIteratorFrom(key));
}

/** Inserts or updates an element */
add(elem: T): SortedSet<T> {
return this.copy(this.data.remove(elem).insert(elem, true));
Expand Down Expand Up @@ -173,3 +168,15 @@ export class SortedSet<T> {
return result;
}
}

export class SortedSetIterator<T> {
constructor(private iter: SortedMapIterator<T, boolean>) {}

getNext(): T {
return this.iter.getNext().key;
}

hasNext(): boolean {
return this.iter.hasNext();
}
}
Loading