Skip to content

Implement Firestore IndexBackfiller #6261

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 28 commits into from
Jun 8, 2022
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
104964e
Integrate Document Overlay with the SDK (#6123)
ehsannas Apr 6, 2022
bdd3eae
Overlay migration (#6131)
ehsannas May 2, 2022
2aabc42
Implement IndexBackfiller
tom-andersen May 10, 2022
1ac2934
Format
tom-andersen May 10, 2022
235564b
Fix unbound method
tom-andersen May 11, 2022
736385e
Update overlay migration code to use DbMutationBatchStore (#6268)
ehsannas May 12, 2022
737284a
Implement IndexBackfiller
tom-andersen May 10, 2022
4a319ee
Format
tom-andersen May 10, 2022
d999f11
Fix unbound method
tom-andersen May 11, 2022
5890b8c
Add Index Backfiller test
tom-andersen May 12, 2022
0c7b979
Add more Index Backfiller tests
tom-andersen May 13, 2022
1023247
Merge remote-tracking branch 'origin/tomandersen/index-backfiller' in…
tom-andersen May 13, 2022
1c1c814
Add more Index Backfiller tests
tom-andersen May 16, 2022
e0a06e7
Changes by andy1
tom-andersen May 16, 2022
f613c3a
Make sqlite3 as dev dependency
tom-andersen May 16, 2022
1a12a6e
Remove sqlite3 as dev dependency
tom-andersen May 16, 2022
e370c7b
Prettier
tom-andersen May 16, 2022
776d95a
Revert
tom-andersen May 24, 2022
4028824
Merge remote-tracking branch 'origin/master' into tomandersen/index-b…
tom-andersen May 24, 2022
00dd2f7
Fix after merge. Make tests pass.
tom-andersen May 30, 2022
f24b0f6
Merge branch 'master' of https://github.com/firebase/firebase-js-sdk …
tom-andersen May 30, 2022
05522e2
Fix according to PR comments
tom-andersen May 30, 2022
665d71f
Keep INDEXING_ENABLED flag since Web implementation is still incomplete.
tom-andersen May 31, 2022
6c5576f
Refactor getMinOffsetFromFieldIndexes to follow Android implementatio…
tom-andersen Jun 2, 2022
90c4227
Disable IndexBackfiller and tests
tom-andersen Jun 2, 2022
fa1238f
Disable IndexBackfiller and tests
tom-andersen Jun 2, 2022
c4f87be
Lint
tom-andersen Jun 2, 2022
7b8398d
Refactor with type OverlayedDocumentMap. Add Comments. Fix nit.
tom-andersen Jun 8, 2022
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
69 changes: 29 additions & 40 deletions packages/firestore/src/local/indexeddb_index_manager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ import { FieldPath, ResourcePath } from '../model/path';
import { TargetIndexMatcher } from '../model/target_index_matcher';
import { isArray, refValue } from '../model/values';
import { Value as ProtoValue } from '../protos/firestore_proto_api';
import {debugAssert, hardAssert} from '../util/assert';
import { debugAssert, fail, hardAssert } from '../util/assert';
import { logDebug } from '../util/log';
import { immediateSuccessor, primitiveComparator } from '../util/misc';
import { ObjectMap } from '../util/obj_map';
Expand Down Expand Up @@ -970,53 +970,22 @@ export class IndexedDbIndexManager implements IndexManager {
transaction: PersistenceTransaction,
collectionGroup: string
): PersistencePromise<IndexOffset> {
return this.getFieldIndexes(transaction, collectionGroup)
.next(fieldIndexes => this.getMinOffsetFromFieldIndexes(fieldIndexes));
}

getMinOffsetFromFieldIndexes(fieldIndexes: FieldIndex[]): IndexOffset {
hardAssert(
fieldIndexes.length !== 0,
"Found empty index group when looking for least recent index offset.");

let minOffset: IndexOffset = fieldIndexes[0].indexState.offset;
let maxBatchId: number = minOffset.largestBatchId;
for (const fieldIndex of fieldIndexes) {
const newOffset: IndexOffset = fieldIndex.indexState.offset;
if (indexOffsetComparator(newOffset, minOffset) < 0) {
minOffset = newOffset;
}
if (maxBatchId < newOffset.largestBatchId) {
maxBatchId = newOffset.largestBatchId;
}
}
return new IndexOffset(
minOffset.readTime,
minOffset.documentKey,
maxBatchId
return this.getFieldIndexes(transaction, collectionGroup).next(
getMinOffsetFromFieldIndexes
);
}

getMinOffset(
transaction: PersistenceTransaction,
target: Target
): PersistencePromise<IndexOffset> {
let offset: IndexOffset | undefined;
return PersistencePromise.forEach(
return PersistencePromise.mapArray(
this.getSubTargets(target),
(subTarget: Target) => {
return this.getFieldIndex(transaction, subTarget).next(index => {
if (!index) {
offset = IndexOffset.min();
} else if (
!offset ||
indexOffsetComparator(index.indexState.offset, offset) < 0
) {
offset = index.indexState.offset;
}
});
}
).next(() => offset!);
(subTarget: Target) =>
this.getFieldIndex(transaction, subTarget).next(index =>
!index ? fail('Target cannot be served from index') : index
)
).next(getMinOffsetFromFieldIndexes);
}
}

Expand Down Expand Up @@ -1062,3 +1031,23 @@ function indexStateStore(
): SimpleDbStore<DbIndexStateKey, DbIndexState> {
return getStore<DbIndexStateKey, DbIndexState>(txn, DbIndexStateStore);
}

function getMinOffsetFromFieldIndexes(fieldIndexes: FieldIndex[]): IndexOffset {
hardAssert(
fieldIndexes.length !== 0,
'Found empty index group when looking for least recent index offset.'
);

let minOffset: IndexOffset = fieldIndexes[0].indexState.offset;
let maxBatchId: number = minOffset.largestBatchId;
for (let i = 1; i < fieldIndexes.length; i++) {
const newOffset: IndexOffset = fieldIndexes[i].indexState.offset;
if (indexOffsetComparator(newOffset, minOffset) < 0) {
minOffset = newOffset;
}
if (maxBatchId < newOffset.largestBatchId) {
maxBatchId = newOffset.largestBatchId;
}
}
return new IndexOffset(minOffset.readTime, minOffset.documentKey, maxBatchId);
}
19 changes: 19 additions & 0 deletions packages/firestore/src/local/local_documents_view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -373,6 +373,21 @@ export class LocalDocumentsView {
}
}

/**
* Given a collection group, returns the next documents that follow the provided offset, along
* with an updated batch ID.
*
* <p>The documents returned by this method are ordered by remote version from the provided
* offset. If there are no more remote documents after the provided offset, documents with
* mutations in order of batch id from the offset are returned. Since all documents in a batch are
* returned together, the total number of documents returned can exceed {@code count}.
*
* @param transaction
* @param collectionGroup The collection group for the documents.
* @param offset The offset to index into.
* @param count The number of documents to return
* @return A LocalWriteResult with the documents that follow the provided offset and the last processed batch id.
*/
getNextDocuments(
transaction: PersistenceTransaction,
collectionGroup: string,
Expand All @@ -391,6 +406,10 @@ export class LocalDocumentsView {
count - originalDocs.size
)
: PersistencePromise.resolve(newOverlayMap());
// The callsite will use the largest batch ID together with the latest read time to create
// a new index offset. Since we only process batch IDs if all remote documents have been read,
// no overlay will increase the overall read time. This is why we only need to special case
// the batch id.
let largestBatchId = INITIAL_LARGEST_BATCH_ID;
let modifiedDocs = originalDocs;
return overlaysPromise.next(overlays => {
Expand Down
31 changes: 29 additions & 2 deletions packages/firestore/src/local/persistence_promise.ts
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,33 @@ export class PersistencePromise<T> {
return this.waitFor(promises);
}

/**
* Concurrently map all array elements through asynchronous function.
*/
static mapArray<T, U>(
array: T[],
f: (t: T) => PersistencePromise<U>
): PersistencePromise<U[]> {
return new PersistencePromise<U[]>((resolve, reject) => {
const expectedCount = array.length;
const results: U[] = new Array(expectedCount);
let resolvedCount = 0;
for (let i = 0; i < expectedCount; i++) {
const current = i;
f(array[current]).next(
result => {
results[current] = result;
++resolvedCount;
if (resolvedCount === expectedCount) {
resolve(results);
}
},
err => reject(err)
);
}
});
}

/**
* An alternative to recursive PersistencePromise calls, that avoids
* potential memory problems from unbounded chains of promises.
Expand All @@ -255,11 +282,11 @@ export class PersistencePromise<T> {
action: () => PersistencePromise<void>
): PersistencePromise<void> {
return new PersistencePromise<void>((resolve, reject) => {
const process = () => {
const process = (): void => {
if (condition() === true) {
action().next(() => {
process();
}, reject)
}, reject);
} else {
resolve();
}
Expand Down