@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
|
@@ -1,67 +1,10 @@
|
|
|
1
|
-
var __addDisposableResource = (this && this.__addDisposableResource) || function (env, value, async) {
|
|
2
|
-
if (value !== null && value !== void 0) {
|
|
3
|
-
if (typeof value !== "object" && typeof value !== "function") throw new TypeError("Object expected.");
|
|
4
|
-
var dispose, inner;
|
|
5
|
-
if (async) {
|
|
6
|
-
if (!Symbol.asyncDispose) throw new TypeError("Symbol.asyncDispose is not defined.");
|
|
7
|
-
dispose = value[Symbol.asyncDispose];
|
|
8
|
-
}
|
|
9
|
-
if (dispose === void 0) {
|
|
10
|
-
if (!Symbol.dispose) throw new TypeError("Symbol.dispose is not defined.");
|
|
11
|
-
dispose = value[Symbol.dispose];
|
|
12
|
-
if (async) inner = dispose;
|
|
13
|
-
}
|
|
14
|
-
if (typeof dispose !== "function") throw new TypeError("Object not disposable.");
|
|
15
|
-
if (inner) dispose = function() { try { inner.call(this); } catch (e) { return Promise.reject(e); } };
|
|
16
|
-
env.stack.push({ value: value, dispose: dispose, async: async });
|
|
17
|
-
}
|
|
18
|
-
else if (async) {
|
|
19
|
-
env.stack.push({ async: true });
|
|
20
|
-
}
|
|
21
|
-
return value;
|
|
22
|
-
};
|
|
23
|
-
var __disposeResources = (this && this.__disposeResources) || (function (SuppressedError) {
|
|
24
|
-
return function (env) {
|
|
25
|
-
function fail(e) {
|
|
26
|
-
env.error = env.hasError ? new SuppressedError(e, env.error, "An error was suppressed during disposal.") : e;
|
|
27
|
-
env.hasError = true;
|
|
28
|
-
}
|
|
29
|
-
var r, s = 0;
|
|
30
|
-
function next() {
|
|
31
|
-
while (r = env.stack.pop()) {
|
|
32
|
-
try {
|
|
33
|
-
if (!r.async && s === 1) return s = 0, env.stack.push(r), Promise.resolve().then(next);
|
|
34
|
-
if (r.dispose) {
|
|
35
|
-
var result = r.dispose.call(r.value);
|
|
36
|
-
if (r.async) return s |= 2, Promise.resolve(result).then(next, function(e) { fail(e); return next(); });
|
|
37
|
-
}
|
|
38
|
-
else s |= 1;
|
|
39
|
-
}
|
|
40
|
-
catch (e) {
|
|
41
|
-
fail(e);
|
|
42
|
-
}
|
|
43
|
-
}
|
|
44
|
-
if (s === 1) return env.hasError ? Promise.reject(env.error) : Promise.resolve();
|
|
45
|
-
if (env.hasError) throw env.error;
|
|
46
|
-
}
|
|
47
|
-
return next();
|
|
48
|
-
};
|
|
49
|
-
})(typeof SuppressedError === "function" ? SuppressedError : function (error, suppressed, message) {
|
|
50
|
-
var e = new Error(message);
|
|
51
|
-
return e.name = "SuppressedError", e.error = error, e.suppressed = suppressed, e;
|
|
52
|
-
});
|
|
53
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
54
|
-
import { BaseObserver,
|
|
55
|
-
import { BroadcastIterable, CHECKPOINT_INVALIDATE_ALL,
|
|
56
|
-
import { JSONBig } from '@powersync/service-jsonbig';
|
|
2
|
+
import { BaseObserver, DO_NOT_LOG, ReplicationAbortedError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { BroadcastIterable, CHECKPOINT_INVALIDATE_ALL, maxLsn, mergeAsyncIterables, storage } from '@powersync/service-core';
|
|
57
4
|
import * as bson from 'bson';
|
|
58
5
|
import { LRUCache } from 'lru-cache';
|
|
59
6
|
import * as timers from 'timers/promises';
|
|
60
|
-
import {
|
|
61
|
-
import { MongoBucketBatch } from './MongoBucketBatch.js';
|
|
62
|
-
import { MongoChecksums } from './MongoChecksums.js';
|
|
63
|
-
import { MongoCompactor } from './MongoCompactor.js';
|
|
64
|
-
import { MongoParameterCompactor } from './MongoParameterCompactor.js';
|
|
7
|
+
import { retryOnMongoMaxTimeMSExpired } from '../../utils/util.js';
|
|
65
8
|
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
66
9
|
/**
|
|
67
10
|
* Only keep checkpoints around for a minute, before fetching a fresh one.
|
|
@@ -79,29 +22,40 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
79
22
|
sync_rules;
|
|
80
23
|
slot_name;
|
|
81
24
|
db;
|
|
25
|
+
[DO_NOT_LOG] = true;
|
|
82
26
|
checksums;
|
|
83
27
|
parsedSyncRulesCache;
|
|
84
28
|
writeCheckpointAPI;
|
|
29
|
+
logger;
|
|
30
|
+
#storageInitialized = false;
|
|
85
31
|
constructor(factory, group_id, sync_rules, slot_name, writeCheckpointMode, options) {
|
|
86
32
|
super();
|
|
87
33
|
this.factory = factory;
|
|
88
34
|
this.group_id = group_id;
|
|
89
35
|
this.sync_rules = sync_rules;
|
|
90
36
|
this.slot_name = slot_name;
|
|
91
|
-
this.db = factory.db;
|
|
92
|
-
this.checksums =
|
|
93
|
-
...options.checksumOptions,
|
|
94
|
-
storageConfig: options?.storageConfig
|
|
95
|
-
});
|
|
37
|
+
this.db = factory.db.versioned(sync_rules.getStorageConfig());
|
|
38
|
+
this.checksums = this.createMongoChecksums(options);
|
|
96
39
|
this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
|
|
97
40
|
db: this.db,
|
|
98
41
|
mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
|
|
99
42
|
sync_rules_id: group_id
|
|
100
43
|
});
|
|
44
|
+
this.logger = sync_rules.logger;
|
|
101
45
|
}
|
|
102
46
|
get writeCheckpointMode() {
|
|
103
47
|
return this.writeCheckpointAPI.writeCheckpointMode;
|
|
104
48
|
}
|
|
49
|
+
get mapping() {
|
|
50
|
+
return this.sync_rules.mapping;
|
|
51
|
+
}
|
|
52
|
+
get versionContext() {
|
|
53
|
+
return {
|
|
54
|
+
db: this.db,
|
|
55
|
+
group_id: this.group_id,
|
|
56
|
+
mapping: this.mapping
|
|
57
|
+
};
|
|
58
|
+
}
|
|
105
59
|
setWriteCheckpointMode(mode) {
|
|
106
60
|
this.writeCheckpointAPI.setWriteCheckpointMode(mode);
|
|
107
61
|
}
|
|
@@ -116,10 +70,6 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
116
70
|
}
|
|
117
71
|
getParsedSyncRules(options) {
|
|
118
72
|
const { parsed, options: cachedOptions } = this.parsedSyncRulesCache ?? {};
|
|
119
|
-
/**
|
|
120
|
-
* Check if the cached sync rules, if present, had the same options.
|
|
121
|
-
* Parse sync rules if the options are different or if there is no cached value.
|
|
122
|
-
*/
|
|
123
73
|
if (!parsed || options.defaultSchema != cachedOptions?.defaultSchema) {
|
|
124
74
|
this.parsedSyncRulesCache = { parsed: this.sync_rules.parsed(options).hydratedSyncRules(), options };
|
|
125
75
|
}
|
|
@@ -135,67 +85,53 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
135
85
|
projection: { _id: 1, state: 1, last_checkpoint: 1, last_checkpoint_lsn: 1, snapshot_done: 1 }
|
|
136
86
|
});
|
|
137
87
|
if (!doc?.snapshot_done || !['ACTIVE', 'ERRORED'].includes(doc.state)) {
|
|
138
|
-
// Sync rules not active - return null
|
|
139
88
|
return null;
|
|
140
89
|
}
|
|
141
|
-
// Specifically using operationTime instead of clusterTime
|
|
142
|
-
// There are 3 fields in the response:
|
|
143
|
-
// 1. operationTime, not exposed for snapshot sessions (used for causal consistency)
|
|
144
|
-
// 2. clusterTime (used for connection management)
|
|
145
|
-
// 3. atClusterTime, which is session.snapshotTime
|
|
146
|
-
// We use atClusterTime, to match the driver's internal snapshot handling.
|
|
147
|
-
// There are cases where clusterTime > operationTime and atClusterTime,
|
|
148
|
-
// which could cause snapshot queries using this as the snapshotTime to timeout.
|
|
149
|
-
// This was specifically observed on MongoDB 6.0 and 7.0.
|
|
150
90
|
const snapshotTime = session.snapshotTime;
|
|
151
91
|
if (snapshotTime == null) {
|
|
152
92
|
throw new ServiceAssertionError('Missing snapshotTime in getCheckpoint()');
|
|
153
93
|
}
|
|
154
|
-
return new MongoReplicationCheckpoint(this,
|
|
155
|
-
// null/0n is a valid checkpoint in some cases, for example if the initial snapshot was empty
|
|
156
|
-
doc.last_checkpoint ?? 0n, doc.last_checkpoint_lsn ?? null, snapshotTime);
|
|
94
|
+
return new MongoReplicationCheckpoint(this, doc.last_checkpoint ?? 0n, doc.last_checkpoint_lsn ?? null, snapshotTime);
|
|
157
95
|
});
|
|
158
96
|
}
|
|
159
|
-
async
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
const doc = await this.db.sync_rules.findOne({
|
|
163
|
-
_id: this.group_id
|
|
164
|
-
}, { projection: { last_checkpoint_lsn: 1, no_checkpoint_before: 1, keepalive_op: 1, snapshot_lsn: 1 } });
|
|
165
|
-
const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
|
|
166
|
-
const batch = __addDisposableResource(env_1, new MongoBucketBatch({
|
|
167
|
-
logger: options.logger,
|
|
168
|
-
db: this.db,
|
|
169
|
-
syncRules: this.sync_rules.parsed(options).hydratedSyncRules(),
|
|
170
|
-
groupId: this.group_id,
|
|
171
|
-
slotName: this.slot_name,
|
|
172
|
-
lastCheckpointLsn: checkpoint_lsn,
|
|
173
|
-
resumeFromLsn: maxLsn(checkpoint_lsn, doc?.snapshot_lsn),
|
|
174
|
-
noCheckpointBeforeLsn: doc?.no_checkpoint_before ?? options.zeroLSN,
|
|
175
|
-
keepaliveOp: doc?.keepalive_op ? BigInt(doc.keepalive_op) : null,
|
|
176
|
-
storeCurrentData: options.storeCurrentData,
|
|
177
|
-
skipExistingRows: options.skipExistingRows ?? false,
|
|
178
|
-
markRecordUnavailable: options.markRecordUnavailable
|
|
179
|
-
}), true);
|
|
180
|
-
this.iterateListeners((cb) => cb.batchStarted?.(batch));
|
|
181
|
-
await callback(batch);
|
|
182
|
-
await batch.flush();
|
|
183
|
-
if (batch.last_flushed_op != null) {
|
|
184
|
-
return { flushed_op: batch.last_flushed_op };
|
|
185
|
-
}
|
|
186
|
-
else {
|
|
187
|
-
return null;
|
|
188
|
-
}
|
|
189
|
-
}
|
|
190
|
-
catch (e_1) {
|
|
191
|
-
env_1.error = e_1;
|
|
192
|
-
env_1.hasError = true;
|
|
193
|
-
}
|
|
194
|
-
finally {
|
|
195
|
-
const result_1 = __disposeResources(env_1);
|
|
196
|
-
if (result_1)
|
|
197
|
-
await result_1;
|
|
97
|
+
async initializeStorage() {
|
|
98
|
+
if (this.#storageInitialized) {
|
|
99
|
+
return;
|
|
198
100
|
}
|
|
101
|
+
await this.db.initializeStreamStorage(this.group_id);
|
|
102
|
+
await this.initializeVersionStorage();
|
|
103
|
+
this.#storageInitialized = true;
|
|
104
|
+
}
|
|
105
|
+
async createWriter(options) {
|
|
106
|
+
await this.initializeStorage();
|
|
107
|
+
const doc = await this.db.sync_rules.findOne({
|
|
108
|
+
_id: this.group_id
|
|
109
|
+
}, { projection: { last_checkpoint_lsn: 1, no_checkpoint_before: 1, keepalive_op: 1, snapshot_lsn: 1 } });
|
|
110
|
+
const checkpoint_lsn = doc?.last_checkpoint_lsn ?? null;
|
|
111
|
+
const batchOptions = {
|
|
112
|
+
logger: options.logger ?? this.logger,
|
|
113
|
+
db: this.db,
|
|
114
|
+
syncRules: this.sync_rules.parsed(options).hydratedSyncRules(),
|
|
115
|
+
mapping: this.sync_rules.mapping,
|
|
116
|
+
groupId: this.group_id,
|
|
117
|
+
slotName: this.slot_name,
|
|
118
|
+
lastCheckpointLsn: checkpoint_lsn,
|
|
119
|
+
resumeFromLsn: maxLsn(checkpoint_lsn, doc?.snapshot_lsn),
|
|
120
|
+
keepaliveOp: doc?.keepalive_op ? BigInt(doc.keepalive_op) : null,
|
|
121
|
+
storeCurrentData: options.storeCurrentData,
|
|
122
|
+
skipExistingRows: options.skipExistingRows ?? false,
|
|
123
|
+
markRecordUnavailable: options.markRecordUnavailable,
|
|
124
|
+
tracer: options.tracer
|
|
125
|
+
};
|
|
126
|
+
const writer = this.createWriterImpl(batchOptions);
|
|
127
|
+
this.iterateListeners((cb) => cb.batchStarted?.(writer));
|
|
128
|
+
return writer;
|
|
129
|
+
}
|
|
130
|
+
async startBatch(options, callback) {
|
|
131
|
+
await using writer = await this.createWriter(options);
|
|
132
|
+
await callback(writer);
|
|
133
|
+
await writer.flush();
|
|
134
|
+
return writer.last_flushed_op != null ? { flushed_op: writer.last_flushed_op } : null;
|
|
199
135
|
}
|
|
200
136
|
async resolveTable(options) {
|
|
201
137
|
const { group_id, connection_id, connection_tag, entity_descriptor } = options;
|
|
@@ -206,10 +142,12 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
206
142
|
type_oid: column.typeId
|
|
207
143
|
}));
|
|
208
144
|
let result = null;
|
|
145
|
+
let initializeSourceRecordsFor = null;
|
|
146
|
+
const baseId = this.sourceTableBaseId();
|
|
209
147
|
await this.db.client.withSession(async (session) => {
|
|
210
|
-
const col = this.db.
|
|
148
|
+
const col = this.db.commonSourceTables(group_id);
|
|
211
149
|
let filter = {
|
|
212
|
-
|
|
150
|
+
...baseId,
|
|
213
151
|
connection_id: connection_id,
|
|
214
152
|
schema_name: schema,
|
|
215
153
|
table_name: name,
|
|
@@ -220,9 +158,18 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
220
158
|
}
|
|
221
159
|
let doc = await col.findOne(filter, { session });
|
|
222
160
|
if (doc == null) {
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
161
|
+
const candidateSourceTable = new storage.SourceTable({
|
|
162
|
+
id: new bson.ObjectId(),
|
|
163
|
+
connectionTag: connection_tag,
|
|
164
|
+
objectId: objectId,
|
|
165
|
+
schema: schema,
|
|
166
|
+
name: name,
|
|
167
|
+
replicaIdColumns: replicaIdColumns,
|
|
168
|
+
snapshotComplete: false
|
|
169
|
+
});
|
|
170
|
+
const createDoc = {
|
|
171
|
+
_id: candidateSourceTable.id,
|
|
172
|
+
...baseId,
|
|
226
173
|
connection_id: connection_id,
|
|
227
174
|
relation_id: objectId,
|
|
228
175
|
schema_name: schema,
|
|
@@ -232,7 +179,10 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
232
179
|
snapshot_done: false,
|
|
233
180
|
snapshot_status: undefined
|
|
234
181
|
};
|
|
182
|
+
this.augmentCreatedSourceTableDocument(createDoc, options, candidateSourceTable);
|
|
183
|
+
doc = createDoc;
|
|
235
184
|
await col.insertOne(doc, { session });
|
|
185
|
+
initializeSourceRecordsFor = doc._id;
|
|
236
186
|
}
|
|
237
187
|
const sourceTable = new storage.SourceTable({
|
|
238
188
|
id: doc._id,
|
|
@@ -255,15 +205,13 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
255
205
|
replicatedCount: doc.snapshot_status.replicated_count
|
|
256
206
|
};
|
|
257
207
|
let dropTables = [];
|
|
258
|
-
// Detect tables that are either renamed, or have different replica_id_columns
|
|
259
208
|
let truncateFilter = [{ schema_name: schema, table_name: name }];
|
|
260
209
|
if (objectId != null) {
|
|
261
|
-
// Only detect renames if the source uses relation ids.
|
|
262
210
|
truncateFilter.push({ relation_id: objectId });
|
|
263
211
|
}
|
|
264
212
|
const truncate = await col
|
|
265
213
|
.find({
|
|
266
|
-
|
|
214
|
+
...baseId,
|
|
267
215
|
connection_id: connection_id,
|
|
268
216
|
_id: { $ne: doc._id },
|
|
269
217
|
$or: truncateFilter
|
|
@@ -283,191 +231,16 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
283
231
|
dropTables: dropTables
|
|
284
232
|
};
|
|
285
233
|
});
|
|
234
|
+
if (initializeSourceRecordsFor != null) {
|
|
235
|
+
await this.initializeResolvedSourceRecords(initializeSourceRecordsFor);
|
|
236
|
+
}
|
|
286
237
|
return result;
|
|
287
238
|
}
|
|
288
|
-
async getParameterSets(checkpoint, lookups) {
|
|
289
|
-
return this.
|
|
290
|
-
// Set the session's snapshot time to the checkpoint's snapshot time.
|
|
291
|
-
// An alternative would be to create the session when the checkpoint is created, but managing
|
|
292
|
-
// the session lifetime would become more complex.
|
|
293
|
-
// Starting and ending sessions are cheap (synchronous when no transactions are used),
|
|
294
|
-
// so this should be fine.
|
|
295
|
-
// This is a roundabout way of setting {readConcern: {atClusterTime: clusterTime}}, since
|
|
296
|
-
// that is not exposed directly by the driver.
|
|
297
|
-
// Future versions of the driver may change the snapshotTime behavior, so we need tests to
|
|
298
|
-
// validate that this works as expected. We test this in the compacting tests.
|
|
299
|
-
setSessionSnapshotTime(session, checkpoint.snapshotTime);
|
|
300
|
-
const lookupFilter = lookups.map((lookup) => {
|
|
301
|
-
return storage.serializeLookup(lookup);
|
|
302
|
-
});
|
|
303
|
-
// This query does not use indexes super efficiently, apart from the lookup filter.
|
|
304
|
-
// From some experimentation I could do individual lookups more efficient using an index
|
|
305
|
-
// on {'key.g': 1, lookup: 1, 'key.t': 1, 'key.k': 1, _id: -1},
|
|
306
|
-
// but could not do the same using $group.
|
|
307
|
-
// For now, just rely on compacting to remove extraneous data.
|
|
308
|
-
// For a description of the data format, see the `/docs/parameters-lookups.md` file.
|
|
309
|
-
const rows = await this.db.bucket_parameters
|
|
310
|
-
.aggregate([
|
|
311
|
-
{
|
|
312
|
-
$match: {
|
|
313
|
-
'key.g': this.group_id,
|
|
314
|
-
lookup: { $in: lookupFilter },
|
|
315
|
-
_id: { $lte: checkpoint.checkpoint }
|
|
316
|
-
}
|
|
317
|
-
},
|
|
318
|
-
{
|
|
319
|
-
$sort: {
|
|
320
|
-
_id: -1
|
|
321
|
-
}
|
|
322
|
-
},
|
|
323
|
-
{
|
|
324
|
-
$group: {
|
|
325
|
-
_id: { key: '$key', lookup: '$lookup' },
|
|
326
|
-
bucket_parameters: {
|
|
327
|
-
$first: '$bucket_parameters'
|
|
328
|
-
}
|
|
329
|
-
}
|
|
330
|
-
}
|
|
331
|
-
], {
|
|
332
|
-
session,
|
|
333
|
-
readConcern: 'snapshot',
|
|
334
|
-
// Limit the time for the operation to complete, to avoid getting connection timeouts
|
|
335
|
-
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
|
|
336
|
-
})
|
|
337
|
-
.toArray()
|
|
338
|
-
.catch((e) => {
|
|
339
|
-
throw lib_mongo.mapQueryError(e, 'while evaluating parameter queries');
|
|
340
|
-
});
|
|
341
|
-
const groupedParameters = rows.map((row) => {
|
|
342
|
-
return row.bucket_parameters;
|
|
343
|
-
});
|
|
344
|
-
return groupedParameters.flat();
|
|
345
|
-
});
|
|
239
|
+
async getParameterSets(checkpoint, lookups, limit) {
|
|
240
|
+
return this.getParameterSetsImpl(checkpoint, lookups, limit);
|
|
346
241
|
}
|
|
347
242
|
async *getBucketDataBatch(checkpoint, dataBuckets, options) {
|
|
348
|
-
|
|
349
|
-
return;
|
|
350
|
-
}
|
|
351
|
-
let filters = [];
|
|
352
|
-
if (checkpoint == null) {
|
|
353
|
-
throw new ServiceAssertionError('checkpoint is null');
|
|
354
|
-
}
|
|
355
|
-
const end = checkpoint;
|
|
356
|
-
for (let [name, start] of dataBuckets.entries()) {
|
|
357
|
-
filters.push({
|
|
358
|
-
_id: {
|
|
359
|
-
$gt: {
|
|
360
|
-
g: this.group_id,
|
|
361
|
-
b: name,
|
|
362
|
-
o: start
|
|
363
|
-
},
|
|
364
|
-
$lte: {
|
|
365
|
-
g: this.group_id,
|
|
366
|
-
b: name,
|
|
367
|
-
o: end
|
|
368
|
-
}
|
|
369
|
-
}
|
|
370
|
-
});
|
|
371
|
-
}
|
|
372
|
-
// Internal naming:
|
|
373
|
-
// We do a query for one "batch", which may consist of multiple "chunks".
|
|
374
|
-
// Each chunk is limited to single bucket, and is limited in length and size.
|
|
375
|
-
// There are also overall batch length and size limits.
|
|
376
|
-
const batchLimit = options?.limit ?? storage.DEFAULT_DOCUMENT_BATCH_LIMIT;
|
|
377
|
-
const chunkSizeLimitBytes = options?.chunkLimitBytes ?? storage.DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
|
|
378
|
-
const cursor = this.db.bucket_data.find({
|
|
379
|
-
$or: filters
|
|
380
|
-
}, {
|
|
381
|
-
session: undefined,
|
|
382
|
-
sort: { _id: 1 },
|
|
383
|
-
limit: batchLimit,
|
|
384
|
-
// Increase batch size above the default 101, so that we can fill an entire batch in
|
|
385
|
-
// one go.
|
|
386
|
-
// batchSize is 1 more than limit to auto-close the cursor.
|
|
387
|
-
// See https://github.com/mongodb/node-mongodb-native/pull/4580
|
|
388
|
-
batchSize: batchLimit + 1,
|
|
389
|
-
// Raw mode is returns an array of Buffer instead of parsed documents.
|
|
390
|
-
// We use it so that:
|
|
391
|
-
// 1. We can calculate the document size accurately without serializing again.
|
|
392
|
-
// 2. We can delay parsing the results until it's needed.
|
|
393
|
-
// We manually use bson.deserialize below
|
|
394
|
-
raw: true,
|
|
395
|
-
// Limit the time for the operation to complete, to avoid getting connection timeouts
|
|
396
|
-
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
|
|
397
|
-
});
|
|
398
|
-
// We want to limit results to a single batch to avoid high memory usage.
|
|
399
|
-
// This approach uses MongoDB's batch limits to limit the data here, which limits
|
|
400
|
-
// to the lower of the batch count and size limits.
|
|
401
|
-
// This is similar to using `singleBatch: true` in the find options, but allows
|
|
402
|
-
// detecting "hasMore".
|
|
403
|
-
let { data, hasMore: batchHasMore } = await readSingleBatch(cursor).catch((e) => {
|
|
404
|
-
throw lib_mongo.mapQueryError(e, 'while reading bucket data');
|
|
405
|
-
});
|
|
406
|
-
if (data.length == batchLimit) {
|
|
407
|
-
// Limit reached - could have more data, despite the cursor being drained.
|
|
408
|
-
batchHasMore = true;
|
|
409
|
-
}
|
|
410
|
-
let chunkSizeBytes = 0;
|
|
411
|
-
let currentChunk = null;
|
|
412
|
-
let targetOp = null;
|
|
413
|
-
// Ordered by _id, meaning buckets are grouped together
|
|
414
|
-
for (let rawData of data) {
|
|
415
|
-
const row = bson.deserialize(rawData, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS);
|
|
416
|
-
const bucket = row._id.b;
|
|
417
|
-
if (currentChunk == null || currentChunk.bucket != bucket || chunkSizeBytes >= chunkSizeLimitBytes) {
|
|
418
|
-
// We need to start a new chunk
|
|
419
|
-
let start = undefined;
|
|
420
|
-
if (currentChunk != null) {
|
|
421
|
-
// There is an existing chunk we need to yield
|
|
422
|
-
if (currentChunk.bucket == bucket) {
|
|
423
|
-
// Current and new chunk have the same bucket, so need has_more on the current one.
|
|
424
|
-
// If currentChunk.bucket != bucket, then we reached the end of the previous bucket,
|
|
425
|
-
// and has_more = false in that case.
|
|
426
|
-
currentChunk.has_more = true;
|
|
427
|
-
start = currentChunk.next_after;
|
|
428
|
-
}
|
|
429
|
-
const yieldChunk = currentChunk;
|
|
430
|
-
currentChunk = null;
|
|
431
|
-
chunkSizeBytes = 0;
|
|
432
|
-
yield { chunkData: yieldChunk, targetOp: targetOp };
|
|
433
|
-
targetOp = null;
|
|
434
|
-
}
|
|
435
|
-
if (start == null) {
|
|
436
|
-
const startOpId = dataBuckets.get(bucket);
|
|
437
|
-
if (startOpId == null) {
|
|
438
|
-
throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
|
|
439
|
-
}
|
|
440
|
-
start = internalToExternalOpId(startOpId);
|
|
441
|
-
}
|
|
442
|
-
currentChunk = {
|
|
443
|
-
bucket,
|
|
444
|
-
after: start,
|
|
445
|
-
has_more: false,
|
|
446
|
-
data: [],
|
|
447
|
-
next_after: start
|
|
448
|
-
};
|
|
449
|
-
targetOp = null;
|
|
450
|
-
}
|
|
451
|
-
const entry = mapOpEntry(row);
|
|
452
|
-
if (row.target_op != null) {
|
|
453
|
-
// MOVE, CLEAR
|
|
454
|
-
if (targetOp == null || row.target_op > targetOp) {
|
|
455
|
-
targetOp = row.target_op;
|
|
456
|
-
}
|
|
457
|
-
}
|
|
458
|
-
currentChunk.data.push(entry);
|
|
459
|
-
currentChunk.next_after = entry.op_id;
|
|
460
|
-
chunkSizeBytes += rawData.byteLength;
|
|
461
|
-
}
|
|
462
|
-
if (currentChunk != null) {
|
|
463
|
-
const yieldChunk = currentChunk;
|
|
464
|
-
currentChunk = null;
|
|
465
|
-
// This is the final chunk in the batch.
|
|
466
|
-
// There may be more data if and only if the batch we retrieved isn't complete.
|
|
467
|
-
yieldChunk.has_more = batchHasMore;
|
|
468
|
-
yield { chunkData: yieldChunk, targetOp: targetOp };
|
|
469
|
-
targetOp = null;
|
|
470
|
-
}
|
|
243
|
+
yield* this.getBucketDataBatchImpl(checkpoint, dataBuckets, options);
|
|
471
244
|
}
|
|
472
245
|
async getChecksums(checkpoint, buckets) {
|
|
473
246
|
return this.checksums.getChecksums(checkpoint, buckets);
|
|
@@ -476,7 +249,6 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
476
249
|
this.checksums.clearCache();
|
|
477
250
|
}
|
|
478
251
|
async terminate(options) {
|
|
479
|
-
// Default is to clear the storage except when explicitly requested not to.
|
|
480
252
|
if (!options || options?.clearStorage) {
|
|
481
253
|
await this.clear(options);
|
|
482
254
|
}
|
|
@@ -503,7 +275,7 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
503
275
|
}
|
|
504
276
|
});
|
|
505
277
|
if (doc == null) {
|
|
506
|
-
throw new ServiceAssertionError('Cannot find
|
|
278
|
+
throw new ServiceAssertionError('Cannot find replication stream status');
|
|
507
279
|
}
|
|
508
280
|
return {
|
|
509
281
|
snapshot_done: doc.snapshot_done,
|
|
@@ -513,29 +285,10 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
513
285
|
};
|
|
514
286
|
}
|
|
515
287
|
async clear(options) {
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
}
|
|
520
|
-
try {
|
|
521
|
-
await this.clearIteration();
|
|
522
|
-
logger.info(`${this.slot_name} Done clearing data`);
|
|
523
|
-
return;
|
|
524
|
-
}
|
|
525
|
-
catch (e) {
|
|
526
|
-
if (lib_mongo.isMongoServerError(e) && e.codeName == 'MaxTimeMSExpired') {
|
|
527
|
-
logger.info(`${this.slot_name} Cleared batch of data in ${lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS}ms, continuing...`);
|
|
528
|
-
await timers.setTimeout(lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS / 5);
|
|
529
|
-
}
|
|
530
|
-
else {
|
|
531
|
-
throw e;
|
|
532
|
-
}
|
|
533
|
-
}
|
|
288
|
+
const signal = options?.signal;
|
|
289
|
+
if (signal?.aborted) {
|
|
290
|
+
throw new ReplicationAbortedError('Aborted clearing data', signal.reason);
|
|
534
291
|
}
|
|
535
|
-
}
|
|
536
|
-
async clearIteration() {
|
|
537
|
-
// Individual operations here may time out with the maxTimeMS option.
|
|
538
|
-
// It is expected to still make progress, and continue on the next try.
|
|
539
292
|
await this.db.sync_rules.updateOne({
|
|
540
293
|
_id: this.group_id
|
|
541
294
|
}, {
|
|
@@ -550,21 +303,22 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
550
303
|
snapshot_lsn: 1
|
|
551
304
|
}
|
|
552
305
|
}, { maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS });
|
|
553
|
-
await this.
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
await this.
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
306
|
+
await this.clearBucketData(signal);
|
|
307
|
+
await this.clearParameterIndexes(signal);
|
|
308
|
+
await this.clearSourceRecords(signal);
|
|
309
|
+
await this.clearBucketState(signal);
|
|
310
|
+
await this.clearSourceTables(signal);
|
|
311
|
+
this.#storageInitialized = false;
|
|
312
|
+
}
|
|
313
|
+
async clearDeleteMany(label, operation, signal) {
|
|
314
|
+
await retryOnMongoMaxTimeMSExpired(operation, {
|
|
315
|
+
signal,
|
|
316
|
+
abortMessage: 'Aborted clearing data',
|
|
317
|
+
retryDelayMs: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS / 5,
|
|
318
|
+
onRetry: () => {
|
|
319
|
+
this.logger.info(`Cleared batch of ${label} in ${lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS}ms, continuing...`);
|
|
320
|
+
}
|
|
321
|
+
});
|
|
568
322
|
}
|
|
569
323
|
async reportError(e) {
|
|
570
324
|
const message = String(e.message ?? 'Replication failure');
|
|
@@ -583,83 +337,52 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
583
337
|
const checkpoint = await this.getCheckpointInternal();
|
|
584
338
|
maxOpId = checkpoint?.checkpoint ?? undefined;
|
|
585
339
|
}
|
|
586
|
-
await
|
|
340
|
+
await this.createMongoCompactor({ ...options, maxOpId, logger: this.logger }).compact();
|
|
587
341
|
if (maxOpId != null && options?.compactParameterData) {
|
|
588
|
-
await
|
|
342
|
+
await this.createMongoParameterCompactor(maxOpId, options).compact();
|
|
589
343
|
}
|
|
590
344
|
}
|
|
591
345
|
async populatePersistentChecksumCache(options) {
|
|
592
|
-
logger.info(`Populating persistent checksum cache...`);
|
|
346
|
+
this.logger.info(`Populating persistent checksum cache...`);
|
|
593
347
|
const start = Date.now();
|
|
594
|
-
|
|
595
|
-
// We can optimize this in the future.
|
|
596
|
-
const compactor = new MongoCompactor(this, this.db, {
|
|
348
|
+
const compactor = this.createMongoCompactor({
|
|
597
349
|
...options,
|
|
598
|
-
|
|
599
|
-
|
|
350
|
+
memoryLimitMB: 0,
|
|
351
|
+
logger: this.logger
|
|
600
352
|
});
|
|
601
353
|
const result = await compactor.populateChecksums({
|
|
602
|
-
// There are cases with millions of small buckets, in which case it can take very long to
|
|
603
|
-
// populate the checksums, with minimal benefit. We skip the small buckets here.
|
|
604
354
|
minBucketChanges: options.minBucketChanges ?? 10
|
|
605
355
|
});
|
|
606
356
|
const duration = Date.now() - start;
|
|
607
|
-
logger.info(`Populated persistent checksum cache in ${(duration / 1000).toFixed(1)}s`);
|
|
357
|
+
this.logger.info(`Populated persistent checksum cache in ${(duration / 1000).toFixed(1)}s`);
|
|
608
358
|
return result;
|
|
609
359
|
}
|
|
610
|
-
/**
|
|
611
|
-
* Instance-wide watch on the latest available checkpoint (op_id + lsn).
|
|
612
|
-
*/
|
|
613
360
|
async *watchActiveCheckpoint(signal) {
|
|
614
361
|
if (signal.aborted) {
|
|
615
362
|
return;
|
|
616
363
|
}
|
|
617
|
-
// If the stream is idle, we wait a max of a minute (CHECKPOINT_TIMEOUT_MS) before we get another checkpoint,
|
|
618
|
-
// to avoid stale checkpoint snapshots. This is what checkpointTimeoutStream() is for.
|
|
619
|
-
// Essentially, even if there are no actual checkpoint changes, we want a new snapshotTime every minute or so,
|
|
620
|
-
// to ensure that any new clients connecting will get a valid snapshotTime.
|
|
621
364
|
const stream = mergeAsyncIterables([this.checkpointChangesStream(signal), this.checkpointTimeoutStream(signal)], signal);
|
|
622
|
-
// We only watch changes to the active sync rules.
|
|
623
|
-
// If it changes to inactive, we abort and restart with the new sync rules.
|
|
624
365
|
for await (const _ of stream) {
|
|
625
366
|
if (signal.aborted) {
|
|
626
|
-
// Would likely have been caught by the signal on the timeout or the upstream stream, but we check here anyway
|
|
627
367
|
break;
|
|
628
368
|
}
|
|
629
369
|
const op = await this.getCheckpointInternal();
|
|
630
370
|
if (op == null) {
|
|
631
|
-
// Sync rules have changed - abort and restart.
|
|
632
|
-
// We do a soft close of the stream here - no error
|
|
633
371
|
break;
|
|
634
372
|
}
|
|
635
|
-
// Previously, we only yielded when the checkpoint or lsn changed.
|
|
636
|
-
// However, we always want to use the latest snapshotTime, so we skip that filtering here.
|
|
637
|
-
// That filtering could be added in the per-user streams if needed, but in general the capped collection
|
|
638
|
-
// should already only contain useful changes in most cases.
|
|
639
373
|
yield op;
|
|
640
374
|
}
|
|
641
375
|
}
|
|
642
|
-
// Nothing is done here until a subscriber starts to iterate
|
|
643
376
|
sharedIter = new BroadcastIterable((signal) => {
|
|
644
377
|
return this.watchActiveCheckpoint(signal);
|
|
645
378
|
});
|
|
646
|
-
/**
|
|
647
|
-
* User-specific watch on the latest checkpoint and/or write checkpoint.
|
|
648
|
-
*/
|
|
649
379
|
async *watchCheckpointChanges(options) {
|
|
650
380
|
let lastCheckpoint = null;
|
|
651
381
|
const iter = this.sharedIter[Symbol.asyncIterator](options.signal);
|
|
652
382
|
let writeCheckpoint = null;
|
|
653
|
-
// true if we queried the initial write checkpoint, even if it doesn't exist
|
|
654
383
|
let queriedInitialWriteCheckpoint = false;
|
|
655
384
|
for await (const nextCheckpoint of iter) {
|
|
656
|
-
// lsn changes are not important by itself.
|
|
657
|
-
// What is important is:
|
|
658
|
-
// 1. checkpoint (op_id) changes.
|
|
659
|
-
// 2. write checkpoint changes for the specific user
|
|
660
385
|
if (nextCheckpoint.lsn != null && !queriedInitialWriteCheckpoint) {
|
|
661
|
-
// Lookup the first write checkpoint for the user when we can.
|
|
662
|
-
// There will not actually be one in all cases.
|
|
663
386
|
writeCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
|
|
664
387
|
sync_rules_id: this.group_id,
|
|
665
388
|
user_id: options.user_id,
|
|
@@ -672,14 +395,10 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
672
395
|
if (lastCheckpoint != null &&
|
|
673
396
|
lastCheckpoint.checkpoint == nextCheckpoint.checkpoint &&
|
|
674
397
|
lastCheckpoint.lsn == nextCheckpoint.lsn) {
|
|
675
|
-
// No change - wait for next one
|
|
676
|
-
// In some cases, many LSNs may be produced in a short time.
|
|
677
|
-
// Add a delay to throttle the loop a bit.
|
|
678
398
|
await timers.setTimeout(20 + 10 * Math.random());
|
|
679
399
|
continue;
|
|
680
400
|
}
|
|
681
401
|
if (lastCheckpoint == null) {
|
|
682
|
-
// First message for this stream - "INVALIDATE_ALL" means it will lookup all data
|
|
683
402
|
yield {
|
|
684
403
|
base: nextCheckpoint,
|
|
685
404
|
writeCheckpoint,
|
|
@@ -693,8 +412,6 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
693
412
|
});
|
|
694
413
|
let updatedWriteCheckpoint = updates.updatedWriteCheckpoints.get(options.user_id) ?? null;
|
|
695
414
|
if (updates.invalidateWriteCheckpoints) {
|
|
696
|
-
// Invalidated means there were too many updates to track the individual ones,
|
|
697
|
-
// so we switch to "polling" (querying directly in each stream).
|
|
698
415
|
updatedWriteCheckpoint = await this.writeCheckpointAPI.lastWriteCheckpoint({
|
|
699
416
|
sync_rules_id: this.group_id,
|
|
700
417
|
user_id: options.user_id,
|
|
@@ -705,8 +422,6 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
705
422
|
}
|
|
706
423
|
if (updatedWriteCheckpoint != null && (writeCheckpoint == null || updatedWriteCheckpoint > writeCheckpoint)) {
|
|
707
424
|
writeCheckpoint = updatedWriteCheckpoint;
|
|
708
|
-
// If it happened that we haven't queried a write checkpoint at this point,
|
|
709
|
-
// then we don't need to anymore, since we got an updated one.
|
|
710
425
|
queriedInitialWriteCheckpoint = true;
|
|
711
426
|
}
|
|
712
427
|
yield {
|
|
@@ -723,12 +438,6 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
723
438
|
lastCheckpoint = nextCheckpoint;
|
|
724
439
|
}
|
|
725
440
|
}
|
|
726
|
-
/**
|
|
727
|
-
* This watches the checkpoint_events capped collection for new documents inserted,
|
|
728
|
-
* and yields whenever one or more documents are inserted.
|
|
729
|
-
*
|
|
730
|
-
* The actual checkpoint must be queried on the sync_rules collection after this.
|
|
731
|
-
*/
|
|
732
441
|
async *checkpointChangesStream(signal) {
|
|
733
442
|
if (signal.aborted) {
|
|
734
443
|
return;
|
|
@@ -740,16 +449,12 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
740
449
|
signal.addEventListener('abort', () => {
|
|
741
450
|
cursor.close().catch(() => { });
|
|
742
451
|
});
|
|
743
|
-
// Yield once on start, regardless of whether there are documents in the cursor.
|
|
744
|
-
// This is to ensure that the first iteration of the generator yields immediately.
|
|
745
452
|
yield;
|
|
746
453
|
try {
|
|
747
454
|
while (!signal.aborted) {
|
|
748
455
|
const doc = await cursor.tryNext().catch((e) => {
|
|
749
456
|
if (lib_mongo.isMongoServerError(e) && e.codeName === 'CappedPositionLost') {
|
|
750
|
-
// Cursor position lost, potentially due to a high rate of notifications
|
|
751
457
|
cursor = query();
|
|
752
|
-
// Treat as an event found, before querying the new cursor again
|
|
753
458
|
return {};
|
|
754
459
|
}
|
|
755
460
|
else {
|
|
@@ -759,8 +464,6 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
759
464
|
if (cursor.closed) {
|
|
760
465
|
return;
|
|
761
466
|
}
|
|
762
|
-
// Skip buffered documents, if any. We don't care about the contents,
|
|
763
|
-
// we only want to know when new documents are inserted.
|
|
764
467
|
cursor.readBufferedDocuments();
|
|
765
468
|
if (doc != null) {
|
|
766
469
|
yield;
|
|
@@ -784,7 +487,6 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
784
487
|
}
|
|
785
488
|
catch (e) {
|
|
786
489
|
if (e.name == 'AbortError') {
|
|
787
|
-
// This is how we typically abort this stream, when all listeners are done
|
|
788
490
|
return;
|
|
789
491
|
}
|
|
790
492
|
throw e;
|
|
@@ -795,74 +497,18 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
795
497
|
}
|
|
796
498
|
}
|
|
797
499
|
async getDataBucketChanges(options) {
|
|
798
|
-
|
|
799
|
-
const bucketStateUpdates = await this.db.bucket_state
|
|
800
|
-
.find({
|
|
801
|
-
// We have an index on (_id.g, last_op).
|
|
802
|
-
'_id.g': this.group_id,
|
|
803
|
-
last_op: { $gt: options.lastCheckpoint.checkpoint }
|
|
804
|
-
}, {
|
|
805
|
-
projection: {
|
|
806
|
-
'_id.b': 1
|
|
807
|
-
},
|
|
808
|
-
limit: limit + 1,
|
|
809
|
-
// batchSize is 1 more than limit to auto-close the cursor.
|
|
810
|
-
// See https://github.com/mongodb/node-mongodb-native/pull/4580
|
|
811
|
-
batchSize: limit + 2,
|
|
812
|
-
singleBatch: true
|
|
813
|
-
})
|
|
814
|
-
.toArray();
|
|
815
|
-
const buckets = bucketStateUpdates.map((doc) => doc._id.b);
|
|
816
|
-
const invalidateDataBuckets = buckets.length > limit;
|
|
817
|
-
return {
|
|
818
|
-
invalidateDataBuckets: invalidateDataBuckets,
|
|
819
|
-
updatedDataBuckets: invalidateDataBuckets ? new Set() : new Set(buckets)
|
|
820
|
-
};
|
|
500
|
+
return this.getDataBucketChangesImpl(options);
|
|
821
501
|
}
|
|
822
502
|
async getParameterBucketChanges(options) {
|
|
823
|
-
|
|
824
|
-
const parameterUpdates = await this.db.bucket_parameters
|
|
825
|
-
.find({
|
|
826
|
-
_id: { $gt: options.lastCheckpoint.checkpoint, $lte: options.nextCheckpoint.checkpoint },
|
|
827
|
-
'key.g': this.group_id
|
|
828
|
-
}, {
|
|
829
|
-
projection: {
|
|
830
|
-
lookup: 1
|
|
831
|
-
},
|
|
832
|
-
limit: limit + 1,
|
|
833
|
-
// batchSize is 1 more than limit to auto-close the cursor.
|
|
834
|
-
// See https://github.com/mongodb/node-mongodb-native/pull/4580
|
|
835
|
-
batchSize: limit + 2,
|
|
836
|
-
singleBatch: true
|
|
837
|
-
})
|
|
838
|
-
.toArray();
|
|
839
|
-
const invalidateParameterUpdates = parameterUpdates.length > limit;
|
|
840
|
-
return {
|
|
841
|
-
invalidateParameterBuckets: invalidateParameterUpdates,
|
|
842
|
-
updatedParameterLookups: invalidateParameterUpdates
|
|
843
|
-
? new Set()
|
|
844
|
-
: new Set(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup))))
|
|
845
|
-
};
|
|
503
|
+
return this.getParameterBucketChangesImpl(options);
|
|
846
504
|
}
|
|
847
|
-
// If we processed all connections together for each checkpoint, we could do a single lookup for all connections.
|
|
848
|
-
// In practice, specific connections may fall behind. So instead, we just cache the results of each specific lookup.
|
|
849
|
-
// TODO (later):
|
|
850
|
-
// We can optimize this by implementing it like ChecksumCache: We can use partial cache results to do
|
|
851
|
-
// more efficient lookups in some cases.
|
|
852
505
|
checkpointChangesCache = new LRUCache({
|
|
853
|
-
// Limit to 50 cache entries, or 10MB, whichever comes first.
|
|
854
|
-
// Some rough calculations:
|
|
855
|
-
// If we process 10 checkpoints per second, and a connection may be 2 seconds behind, we could have
|
|
856
|
-
// up to 20 relevant checkpoints. That gives us 20*20 = 400 potentially-relevant cache entries.
|
|
857
|
-
// That is a worst-case scenario, so we don't actually store that many. In real life, the cache keys
|
|
858
|
-
// would likely be clustered around a few values, rather than spread over all 400 potential values.
|
|
859
506
|
max: 50,
|
|
860
507
|
maxSize: 12 * 1024 * 1024,
|
|
861
508
|
sizeCalculation: (value) => {
|
|
862
|
-
// Estimate of memory usage
|
|
863
509
|
const paramSize = [...value.updatedParameterLookups].reduce((a, b) => a + b.length, 0);
|
|
864
510
|
const bucketSize = [...value.updatedDataBuckets].reduce((a, b) => a + b.length, 0);
|
|
865
|
-
const writeCheckpointSize = value.updatedWriteCheckpoints.size * 30;
|
|
511
|
+
const writeCheckpointSize = value.updatedWriteCheckpoints.size * 30;
|
|
866
512
|
return 100 + paramSize + bucketSize + writeCheckpointSize;
|
|
867
513
|
},
|
|
868
514
|
fetchMethod: async (_key, _staleValue, options) => {
|
|
@@ -886,24 +532,24 @@ export class MongoSyncBucketStorage extends BaseObserver {
|
|
|
886
532
|
}
|
|
887
533
|
}
|
|
888
534
|
class MongoReplicationCheckpoint {
|
|
889
|
-
storage;
|
|
890
535
|
checkpoint;
|
|
891
536
|
lsn;
|
|
892
537
|
snapshotTime;
|
|
538
|
+
#storage;
|
|
893
539
|
constructor(storage, checkpoint, lsn, snapshotTime) {
|
|
894
|
-
this.storage = storage;
|
|
895
540
|
this.checkpoint = checkpoint;
|
|
896
541
|
this.lsn = lsn;
|
|
897
542
|
this.snapshotTime = snapshotTime;
|
|
543
|
+
this.#storage = storage;
|
|
898
544
|
}
|
|
899
|
-
async getParameterSets(lookups) {
|
|
900
|
-
return this
|
|
545
|
+
async getParameterSets(lookups, limit) {
|
|
546
|
+
return this.#storage.getParameterSets(this, lookups, limit);
|
|
901
547
|
}
|
|
902
548
|
}
|
|
903
549
|
class EmptyReplicationCheckpoint {
|
|
904
550
|
checkpoint = 0n;
|
|
905
551
|
lsn = null;
|
|
906
|
-
async getParameterSets(
|
|
552
|
+
async getParameterSets(_lookups) {
|
|
907
553
|
return [];
|
|
908
554
|
}
|
|
909
555
|
}
|