Skip to content

Commit 74607ac

Browse files
committed
Merge remote-tracking branch 'origin/mila/BloomFilter' into BloomFilterComplexIntegrationTest
2 parents daf049c + 5c2ec00 commit 74607ac

File tree

12 files changed

+147
-52
lines changed

12 files changed

+147
-52
lines changed

packages/firestore/src/core/sync_engine_impl.ts

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,6 @@ import { primitiveComparator } from '../util/misc';
7171
import { ObjectMap } from '../util/obj_map';
7272
import { Deferred } from '../util/promise';
7373
import { SortedMap } from '../util/sorted_map';
74-
import { SortedSet } from '../util/sorted_set';
7574
import { BATCHID_UNKNOWN } from '../util/types';
7675

7776
import {
@@ -639,7 +638,9 @@ export async function syncEngineRejectListen(
639638
const event = new RemoteEvent(
640639
SnapshotVersion.min(),
641640
/* targetChanges= */ new Map<TargetId, TargetChange>(),
642-
/* targetMismatches= */ new SortedSet<TargetId>(primitiveComparator),
641+
/* targetMismatches= */ new SortedMap<TargetId, TargetPurpose>(
642+
primitiveComparator
643+
),
643644
documentUpdates,
644645
resolvedLimboDocuments
645646
);

packages/firestore/src/local/local_store_impl.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -601,7 +601,7 @@ export function localStoreApplyRemoteEventToLocalCache(
601601
let newTargetData = oldTargetData.withSequenceNumber(
602602
txn.currentSequenceNumber
603603
);
604-
if (remoteEvent.targetMismatches.has(targetId)) {
604+
if (remoteEvent.targetMismatches.get(targetId) !== null) {
605605
newTargetData = newTargetData
606606
.withResumeToken(
607607
ByteString.EMPTY_BYTE_STRING,

packages/firestore/src/local/target_data.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,10 +26,17 @@ export const enum TargetPurpose {
2626
Listen,
2727

2828
/**
29-
* The query target was used to refill a query after an existence filter mismatch.
29+
* The query target was used to refill a query after an existence filter
30+
* mismatch.
3031
*/
3132
ExistenceFilterMismatch,
3233

34+
/**
35+
* The query target was used if the query is the result of a false positive in
36+
* the bloom filter.
37+
*/
38+
ExistenceFilterMismatchBloom,
39+
3340
/** The query target was used to resolve a limbo document. */
3441
LimboResolution
3542
}

packages/firestore/src/remote/remote_event.ts

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -17,15 +17,16 @@
1717

1818
import { SnapshotVersion } from '../core/snapshot_version';
1919
import { TargetId } from '../core/types';
20+
import { TargetPurpose } from '../local/target_data';
2021
import {
2122
documentKeySet,
2223
DocumentKeySet,
2324
mutableDocumentMap,
24-
MutableDocumentMap,
25-
targetIdSet
25+
MutableDocumentMap
2626
} from '../model/collections';
2727
import { ByteString } from '../util/byte_string';
28-
import { SortedSet } from '../util/sorted_set';
28+
import { primitiveComparator } from '../util/misc';
29+
import { SortedMap } from '../util/sorted_map';
2930

3031
/**
3132
* An event from the RemoteStore. It is split into targetChanges (changes to the
@@ -43,10 +44,11 @@ export class RemoteEvent {
4344
*/
4445
readonly targetChanges: Map<TargetId, TargetChange>,
4546
/**
46-
* A set of targets that is known to be inconsistent. Listens for these
47-
* targets should be re-established without resume tokens.
47+
* A map of targets that is known to be inconsistent, and the purpose for
48+
* re-listening. Listens for these targets should be re-established without
49+
* resume tokens.
4850
*/
49-
readonly targetMismatches: SortedSet<TargetId>,
51+
readonly targetMismatches: SortedMap<TargetId, TargetPurpose>,
5052
/**
5153
* A set of which documents have changed or been deleted, along with the
5254
* doc's new values (if not deleted).
@@ -82,7 +84,7 @@ export class RemoteEvent {
8284
return new RemoteEvent(
8385
SnapshotVersion.min(),
8486
targetChanges,
85-
targetIdSet(),
87+
new SortedMap<TargetId, TargetPurpose>(primitiveComparator),
8688
mutableDocumentMap(),
8789
documentKeySet()
8890
);

packages/firestore/src/remote/remote_store.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ import {
2424
localStoreGetNextMutationBatch
2525
} from '../local/local_store_impl';
2626
import { isIndexedDbTransactionError } from '../local/simple_db';
27-
import { TargetData, TargetPurpose } from '../local/target_data';
27+
import { TargetData } from '../local/target_data';
2828
import { MutationResult } from '../model/mutation';
2929
import { MutationBatch, MutationBatchResult } from '../model/mutation_batch';
3030
import { debugAssert, debugCast } from '../util/assert';
@@ -587,7 +587,7 @@ function raiseWatchSnapshot(
587587

588588
// Re-establish listens for the targets that have been invalidated by
589589
// existence filter mismatches.
590-
remoteEvent.targetMismatches.forEach(targetId => {
590+
remoteEvent.targetMismatches.forEach((targetId, targetPurpose) => {
591591
const targetData = remoteStoreImpl.listenTargets.get(targetId);
592592
if (!targetData) {
593593
// A watched target might have been removed already.
@@ -615,7 +615,7 @@ function raiseWatchSnapshot(
615615
const requestTargetData = new TargetData(
616616
targetData.target,
617617
targetId,
618-
TargetPurpose.ExistenceFilterMismatch,
618+
targetPurpose,
619619
targetData.sequenceNumber
620620
);
621621
sendWatchRequest(remoteStoreImpl, requestTargetData);

packages/firestore/src/remote/serializer.ts

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1008,7 +1008,7 @@ export function toListenRequestLabels(
10081008
serializer: JsonProtoSerializer,
10091009
targetData: TargetData
10101010
): ProtoApiClientObjectMap<string> | null {
1011-
const value = toLabel(serializer, targetData.purpose);
1011+
const value = toLabel(targetData.purpose);
10121012
if (value == null) {
10131013
return null;
10141014
} else {
@@ -1018,15 +1018,14 @@ export function toListenRequestLabels(
10181018
}
10191019
}
10201020

1021-
function toLabel(
1022-
serializer: JsonProtoSerializer,
1023-
purpose: TargetPurpose
1024-
): string | null {
1021+
export function toLabel(purpose: TargetPurpose): string | null {
10251022
switch (purpose) {
10261023
case TargetPurpose.Listen:
10271024
return null;
10281025
case TargetPurpose.ExistenceFilterMismatch:
10291026
return 'existence-filter-mismatch';
1027+
case TargetPurpose.ExistenceFilterMismatchBloom:
1028+
return 'existence-filter-mismatch-bloom';
10301029
case TargetPurpose.LimboResolution:
10311030
return 'limbo-document';
10321031
default:

packages/firestore/src/remote/watch_change.ts

Lines changed: 41 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -91,6 +91,11 @@ export const enum WatchTargetChangeState {
9191
Reset
9292
}
9393

94+
const enum BloomFilterApplicationStatus {
95+
Success,
96+
Skipped,
97+
FalsePositive
98+
}
9499
export class WatchTargetChange {
95100
constructor(
96101
/** What kind of change occurred to the watch target. */
@@ -284,11 +289,13 @@ export class WatchChangeAggregator {
284289
private pendingDocumentTargetMapping = documentTargetMap();
285290

286291
/**
287-
* A list of targets with existence filter mismatches. These targets are
292+
* A map of targets with existence filter mismatches. These targets are
288293
* known to be inconsistent and their listens needs to be re-established by
289294
* RemoteStore.
290295
*/
291-
private pendingTargetResets = new SortedSet<TargetId>(primitiveComparator);
296+
private pendingTargetResets = new SortedMap<TargetId, TargetPurpose>(
297+
primitiveComparator
298+
);
292299

293300
/**
294301
* Processes and adds the DocumentWatchChange to the current set of changes.
@@ -426,15 +433,21 @@ export class WatchChangeAggregator {
426433
// raise a snapshot with `isFromCache:true`.
427434
if (currentSize !== expectedCount) {
428435
// Apply bloom filter to identify and mark removed documents.
429-
const bloomFilterApplied = this.applyBloomFilter(
430-
watchChange,
431-
currentSize
432-
);
433-
if (!bloomFilterApplied) {
436+
const status = this.applyBloomFilter(watchChange, currentSize);
437+
438+
if (status !== BloomFilterApplicationStatus.Success) {
434439
// If bloom filter application fails, we reset the mapping and
435440
// trigger re-run of the query.
436441
this.resetTarget(targetId);
437-
this.pendingTargetResets = this.pendingTargetResets.add(targetId);
442+
443+
const purpose: TargetPurpose =
444+
status === BloomFilterApplicationStatus.FalsePositive
445+
? TargetPurpose.ExistenceFilterMismatchBloom
446+
: TargetPurpose.ExistenceFilterMismatch;
447+
this.pendingTargetResets = this.pendingTargetResets.insert(
448+
targetId,
449+
purpose
450+
);
438451
}
439452
TestingHooks.instance?.notifyOnExistenceFilterMismatch(
440453
createExistenceFilterMismatchInfoForTestingHooks(
@@ -448,16 +461,19 @@ export class WatchChangeAggregator {
448461
}
449462
}
450463

451-
/** Returns whether a bloom filter removed the deleted documents successfully. */
464+
/**
465+
* Apply bloom filter to remove the deleted documents, and return the
466+
* application status.
467+
*/
452468
private applyBloomFilter(
453469
watchChange: ExistenceFilterChange,
454470
currentCount: number
455-
): boolean {
471+
): BloomFilterApplicationStatus {
456472
const { unchangedNames, count: expectedCount } =
457473
watchChange.existenceFilter;
458474

459475
if (!unchangedNames || !unchangedNames.bits) {
460-
return false;
476+
return BloomFilterApplicationStatus.Skipped;
461477
}
462478

463479
const {
@@ -475,7 +491,7 @@ export class WatchChangeAggregator {
475491
err.message +
476492
'); ignoring the bloom filter and falling back to full re-query.'
477493
);
478-
return false;
494+
return BloomFilterApplicationStatus.Skipped;
479495
} else {
480496
throw err;
481497
}
@@ -491,15 +507,23 @@ export class WatchChangeAggregator {
491507
} else {
492508
logWarn('Applying bloom filter failed: ', err);
493509
}
494-
return false;
510+
return BloomFilterApplicationStatus.Skipped;
511+
}
512+
513+
if (bloomFilter.bitCount === 0) {
514+
return BloomFilterApplicationStatus.Skipped;
495515
}
496516

497517
const removedDocumentCount = this.filterRemovedDocuments(
498518
watchChange.targetId,
499519
bloomFilter
500520
);
501521

502-
return expectedCount === currentCount - removedDocumentCount;
522+
if (expectedCount !== currentCount - removedDocumentCount) {
523+
return BloomFilterApplicationStatus.FalsePositive;
524+
}
525+
526+
return BloomFilterApplicationStatus.Success;
503527
}
504528

505529
/**
@@ -610,7 +634,9 @@ export class WatchChangeAggregator {
610634

611635
this.pendingDocumentUpdates = mutableDocumentMap();
612636
this.pendingDocumentTargetMapping = documentTargetMap();
613-
this.pendingTargetResets = new SortedSet<TargetId>(primitiveComparator);
637+
this.pendingTargetResets = new SortedMap<TargetId, TargetPurpose>(
638+
primitiveComparator
639+
);
614640

615641
return remoteEvent;
616642
}

packages/firestore/test/unit/remote/remote_event.test.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -431,6 +431,9 @@ describe('RemoteEvent', () => {
431431
expectTargetChangeEquals(event.targetChanges.get(1)!, expected);
432432
});
433433

434+
// TODO(b/272564458): Add test cases for existence filter with bloom filter,
435+
// one will skip the re-query, one will yield false positive result and clears
436+
// target mapping.
434437
it('existence filters clears target mapping', () => {
435438
const targets = listens(1, 2);
436439

@@ -459,6 +462,9 @@ describe('RemoteEvent', () => {
459462
event = aggregator.createRemoteEvent(version(3));
460463
expect(event.documentUpdates.size).to.equal(0);
461464
expect(event.targetMismatches.size).to.equal(1);
465+
expect(event.targetMismatches.get(1)).to.equal(
466+
TargetPurpose.ExistenceFilterMismatch
467+
);
462468
expect(event.targetChanges.size).to.equal(1);
463469

464470
const expected = updateMapping(
@@ -499,6 +505,9 @@ describe('RemoteEvent', () => {
499505
const event = aggregator.createRemoteEvent(version(3));
500506
expect(event.documentUpdates.size).to.equal(1);
501507
expect(event.targetMismatches.size).to.equal(1);
508+
expect(event.targetMismatches.get(1)).to.equal(
509+
TargetPurpose.ExistenceFilterMismatch
510+
);
502511
expect(event.targetChanges.get(1)!.current).to.be.false;
503512
});
504513

packages/firestore/test/unit/specs/existence_filter_spec.test.ts

Lines changed: 21 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
*/
1717

1818
import { newQueryForPath } from '../../../src/core/query';
19+
import { TargetPurpose } from '../../../src/local/target_data';
1920
import { Code } from '../../../src/util/error';
2021
import {
2122
deletedDoc,
@@ -305,7 +306,11 @@ describeSpec('Existence Filters:', [], () => {
305306
// BloomFilter correctly identifies docC is deleted, but yields false
306307
// positive results for docB. Re-run query is triggered.
307308
.expectEvents(query1, { fromCache: true })
308-
.expectActiveTargets({ query: query1, resumeToken: '' })
309+
.expectActiveTargets({
310+
query: query1,
311+
resumeToken: '',
312+
targetPurpose: TargetPurpose.ExistenceFilterMismatchBloom
313+
})
309314
);
310315
}
311316
);
@@ -394,7 +399,11 @@ describeSpec('Existence Filters:', [], () => {
394399
.watchSnapshots(2000)
395400
// Re-run query is triggered.
396401
.expectEvents(query1, { fromCache: true })
397-
.expectActiveTargets({ query: query1, resumeToken: '' })
402+
.expectActiveTargets({
403+
query: query1,
404+
resumeToken: '',
405+
targetPurpose: TargetPurpose.ExistenceFilterMismatch
406+
})
398407
);
399408
}
400409
);
@@ -424,7 +433,11 @@ describeSpec('Existence Filters:', [], () => {
424433
.watchSnapshots(2000)
425434
// Re-run query is triggered.
426435
.expectEvents(query1, { fromCache: true })
427-
.expectActiveTargets({ query: query1, resumeToken: '' })
436+
.expectActiveTargets({
437+
query: query1,
438+
resumeToken: '',
439+
targetPurpose: TargetPurpose.ExistenceFilterMismatch
440+
})
428441
);
429442
}
430443
);
@@ -452,7 +465,11 @@ describeSpec('Existence Filters:', [], () => {
452465
.watchSnapshots(2000)
453466
// Re-run query is triggered.
454467
.expectEvents(query1, { fromCache: true })
455-
.expectActiveTargets({ query: query1, resumeToken: '' })
468+
.expectActiveTargets({
469+
query: query1,
470+
resumeToken: '',
471+
targetPurpose: TargetPurpose.ExistenceFilterMismatch
472+
})
456473
);
457474
});
458475

0 commit comments

Comments
 (0)