@powersync/service-module-mongodb-storage 0.20.0 → 0.22.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +62 -0
- package/dist/storage/MongoBucketStorage.d.ts +36 -3
- package/dist/storage/MongoBucketStorage.js +372 -207
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +17 -5
- package/dist/storage/implementation/MongoBucketBatch.js +44 -17
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
- package/dist/storage/implementation/MongoCompactor.js +27 -238
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
- package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +7 -3
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
- package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
- package/dist/storage/implementation/MongoWriteBatch.js +149 -0
- package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
- package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
- package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
- package/dist/storage/implementation/common/PersistedBatch.js +49 -6
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +9 -1
- package/dist/storage/implementation/db.js +27 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +31 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +2 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +33 -4
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
- package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
- package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
- package/dist/storage/implementation/v3/CompactionLease.js +131 -0
- package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +10 -2
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +100 -26
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +82 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +698 -159
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +14 -8
- package/dist/storage/implementation/v3/PersistedBatchV3.js +76 -42
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.js +2 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
- package/dist/storage/implementation/v3/compact-utils.js +216 -0
- package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
- package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
- package/dist/storage/implementation/v3/compaction-constants.js +6 -0
- package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +84 -4
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +6 -6
- package/dist/storage/implementation/v3/source-table-utils.js +30 -37
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
- package/dist/storage/storage-index.d.ts +2 -1
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +28 -1
- package/dist/types/types.js +28 -7
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.js +1 -0
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.js +3 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -9
- package/src/storage/MongoBucketStorage.ts +507 -269
- package/src/storage/implementation/MongoBucketBatch.ts +67 -24
- package/src/storage/implementation/MongoCompactor.ts +35 -346
- package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
- package/src/storage/implementation/MongoStorageProvider.ts +15 -3
- package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
- package/src/storage/implementation/MongoWriteBatch.ts +172 -0
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +66 -10
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
- package/src/storage/implementation/db.ts +35 -2
- package/src/storage/implementation/models.ts +34 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +46 -6
- package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
- package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
- package/src/storage/implementation/v1/models.ts +1 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +138 -34
- package/src/storage/implementation/v3/MongoCompactorV3.ts +958 -225
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +93 -48
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
- package/src/storage/implementation/v3/bucket-format.ts +2 -0
- package/src/storage/implementation/v3/compact-utils.ts +320 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +91 -3
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
- package/src/storage/implementation/v3/source-table-utils.ts +39 -41
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +42 -7
- package/src/utils/test-utils.ts +1 -0
- package/src/utils/util.ts +3 -1
- package/test/src/__snapshots__/storage.test.ts.snap +25 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
- package/test/src/chunk_compaction_config.test.ts +38 -0
- package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +14 -6
- package/test/src/object_storage_usage.test.ts +267 -0
- package/test/src/parameter_compacting_v1.test.ts +120 -0
- package/test/src/parameter_compacting_v3.test.ts +192 -0
- package/test/src/parameter_compaction_fence.test.ts +276 -0
- package/test/src/storage.test.ts +273 -22
- package/test/src/storage_compacting.test.ts +968 -246
- package/test/src/storage_s3_checksums.test.ts +3 -4
- package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
- package/test/src/storage_s3_reading.test.ts +290 -2
- package/test/src/storage_s3_writing.test.ts +1 -0
- package/test/src/storage_sync.test.ts +336 -41
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,10 +1,19 @@
|
|
|
1
|
-
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
-
import {
|
|
1
|
+
import { mongo, MONGO_OPERATION_TIMEOUT_MS } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { ReplicationAssertionError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import {
|
|
4
|
+
addChecksums,
|
|
5
|
+
CompactInitialReplicationResults,
|
|
6
|
+
InternalOpId,
|
|
7
|
+
isPartialChecksum,
|
|
8
|
+
storage,
|
|
9
|
+
SyncRuleState,
|
|
10
|
+
utils
|
|
11
|
+
} from '@powersync/service-core';
|
|
4
12
|
import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
5
13
|
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
6
14
|
import { BucketStateDocumentBase, LEGACY_BUCKET_DATA_DEFINITION_ID } from '../models.js';
|
|
7
|
-
import {
|
|
15
|
+
import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
|
|
16
|
+
import { MongoWriteBatch } from '../MongoWriteBatch.js';
|
|
8
17
|
import { cacheKey } from '../OperationBatch.js';
|
|
9
18
|
import { BucketDataDocumentV1, BucketStateDocumentV1 } from './models.js';
|
|
10
19
|
import type { MongoSyncBucketStorageV1 } from './MongoSyncBucketStorageV1.js';
|
|
@@ -13,12 +22,79 @@ import { VersionedPowerSyncMongoV1 } from './VersionedPowerSyncMongoV1.js';
|
|
|
13
22
|
|
|
14
23
|
type CompactClearProperties = 'op' | 'checksum' | 'target_op';
|
|
15
24
|
|
|
25
|
+
const DEFAULT_MIN_BUCKET_CHANGES = 10;
|
|
26
|
+
const DEFAULT_MIN_CHANGE_RATIO = 0.1;
|
|
27
|
+
const DIRTY_BUCKET_SCAN_BATCH_SIZE = 2_000;
|
|
28
|
+
|
|
29
|
+
interface CurrentBucketState {
|
|
30
|
+
bucket: string;
|
|
31
|
+
definitionId: BucketDefinitionId;
|
|
32
|
+
seen: Map<string, InternalOpId>;
|
|
33
|
+
trackingSize: number;
|
|
34
|
+
lastNotPut: InternalOpId | null;
|
|
35
|
+
opsSincePut: number;
|
|
36
|
+
checksum: number;
|
|
37
|
+
opCount: number;
|
|
38
|
+
opBytes: number;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
interface DirtyBucket {
|
|
42
|
+
bucket: string;
|
|
43
|
+
definitionId: BucketDefinitionId | null;
|
|
44
|
+
estimatedCount: number;
|
|
45
|
+
dirtyRatio?: number;
|
|
46
|
+
}
|
|
47
|
+
|
|
16
48
|
export class MongoCompactorV1 extends MongoCompactor {
|
|
17
49
|
// Override types to the more specific ones
|
|
18
50
|
declare protected readonly db: VersionedPowerSyncMongoV1;
|
|
19
51
|
declare protected readonly storage: MongoSyncBucketStorageV1;
|
|
20
52
|
|
|
21
53
|
private updates: mongo.AnyBulkWriteOperation<BucketDataDocumentV1>[] = [];
|
|
54
|
+
private bucketStateUpdates: mongo.AnyBulkWriteOperation<BucketStateDocumentBase>[] = [];
|
|
55
|
+
private readonly minBucketChanges: number;
|
|
56
|
+
private readonly minChangeRatio: number;
|
|
57
|
+
private readonly maxOpId: bigint;
|
|
58
|
+
|
|
59
|
+
constructor(
|
|
60
|
+
bucketStorage: MongoSyncBucketStorageV1,
|
|
61
|
+
db: VersionedPowerSyncMongoV1,
|
|
62
|
+
private options: MongoCompactOptions
|
|
63
|
+
) {
|
|
64
|
+
super(bucketStorage, db, options);
|
|
65
|
+
this.minBucketChanges = options.minBucketChanges ?? DEFAULT_MIN_BUCKET_CHANGES;
|
|
66
|
+
this.minChangeRatio = options.minChangeRatio ?? DEFAULT_MIN_CHANGE_RATIO;
|
|
67
|
+
this.maxOpId = options.maxOpId ?? 0n;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Compact buckets by converting operations into MOVE and/or CLEAR operations.
|
|
72
|
+
*
|
|
73
|
+
* See /docs/storage/compacting-operations.md for details.
|
|
74
|
+
*/
|
|
75
|
+
override async compact(): Promise<number> {
|
|
76
|
+
if (this.options?.incrementalOnly) {
|
|
77
|
+
// Not supported for V1
|
|
78
|
+
this.logger.info('Incremental compacting is not supported on MongoDB storage V1/V2');
|
|
79
|
+
return 0;
|
|
80
|
+
} else if (this.storage.replicationStream.state != SyncRuleState.ACTIVE) {
|
|
81
|
+
this.logger.info(`Skipping compacting of replication stream in ${this.storage.replicationStream.state} state.`);
|
|
82
|
+
return 0;
|
|
83
|
+
}
|
|
84
|
+
await this.deleteOldCheckpointRequests();
|
|
85
|
+
|
|
86
|
+
if (this.buckets) {
|
|
87
|
+
for (const bucket of this.buckets) {
|
|
88
|
+
// We can make this more efficient later on by iterating through the buckets in a single query.
|
|
89
|
+
// That makes batching more tricky, so we leave for later.
|
|
90
|
+
await this.compactSingleBucketRetried(bucket);
|
|
91
|
+
}
|
|
92
|
+
} else {
|
|
93
|
+
await this.compactDirtyBuckets();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
return this.compactedBucketCount;
|
|
97
|
+
}
|
|
22
98
|
|
|
23
99
|
public async *dirtyBucketBatches(options: {
|
|
24
100
|
minBucketChanges: number;
|
|
@@ -34,8 +110,7 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
34
110
|
this.db.bucketStateV1,
|
|
35
111
|
{ g: this.group_id, b: new mongo.MinKey() as any },
|
|
36
112
|
{ g: this.group_id, b: new mongo.MaxKey() as any },
|
|
37
|
-
options
|
|
38
|
-
() => null
|
|
113
|
+
options
|
|
39
114
|
);
|
|
40
115
|
}
|
|
41
116
|
|
|
@@ -45,20 +120,232 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
45
120
|
}
|
|
46
121
|
// Unlike dirtyBucketBatches, this path is resumable after restart because populateChecksums resets
|
|
47
122
|
// estimate_since_compact as it progresses.
|
|
48
|
-
return this.dirtyBucketBatchForChecksumsForCollection(
|
|
49
|
-
this.
|
|
50
|
-
{
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
123
|
+
return this.dirtyBucketBatchForChecksumsForCollection({
|
|
124
|
+
'_id.g': this.group_id,
|
|
125
|
+
'estimate_since_compact.count': { $gte: options.minBucketChanges }
|
|
126
|
+
});
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
private async *dirtyBucketBatchesForCollection<TBucketState extends BucketStateDocumentBase>(
|
|
130
|
+
collection: mongo.Collection<TBucketState>,
|
|
131
|
+
lastId: TBucketState['_id'],
|
|
132
|
+
maxId: TBucketState['_id'],
|
|
133
|
+
options: { minBucketChanges: number; minChangeRatio: number }
|
|
134
|
+
): AsyncGenerator<DirtyBucket[]> {
|
|
135
|
+
// Paginate through the bucket state collection using cursor-based scanning.
|
|
136
|
+
while (true) {
|
|
137
|
+
// To avoid timeouts from too many buckets not meeting the minimum-change
|
|
138
|
+
// criteria, scan a fixed batch and only return matching buckets.
|
|
139
|
+
const [result] = await collection
|
|
140
|
+
.aggregate<{ buckets: TBucketState[]; cursor: Pick<TBucketState, '_id'>[] }>(
|
|
141
|
+
[
|
|
142
|
+
{ $match: { _id: { $gt: lastId, $lt: maxId } } },
|
|
143
|
+
{ $sort: { _id: 1 } },
|
|
144
|
+
// Scan a fixed number of docs each query so sparse matches don't block progress.
|
|
145
|
+
{ $limit: DIRTY_BUCKET_SCAN_BATCH_SIZE },
|
|
146
|
+
{
|
|
147
|
+
$facet: {
|
|
148
|
+
buckets: [
|
|
149
|
+
{ $match: { 'estimate_since_compact.count': { $gte: options.minBucketChanges } } },
|
|
150
|
+
{ $project: { _id: 1, estimate_since_compact: 1, compacted_state: 1 } }
|
|
151
|
+
],
|
|
152
|
+
cursor: [{ $sort: { _id: -1 } }, { $limit: 1 }, { $project: { _id: 1 } }]
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
],
|
|
156
|
+
{ maxTimeMS: MONGO_OPERATION_TIMEOUT_MS }
|
|
157
|
+
)
|
|
158
|
+
.toArray();
|
|
159
|
+
|
|
160
|
+
const cursor = result?.cursor?.[0];
|
|
161
|
+
if (cursor == null) {
|
|
162
|
+
break;
|
|
163
|
+
}
|
|
164
|
+
lastId = cursor._id;
|
|
165
|
+
|
|
166
|
+
const dirtyBuckets = (result?.buckets ?? []).map((bucketState) => {
|
|
167
|
+
// BigInt precision is not needed here since this is only an estimate.
|
|
168
|
+
const updatedCount = bucketState.estimate_since_compact?.count ?? 0;
|
|
169
|
+
const totalCount = (bucketState.compacted_state?.count ?? 0) + updatedCount;
|
|
170
|
+
const updatedBytes = Number(bucketState.estimate_since_compact?.bytes ?? 0);
|
|
171
|
+
const totalBytes = Number(bucketState.compacted_state?.bytes ?? 0) + updatedBytes;
|
|
172
|
+
return {
|
|
173
|
+
bucket: bucketState._id.b,
|
|
174
|
+
definitionId: null,
|
|
175
|
+
estimatedCount: totalCount,
|
|
176
|
+
dirtyRatio: Math.max(
|
|
177
|
+
totalCount > 0 ? updatedCount / totalCount : 0,
|
|
178
|
+
totalBytes > 0 ? updatedBytes / totalBytes : 0
|
|
179
|
+
)
|
|
180
|
+
};
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
yield dirtyBuckets.filter(
|
|
184
|
+
(bucket) => bucket.estimatedCount >= options.minBucketChanges && bucket.dirtyRatio >= options.minChangeRatio
|
|
185
|
+
);
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
private async dirtyBucketBatchForChecksumsForCollection(
|
|
190
|
+
filter: mongo.Filter<BucketStateDocumentV1>
|
|
191
|
+
): Promise<DirtyBucket[]> {
|
|
192
|
+
const dirtyBuckets = await this.db.bucketStateV1
|
|
193
|
+
.find(filter, {
|
|
194
|
+
projection: { _id: 1, estimate_since_compact: 1, compacted_state: 1 },
|
|
195
|
+
sort: { 'estimate_since_compact.count': -1 },
|
|
196
|
+
limit: 200,
|
|
197
|
+
maxTimeMS: MONGO_OPERATION_TIMEOUT_MS
|
|
198
|
+
})
|
|
199
|
+
.toArray();
|
|
200
|
+
|
|
201
|
+
return dirtyBuckets.map((bucket) => ({
|
|
202
|
+
bucket: bucket._id.b,
|
|
203
|
+
definitionId: null,
|
|
204
|
+
estimatedCount: Number(bucket.estimate_since_compact!.count) + Number(bucket.compacted_state?.count ?? 0)
|
|
205
|
+
}));
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
private async compactSingleBucketRetried(bucket: string, _definitionId: BucketDefinitionId | null = null) {
|
|
209
|
+
await this.retryCompaction(bucket, () => this.compactSingleBucket(bucket));
|
|
210
|
+
this.compactedBucketCount++;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
private async compactDirtyBuckets() {
|
|
214
|
+
for await (const buckets of this.dirtyBucketBatches({
|
|
215
|
+
minBucketChanges: this.minBucketChanges,
|
|
216
|
+
minChangeRatio: this.minChangeRatio
|
|
217
|
+
})) {
|
|
218
|
+
this.signal?.throwIfAborted();
|
|
219
|
+
for (const { bucket, definitionId } of buckets) {
|
|
220
|
+
await this.compactSingleBucketRetried(bucket, definitionId);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Subset of compact, only populating checksums where relevant.
|
|
227
|
+
*/
|
|
228
|
+
async populateChecksums(options: { minBucketChanges: number }): Promise<CompactInitialReplicationResults> {
|
|
229
|
+
let count = 0;
|
|
230
|
+
// Paginate through dirty buckets in batches until no more buckets meet the criteria.
|
|
231
|
+
while (true) {
|
|
232
|
+
this.signal?.throwIfAborted();
|
|
233
|
+
const buckets = await this.dirtyBucketBatchForChecksums(options);
|
|
234
|
+
if (buckets.length == 0) {
|
|
235
|
+
break;
|
|
236
|
+
}
|
|
237
|
+
this.signal?.throwIfAborted();
|
|
238
|
+
|
|
239
|
+
const start = Date.now();
|
|
240
|
+
// Filter batch by estimated bucket size, to reduce possibility of timeouts.
|
|
241
|
+
const checkBuckets: typeof buckets = [];
|
|
242
|
+
let totalCountEstimate = 0;
|
|
243
|
+
for (const bucket of buckets) {
|
|
244
|
+
checkBuckets.push(bucket);
|
|
245
|
+
totalCountEstimate += bucket.estimatedCount;
|
|
246
|
+
if (totalCountEstimate > 50_000) {
|
|
247
|
+
break;
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
this.logger.info(
|
|
251
|
+
`Calculating checksums for batch of ${buckets.length} buckets, estimated count of ${totalCountEstimate}`
|
|
252
|
+
);
|
|
253
|
+
await this.updateChecksumsBatch(checkBuckets);
|
|
254
|
+
this.logger.info(`Updated checksums for batch of ${checkBuckets.length} buckets in ${Date.now() - start}ms`);
|
|
255
|
+
count += checkBuckets.length;
|
|
256
|
+
}
|
|
257
|
+
return { buckets: count };
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
private collectBucketStateUpdate(
|
|
261
|
+
state: CurrentBucketState,
|
|
262
|
+
compactedOpId: bigint
|
|
263
|
+
): mongo.AnyBulkWriteOperation<BucketStateDocumentBase> {
|
|
264
|
+
if (state.opCount < 0) {
|
|
265
|
+
throw new ServiceAssertionError(
|
|
266
|
+
`Invalid opCount: ${state.opCount} checksum ${state.checksum} opsSincePut: ${state.opsSincePut} maxOpId: ${this.maxOpId}`
|
|
267
|
+
);
|
|
268
|
+
}
|
|
269
|
+
return {
|
|
270
|
+
updateOne: {
|
|
271
|
+
filter: this.bucketStateFilter(state.bucket),
|
|
272
|
+
update: {
|
|
273
|
+
$set: {
|
|
274
|
+
compacted_state: {
|
|
275
|
+
op_id: compactedOpId,
|
|
276
|
+
count: state.opCount,
|
|
277
|
+
checksum: BigInt(state.checksum),
|
|
278
|
+
bytes: state.opBytes
|
|
279
|
+
},
|
|
280
|
+
estimate_since_compact: {
|
|
281
|
+
// There could have been a whole bunch of new operations added to the bucket while compacting,
|
|
282
|
+
// which we don't currently cater for. We could potentially query for that, but that adds overhead.
|
|
283
|
+
count: 0,
|
|
284
|
+
bytes: 0
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
} satisfies mongo.UpdateFilter<BucketStateDocumentBase>,
|
|
288
|
+
// We generally expect this to have been created before.
|
|
289
|
+
// We don't create new ones here, to avoid issues with the unique index on bucket_updates.
|
|
290
|
+
upsert: false
|
|
291
|
+
}
|
|
292
|
+
};
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
private updateBucketChecksums(state: CurrentBucketState, compactedOpId: bigint) {
|
|
296
|
+
this.bucketStateUpdates.push(this.collectBucketStateUpdate(state, compactedOpId));
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
private async flushBucketStateUpdates() {
|
|
300
|
+
const writes = this.db.createWriteBatch(undefined, { ordered: false });
|
|
301
|
+
this.writeBucketStateUpdates(writes);
|
|
302
|
+
await writes.execute();
|
|
303
|
+
this.bucketStateUpdates = [];
|
|
56
304
|
}
|
|
57
305
|
|
|
58
|
-
|
|
59
|
-
await this.
|
|
60
|
-
|
|
61
|
-
|
|
306
|
+
private async updateChecksumsBatch(buckets: Pick<DirtyBucket, 'bucket' | 'definitionId'>[]) {
|
|
307
|
+
const checksums = await this.computeChecksumsForBuckets(buckets);
|
|
308
|
+
const definitionIdByBucket = new Map(buckets.map((bucket) => [bucket.bucket, bucket.definitionId]));
|
|
309
|
+
|
|
310
|
+
for (const bucketChecksum of checksums.values()) {
|
|
311
|
+
if (isPartialChecksum(bucketChecksum)) {
|
|
312
|
+
// Should never happen since we don't specify `start`.
|
|
313
|
+
throw new ServiceAssertionError(`Full checksum expected, got ${JSON.stringify(bucketChecksum)}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.bucketStateUpdates.push({
|
|
317
|
+
updateOne: {
|
|
318
|
+
filter: this.bucketStateFilter(bucketChecksum.bucket),
|
|
319
|
+
update: {
|
|
320
|
+
$set: {
|
|
321
|
+
compacted_state: {
|
|
322
|
+
op_id: this.maxOpId,
|
|
323
|
+
count: bucketChecksum.count,
|
|
324
|
+
checksum: BigInt(bucketChecksum.checksum),
|
|
325
|
+
bytes: null
|
|
326
|
+
},
|
|
327
|
+
estimate_since_compact: {
|
|
328
|
+
count: 0,
|
|
329
|
+
bytes: 0
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} satisfies mongo.UpdateFilter<BucketStateDocumentBase>,
|
|
333
|
+
// We don't create new ones here - it gets tricky to get the last_op right with the unique index on
|
|
334
|
+
// bucket_updates.
|
|
335
|
+
upsert: false
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await this.flushBucketStateUpdates();
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
protected writeBucketStateUpdates(writes: MongoWriteBatch): void {
|
|
344
|
+
if (this.bucketStateUpdates.length === 0) return;
|
|
345
|
+
this.logger.info(`Updating ${this.bucketStateUpdates.length} bucket states`);
|
|
346
|
+
writes.bulkWriteUnordered(
|
|
347
|
+
this.db.bucketStateV1,
|
|
348
|
+
this.bucketStateUpdates as mongo.AnyBulkWriteOperation<BucketStateDocumentV1>[]
|
|
62
349
|
);
|
|
63
350
|
}
|
|
64
351
|
|
|
@@ -74,10 +361,7 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
74
361
|
);
|
|
75
362
|
}
|
|
76
363
|
|
|
77
|
-
|
|
78
|
-
bucket: string,
|
|
79
|
-
_definitionId: BucketDefinitionId | null
|
|
80
|
-
): mongo.Filter<BucketStateDocumentBase> {
|
|
364
|
+
private bucketStateFilter(bucket: string): mongo.Filter<BucketStateDocumentBase> {
|
|
81
365
|
return {
|
|
82
366
|
_id: {
|
|
83
367
|
g: this.group_id,
|
|
@@ -95,8 +379,9 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
95
379
|
}
|
|
96
380
|
|
|
97
381
|
protected async compactSingleBucket(bucket: string) {
|
|
98
|
-
// Do not carry queued writes from a failed attempt into the rescan
|
|
382
|
+
// Do not carry queued writes from a failed attempt into the rescan.
|
|
99
383
|
this.updates = [];
|
|
384
|
+
this.bucketStateUpdates = [];
|
|
100
385
|
|
|
101
386
|
const idLimitBytes = this.idLimitBytes;
|
|
102
387
|
const bucketContext = this.getBucketDataContext(bucket);
|
|
@@ -200,6 +485,7 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
200
485
|
$unset: {
|
|
201
486
|
source_table: 1,
|
|
202
487
|
source_key: 1,
|
|
488
|
+
subkey: 1,
|
|
203
489
|
table: 1,
|
|
204
490
|
row_id: 1,
|
|
205
491
|
data: 1
|
|
@@ -257,17 +543,19 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
257
543
|
}
|
|
258
544
|
|
|
259
545
|
private async flush(bucketContext: SingleBucketStoreV1) {
|
|
546
|
+
// Persist data changes before advertising the resulting bucket state.
|
|
547
|
+
const writes = this.db.createWriteBatch(undefined, { ordered: true });
|
|
260
548
|
if (this.updates.length > 0) {
|
|
261
549
|
this.logger.info(`Compacting ${this.updates.length} ops`);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
ordered: false
|
|
266
|
-
});
|
|
267
|
-
this.updates = [];
|
|
550
|
+
// Order is not important. Since checksums are not affected, these operations can happen in any order,
|
|
551
|
+
// and it's fine if the operations are partially applied. Each individual operation is atomic.
|
|
552
|
+
writes.bulkWriteUnordered(bucketContext.collection, this.updates);
|
|
268
553
|
}
|
|
269
554
|
|
|
270
|
-
|
|
555
|
+
this.writeBucketStateUpdates(writes);
|
|
556
|
+
await writes.execute();
|
|
557
|
+
this.updates = [];
|
|
558
|
+
this.bucketStateUpdates = [];
|
|
271
559
|
}
|
|
272
560
|
|
|
273
561
|
/**
|
|
@@ -338,15 +626,13 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
338
626
|
}
|
|
339
627
|
|
|
340
628
|
this.logger.info(`Flushing CLEAR for ${numberOfOpsToClear} ops at ${lastOp?.o}`);
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
{ session }
|
|
349
|
-
);
|
|
629
|
+
const writes = this.db.createWriteBatch(session, { ordered: true });
|
|
630
|
+
writes.deleteMany(bucketContext.collection, {
|
|
631
|
+
_id: {
|
|
632
|
+
$gte: bucketContext.minId,
|
|
633
|
+
$lte: bucketContext.docId(lastOp!.o)
|
|
634
|
+
}
|
|
635
|
+
});
|
|
350
636
|
|
|
351
637
|
const op = bucketContext.toPersistedDocument({
|
|
352
638
|
o: lastOp!.o,
|
|
@@ -355,7 +641,8 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
355
641
|
data: null,
|
|
356
642
|
target_op: targetOp
|
|
357
643
|
});
|
|
358
|
-
|
|
644
|
+
writes.insertOne(bucketContext.collection, op);
|
|
645
|
+
await writes.execute();
|
|
359
646
|
|
|
360
647
|
opCountDiff = -numberOfOpsToClear + 1;
|
|
361
648
|
},
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { bson, InternalOpId } from '@powersync/service-core';
|
|
2
3
|
import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
|
|
3
4
|
import { VersionedPowerSyncMongoV1 } from './VersionedPowerSyncMongoV1.js';
|
|
4
5
|
|
|
@@ -9,18 +10,33 @@ export class MongoParameterCompactorV1 extends MongoParameterCompactor {
|
|
|
9
10
|
return [this.db.parameterIndexV1 as unknown as mongo.Collection<mongo.Document>];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
// The shared V1 collection is scanned using only its default `_id` index. Filter the stream in
|
|
14
|
+
// code so compaction does not require an index on `key.g`.
|
|
15
|
+
protected override shouldCompactDocument(doc: { key: mongo.Document }): boolean {
|
|
16
|
+
return doc.key.g === this.replicationStreamId;
|
|
16
17
|
}
|
|
17
18
|
|
|
18
|
-
|
|
19
|
+
/**
|
|
20
|
+
* Uses the legacy `{ 'key.g': 1, lookup: 1, _id: 1 }` index to narrow the stream, lookup and
|
|
21
|
+
* operation-id range. `key` is not part of that index at all, so it is a residual predicate applied
|
|
22
|
+
* to every document the range scan returns.
|
|
23
|
+
*
|
|
24
|
+
* That scan may therefore have to filter through many keys for the same lookup, but the cost is
|
|
25
|
+
* amortized: a single scan covers up to 1000 keys, and identities seen again in a later batch skip
|
|
26
|
+
* the scan entirely - they are deleted by `_id`.
|
|
27
|
+
*
|
|
28
|
+
* The V3 storage format uses an index more suitable for this.
|
|
29
|
+
*/
|
|
30
|
+
protected leadingHistoryDeleteFilter(
|
|
31
|
+
lookup: bson.Binary,
|
|
32
|
+
keys: mongo.Document[],
|
|
33
|
+
before: InternalOpId
|
|
34
|
+
): mongo.Document {
|
|
19
35
|
return {
|
|
20
|
-
'key.g':
|
|
21
|
-
lookup
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
'key.g': this.replicationStreamId,
|
|
37
|
+
lookup,
|
|
38
|
+
key: { $in: keys },
|
|
39
|
+
_id: { $lt: before }
|
|
24
40
|
};
|
|
25
41
|
}
|
|
26
42
|
}
|
|
@@ -3,6 +3,8 @@ import { mongo } from '@powersync/lib-service-mongodb';
|
|
|
3
3
|
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
4
4
|
import {
|
|
5
5
|
CheckpointChanges,
|
|
6
|
+
CompactInitialReplicationOptions,
|
|
7
|
+
CompactInitialReplicationResults,
|
|
6
8
|
deserializeParameterLookup,
|
|
7
9
|
GetCheckpointChangesOptions,
|
|
8
10
|
InternalOpId,
|
|
@@ -26,13 +28,20 @@ import {
|
|
|
26
28
|
setSessionSnapshotTime
|
|
27
29
|
} from '../../../utils/util.js';
|
|
28
30
|
import { MongoBucketStorage } from '../../MongoBucketStorage.js';
|
|
29
|
-
import {
|
|
31
|
+
import {
|
|
32
|
+
MongoGetCheckpointChangesOptions,
|
|
33
|
+
MongoSyncBucketStorageCheckpoint
|
|
34
|
+
} from '../common/MongoSyncBucketStorageCheckpoint.js';
|
|
30
35
|
import { SourceKey } from '../models.js';
|
|
31
36
|
import { MongoChecksums } from '../MongoChecksums.js';
|
|
32
|
-
import { MongoCompactOptions
|
|
37
|
+
import { MongoCompactOptions } from '../MongoCompactor.js';
|
|
33
38
|
import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
|
|
34
39
|
import { MongoPersistedReplicationStream } from '../MongoPersistedReplicationStream.js';
|
|
35
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
MongoCheckpointState,
|
|
42
|
+
MongoSyncBucketStorage,
|
|
43
|
+
MongoSyncBucketStorageOptions
|
|
44
|
+
} from '../MongoSyncBucketStorage.js';
|
|
36
45
|
import {
|
|
37
46
|
BucketDataDocumentV1,
|
|
38
47
|
BucketDataKeyV1,
|
|
@@ -100,14 +109,20 @@ export class MongoSyncBucketStorageV1 extends MongoSyncBucketStorage {
|
|
|
100
109
|
});
|
|
101
110
|
}
|
|
102
111
|
|
|
103
|
-
protected async fetchCheckpointState(
|
|
104
|
-
session: mongo.ClientSession
|
|
105
|
-
): Promise<{ checkpoint: bigint; lsn: string | null } | null> {
|
|
112
|
+
protected async fetchCheckpointState(session: mongo.ClientSession): Promise<MongoCheckpointState | null> {
|
|
106
113
|
const doc = (await this.db.sync_rules.findOne(
|
|
107
114
|
{ _id: this.replicationStreamId },
|
|
108
115
|
{
|
|
109
116
|
session,
|
|
110
|
-
projection: {
|
|
117
|
+
projection: {
|
|
118
|
+
_id: 1,
|
|
119
|
+
state: 1,
|
|
120
|
+
last_checkpoint: 1,
|
|
121
|
+
last_checkpoint_lsn: 1,
|
|
122
|
+
snapshot_done: 1,
|
|
123
|
+
// Must be read in the same snapshot as the checkpoint.
|
|
124
|
+
'parameter_compaction.checkpoint_changes_invalid_before': 1
|
|
125
|
+
}
|
|
111
126
|
}
|
|
112
127
|
)) as SyncRuleDocumentV1;
|
|
113
128
|
if (!doc?.snapshot_done || ![storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED].includes(doc.state)) {
|
|
@@ -115,7 +130,9 @@ export class MongoSyncBucketStorageV1 extends MongoSyncBucketStorage {
|
|
|
115
130
|
}
|
|
116
131
|
return {
|
|
117
132
|
checkpoint: doc.last_checkpoint ?? 0n,
|
|
118
|
-
lsn: doc.last_checkpoint_lsn ?? null
|
|
133
|
+
lsn: doc.last_checkpoint_lsn ?? null,
|
|
134
|
+
// Defaults to 0n for streams that have never been compacted.
|
|
135
|
+
parameterChangesInvalidBefore: doc.parameter_compaction?.checkpoint_changes_invalid_before ?? 0n
|
|
119
136
|
};
|
|
120
137
|
}
|
|
121
138
|
|
|
@@ -171,7 +188,8 @@ export class MongoSyncBucketStorageV1 extends MongoSyncBucketStorage {
|
|
|
171
188
|
no_checkpoint_before: null
|
|
172
189
|
},
|
|
173
190
|
$unset: {
|
|
174
|
-
snapshot_lsn: 1
|
|
191
|
+
snapshot_lsn: 1,
|
|
192
|
+
parameter_compaction: 1
|
|
175
193
|
}
|
|
176
194
|
},
|
|
177
195
|
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
@@ -186,10 +204,31 @@ export class MongoSyncBucketStorageV1 extends MongoSyncBucketStorage {
|
|
|
186
204
|
});
|
|
187
205
|
}
|
|
188
206
|
|
|
189
|
-
createMongoCompactor(options: MongoCompactOptions):
|
|
207
|
+
createMongoCompactor(options: MongoCompactOptions): MongoCompactorV1 {
|
|
190
208
|
return new MongoCompactorV1(this, this.db, options);
|
|
191
209
|
}
|
|
192
210
|
|
|
211
|
+
async compactInitialReplication(
|
|
212
|
+
options: CompactInitialReplicationOptions
|
|
213
|
+
): Promise<CompactInitialReplicationResults> {
|
|
214
|
+
this.logger.info(`Compacting after initial replication...`);
|
|
215
|
+
const start = Date.now();
|
|
216
|
+
const maxOpId = options.maxOpId ?? (await this.fetchPersistedOpHead()) ?? undefined;
|
|
217
|
+
const compactor = this.createMongoCompactor({
|
|
218
|
+
...options,
|
|
219
|
+
maxOpId,
|
|
220
|
+
memoryLimitMB: 0,
|
|
221
|
+
logger: this.logger
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
const result = await compactor.populateChecksums({
|
|
225
|
+
minBucketChanges: options.minBucketChanges ?? 10
|
|
226
|
+
});
|
|
227
|
+
const duration = Date.now() - start;
|
|
228
|
+
this.logger.info(`Compacted after initial replication in ${(duration / 1000).toFixed(1)}s`);
|
|
229
|
+
return result;
|
|
230
|
+
}
|
|
231
|
+
|
|
193
232
|
protected createMongoParameterCompactor(
|
|
194
233
|
checkpoint: InternalOpId,
|
|
195
234
|
options: storage.CompactOptions
|
|
@@ -303,7 +342,7 @@ export class MongoSyncBucketStorageV1 extends MongoSyncBucketStorage {
|
|
|
303
342
|
}
|
|
304
343
|
|
|
305
344
|
protected getParameterBucketChangesImpl(
|
|
306
|
-
options:
|
|
345
|
+
options: MongoGetCheckpointChangesOptions
|
|
307
346
|
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
|
|
308
347
|
return getParameterBucketChangesV1(this.versionContext, options);
|
|
309
348
|
}
|
|
@@ -548,27 +587,48 @@ export async function getDataBucketChangesV1(
|
|
|
548
587
|
};
|
|
549
588
|
}
|
|
550
589
|
|
|
590
|
+
/**
|
|
591
|
+
* Query the parameter entries changed between the two checkpoints, to determine which parameter
|
|
592
|
+
* lookups need to be re-evaluated.
|
|
593
|
+
*
|
|
594
|
+
* This runs at the next checkpoint's snapshot, so it still sees entries that parameter compaction
|
|
595
|
+
* deleted after that snapshot. Compaction that deleted entries before the snapshot is covered by
|
|
596
|
+
* the invalidation fence, checked before we get here.
|
|
597
|
+
*/
|
|
551
598
|
export async function getParameterBucketChangesV1(
|
|
552
599
|
ctx: MongoSyncBucketStorageContextV1,
|
|
553
|
-
options:
|
|
600
|
+
options: MongoGetCheckpointChangesOptions
|
|
554
601
|
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
|
|
555
602
|
const limit = 1000;
|
|
556
|
-
const parameterUpdates = await ctx.db.
|
|
557
|
-
.
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
projection: {
|
|
564
|
-
lookup: 1
|
|
603
|
+
const parameterUpdates = await ctx.db.client.withSession({ snapshot: true }, async (session) => {
|
|
604
|
+
setSessionSnapshotTime(session, options.nextCheckpoint.snapshotTime);
|
|
605
|
+
return await ctx.db.parameterIndexV1
|
|
606
|
+
.find(
|
|
607
|
+
{
|
|
608
|
+
_id: { $gt: options.lastCheckpoint.checkpoint, $lte: options.nextCheckpoint.checkpoint },
|
|
609
|
+
'key.g': ctx.replicationStreamId
|
|
565
610
|
},
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
611
|
+
{
|
|
612
|
+
session,
|
|
613
|
+
readConcern: 'snapshot',
|
|
614
|
+
projection: {
|
|
615
|
+
lookup: 1
|
|
616
|
+
},
|
|
617
|
+
limit: limit + 1,
|
|
618
|
+
batchSize: limit + 2,
|
|
619
|
+
singleBatch: true
|
|
620
|
+
}
|
|
621
|
+
)
|
|
622
|
+
.toArray()
|
|
623
|
+
.catch((e) => {
|
|
624
|
+
// Includes the case where the checkpoint snapshot has expired. Degrading to
|
|
625
|
+
// invalidateParameterBuckets would be safe in itself - it reads nothing - but it gains
|
|
626
|
+
// nothing: the caller responds to that by re-evaluating the parameter queries at this
|
|
627
|
+
// same snapshot, which fails too. The checkpoint has to be refetched instead, which is
|
|
628
|
+
// what the existing sync retry behavior does.
|
|
629
|
+
throw lib_mongo.mapQueryError(e, 'while querying parameter changes');
|
|
630
|
+
});
|
|
631
|
+
});
|
|
572
632
|
const invalidateParameterUpdates = parameterUpdates.length > limit;
|
|
573
633
|
|
|
574
634
|
return {
|