@powersync/service-module-mongodb-storage 0.0.0-dev-20260225160713 → 0.0.0-dev-20260511080634
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 +119 -6
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +1 -1
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +3 -3
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/migrations/db/migrations/1770213298299-storage-version.js.map +1 -1
- package/dist/storage/MongoBucketStorage.d.ts +5 -3
- package/dist/storage/MongoBucketStorage.js +53 -26
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/MongoReportStorage.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +17 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
- package/dist/storage/implementation/MongoBucketBatch.d.ts +25 -21
- package/dist/storage/implementation/MongoBucketBatch.js +256 -210
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
- package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
- package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
- package/dist/storage/implementation/MongoChecksums.d.ts +30 -19
- package/dist/storage/implementation/MongoChecksums.js +13 -71
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +99 -53
- package/dist/storage/implementation/MongoCompactor.js +265 -303
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +12 -7
- package/dist/storage/implementation/MongoParameterCompactor.js +24 -9
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
- package/dist/storage/implementation/MongoPersistedSyncRules.js +64 -0
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +3 -0
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +11 -7
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +54 -30
- package/dist/storage/implementation/MongoSyncBucketStorage.js +118 -472
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +7 -5
- package/dist/storage/implementation/MongoSyncRulesLock.js +12 -10
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +4 -4
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/OperationBatch.js +3 -2
- package/dist/storage/implementation/OperationBatch.js.map +1 -1
- package/dist/storage/implementation/common/BucketDataDoc.d.ts +35 -0
- package/dist/storage/implementation/common/BucketDataDoc.js +2 -0
- package/dist/storage/implementation/common/BucketDataDoc.js.map +1 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +13 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +108 -0
- package/dist/storage/implementation/common/PersistedBatch.js +237 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -0
- package/dist/storage/implementation/common/SingleBucketStore.d.ts +54 -0
- package/dist/storage/implementation/common/SingleBucketStore.js +3 -0
- package/dist/storage/implementation/common/SingleBucketStore.js.map +1 -0
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +36 -0
- package/dist/storage/implementation/common/SourceRecordStore.js +2 -0
- package/dist/storage/implementation/common/SourceRecordStore.js.map +1 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +27 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +57 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +7 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +9 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -0
- package/dist/storage/implementation/db.d.ts +38 -4
- package/dist/storage/implementation/db.js +80 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +66 -25
- package/dist/storage/implementation/models.js +22 -2
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +13 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +22 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +12 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +56 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +23 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.js +52 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +9 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +41 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +269 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +26 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.js +183 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +18 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js +57 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +19 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.js +105 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +12 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +20 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -0
- package/dist/storage/implementation/v1/models.d.ts +34 -0
- package/dist/storage/implementation/v1/models.js +37 -0
- package/dist/storage/implementation/v1/models.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +13 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +34 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +15 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +84 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.js +68 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +9 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +5 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +9 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +41 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +404 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +29 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +259 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +18 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +48 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +22 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +164 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +21 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +71 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +43 -0
- package/dist/storage/implementation/v3/models.js +34 -0
- package/dist/storage/implementation/v3/models.js.map +1 -0
- package/dist/storage/storage-index.d.ts +8 -5
- package/dist/storage/storage-index.js +8 -5
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/utils/test-utils.d.ts +4 -1
- package/dist/utils/test-utils.js +15 -12
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +13 -5
- package/dist/utils/util.js +40 -5
- package/dist/utils/util.js.map +1 -1
- package/package.json +9 -9
- package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
- package/src/migrations/db/migrations/1770213298299-storage-version.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +111 -55
- package/src/storage/MongoReportStorage.ts +2 -2
- package/src/storage/implementation/BucketDefinitionMapping.ts +72 -0
- package/src/storage/implementation/MongoBucketBatch.ts +322 -270
- package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
- package/src/storage/implementation/MongoChecksums.ts +54 -75
- package/src/storage/implementation/MongoCompactor.ts +411 -412
- package/src/storage/implementation/MongoParameterCompactor.ts +52 -26
- package/src/storage/implementation/MongoPersistedSyncRules.ts +76 -0
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +19 -10
- package/src/storage/implementation/MongoStorageProvider.ts +1 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +206 -466
- package/src/storage/implementation/MongoSyncRulesLock.ts +14 -16
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +7 -5
- package/src/storage/implementation/OperationBatch.ts +3 -2
- package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
- package/src/storage/implementation/common/PersistedBatch.ts +364 -0
- package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
- package/src/storage/implementation/common/SourceRecordStore.ts +49 -0
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
- package/src/storage/implementation/db.ts +115 -8
- package/src/storage/implementation/models.ts +88 -28
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +32 -0
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
- package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +432 -0
- package/src/storage/implementation/v1/PersistedBatchV1.ts +230 -0
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
- package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
- package/src/storage/implementation/v1/models.ts +84 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +44 -0
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
- package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +12 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +545 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +318 -0
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +112 -0
- package/src/storage/implementation/v3/models.ts +96 -0
- package/src/storage/storage-index.ts +8 -5
- package/src/utils/test-utils.ts +15 -12
- package/src/utils/util.ts +53 -9
- package/test/src/__snapshots__/storage.test.ts.snap +201 -0
- package/test/src/__snapshots__/storage_compacting.test.ts.snap +17 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +1393 -16
- package/test/src/connection-report-storage.test.ts +3 -3
- package/test/src/setup.ts +1 -1
- package/test/src/storage.test.ts +10 -8
- package/test/src/storage_compacting.test.ts +146 -46
- package/test/src/storage_sync.test.ts +404 -56
- package/test/src/util.ts +3 -3
- package/test/tsconfig.json +0 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/PersistedBatch.d.ts +0 -64
- package/dist/storage/implementation/PersistedBatch.js +0 -323
- package/dist/storage/implementation/PersistedBatch.js.map +0 -1
- package/src/storage/implementation/PersistedBatch.ts +0 -401
|
@@ -2,7 +2,8 @@ import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
|
2
2
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
3
|
import {
|
|
4
4
|
BaseObserver,
|
|
5
|
-
|
|
5
|
+
DO_NOT_LOG,
|
|
6
|
+
Logger,
|
|
6
7
|
ReplicationAbortedError,
|
|
7
8
|
ServiceAssertionError
|
|
8
9
|
} from '@powersync/lib-services-framework';
|
|
@@ -10,47 +11,43 @@ import {
|
|
|
10
11
|
BroadcastIterable,
|
|
11
12
|
CHECKPOINT_INVALIDATE_ALL,
|
|
12
13
|
CheckpointChanges,
|
|
13
|
-
deserializeParameterLookup,
|
|
14
14
|
GetCheckpointChangesOptions,
|
|
15
15
|
InternalOpId,
|
|
16
|
-
internalToExternalOpId,
|
|
17
16
|
maxLsn,
|
|
18
17
|
mergeAsyncIterables,
|
|
19
18
|
PopulateChecksumCacheOptions,
|
|
20
19
|
PopulateChecksumCacheResults,
|
|
21
|
-
ProtocolOpId,
|
|
22
20
|
ReplicationCheckpoint,
|
|
23
21
|
storage,
|
|
24
22
|
utils,
|
|
25
23
|
WatchWriteCheckpointOptions
|
|
26
24
|
} from '@powersync/service-core';
|
|
27
|
-
import { JSONBig } from '@powersync/service-jsonbig';
|
|
28
25
|
import { HydratedSyncRules, ScopedParameterLookup, SqliteJsonRow } from '@powersync/service-sync-rules';
|
|
29
26
|
import * as bson from 'bson';
|
|
30
27
|
import { LRUCache } from 'lru-cache';
|
|
31
28
|
import * as timers from 'timers/promises';
|
|
32
|
-
import {
|
|
29
|
+
import { retryOnMongoMaxTimeMSExpired } from '../../utils/util.js';
|
|
33
30
|
import { MongoBucketStorage } from '../MongoBucketStorage.js';
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
BucketStateDocument,
|
|
39
|
-
SourceKey,
|
|
40
|
-
SourceTableDocument,
|
|
41
|
-
StorageConfig
|
|
42
|
-
} from './models.js';
|
|
43
|
-
import { MongoBucketBatch } from './MongoBucketBatch.js';
|
|
31
|
+
import { MongoSyncBucketStorageContext } from './common/MongoSyncBucketStorageContext.js';
|
|
32
|
+
import type { VersionedPowerSyncMongo } from './db.js';
|
|
33
|
+
import { CommonSourceTableDocument, StorageConfig } from './models.js';
|
|
34
|
+
import { MongoBucketBatchOptions } from './MongoBucketBatch.js';
|
|
44
35
|
import { MongoChecksumOptions, MongoChecksums } from './MongoChecksums.js';
|
|
45
|
-
import { MongoCompactor } from './MongoCompactor.js';
|
|
36
|
+
import { MongoCompactOptions, MongoCompactor } from './MongoCompactor.js';
|
|
46
37
|
import { MongoParameterCompactor } from './MongoParameterCompactor.js';
|
|
38
|
+
import { MongoPersistedSyncRulesContent } from './MongoPersistedSyncRulesContent.js';
|
|
47
39
|
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
48
40
|
|
|
49
41
|
export interface MongoSyncBucketStorageOptions {
|
|
50
|
-
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
|
|
42
|
+
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig' | 'mapping'>;
|
|
51
43
|
storageConfig: StorageConfig;
|
|
52
44
|
}
|
|
53
45
|
|
|
46
|
+
interface InternalCheckpointChanges extends CheckpointChanges {
|
|
47
|
+
updatedWriteCheckpoints: Map<string, bigint>;
|
|
48
|
+
invalidateWriteCheckpoints: boolean;
|
|
49
|
+
}
|
|
50
|
+
|
|
54
51
|
/**
|
|
55
52
|
* Only keep checkpoints around for a minute, before fetching a fresh one.
|
|
56
53
|
*
|
|
@@ -62,41 +59,68 @@ export interface MongoSyncBucketStorageOptions {
|
|
|
62
59
|
*/
|
|
63
60
|
const CHECKPOINT_TIMEOUT_MS = 60_000;
|
|
64
61
|
|
|
65
|
-
export class MongoSyncBucketStorage
|
|
62
|
+
export abstract class MongoSyncBucketStorage
|
|
66
63
|
extends BaseObserver<storage.SyncRulesBucketStorageListener>
|
|
67
64
|
implements storage.SyncRulesBucketStorage
|
|
68
65
|
{
|
|
69
|
-
|
|
66
|
+
readonly db: VersionedPowerSyncMongo;
|
|
67
|
+
[DO_NOT_LOG] = true;
|
|
68
|
+
|
|
70
69
|
readonly checksums: MongoChecksums;
|
|
71
70
|
|
|
72
71
|
private parsedSyncRulesCache: { parsed: HydratedSyncRules; options: storage.ParseSyncRulesOptions } | undefined;
|
|
73
72
|
private writeCheckpointAPI: MongoWriteCheckpointAPI;
|
|
73
|
+
public readonly logger: Logger;
|
|
74
|
+
#storageInitialized = false;
|
|
74
75
|
|
|
75
76
|
constructor(
|
|
76
77
|
public readonly factory: MongoBucketStorage,
|
|
77
78
|
public readonly group_id: number,
|
|
78
|
-
|
|
79
|
+
protected readonly sync_rules: MongoPersistedSyncRulesContent,
|
|
79
80
|
public readonly slot_name: string,
|
|
80
81
|
writeCheckpointMode: storage.WriteCheckpointMode | undefined,
|
|
81
82
|
options: MongoSyncBucketStorageOptions
|
|
82
83
|
) {
|
|
83
84
|
super();
|
|
84
|
-
this.db = factory.db;
|
|
85
|
-
this.checksums =
|
|
86
|
-
...options.checksumOptions,
|
|
87
|
-
storageConfig: options?.storageConfig
|
|
88
|
-
});
|
|
85
|
+
this.db = factory.db.versioned(sync_rules.getStorageConfig());
|
|
86
|
+
this.checksums = this.createMongoChecksums(options);
|
|
89
87
|
this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
|
|
90
88
|
db: this.db,
|
|
91
89
|
mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
|
|
92
90
|
sync_rules_id: group_id
|
|
93
91
|
});
|
|
92
|
+
this.logger = sync_rules.logger;
|
|
94
93
|
}
|
|
95
94
|
|
|
95
|
+
/**
|
|
96
|
+
* Not for external use - public here for tests only.
|
|
97
|
+
*
|
|
98
|
+
* @internal
|
|
99
|
+
*/
|
|
100
|
+
abstract createMongoCompactor(options: MongoCompactOptions): MongoCompactor;
|
|
101
|
+
|
|
102
|
+
protected abstract createMongoChecksums(options: MongoSyncBucketStorageOptions): MongoChecksums;
|
|
103
|
+
protected abstract createMongoParameterCompactor(
|
|
104
|
+
checkpoint: InternalOpId,
|
|
105
|
+
options: storage.CompactOptions
|
|
106
|
+
): MongoParameterCompactor;
|
|
107
|
+
|
|
96
108
|
get writeCheckpointMode() {
|
|
97
109
|
return this.writeCheckpointAPI.writeCheckpointMode;
|
|
98
110
|
}
|
|
99
111
|
|
|
112
|
+
get mapping() {
|
|
113
|
+
return this.sync_rules.mapping;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
protected get versionContext(): MongoSyncBucketStorageContext {
|
|
117
|
+
return {
|
|
118
|
+
db: this.db,
|
|
119
|
+
group_id: this.group_id,
|
|
120
|
+
mapping: this.mapping
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
|
|
100
124
|
setWriteCheckpointMode(mode: storage.WriteCheckpointMode): void {
|
|
101
125
|
this.writeCheckpointAPI.setWriteCheckpointMode(mode);
|
|
102
126
|
}
|
|
@@ -114,10 +138,6 @@ export class MongoSyncBucketStorage
|
|
|
114
138
|
|
|
115
139
|
getParsedSyncRules(options: storage.ParseSyncRulesOptions): HydratedSyncRules {
|
|
116
140
|
const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
|
|
117
|
-
/**
|
|
118
|
-
* Check if the cached sync rules, if present, had the same options.
|
|
119
|
-
* Parse sync rules if the options are different or if there is no cached value.
|
|
120
|
-
*/
|
|
121
141
|
if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
|
|
122
142
|
this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncRules(), options };
|
|
123
143
|
}
|
|
@@ -139,26 +159,15 @@ export class MongoSyncBucketStorage
|
|
|
139
159
|
}
|
|
140
160
|
);
|
|
141
161
|
if (!doc?.snapshot_done || !['ACTIVE', 'ERRORED'].includes(doc.state)) {
|
|
142
|
-
// Sync rules not active - return null
|
|
143
162
|
return null;
|
|
144
163
|
}
|
|
145
164
|
|
|
146
|
-
// Specifically using operationTime instead of clusterTime
|
|
147
|
-
// There are 3 fields in the response:
|
|
148
|
-
// 1. operationTime, not exposed for snapshot sessions (used for causal consistency)
|
|
149
|
-
// 2. clusterTime (used for connection management)
|
|
150
|
-
// 3. atClusterTime, which is session.snapshotTime
|
|
151
|
-
// We use atClusterTime, to match the driver's internal snapshot handling.
|
|
152
|
-
// There are cases where clusterTime > operationTime and atClusterTime,
|
|
153
|
-
// which could cause snapshot queries using this as the snapshotTime to timeout.
|
|
154
|
-
// This was specifically observed on MongoDB 6.0 and 7.0.
|
|
155
165
|
const snapshotTime = (session as any).snapshotTime as bson.Timestamp | undefined;
|
|
156
166
|
if (snapshotTime == null) {
|
|
157
167
|
throw new ServiceAssertionError('Missing snapshotTime in getCheckpoint()');
|
|
158
168
|
}
|
|
159
169
|
return new MongoReplicationCheckpoint(
|
|
160
170
|
this,
|
|
161
|
-
// null/0n is a valid checkpoint in some cases, for example if the initial snapshot was empty
|
|
162
171
|
doc.last_checkpoint ?? 0n,
|
|
163
172
|
doc.last_checkpoint_lsn ?? null,
|
|
164
173
|
snapshotTime
|
|
@@ -166,10 +175,23 @@ export class MongoSyncBucketStorage
|
|
|
166
175
|
});
|
|
167
176
|
}
|
|
168
177
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
178
|
+
protected abstract initializeVersionStorage(): Promise<void>;
|
|
179
|
+
|
|
180
|
+
private async initializeStorage() {
|
|
181
|
+
if (this.#storageInitialized) {
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
await this.db.initializeStreamStorage(this.group_id);
|
|
186
|
+
await this.initializeVersionStorage();
|
|
187
|
+
this.#storageInitialized = true;
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
protected abstract createWriterImpl(batchOptions: MongoBucketBatchOptions): storage.BucketStorageBatch;
|
|
191
|
+
|
|
192
|
+
async createWriter(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
|
|
193
|
+
await this.initializeStorage();
|
|
194
|
+
|
|
173
195
|
const doc = await this.db.sync_rules.findOne(
|
|
174
196
|
{
|
|
175
197
|
_id: this.group_id
|
|
@@ -178,31 +200,46 @@ export class MongoSyncBucketStorage
|
|
|
178
200
|
);
|
|
179
201
|
const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
|
|
180
202
|
|
|
181
|
-
|
|
182
|
-
logger: options.logger,
|
|
203
|
+
const batchOptions: MongoBucketBatchOptions = {
|
|
204
|
+
logger: options.logger ?? this.logger,
|
|
183
205
|
db: this.db,
|
|
184
206
|
syncRules: this.sync_rules.parsed(options).hydratedSyncRules(),
|
|
207
|
+
mapping: this.sync_rules.mapping,
|
|
185
208
|
groupId: this.group_id,
|
|
186
209
|
slotName: this.slot_name,
|
|
187
210
|
lastCheckpointLsn: checkpoint_lsn,
|
|
188
211
|
resumeFromLsn: maxLsn(checkpoint_lsn, doc?.snapshot_lsn),
|
|
189
|
-
noCheckpointBeforeLsn: doc?.no_checkpoint_before ?? options.zeroLSN,
|
|
190
212
|
keepaliveOp: doc?.keepalive_op ? BigInt(doc.keepalive_op) : null,
|
|
191
213
|
storeCurrentData: options.storeCurrentData,
|
|
192
214
|
skipExistingRows: options.skipExistingRows ?? false,
|
|
193
|
-
markRecordUnavailable: options.markRecordUnavailable
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
215
|
+
markRecordUnavailable: options.markRecordUnavailable,
|
|
216
|
+
tracer: options.tracer
|
|
217
|
+
};
|
|
218
|
+
const writer = this.createWriterImpl(batchOptions);
|
|
219
|
+
this.iterateListeners((cb) => cb.batchStarted?.(writer));
|
|
220
|
+
return writer;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
async startBatch(
|
|
224
|
+
options: storage.CreateWriterOptions,
|
|
225
|
+
callback: (batch: storage.BucketStorageBatch) => Promise<void>
|
|
226
|
+
): Promise<storage.FlushedResult | null> {
|
|
227
|
+
await using writer = await this.createWriter(options);
|
|
228
|
+
await callback(writer);
|
|
229
|
+
await writer.flush();
|
|
230
|
+
return writer.last_flushed_op != null ? { flushed_op: writer.last_flushed_op } : null;
|
|
204
231
|
}
|
|
205
232
|
|
|
233
|
+
protected abstract sourceTableBaseId(): Partial<CommonSourceTableDocument>;
|
|
234
|
+
|
|
235
|
+
protected abstract augmentCreatedSourceTableDocument(
|
|
236
|
+
createDoc: CommonSourceTableDocument,
|
|
237
|
+
options: storage.ResolveTableOptions,
|
|
238
|
+
candidateSourceTable: storage.SourceTable
|
|
239
|
+
): void;
|
|
240
|
+
|
|
241
|
+
protected abstract initializeResolvedSourceRecords(sourceTableId: bson.ObjectId): Promise<void>;
|
|
242
|
+
|
|
206
243
|
async resolveTable(options: storage.ResolveTableOptions): Promise<storage.ResolveTableResult> {
|
|
207
244
|
const { group_id, connection_id, connection_tag, entity_descriptor } = options;
|
|
208
245
|
|
|
@@ -214,23 +251,36 @@ export class MongoSyncBucketStorage
|
|
|
214
251
|
type_oid: column.typeId
|
|
215
252
|
}));
|
|
216
253
|
let result: storage.ResolveTableResult | null = null;
|
|
254
|
+
let initializeSourceRecordsFor: bson.ObjectId | null = null;
|
|
255
|
+
|
|
256
|
+
const baseId = this.sourceTableBaseId();
|
|
217
257
|
await this.db.client.withSession(async (session) => {
|
|
218
|
-
const col = this.db.
|
|
219
|
-
let filter: Partial<
|
|
220
|
-
|
|
258
|
+
const col = this.db.commonSourceTables(group_id);
|
|
259
|
+
let filter: Partial<CommonSourceTableDocument> = {
|
|
260
|
+
...baseId,
|
|
221
261
|
connection_id: connection_id,
|
|
222
262
|
schema_name: schema,
|
|
223
263
|
table_name: name,
|
|
224
264
|
replica_id_columns2: normalizedReplicaIdColumns
|
|
225
265
|
};
|
|
266
|
+
|
|
226
267
|
if (objectId != null) {
|
|
227
268
|
filter.relation_id = objectId;
|
|
228
269
|
}
|
|
229
270
|
let doc = await col.findOne(filter, { session });
|
|
230
271
|
if (doc == null) {
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
272
|
+
const candidateSourceTable = new storage.SourceTable({
|
|
273
|
+
id: new bson.ObjectId(),
|
|
274
|
+
connectionTag: connection_tag,
|
|
275
|
+
objectId: objectId,
|
|
276
|
+
schema: schema,
|
|
277
|
+
name: name,
|
|
278
|
+
replicaIdColumns: replicaIdColumns,
|
|
279
|
+
snapshotComplete: false
|
|
280
|
+
});
|
|
281
|
+
const createDoc: CommonSourceTableDocument = {
|
|
282
|
+
_id: candidateSourceTable.id as bson.ObjectId,
|
|
283
|
+
...(baseId as any),
|
|
234
284
|
connection_id: connection_id,
|
|
235
285
|
relation_id: objectId,
|
|
236
286
|
schema_name: schema,
|
|
@@ -240,8 +290,11 @@ export class MongoSyncBucketStorage
|
|
|
240
290
|
snapshot_done: false,
|
|
241
291
|
snapshot_status: undefined
|
|
242
292
|
};
|
|
293
|
+
this.augmentCreatedSourceTableDocument(createDoc, options, candidateSourceTable);
|
|
294
|
+
doc = createDoc;
|
|
243
295
|
|
|
244
296
|
await col.insertOne(doc, { session });
|
|
297
|
+
initializeSourceRecordsFor = doc._id;
|
|
245
298
|
}
|
|
246
299
|
const sourceTable = new storage.SourceTable({
|
|
247
300
|
id: doc._id,
|
|
@@ -265,16 +318,14 @@ export class MongoSyncBucketStorage
|
|
|
265
318
|
};
|
|
266
319
|
|
|
267
320
|
let dropTables: storage.SourceTable[] = [];
|
|
268
|
-
// Detect tables that are either renamed, or have different replica_id_columns
|
|
269
321
|
let truncateFilter = [{ schema_name: schema, table_name: name }] as any[];
|
|
270
322
|
if (objectId != null) {
|
|
271
|
-
// Only detect renames if the source uses relation ids.
|
|
272
323
|
truncateFilter.push({ relation_id: objectId });
|
|
273
324
|
}
|
|
274
325
|
const truncate = await col
|
|
275
326
|
.find(
|
|
276
327
|
{
|
|
277
|
-
|
|
328
|
+
...baseId,
|
|
278
329
|
connection_id: connection_id,
|
|
279
330
|
_id: { $ne: doc._id },
|
|
280
331
|
$or: truncateFilter
|
|
@@ -301,225 +352,44 @@ export class MongoSyncBucketStorage
|
|
|
301
352
|
dropTables: dropTables
|
|
302
353
|
};
|
|
303
354
|
});
|
|
355
|
+
if (initializeSourceRecordsFor != null) {
|
|
356
|
+
await this.initializeResolvedSourceRecords(initializeSourceRecordsFor);
|
|
357
|
+
}
|
|
304
358
|
return result!;
|
|
305
359
|
}
|
|
306
360
|
|
|
361
|
+
protected abstract getParameterSetsImpl(
|
|
362
|
+
checkpoint: MongoReplicationCheckpoint,
|
|
363
|
+
lookups: ScopedParameterLookup[],
|
|
364
|
+
limit: number
|
|
365
|
+
): Promise<SqliteJsonRow[]>;
|
|
366
|
+
|
|
307
367
|
async getParameterSets(
|
|
308
368
|
checkpoint: MongoReplicationCheckpoint,
|
|
309
|
-
lookups: ScopedParameterLookup[]
|
|
369
|
+
lookups: ScopedParameterLookup[],
|
|
370
|
+
limit: number
|
|
310
371
|
): Promise<SqliteJsonRow[]> {
|
|
311
|
-
return this.
|
|
312
|
-
// Set the session's snapshot time to the checkpoint's snapshot time.
|
|
313
|
-
// An alternative would be to create the session when the checkpoint is created, but managing
|
|
314
|
-
// the session lifetime would become more complex.
|
|
315
|
-
// Starting and ending sessions are cheap (synchronous when no transactions are used),
|
|
316
|
-
// so this should be fine.
|
|
317
|
-
// This is a roundabout way of setting {readConcern: {atClusterTime: clusterTime}}, since
|
|
318
|
-
// that is not exposed directly by the driver.
|
|
319
|
-
// Future versions of the driver may change the snapshotTime behavior, so we need tests to
|
|
320
|
-
// validate that this works as expected. We test this in the compacting tests.
|
|
321
|
-
setSessionSnapshotTime(session, checkpoint.snapshotTime);
|
|
322
|
-
const lookupFilter = lookups.map((lookup) => {
|
|
323
|
-
return storage.serializeLookup(lookup);
|
|
324
|
-
});
|
|
325
|
-
// This query does not use indexes super efficiently, apart from the lookup filter.
|
|
326
|
-
// From some experimentation I could do individual lookups more efficient using an index
|
|
327
|
-
// on {'key.g': 1, lookup: 1, 'key.t': 1, 'key.k': 1, _id: -1},
|
|
328
|
-
// but could not do the same using $group.
|
|
329
|
-
// For now, just rely on compacting to remove extraneous data.
|
|
330
|
-
// For a description of the data format, see the `/docs/parameters-lookups.md` file.
|
|
331
|
-
const rows = await this.db.bucket_parameters
|
|
332
|
-
.aggregate(
|
|
333
|
-
[
|
|
334
|
-
{
|
|
335
|
-
$match: {
|
|
336
|
-
'key.g': this.group_id,
|
|
337
|
-
lookup: { $in: lookupFilter },
|
|
338
|
-
_id: { $lte: checkpoint.checkpoint }
|
|
339
|
-
}
|
|
340
|
-
},
|
|
341
|
-
{
|
|
342
|
-
$sort: {
|
|
343
|
-
_id: -1
|
|
344
|
-
}
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
$group: {
|
|
348
|
-
_id: { key: '$key', lookup: '$lookup' },
|
|
349
|
-
bucket_parameters: {
|
|
350
|
-
$first: '$bucket_parameters'
|
|
351
|
-
}
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
],
|
|
355
|
-
{
|
|
356
|
-
session,
|
|
357
|
-
readConcern: 'snapshot',
|
|
358
|
-
// Limit the time for the operation to complete, to avoid getting connection timeouts
|
|
359
|
-
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
|
|
360
|
-
}
|
|
361
|
-
)
|
|
362
|
-
.toArray()
|
|
363
|
-
.catch((e) => {
|
|
364
|
-
throw lib_mongo.mapQueryError(e, 'while evaluating parameter queries');
|
|
365
|
-
});
|
|
366
|
-
const groupedParameters = rows.map((row) => {
|
|
367
|
-
return row.bucket_parameters;
|
|
368
|
-
});
|
|
369
|
-
return groupedParameters.flat();
|
|
370
|
-
});
|
|
372
|
+
return this.getParameterSetsImpl(checkpoint, lookups, limit);
|
|
371
373
|
}
|
|
372
374
|
|
|
375
|
+
protected abstract getBucketDataBatchImpl(
|
|
376
|
+
checkpoint: utils.InternalOpId,
|
|
377
|
+
dataBuckets: storage.BucketDataRequest[],
|
|
378
|
+
options?: storage.BucketDataBatchOptions
|
|
379
|
+
): AsyncIterable<storage.SyncBucketDataChunk>;
|
|
380
|
+
|
|
373
381
|
async *getBucketDataBatch(
|
|
374
382
|
checkpoint: utils.InternalOpId,
|
|
375
|
-
dataBuckets:
|
|
383
|
+
dataBuckets: storage.BucketDataRequest[],
|
|
376
384
|
options?: storage.BucketDataBatchOptions
|
|
377
385
|
): AsyncIterable<storage.SyncBucketDataChunk> {
|
|
378
|
-
|
|
379
|
-
return;
|
|
380
|
-
}
|
|
381
|
-
let filters: mongo.Filter<BucketDataDocument>[] = [];
|
|
382
|
-
|
|
383
|
-
if (checkpoint == null) {
|
|
384
|
-
throw new ServiceAssertionError('checkpoint is null');
|
|
385
|
-
}
|
|
386
|
-
const end = checkpoint;
|
|
387
|
-
for (let [name, start] of dataBuckets.entries()) {
|
|
388
|
-
filters.push({
|
|
389
|
-
_id: {
|
|
390
|
-
$gt: {
|
|
391
|
-
g: this.group_id,
|
|
392
|
-
b: name,
|
|
393
|
-
o: start
|
|
394
|
-
},
|
|
395
|
-
$lte: {
|
|
396
|
-
g: this.group_id,
|
|
397
|
-
b: name,
|
|
398
|
-
o: end as any
|
|
399
|
-
}
|
|
400
|
-
}
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
// Internal naming:
|
|
405
|
-
// We do a query for one "batch", which may consist of multiple "chunks".
|
|
406
|
-
// Each chunk is limited to single bucket, and is limited in length and size.
|
|
407
|
-
// There are also overall batch length and size limits.
|
|
408
|
-
|
|
409
|
-
const batchLimit = options?.limit ?? storage.DEFAULT_DOCUMENT_BATCH_LIMIT;
|
|
410
|
-
const chunkSizeLimitBytes = options?.chunkLimitBytes ?? storage.DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
|
|
411
|
-
|
|
412
|
-
const cursor = this.db.bucket_data.find(
|
|
413
|
-
{
|
|
414
|
-
$or: filters
|
|
415
|
-
},
|
|
416
|
-
{
|
|
417
|
-
session: undefined,
|
|
418
|
-
sort: { _id: 1 },
|
|
419
|
-
limit: batchLimit,
|
|
420
|
-
// Increase batch size above the default 101, so that we can fill an entire batch in
|
|
421
|
-
// one go.
|
|
422
|
-
// batchSize is 1 more than limit to auto-close the cursor.
|
|
423
|
-
// See https://github.com/mongodb/node-mongodb-native/pull/4580
|
|
424
|
-
batchSize: batchLimit + 1,
|
|
425
|
-
// Raw mode is returns an array of Buffer instead of parsed documents.
|
|
426
|
-
// We use it so that:
|
|
427
|
-
// 1. We can calculate the document size accurately without serializing again.
|
|
428
|
-
// 2. We can delay parsing the results until it's needed.
|
|
429
|
-
// We manually use bson.deserialize below
|
|
430
|
-
raw: true,
|
|
431
|
-
|
|
432
|
-
// Limit the time for the operation to complete, to avoid getting connection timeouts
|
|
433
|
-
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
|
|
434
|
-
}
|
|
435
|
-
) as unknown as mongo.FindCursor<Buffer>;
|
|
436
|
-
|
|
437
|
-
// We want to limit results to a single batch to avoid high memory usage.
|
|
438
|
-
// This approach uses MongoDB's batch limits to limit the data here, which limits
|
|
439
|
-
// to the lower of the batch count and size limits.
|
|
440
|
-
// This is similar to using `singleBatch: true` in the find options, but allows
|
|
441
|
-
// detecting "hasMore".
|
|
442
|
-
let { data, hasMore: batchHasMore } = await readSingleBatch(cursor).catch((e) => {
|
|
443
|
-
throw lib_mongo.mapQueryError(e, 'while reading bucket data');
|
|
444
|
-
});
|
|
445
|
-
if (data.length == batchLimit) {
|
|
446
|
-
// Limit reached - could have more data, despite the cursor being drained.
|
|
447
|
-
batchHasMore = true;
|
|
448
|
-
}
|
|
449
|
-
|
|
450
|
-
let chunkSizeBytes = 0;
|
|
451
|
-
let currentChunk: utils.SyncBucketData | null = null;
|
|
452
|
-
let targetOp: InternalOpId | null = null;
|
|
453
|
-
|
|
454
|
-
// Ordered by _id, meaning buckets are grouped together
|
|
455
|
-
for (let rawData of data) {
|
|
456
|
-
const row = bson.deserialize(rawData, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS) as BucketDataDocument;
|
|
457
|
-
const bucket = row._id.b;
|
|
458
|
-
|
|
459
|
-
if (currentChunk == null || currentChunk.bucket != bucket || chunkSizeBytes >= chunkSizeLimitBytes) {
|
|
460
|
-
// We need to start a new chunk
|
|
461
|
-
let start: ProtocolOpId | undefined = undefined;
|
|
462
|
-
if (currentChunk != null) {
|
|
463
|
-
// There is an existing chunk we need to yield
|
|
464
|
-
if (currentChunk.bucket == bucket) {
|
|
465
|
-
// Current and new chunk have the same bucket, so need has_more on the current one.
|
|
466
|
-
// If currentChunk.bucket != bucket, then we reached the end of the previous bucket,
|
|
467
|
-
// and has_more = false in that case.
|
|
468
|
-
currentChunk.has_more = true;
|
|
469
|
-
start = currentChunk.next_after;
|
|
470
|
-
}
|
|
471
|
-
|
|
472
|
-
const yieldChunk = currentChunk;
|
|
473
|
-
currentChunk = null;
|
|
474
|
-
chunkSizeBytes = 0;
|
|
475
|
-
yield { chunkData: yieldChunk, targetOp: targetOp };
|
|
476
|
-
targetOp = null;
|
|
477
|
-
}
|
|
478
|
-
|
|
479
|
-
if (start == null) {
|
|
480
|
-
const startOpId = dataBuckets.get(bucket);
|
|
481
|
-
if (startOpId == null) {
|
|
482
|
-
throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
|
|
483
|
-
}
|
|
484
|
-
start = internalToExternalOpId(startOpId);
|
|
485
|
-
}
|
|
486
|
-
currentChunk = {
|
|
487
|
-
bucket,
|
|
488
|
-
after: start,
|
|
489
|
-
has_more: false,
|
|
490
|
-
data: [],
|
|
491
|
-
next_after: start
|
|
492
|
-
};
|
|
493
|
-
targetOp = null;
|
|
494
|
-
}
|
|
495
|
-
|
|
496
|
-
const entry = mapOpEntry(row);
|
|
497
|
-
|
|
498
|
-
if (row.target_op != null) {
|
|
499
|
-
// MOVE, CLEAR
|
|
500
|
-
if (targetOp == null || row.target_op > targetOp) {
|
|
501
|
-
targetOp = row.target_op;
|
|
502
|
-
}
|
|
503
|
-
}
|
|
504
|
-
|
|
505
|
-
currentChunk.data.push(entry);
|
|
506
|
-
currentChunk.next_after = entry.op_id;
|
|
507
|
-
|
|
508
|
-
chunkSizeBytes += rawData.byteLength;
|
|
509
|
-
}
|
|
510
|
-
|
|
511
|
-
if (currentChunk != null) {
|
|
512
|
-
const yieldChunk = currentChunk;
|
|
513
|
-
currentChunk = null;
|
|
514
|
-
// This is the final chunk in the batch.
|
|
515
|
-
// There may be more data if and only if the batch we retrieved isn't complete.
|
|
516
|
-
yieldChunk.has_more = batchHasMore;
|
|
517
|
-
yield { chunkData: yieldChunk, targetOp: targetOp };
|
|
518
|
-
targetOp = null;
|
|
519
|
-
}
|
|
386
|
+
yield* this.getBucketDataBatchImpl(checkpoint, dataBuckets, options);
|
|
520
387
|
}
|
|
521
388
|
|
|
522
|
-
async getChecksums(
|
|
389
|
+
async getChecksums(
|
|
390
|
+
checkpoint: utils.InternalOpId,
|
|
391
|
+
buckets: storage.BucketChecksumRequest[]
|
|
392
|
+
): Promise<utils.ChecksumMap> {
|
|
523
393
|
return this.checksums.getChecksums(checkpoint, buckets);
|
|
524
394
|
}
|
|
525
395
|
|
|
@@ -528,7 +398,6 @@ export class MongoSyncBucketStorage
|
|
|
528
398
|
}
|
|
529
399
|
|
|
530
400
|
async terminate(options?: storage.TerminateOptions) {
|
|
531
|
-
// Default is to clear the storage except when explicitly requested not to.
|
|
532
401
|
if (!options || options?.clearStorage) {
|
|
533
402
|
await this.clear(options);
|
|
534
403
|
}
|
|
@@ -562,7 +431,7 @@ export class MongoSyncBucketStorage
|
|
|
562
431
|
}
|
|
563
432
|
);
|
|
564
433
|
if (doc == null) {
|
|
565
|
-
throw new ServiceAssertionError('Cannot find
|
|
434
|
+
throw new ServiceAssertionError('Cannot find replication stream status');
|
|
566
435
|
}
|
|
567
436
|
|
|
568
437
|
return {
|
|
@@ -573,32 +442,22 @@ export class MongoSyncBucketStorage
|
|
|
573
442
|
};
|
|
574
443
|
}
|
|
575
444
|
|
|
445
|
+
protected abstract clearBucketData(signal?: AbortSignal): Promise<void>;
|
|
446
|
+
|
|
447
|
+
protected abstract clearParameterIndexes(signal?: AbortSignal): Promise<void>;
|
|
448
|
+
|
|
449
|
+
protected abstract clearSourceRecords(signal?: AbortSignal): Promise<void>;
|
|
450
|
+
|
|
451
|
+
protected abstract clearBucketState(signal?: AbortSignal): Promise<void>;
|
|
452
|
+
|
|
453
|
+
protected abstract clearSourceTables(signal?: AbortSignal): Promise<void>;
|
|
454
|
+
|
|
576
455
|
async clear(options?: storage.ClearStorageOptions): Promise<void> {
|
|
577
|
-
|
|
578
|
-
if (options?.signal?.aborted) {
|
|
579
|
-
throw new ReplicationAbortedError('Aborted clearing data');
|
|
580
|
-
}
|
|
581
|
-
try {
|
|
582
|
-
await this.clearIteration();
|
|
456
|
+
const signal = options?.signal;
|
|
583
457
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
} catch (e: unknown) {
|
|
587
|
-
if (lib_mongo.isMongoServerError(e) && e.codeName == 'MaxTimeMSExpired') {
|
|
588
|
-
logger.info(
|
|
589
|
-
`${this.slot_name} Cleared batch of data in ${lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS}ms, continuing...`
|
|
590
|
-
);
|
|
591
|
-
await timers.setTimeout(lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS / 5);
|
|
592
|
-
} else {
|
|
593
|
-
throw e;
|
|
594
|
-
}
|
|
595
|
-
}
|
|
458
|
+
if (signal?.aborted) {
|
|
459
|
+
throw new ReplicationAbortedError('Aborted clearing data', signal.reason);
|
|
596
460
|
}
|
|
597
|
-
}
|
|
598
|
-
|
|
599
|
-
private async clearIteration(): Promise<void> {
|
|
600
|
-
// Individual operations here may time out with the maxTimeMS option.
|
|
601
|
-
// It is expected to still make progress, and continue on the next try.
|
|
602
461
|
|
|
603
462
|
await this.db.sync_rules.updateOne(
|
|
604
463
|
{
|
|
@@ -618,39 +477,31 @@ export class MongoSyncBucketStorage
|
|
|
618
477
|
},
|
|
619
478
|
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
620
479
|
);
|
|
621
|
-
await this.db.bucket_data.deleteMany(
|
|
622
|
-
{
|
|
623
|
-
_id: idPrefixFilter<BucketDataKey>({ g: this.group_id }, ['b', 'o'])
|
|
624
|
-
},
|
|
625
|
-
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
626
|
-
);
|
|
627
|
-
await this.db.bucket_parameters.deleteMany(
|
|
628
|
-
{
|
|
629
|
-
'key.g': this.group_id
|
|
630
|
-
},
|
|
631
|
-
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
632
|
-
);
|
|
633
480
|
|
|
634
|
-
await this.
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
);
|
|
481
|
+
await this.clearBucketData(signal);
|
|
482
|
+
await this.clearParameterIndexes(signal);
|
|
483
|
+
await this.clearSourceRecords(signal);
|
|
484
|
+
await this.clearBucketState(signal);
|
|
485
|
+
await this.clearSourceTables(signal);
|
|
640
486
|
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
_id: idPrefixFilter<BucketStateDocument['_id']>({ g: this.group_id }, ['b'])
|
|
644
|
-
},
|
|
645
|
-
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
646
|
-
);
|
|
487
|
+
this.#storageInitialized = false;
|
|
488
|
+
}
|
|
647
489
|
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
490
|
+
protected async clearDeleteMany(
|
|
491
|
+
label: string,
|
|
492
|
+
operation: () => Promise<mongo.DeleteResult>,
|
|
493
|
+
signal?: AbortSignal
|
|
494
|
+
): Promise<void> {
|
|
495
|
+
await retryOnMongoMaxTimeMSExpired(operation, {
|
|
496
|
+
signal,
|
|
497
|
+
abortMessage: 'Aborted clearing data',
|
|
498
|
+
retryDelayMs: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS / 5,
|
|
499
|
+
onRetry: () => {
|
|
500
|
+
this.logger.info(
|
|
501
|
+
`Cleared batch of ${label} in ${lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS}ms, continuing...`
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
});
|
|
654
505
|
}
|
|
655
506
|
|
|
656
507
|
async reportError(e: any): Promise<void> {
|
|
@@ -674,100 +525,68 @@ export class MongoSyncBucketStorage
|
|
|
674
525
|
const checkpoint = await this.getCheckpointInternal();
|
|
675
526
|
maxOpId = checkpoint?.checkpoint ?? undefined;
|
|
676
527
|
}
|
|
677
|
-
await
|
|
528
|
+
await this.createMongoCompactor({ ...options, maxOpId, logger: this.logger }).compact();
|
|
678
529
|
|
|
679
530
|
if (maxOpId != null && options?.compactParameterData) {
|
|
680
|
-
await
|
|
531
|
+
await this.createMongoParameterCompactor(maxOpId, options).compact();
|
|
681
532
|
}
|
|
682
533
|
}
|
|
683
534
|
|
|
684
535
|
async populatePersistentChecksumCache(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
|
|
685
|
-
logger.info(`Populating persistent checksum cache...`);
|
|
536
|
+
this.logger.info(`Populating persistent checksum cache...`);
|
|
686
537
|
const start = Date.now();
|
|
687
|
-
|
|
688
|
-
// We can optimize this in the future.
|
|
689
|
-
const compactor = new MongoCompactor(this, this.db, {
|
|
538
|
+
const compactor = this.createMongoCompactor({
|
|
690
539
|
...options,
|
|
691
|
-
|
|
692
|
-
|
|
540
|
+
memoryLimitMB: 0,
|
|
541
|
+
logger: this.logger
|
|
693
542
|
});
|
|
694
543
|
|
|
695
544
|
const result = await compactor.populateChecksums({
|
|
696
|
-
// There are cases with millions of small buckets, in which case it can take very long to
|
|
697
|
-
// populate the checksums, with minimal benefit. We skip the small buckets here.
|
|
698
545
|
minBucketChanges: options.minBucketChanges ?? 10
|
|
699
546
|
});
|
|
700
547
|
const duration = Date.now() - start;
|
|
701
|
-
logger.info(`Populated persistent checksum cache in ${(duration / 1000).toFixed(1)}s`);
|
|
548
|
+
this.logger.info(`Populated persistent checksum cache in ${(duration / 1000).toFixed(1)}s`);
|
|
702
549
|
return result;
|
|
703
550
|
}
|
|
704
551
|
|
|
705
|
-
/**
|
|
706
|
-
* Instance-wide watch on the latest available checkpoint (op_id + lsn).
|
|
707
|
-
*/
|
|
708
552
|
private async *watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ReplicationCheckpoint> {
|
|
709
553
|
if (signal.aborted) {
|
|
710
554
|
return;
|
|
711
555
|
}
|
|
712
556
|
|
|
713
|
-
// If the stream is idle, we wait a max of a minute (CHECKPOINT_TIMEOUT_MS) before we get another checkpoint,
|
|
714
|
-
// to avoid stale checkpoint snapshots. This is what checkpointTimeoutStream() is for.
|
|
715
|
-
// Essentially, even if there are no actual checkpoint changes, we want a new snapshotTime every minute or so,
|
|
716
|
-
// to ensure that any new clients connecting will get a valid snapshotTime.
|
|
717
557
|
const stream = mergeAsyncIterables(
|
|
718
558
|
[this.checkpointChangesStream(signal), this.checkpointTimeoutStream(signal)],
|
|
719
559
|
signal
|
|
720
560
|
);
|
|
721
561
|
|
|
722
|
-
// We only watch changes to the active sync rules.
|
|
723
|
-
// If it changes to inactive, we abort and restart with the new sync rules.
|
|
724
562
|
for await (const _ of stream) {
|
|
725
563
|
if (signal.aborted) {
|
|
726
|
-
// Would likely have been caught by the signal on the timeout or the upstream stream, but we check here anyway
|
|
727
564
|
break;
|
|
728
565
|
}
|
|
729
566
|
|
|
730
567
|
const op = await this.getCheckpointInternal();
|
|
731
568
|
if (op == null) {
|
|
732
|
-
// Sync rules have changed - abort and restart.
|
|
733
|
-
// We do a soft close of the stream here - no error
|
|
734
569
|
break;
|
|
735
570
|
}
|
|
736
571
|
|
|
737
|
-
// Previously, we only yielded when the checkpoint or lsn changed.
|
|
738
|
-
// However, we always want to use the latest snapshotTime, so we skip that filtering here.
|
|
739
|
-
// That filtering could be added in the per-user streams if needed, but in general the capped collection
|
|
740
|
-
// should already only contain useful changes in most cases.
|
|
741
572
|
yield op;
|
|
742
573
|
}
|
|
743
574
|
}
|
|
744
575
|
|
|
745
|
-
// Nothing is done here until a subscriber starts to iterate
|
|
746
576
|
private readonly sharedIter = new BroadcastIterable((signal) => {
|
|
747
577
|
return this.watchActiveCheckpoint(signal);
|
|
748
578
|
});
|
|
749
579
|
|
|
750
|
-
/**
|
|
751
|
-
* User-specific watch on the latest checkpoint and/or write checkpoint.
|
|
752
|
-
*/
|
|
753
580
|
async *watchCheckpointChanges(options: WatchWriteCheckpointOptions): AsyncIterable<storage.StorageCheckpointUpdate> {
|
|
754
581
|
let lastCheckpoint: ReplicationCheckpoint | null = null;
|
|
755
582
|
|
|
756
583
|
const iter = this.sharedIter[Symbol.asyncIterator](options.signal);
|
|
757
584
|
|
|
758
585
|
let writeCheckpoint: bigint | null = null;
|
|
759
|
-
// true if we queried the initial write checkpoint, even if it doesn't exist
|
|
760
586
|
let queriedInitialWriteCheckpoint = false;
|
|
761
587
|
|
|
762
588
|
for await (const nextCheckpoint of iter) {
|
|
763
|
-
// lsn changes are not important by itself.
|
|
764
|
-
// What is important is:
|
|
765
|
-
// 1. checkpoint (op_id) changes.
|
|
766
|
-
// 2. write checkpoint changes for the specific user
|
|
767
|
-
|
|
768
589
|
if (nextCheckpoint.lsn != null && !queriedInitialWriteCheckpoint) {
|
|
769
|
-
// Lookup the first write checkpoint for the user when we can.
|
|
770
|
-
// There will not actually be one in all cases.
|
|
771
590
|
writeCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
|
|
772
591
|
sync_rules_id: this.group_id,
|
|
773
592
|
user_id: options.user_id,
|
|
@@ -783,15 +602,11 @@ export class MongoSyncBucketStorage
|
|
|
783
602
|
lastCheckpoint.checkpoint == nextCheckpoint.checkpoint &&
|
|
784
603
|
lastCheckpoint.lsn == nextCheckpoint.lsn
|
|
785
604
|
) {
|
|
786
|
-
// No change - wait for next one
|
|
787
|
-
// In some cases, many LSNs may be produced in a short time.
|
|
788
|
-
// Add a delay to throttle the loop a bit.
|
|
789
605
|
await timers.setTimeout(20 + 10 * Math.random());
|
|
790
606
|
continue;
|
|
791
607
|
}
|
|
792
608
|
|
|
793
609
|
if (lastCheckpoint == null) {
|
|
794
|
-
// First message for this stream - "INVALIDATE_ALL" means it will lookup all data
|
|
795
610
|
yield {
|
|
796
611
|
base: nextCheckpoint,
|
|
797
612
|
writeCheckpoint,
|
|
@@ -805,8 +620,6 @@ export class MongoSyncBucketStorage
|
|
|
805
620
|
|
|
806
621
|
let updatedWriteCheckpoint = updates.updatedWriteCheckpoints.get(options.user_id) ?? null;
|
|
807
622
|
if (updates.invalidateWriteCheckpoints) {
|
|
808
|
-
// Invalidated means there were too many updates to track the individual ones,
|
|
809
|
-
// so we switch to "polling" (querying directly in each stream).
|
|
810
623
|
updatedWriteCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
|
|
811
624
|
sync_rules_id: this.group_id,
|
|
812
625
|
user_id: options.user_id,
|
|
@@ -817,8 +630,6 @@ export class MongoSyncBucketStorage
|
|
|
817
630
|
}
|
|
818
631
|
if (updatedWriteCheckpoint != null && (writeCheckpoint == null || updatedWriteCheckpoint > writeCheckpoint)) {
|
|
819
632
|
writeCheckpoint = updatedWriteCheckpoint;
|
|
820
|
-
// If it happened that we haven't queried a write checkpoint at this point,
|
|
821
|
-
// then we don't need to anymore, since we got an updated one.
|
|
822
633
|
queriedInitialWriteCheckpoint = true;
|
|
823
634
|
}
|
|
824
635
|
|
|
@@ -838,12 +649,6 @@ export class MongoSyncBucketStorage
|
|
|
838
649
|
}
|
|
839
650
|
}
|
|
840
651
|
|
|
841
|
-
/**
|
|
842
|
-
* This watches the checkpoint_events capped collection for new documents inserted,
|
|
843
|
-
* and yields whenever one or more documents are inserted.
|
|
844
|
-
*
|
|
845
|
-
* The actual checkpoint must be queried on the sync_rules collection after this.
|
|
846
|
-
*/
|
|
847
652
|
private async *checkpointChangesStream(signal: AbortSignal): AsyncGenerator<void> {
|
|
848
653
|
if (signal.aborted) {
|
|
849
654
|
return;
|
|
@@ -862,17 +667,13 @@ export class MongoSyncBucketStorage
|
|
|
862
667
|
cursor.close().catch(() => {});
|
|
863
668
|
});
|
|
864
669
|
|
|
865
|
-
// Yield once on start, regardless of whether there are documents in the cursor.
|
|
866
|
-
// This is to ensure that the first iteration of the generator yields immediately.
|
|
867
670
|
yield;
|
|
868
671
|
|
|
869
672
|
try {
|
|
870
673
|
while (!signal.aborted) {
|
|
871
674
|
const doc = await cursor.tryNext().catch((e) => {
|
|
872
675
|
if (lib_mongo.isMongoServerError(e) && e.codeName === 'CappedPositionLost') {
|
|
873
|
-
// Cursor position lost, potentially due to a high rate of notifications
|
|
874
676
|
cursor = query();
|
|
875
|
-
// Treat as an event found, before querying the new cursor again
|
|
876
677
|
return {};
|
|
877
678
|
} else {
|
|
878
679
|
return Promise.reject(e);
|
|
@@ -881,8 +682,6 @@ export class MongoSyncBucketStorage
|
|
|
881
682
|
if (cursor.closed) {
|
|
882
683
|
return;
|
|
883
684
|
}
|
|
884
|
-
// Skip buffered documents, if any. We don't care about the contents,
|
|
885
|
-
// we only want to know when new documents are inserted.
|
|
886
685
|
cursor.readBufferedDocuments();
|
|
887
686
|
if (doc != null) {
|
|
888
687
|
yield;
|
|
@@ -904,7 +703,6 @@ export class MongoSyncBucketStorage
|
|
|
904
703
|
await timers.setTimeout(CHECKPOINT_TIMEOUT_MS, undefined, { signal });
|
|
905
704
|
} catch (e) {
|
|
906
705
|
if (e.name == 'AbortError') {
|
|
907
|
-
// This is how we typically abort this stream, when all listeners are done
|
|
908
706
|
return;
|
|
909
707
|
}
|
|
910
708
|
throw e;
|
|
@@ -916,94 +714,37 @@ export class MongoSyncBucketStorage
|
|
|
916
714
|
}
|
|
917
715
|
}
|
|
918
716
|
|
|
717
|
+
protected abstract getDataBucketChangesImpl(
|
|
718
|
+
options: GetCheckpointChangesOptions
|
|
719
|
+
): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>>;
|
|
720
|
+
|
|
919
721
|
private async getDataBucketChanges(
|
|
920
722
|
options: GetCheckpointChangesOptions
|
|
921
723
|
): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
|
|
922
|
-
|
|
923
|
-
const bucketStateUpdates = await this.db.bucket_state
|
|
924
|
-
.find(
|
|
925
|
-
{
|
|
926
|
-
// We have an index on (_id.g, last_op).
|
|
927
|
-
'_id.g': this.group_id,
|
|
928
|
-
last_op: { $gt: options.lastCheckpoint.checkpoint }
|
|
929
|
-
},
|
|
930
|
-
{
|
|
931
|
-
projection: {
|
|
932
|
-
'_id.b': 1
|
|
933
|
-
},
|
|
934
|
-
limit: limit + 1,
|
|
935
|
-
// batchSize is 1 more than limit to auto-close the cursor.
|
|
936
|
-
// See https://github.com/mongodb/node-mongodb-native/pull/4580
|
|
937
|
-
batchSize: limit + 2,
|
|
938
|
-
singleBatch: true
|
|
939
|
-
}
|
|
940
|
-
)
|
|
941
|
-
.toArray();
|
|
942
|
-
|
|
943
|
-
const buckets = bucketStateUpdates.map((doc) => doc._id.b);
|
|
944
|
-
const invalidateDataBuckets = buckets.length > limit;
|
|
945
|
-
|
|
946
|
-
return {
|
|
947
|
-
invalidateDataBuckets: invalidateDataBuckets,
|
|
948
|
-
updatedDataBuckets: invalidateDataBuckets ? new Set<string>() : new Set(buckets)
|
|
949
|
-
};
|
|
724
|
+
return this.getDataBucketChangesImpl(options);
|
|
950
725
|
}
|
|
951
726
|
|
|
727
|
+
protected abstract getParameterBucketChangesImpl(
|
|
728
|
+
options: GetCheckpointChangesOptions
|
|
729
|
+
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>>;
|
|
730
|
+
|
|
952
731
|
private async getParameterBucketChanges(
|
|
953
732
|
options: GetCheckpointChangesOptions
|
|
954
733
|
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
|
|
955
|
-
|
|
956
|
-
const parameterUpdates = await this.db.bucket_parameters
|
|
957
|
-
.find(
|
|
958
|
-
{
|
|
959
|
-
_id: { $gt: options.lastCheckpoint.checkpoint, $lte: options.nextCheckpoint.checkpoint },
|
|
960
|
-
'key.g': this.group_id
|
|
961
|
-
},
|
|
962
|
-
{
|
|
963
|
-
projection: {
|
|
964
|
-
lookup: 1
|
|
965
|
-
},
|
|
966
|
-
limit: limit + 1,
|
|
967
|
-
// batchSize is 1 more than limit to auto-close the cursor.
|
|
968
|
-
// See https://github.com/mongodb/node-mongodb-native/pull/4580
|
|
969
|
-
batchSize: limit + 2,
|
|
970
|
-
singleBatch: true
|
|
971
|
-
}
|
|
972
|
-
)
|
|
973
|
-
.toArray();
|
|
974
|
-
const invalidateParameterUpdates = parameterUpdates.length > limit;
|
|
975
|
-
|
|
976
|
-
return {
|
|
977
|
-
invalidateParameterBuckets: invalidateParameterUpdates,
|
|
978
|
-
updatedParameterLookups: invalidateParameterUpdates
|
|
979
|
-
? new Set<string>()
|
|
980
|
-
: new Set<string>(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup))))
|
|
981
|
-
};
|
|
734
|
+
return this.getParameterBucketChangesImpl(options);
|
|
982
735
|
}
|
|
983
736
|
|
|
984
|
-
// If we processed all connections together for each checkpoint, we could do a single lookup for all connections.
|
|
985
|
-
// In practice, specific connections may fall behind. So instead, we just cache the results of each specific lookup.
|
|
986
|
-
// TODO (later):
|
|
987
|
-
// We can optimize this by implementing it like ChecksumCache: We can use partial cache results to do
|
|
988
|
-
// more efficient lookups in some cases.
|
|
989
737
|
private checkpointChangesCache = new LRUCache<
|
|
990
738
|
string,
|
|
991
739
|
InternalCheckpointChanges,
|
|
992
740
|
{ options: GetCheckpointChangesOptions }
|
|
993
741
|
>({
|
|
994
|
-
// Limit to 50 cache entries, or 10MB, whichever comes first.
|
|
995
|
-
// Some rough calculations:
|
|
996
|
-
// If we process 10 checkpoints per second, and a connection may be 2 seconds behind, we could have
|
|
997
|
-
// up to 20 relevant checkpoints. That gives us 20*20 = 400 potentially-relevant cache entries.
|
|
998
|
-
// That is a worst-case scenario, so we don't actually store that many. In real life, the cache keys
|
|
999
|
-
// would likely be clustered around a few values, rather than spread over all 400 potential values.
|
|
1000
742
|
max: 50,
|
|
1001
743
|
maxSize: 12 * 1024 * 1024,
|
|
1002
744
|
sizeCalculation: (value: InternalCheckpointChanges) => {
|
|
1003
|
-
// Estimate of memory usage
|
|
1004
745
|
const paramSize = [...value.updatedParameterLookups].reduce<number>((a, b) => a + b.length, 0);
|
|
1005
746
|
const bucketSize = [...value.updatedDataBuckets].reduce<number>((a, b) => a + b.length, 0);
|
|
1006
|
-
const writeCheckpointSize = value.updatedWriteCheckpoints.size * 30;
|
|
747
|
+
const writeCheckpointSize = value.updatedWriteCheckpoints.size * 30;
|
|
1007
748
|
return 100 + paramSize + bucketSize + writeCheckpointSize;
|
|
1008
749
|
},
|
|
1009
750
|
fetchMethod: async (_key, _staleValue, options) => {
|
|
@@ -1030,21 +771,20 @@ export class MongoSyncBucketStorage
|
|
|
1030
771
|
}
|
|
1031
772
|
}
|
|
1032
773
|
|
|
1033
|
-
interface InternalCheckpointChanges extends CheckpointChanges {
|
|
1034
|
-
updatedWriteCheckpoints: Map<string, bigint>;
|
|
1035
|
-
invalidateWriteCheckpoints: boolean;
|
|
1036
|
-
}
|
|
1037
|
-
|
|
1038
774
|
class MongoReplicationCheckpoint implements ReplicationCheckpoint {
|
|
775
|
+
#storage: MongoSyncBucketStorage;
|
|
776
|
+
|
|
1039
777
|
constructor(
|
|
1040
|
-
|
|
778
|
+
storage: MongoSyncBucketStorage,
|
|
1041
779
|
public readonly checkpoint: InternalOpId,
|
|
1042
780
|
public readonly lsn: string | null,
|
|
1043
781
|
public snapshotTime: mongo.Timestamp
|
|
1044
|
-
) {
|
|
782
|
+
) {
|
|
783
|
+
this.#storage = storage;
|
|
784
|
+
}
|
|
1045
785
|
|
|
1046
|
-
async getParameterSets(lookups: ScopedParameterLookup[]): Promise<SqliteJsonRow[]> {
|
|
1047
|
-
return this
|
|
786
|
+
async getParameterSets(lookups: ScopedParameterLookup[], limit: number): Promise<SqliteJsonRow[]> {
|
|
787
|
+
return this.#storage.getParameterSets(this, lookups, limit);
|
|
1048
788
|
}
|
|
1049
789
|
}
|
|
1050
790
|
|
|
@@ -1052,7 +792,7 @@ class EmptyReplicationCheckpoint implements ReplicationCheckpoint {
|
|
|
1052
792
|
readonly checkpoint: InternalOpId = 0n;
|
|
1053
793
|
readonly lsn: string | null = null;
|
|
1054
794
|
|
|
1055
|
-
async getParameterSets(
|
|
795
|
+
async getParameterSets(_lookups: ScopedParameterLookup[]): Promise<SqliteJsonRow[]> {
|
|
1056
796
|
return [];
|
|
1057
797
|
}
|
|
1058
798
|
}
|