@powersync/service-module-mongodb-storage 0.0.0-dev-20260602073133 → 0.0.0-dev-20260909133214
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 +177 -5
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
- package/dist/storage/MongoBucketStorage.d.ts +71 -18
- package/dist/storage/MongoBucketStorage.js +516 -202
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +71 -29
- package/dist/storage/implementation/MongoBucketBatch.js +140 -66
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +41 -13
- package/dist/storage/implementation/MongoChecksums.js +36 -121
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +28 -90
- package/dist/storage/implementation/MongoCompactor.js +87 -481
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
- package/dist/storage/implementation/MongoHydrationState.js +49 -0
- package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +82 -8
- package/dist/storage/implementation/MongoParameterCompactor.js +312 -76
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +21 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +93 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +125 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/implementation/MongoStorageProvider.js +41 -4
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +81 -37
- package/dist/storage/implementation/MongoSyncBucketStorage.js +159 -86
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +23 -11
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +11 -8
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +159 -33
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
- package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +28 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +34 -2
- package/dist/storage/implementation/common/PersistedBatch.js +63 -10
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +4 -1
- package/dist/storage/implementation/db.js +31 -16
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +55 -13
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +135 -107
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +47 -7
- package/dist/storage/implementation/v1/MongoCompactorV1.js +478 -8
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +33 -27
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +146 -73
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.d.ts +10 -1
- package/dist/storage/implementation/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
- package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
- package/dist/storage/implementation/v3/CompactionLease.js +131 -0
- package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +16 -5
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +393 -262
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +108 -18
- package/dist/storage/implementation/v3/MongoCompactorV3.js +1115 -47
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +12 -3
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +13 -8
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +54 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +319 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +61 -36
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +353 -161
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +8 -3
- package/dist/storage/implementation/v3/PersistedBatchV3.js +131 -29
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +26 -14
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +73 -29
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +13 -0
- package/dist/storage/implementation/v3/bucket-format.js +79 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
- package/dist/storage/implementation/v3/chunking.d.ts +10 -0
- package/dist/storage/implementation/v3/chunking.js +30 -0
- package/dist/storage/implementation/v3/chunking.js.map +1 -0
- package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
- package/dist/storage/implementation/v3/compact-utils.js +216 -0
- package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
- package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
- package/dist/storage/implementation/v3/compaction-constants.js +6 -0
- package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +187 -23
- package/dist/storage/implementation/v3/models.js +11 -28
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +20 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +48 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +33 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +68 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +154 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +67 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +439 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/source-table-utils.d.ts +63 -0
- package/dist/storage/implementation/v3/source-table-utils.js +206 -0
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
- package/dist/storage/storage-index.d.ts +5 -4
- package/dist/storage/storage-index.js +5 -4
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +71 -3
- package/dist/types/types.js +58 -2
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.d.ts +1 -2
- package/dist/utils/test-utils.js +7 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +9 -2
- package/dist/utils/util.js +131 -5
- package/dist/utils/util.js.map +1 -1
- package/package.json +13 -19
- package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
- package/src/storage/MongoBucketStorage.ts +728 -242
- package/src/storage/implementation/MongoBucketBatch.ts +196 -91
- package/src/storage/implementation/MongoChecksums.ts +93 -153
- package/src/storage/implementation/MongoCompactor.ts +106 -631
- package/src/storage/implementation/MongoHydrationState.ts +61 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +392 -87
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +122 -0
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +156 -0
- package/src/storage/implementation/MongoStorageProvider.ts +48 -4
- package/src/storage/implementation/MongoSyncBucketStorage.ts +226 -118
- package/src/storage/implementation/MongoSyncRulesLock.ts +28 -12
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +194 -52
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +31 -0
- package/src/storage/implementation/common/PersistedBatch.ts +83 -15
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +40 -22
- package/src/storage/implementation/models.ts +58 -14
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +174 -125
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
- package/src/storage/implementation/v1/MongoCompactorV1.ts +594 -25
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +226 -129
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +12 -1
- package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +502 -324
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
- package/src/storage/implementation/v3/MongoCompactorV3.ts +1439 -70
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +19 -9
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +473 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +453 -211
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +169 -45
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +99 -38
- package/src/storage/implementation/v3/bucket-format.ts +99 -0
- package/src/storage/implementation/v3/chunking.ts +40 -0
- package/src/storage/implementation/v3/compact-utils.ts +320 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +228 -58
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +62 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +44 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +182 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +573 -0
- package/src/storage/implementation/v3/source-table-utils.ts +398 -0
- package/src/storage/storage-index.ts +14 -4
- package/src/types/types.ts +76 -2
- package/src/utils/test-utils.ts +8 -3
- package/src/utils/util.ts +188 -7
- package/test/src/__snapshots__/storage.test.ts.snap +4 -54
- package/test/src/__snapshots__/storage_sync.test.ts.snap +652 -645
- package/test/src/cleanup-stopped-sync-configs.test.ts +698 -0
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +61 -0
- package/test/src/helpers/s3TestFactory.ts +69 -0
- package/test/src/object_storage_lifecycle.test.ts +59 -0
- package/test/src/object_storage_usage.test.ts +255 -0
- package/test/src/parameter_compacting_v1.test.ts +120 -0
- package/test/src/parameter_compacting_v3.test.ts +192 -0
- package/test/src/parameter_compaction_fence.test.ts +276 -0
- package/test/src/setup.ts +6 -1
- package/test/src/storage.test.ts +478 -13
- package/test/src/storage_compacting.test.ts +2516 -107
- package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
- package/test/src/storage_s3_checksums.test.ts +190 -0
- package/test/src/storage_s3_compaction_lifecycle.test.ts +543 -0
- package/test/src/storage_s3_reading.test.ts +768 -0
- package/test/src/storage_s3_writing.test.ts +136 -0
- package/test/src/storage_sync.test.ts +1507 -76
- package/test/src/storeCurrentData.test.ts +227 -0
- package/test/src/util.ts +3 -2
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
- package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
- package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
- package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
- package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
- package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
- package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
- package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
- package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
|
@@ -1,55 +1,62 @@
|
|
|
1
|
-
import { LEGACY_STORAGE_VERSION, storage } from '@powersync/service-core';
|
|
2
|
-
import { DO_NOT_LOG, ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
1
|
+
import { LEGACY_STORAGE_VERSION, SingleSyncConfigBucketDefinitionMapping, storage } from '@powersync/service-core';
|
|
2
|
+
import { DO_NOT_LOG, ErrorCode, logger, ReplicationAssertionError, ServiceError } from '@powersync/lib-services-framework';
|
|
3
3
|
import { v4 as uuid } from 'uuid';
|
|
4
4
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
5
|
+
import { describeIncrementalSyncConfigUpdate, formatIncrementalSyncConfigUpdateLog, isCompatible } from '@powersync/service-core';
|
|
5
6
|
import { ObjectId } from 'bson';
|
|
6
|
-
import {
|
|
7
|
-
import {
|
|
7
|
+
import { DEFAULT_CLEAR_BATCH_THROTTLE_RATE } from '../types/types.js';
|
|
8
|
+
import { generateReplicationStreamName } from '../utils/util.js';
|
|
8
9
|
import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
9
10
|
import { getMongoStorageConfig } from './implementation/models.js';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
11
|
+
import { MongoPersistedReplicationStream } from './implementation/MongoPersistedReplicationStream.js';
|
|
12
|
+
import { stopReplicationStreamPipeline } from './implementation/SyncRuleStateUpdate.js';
|
|
13
|
+
import { ObjectStorageUsage } from './implementation/v3/object-storage/ObjectStorageUsage.js';
|
|
12
14
|
export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
13
|
-
|
|
15
|
+
options;
|
|
14
16
|
[DO_NOT_LOG] = true;
|
|
15
17
|
client;
|
|
16
|
-
|
|
17
|
-
// TODO: This is still Postgres specific and needs to be reworked
|
|
18
|
-
slot_name_prefix;
|
|
18
|
+
replicationStreamNamePrefix;
|
|
19
19
|
activeStorageCache;
|
|
20
20
|
db;
|
|
21
|
-
constructor(db, options
|
|
21
|
+
constructor(db, options) {
|
|
22
22
|
super();
|
|
23
|
-
this.
|
|
23
|
+
this.options = options;
|
|
24
24
|
this.client = db.client;
|
|
25
25
|
this.db = db;
|
|
26
|
-
this.
|
|
27
|
-
this.slot_name_prefix = options.slot_name_prefix;
|
|
26
|
+
this.replicationStreamNamePrefix = options.replicationStreamNamePrefix;
|
|
28
27
|
}
|
|
29
28
|
async [Symbol.asyncDispose]() {
|
|
30
29
|
// No-op
|
|
31
30
|
}
|
|
32
|
-
getInstance(
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
id = Number(id);
|
|
31
|
+
getInstance(replicationStream, options) {
|
|
32
|
+
if (!(replicationStream instanceof MongoPersistedReplicationStream)) {
|
|
33
|
+
throw new Error(`Expected MongoPersistedReplicationStream`);
|
|
36
34
|
}
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
let { replicationStreamId, replicationStreamName } = replicationStream;
|
|
36
|
+
if (typeof replicationStreamId == 'bigint') {
|
|
37
|
+
replicationStreamId = Number(replicationStreamId);
|
|
38
|
+
}
|
|
39
|
+
const storageConfig = replicationStream.getStorageConfig();
|
|
40
|
+
const syncRuleStorage = createMongoSyncBucketStorage(this, replicationStreamId, replicationStream, replicationStreamName, undefined, {
|
|
41
|
+
checksumOptions: this.options.checksumOptions,
|
|
42
|
+
readPreference: this.options.readPreference,
|
|
43
|
+
checksumCacheTtlMs: this.options.checksumCacheTtlMs,
|
|
44
|
+
storageConfig,
|
|
45
|
+
objectStorage: this.options.objectStorage,
|
|
46
|
+
inlineThresholdBytes: this.options.inlineThresholdBytes,
|
|
47
|
+
clearBatchThrottleRate: this.options.clearBatchThrottleRate ?? DEFAULT_CLEAR_BATCH_THROTTLE_RATE
|
|
41
48
|
});
|
|
42
49
|
if (!options?.skipLifecycleHooks) {
|
|
43
|
-
this.iterateListeners((cb) => cb.syncStorageCreated?.(
|
|
50
|
+
this.iterateListeners((cb) => cb.syncStorageCreated?.(syncRuleStorage));
|
|
44
51
|
}
|
|
45
|
-
|
|
52
|
+
syncRuleStorage.registerListener({
|
|
46
53
|
batchStarted: (batch) => {
|
|
47
54
|
batch.registerListener({
|
|
48
55
|
replicationEvent: (payload) => this.iterateListeners((cb) => cb.replicationEvent?.(payload))
|
|
49
56
|
});
|
|
50
57
|
}
|
|
51
58
|
});
|
|
52
|
-
return
|
|
59
|
+
return syncRuleStorage;
|
|
53
60
|
}
|
|
54
61
|
async getSystemIdentifier() {
|
|
55
62
|
const { setName: id } = await this.db.db.command({
|
|
@@ -63,234 +70,469 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
63
70
|
type: lib_mongo.MONGO_CONNECTION_TYPE
|
|
64
71
|
};
|
|
65
72
|
}
|
|
66
|
-
async restartReplication(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
await this.db.notifyCheckpoint();
|
|
98
|
-
}
|
|
73
|
+
async restartReplication(replicationStreamId) {
|
|
74
|
+
await this.withTransaction(async (session) => {
|
|
75
|
+
const next = await this.getDeployingSyncConfigInternal(session);
|
|
76
|
+
const active = await this.getActiveSyncConfigInternal(session);
|
|
77
|
+
if (next != null && next.content.replicationStreamId == replicationStreamId) {
|
|
78
|
+
// We need to redo the "next" replication stream.
|
|
79
|
+
// This creates a new stream, and stops the existing PROCESSING one.
|
|
80
|
+
await this.updateSyncRulesInTransaction(next.content.asUpdateOptions({ forceNewReplicationStream: true }), session);
|
|
81
|
+
const sharedActiveStream = active?.content.replicationStreamId == replicationStreamId;
|
|
82
|
+
if (sharedActiveStream) {
|
|
83
|
+
// The same stream was also used for the ACTIVE config. Transition that to ERRORED
|
|
84
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (next == null && active?.content.replicationStreamId == replicationStreamId) {
|
|
88
|
+
// Slot removed for "active" replication stream, while there is no "next" one.
|
|
89
|
+
await this.updateSyncRulesInTransaction(active.content.asUpdateOptions({ forceNewReplicationStream: true }), session);
|
|
90
|
+
// In this case we keep the old one as active for clients, so that that existing clients
|
|
91
|
+
// can still get the latest data while we replicate the new ones.
|
|
92
|
+
// It will however not replicate anymore.
|
|
93
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
94
|
+
}
|
|
95
|
+
else if (next != null && active?.content.replicationStreamId == replicationStreamId) {
|
|
96
|
+
// Already have next replication stream, but need to stop replicating the active one.
|
|
97
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// replicationStreamId does not match the ACTIVE or PROCESSING streams - no-op.
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
await this.db.notifyCheckpoint();
|
|
99
104
|
}
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
105
|
+
/**
|
|
106
|
+
* Keep an active config readable after its replication stream is invalidated.
|
|
107
|
+
*
|
|
108
|
+
* Incremental V3 streams can retain stopped historical configs and embed a deploying config.
|
|
109
|
+
* Read-side storage requires exactly one ACTIVE or ERRORED config, so only the active config
|
|
110
|
+
* transitions to ERRORED; every other embedded config remains or becomes STOP.
|
|
111
|
+
*/
|
|
112
|
+
async errorActiveStreamForReplacement(active, session) {
|
|
113
|
+
const activeSyncConfigId = active.content.syncConfigId;
|
|
114
|
+
if (activeSyncConfigId == null) {
|
|
115
|
+
// V1 documents
|
|
116
|
+
const result = await this.db.sync_rules.updateOne({
|
|
117
|
+
_id: active.content.replicationStreamId,
|
|
118
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
111
119
|
}, {
|
|
112
|
-
$
|
|
113
|
-
|
|
120
|
+
$set: {
|
|
121
|
+
state: storage.SyncRuleState.ERRORED
|
|
114
122
|
}
|
|
115
|
-
}, {
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
const id = Number(id_doc.op_id);
|
|
121
|
-
const slot_name = generateSlotName(this.slot_name_prefix, id);
|
|
122
|
-
const mapping = BucketDefinitionMapping.fromParsedSyncRules(options.config.parsed);
|
|
123
|
-
const syncConfigDoc = {
|
|
124
|
-
_id: new ObjectId(),
|
|
125
|
-
replication_stream_id: id,
|
|
126
|
-
created_at: new Date(),
|
|
127
|
-
storage_version: storageVersion,
|
|
128
|
-
content: options.config.yaml,
|
|
129
|
-
serialized_plan: options.config.plan,
|
|
130
|
-
rule_mapping: mapping.serialize()
|
|
131
|
-
};
|
|
132
|
-
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
133
|
-
const doc = {
|
|
134
|
-
_id: id,
|
|
135
|
-
storage_version: storageVersion,
|
|
136
|
-
sync_configs: [
|
|
137
|
-
{
|
|
138
|
-
_id: syncConfigDoc._id,
|
|
139
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
140
|
-
keepalive_op: null,
|
|
141
|
-
last_checkpoint: null,
|
|
142
|
-
last_checkpoint_lsn: null,
|
|
143
|
-
no_checkpoint_before: null,
|
|
144
|
-
snapshot_done: false
|
|
145
|
-
}
|
|
146
|
-
],
|
|
147
|
-
snapshot_lsn: undefined,
|
|
148
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
149
|
-
slot_name: slot_name,
|
|
150
|
-
last_checkpoint_ts: null,
|
|
151
|
-
last_fatal_error: null,
|
|
152
|
-
last_fatal_error_ts: null,
|
|
153
|
-
last_keepalive_ts: null
|
|
154
|
-
};
|
|
155
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
156
|
-
await this.db.notifyCheckpoint();
|
|
157
|
-
rules = new MongoPersistedSyncRulesContentV3(this.db, doc, syncConfigDoc);
|
|
158
|
-
if (options.lock) {
|
|
159
|
-
// The lock is persisted on rules.current_lock
|
|
160
|
-
await rules.lock(session);
|
|
123
|
+
}, { session });
|
|
124
|
+
if (result.matchedCount != 1) {
|
|
125
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
126
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
127
|
+
throw new ReplicationAssertionError(`Active replication stream ${active.content.replicationStreamId} changed during restart`);
|
|
161
128
|
}
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const activeConfigObjectId = new ObjectId(activeSyncConfigId);
|
|
132
|
+
const result = await this.db.sync_rules.updateOne({
|
|
133
|
+
_id: active.content.replicationStreamId,
|
|
134
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
135
|
+
sync_configs: {
|
|
136
|
+
// Confirm that this is still the ACTIVE/ERRORED one.
|
|
137
|
+
$elemMatch: {
|
|
138
|
+
_id: activeConfigObjectId,
|
|
139
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}, {
|
|
143
|
+
$set: {
|
|
144
|
+
state: storage.SyncRuleState.ERRORED,
|
|
145
|
+
// There must be exactly one config with an ERRORED state: This will be used to serve
|
|
146
|
+
// clients until there is a new ACTIVE one.
|
|
147
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.ERRORED,
|
|
148
|
+
// PROCESSING configs must stop. Historical STOP configs remain unchanged.
|
|
149
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.STOP
|
|
150
|
+
}
|
|
151
|
+
}, {
|
|
152
|
+
session,
|
|
153
|
+
arrayFilters: [
|
|
154
|
+
{
|
|
155
|
+
'activeConfig._id': activeConfigObjectId,
|
|
156
|
+
'activeConfig.state': { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
'processingConfig._id': { $ne: activeConfigObjectId },
|
|
160
|
+
'processingConfig.state': storage.SyncRuleState.PROCESSING
|
|
161
|
+
}
|
|
162
|
+
]
|
|
162
163
|
});
|
|
163
|
-
|
|
164
|
+
if (result.matchedCount != 1) {
|
|
165
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
166
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
167
|
+
throw new ReplicationAssertionError(`Active replication stream ${active.content.replicationStreamId} changed during restart`);
|
|
168
|
+
}
|
|
164
169
|
}
|
|
165
|
-
async
|
|
166
|
-
const storageVersion = options.storageVersion ??
|
|
170
|
+
async updateSyncRulesInTransaction(options, session) {
|
|
171
|
+
const storageVersion = options.storageVersion ??
|
|
172
|
+
options.config.parsed.config.storageVersion ??
|
|
173
|
+
this.options.defaultStorageVersion ??
|
|
174
|
+
storage.CURRENT_STORAGE_VERSION;
|
|
167
175
|
const storageConfig = getMongoStorageConfig(storageVersion);
|
|
168
176
|
if (storageConfig.incrementalReprocessing) {
|
|
169
|
-
return this.
|
|
177
|
+
return this.updateSyncConfigV3InTransaction(options, storageVersion, storageConfig, session);
|
|
170
178
|
}
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
179
|
+
return this.updateSyncConfigV1InTransaction(options, storageVersion, session);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Update to a V3 sync config.
|
|
183
|
+
*
|
|
184
|
+
* Does support cases where existing replication streams are on V1.
|
|
185
|
+
*/
|
|
186
|
+
async updateSyncConfigV3InTransaction(options, storageVersion, storageConfig, session) {
|
|
187
|
+
const versioned = this.db.versioned(storageConfig);
|
|
188
|
+
const active = await this.db.sync_rules.findOne({
|
|
189
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
190
|
+
storage_version: storageVersion
|
|
191
|
+
}, { session, sort: { _id: -1 }, limit: 1 });
|
|
192
|
+
if (active != null) {
|
|
193
|
+
const existingConfigDocs = await this.loadSyncConfigDefinitions(versioned, active, session);
|
|
194
|
+
if (!options.forceNewReplicationStream &&
|
|
195
|
+
this.options.supportsMultipleSyncConfigs &&
|
|
196
|
+
isCompatible(existingConfigDocs.map((d) => d.serialized_plan ?? null), options.config, logger)) {
|
|
197
|
+
logger.info(`Using incremental reprocessing`);
|
|
198
|
+
await this.stopExistingProcessingWork(session);
|
|
199
|
+
return await this.appendSyncConfigToStream({
|
|
200
|
+
versioned,
|
|
201
|
+
existing: active,
|
|
202
|
+
existingConfigDocs,
|
|
203
|
+
options,
|
|
204
|
+
storageVersion,
|
|
205
|
+
session
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
await this.stopExistingProcessingWork(session);
|
|
210
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate({
|
|
211
|
+
_id: 'sync_rules'
|
|
212
|
+
}, {
|
|
213
|
+
$inc: {
|
|
214
|
+
op_id: 1n
|
|
215
|
+
}
|
|
216
|
+
}, {
|
|
217
|
+
upsert: true,
|
|
218
|
+
returnDocument: 'after',
|
|
219
|
+
session
|
|
220
|
+
});
|
|
221
|
+
const id = Number(id_doc.op_id);
|
|
222
|
+
const replicationStreamName = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
223
|
+
const mapping = options.config.plan == null
|
|
224
|
+
? // For legacy sync rules and streams, use the parsed config directly to create a mapping
|
|
225
|
+
SingleSyncConfigBucketDefinitionMapping.fromParsedSyncConfig(options.config.parsed)
|
|
226
|
+
: // For new sync streams, always use the serialized version
|
|
227
|
+
SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingFromSerializedPlans([], options.config.plan.plan, []);
|
|
228
|
+
const syncConfigDoc = {
|
|
229
|
+
_id: new ObjectId(),
|
|
230
|
+
replication_stream_id: id,
|
|
231
|
+
created_at: new Date(),
|
|
232
|
+
storage_version: storageVersion,
|
|
233
|
+
content: options.config.yaml,
|
|
234
|
+
version_label: options.version_label,
|
|
235
|
+
serialized_plan: options.config.plan,
|
|
236
|
+
rule_mapping: mapping.serialize()
|
|
237
|
+
};
|
|
238
|
+
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
239
|
+
const doc = {
|
|
240
|
+
_id: id,
|
|
241
|
+
storage_version: storageVersion,
|
|
242
|
+
sync_configs: [
|
|
243
|
+
{
|
|
244
|
+
_id: syncConfigDoc._id,
|
|
245
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
246
|
+
last_checkpoint: null,
|
|
247
|
+
last_checkpoint_lsn: null,
|
|
248
|
+
no_checkpoint_before: null,
|
|
249
|
+
snapshot_done: false
|
|
183
250
|
}
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
251
|
+
],
|
|
252
|
+
snapshot_lsn: undefined,
|
|
253
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
254
|
+
slot_name: replicationStreamName,
|
|
255
|
+
last_checkpoint_ts: null,
|
|
256
|
+
last_fatal_error: null,
|
|
257
|
+
last_fatal_error_ts: null,
|
|
258
|
+
last_keepalive_ts: null
|
|
259
|
+
};
|
|
260
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
261
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc, [syncConfigDoc]);
|
|
262
|
+
if (options.lock) {
|
|
263
|
+
// We only lock when creating a new stream - otherwise we'll likely get lock contention
|
|
264
|
+
// from an existing job on the stream, which would fail the entire transaction.
|
|
265
|
+
// The lock is persisted on rules.current_lock
|
|
266
|
+
await rules.lock(session);
|
|
267
|
+
}
|
|
268
|
+
return rules;
|
|
269
|
+
}
|
|
270
|
+
async loadSyncConfigDefinitions(versioned, existing, session) {
|
|
271
|
+
const activeConfigIds = existing.sync_configs
|
|
272
|
+
.filter((config) => config.state == storage.SyncRuleState.ACTIVE)
|
|
273
|
+
.map((config) => config._id);
|
|
274
|
+
return versioned.syncConfigDefinitions
|
|
275
|
+
.find({
|
|
276
|
+
_id: { $in: activeConfigIds }
|
|
277
|
+
}, { session })
|
|
278
|
+
.toArray();
|
|
279
|
+
}
|
|
280
|
+
/**
|
|
281
|
+
* Load _all_ definition mappings for a replication stream - used as a base to generate new ids.
|
|
282
|
+
*/
|
|
283
|
+
async loadHistoricalSyncConfigRuleMappings(versioned, replicationStreamId, session) {
|
|
284
|
+
return versioned.syncConfigDefinitions
|
|
285
|
+
.find({
|
|
286
|
+
replication_stream_id: replicationStreamId
|
|
287
|
+
}, {
|
|
288
|
+
session,
|
|
289
|
+
projection: {
|
|
290
|
+
rule_mapping: 1
|
|
291
|
+
}
|
|
292
|
+
})
|
|
293
|
+
.toArray();
|
|
294
|
+
}
|
|
295
|
+
logIncrementalDefinitionChanges(changes) {
|
|
296
|
+
logger.info(`Incremental reprocessing sync config update:\n${formatIncrementalSyncConfigUpdateLog(changes)}`);
|
|
297
|
+
}
|
|
298
|
+
async appendSyncConfigToStream(options) {
|
|
299
|
+
const { versioned, existing, existingConfigDocs, options: updateOptions, storageVersion, session } = options;
|
|
300
|
+
const compatibleConfigs = existingConfigDocs.map((doc) => ({
|
|
301
|
+
plan: doc.serialized_plan.plan,
|
|
302
|
+
mapping: SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping)
|
|
303
|
+
}));
|
|
304
|
+
const historicalRuleMappings = await this.loadHistoricalSyncConfigRuleMappings(versioned, existing._id, session);
|
|
305
|
+
const reservedMappings = historicalRuleMappings.map((doc) => SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping));
|
|
306
|
+
const mappingResult = SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingWithChanges(compatibleConfigs, updateOptions.config.plan.plan, reservedMappings);
|
|
307
|
+
const mapping = mappingResult.mapping;
|
|
308
|
+
this.logIncrementalDefinitionChanges(describeIncrementalSyncConfigUpdate({
|
|
309
|
+
activeMappings: existingConfigDocs.map((doc) => SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping)),
|
|
310
|
+
newMapping: mapping,
|
|
311
|
+
newSyncConfig: updateOptions.config.parsed,
|
|
312
|
+
mappingChanges: mappingResult.changes
|
|
313
|
+
}));
|
|
314
|
+
const syncConfigDoc = {
|
|
315
|
+
_id: new ObjectId(),
|
|
316
|
+
replication_stream_id: existing._id,
|
|
317
|
+
created_at: new Date(),
|
|
318
|
+
storage_version: storageVersion,
|
|
319
|
+
content: updateOptions.config.yaml,
|
|
320
|
+
version_label: updateOptions.version_label,
|
|
321
|
+
serialized_plan: updateOptions.config.plan,
|
|
322
|
+
rule_mapping: mapping.serialize()
|
|
323
|
+
};
|
|
324
|
+
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
325
|
+
const syncConfigState = {
|
|
326
|
+
_id: syncConfigDoc._id,
|
|
327
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
328
|
+
last_checkpoint: null,
|
|
329
|
+
last_checkpoint_lsn: null,
|
|
330
|
+
no_checkpoint_before: null,
|
|
331
|
+
snapshot_done: false
|
|
332
|
+
};
|
|
333
|
+
await this.db.sync_rules.updateOne({ _id: existing._id }, {
|
|
334
|
+
$push: {
|
|
335
|
+
sync_configs: syncConfigState
|
|
336
|
+
},
|
|
337
|
+
$set: {
|
|
205
338
|
last_fatal_error: null,
|
|
206
|
-
last_fatal_error_ts: null
|
|
207
|
-
last_keepalive_ts: null
|
|
208
|
-
};
|
|
209
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
210
|
-
await this.db.notifyCheckpoint();
|
|
211
|
-
rules = new MongoPersistedSyncRulesContentV1(this.db, doc);
|
|
212
|
-
if (options.lock) {
|
|
213
|
-
// The lock is persisted on rules.current_lock
|
|
214
|
-
await rules.lock(session);
|
|
339
|
+
last_fatal_error_ts: null
|
|
215
340
|
}
|
|
341
|
+
}, { session });
|
|
342
|
+
const syncConfigStates = [
|
|
343
|
+
...existing.sync_configs.filter((config) => config.state == storage.SyncRuleState.ACTIVE),
|
|
344
|
+
syncConfigState
|
|
345
|
+
];
|
|
346
|
+
const stream = new MongoPersistedReplicationStream(this.db, {
|
|
347
|
+
...existing,
|
|
348
|
+
sync_configs: syncConfigStates
|
|
349
|
+
}, [...existingConfigDocs, syncConfigDoc]);
|
|
350
|
+
// The stream already exists, so an active replication job may already hold the stream lock.
|
|
351
|
+
// Deployment only persists the appended sync config; replication job locking is handled by
|
|
352
|
+
// the replicator.
|
|
353
|
+
return stream;
|
|
354
|
+
}
|
|
355
|
+
async updateSyncRules(options) {
|
|
356
|
+
let rules;
|
|
357
|
+
await this.withTransaction(async (session) => {
|
|
358
|
+
rules = await this.updateSyncRulesInTransaction(options, session);
|
|
359
|
+
});
|
|
360
|
+
await this.db.notifyCheckpoint();
|
|
361
|
+
return rules;
|
|
362
|
+
}
|
|
363
|
+
async withTransaction(callback) {
|
|
364
|
+
return this.client.withSession((session) => session.withTransaction(callback));
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Update to a V1 sync config.
|
|
368
|
+
*
|
|
369
|
+
* Does support cases where existing replication streams are on V3.
|
|
370
|
+
*/
|
|
371
|
+
async updateSyncConfigV1InTransaction(options, storageVersion, session) {
|
|
372
|
+
await this.stopExistingProcessingWork(session);
|
|
373
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate({
|
|
374
|
+
_id: 'sync_rules'
|
|
375
|
+
}, {
|
|
376
|
+
$inc: {
|
|
377
|
+
op_id: 1n
|
|
378
|
+
}
|
|
379
|
+
}, {
|
|
380
|
+
upsert: true,
|
|
381
|
+
returnDocument: 'after',
|
|
382
|
+
session
|
|
216
383
|
});
|
|
384
|
+
const id = Number(id_doc.op_id);
|
|
385
|
+
const slot_name = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
386
|
+
// All V1 replication streams share both the `main` op id sequence and the `bucket_parameters`
|
|
387
|
+
// collection, so every parameter entry this stream writes gets an op id above the current
|
|
388
|
+
// head. Seeding the parameter compaction cursor with that head keeps the stream's first
|
|
389
|
+
// compaction from scanning other streams' history, which would otherwise be repeated for
|
|
390
|
+
// every new deployment. A concurrent replication flush can only advance the head after this
|
|
391
|
+
// read, which makes the seed conservative, never too high.
|
|
392
|
+
const opSequence = await this.db.op_id_sequence.findOne({ _id: 'main' }, { session });
|
|
393
|
+
const doc = {
|
|
394
|
+
_id: id,
|
|
395
|
+
storage_version: storageVersion,
|
|
396
|
+
content: options.config.yaml,
|
|
397
|
+
version_label: options.version_label,
|
|
398
|
+
serialized_plan: options.config.plan,
|
|
399
|
+
last_checkpoint: null,
|
|
400
|
+
last_checkpoint_lsn: null,
|
|
401
|
+
no_checkpoint_before: null,
|
|
402
|
+
keepalive_op: null,
|
|
403
|
+
snapshot_done: false,
|
|
404
|
+
snapshot_lsn: undefined,
|
|
405
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
406
|
+
slot_name: slot_name,
|
|
407
|
+
last_checkpoint_ts: null,
|
|
408
|
+
last_fatal_error: null,
|
|
409
|
+
last_fatal_error_ts: null,
|
|
410
|
+
last_keepalive_ts: null,
|
|
411
|
+
parameter_compaction: { compacted_before: opSequence?.op_id ?? 0n }
|
|
412
|
+
};
|
|
413
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
414
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc);
|
|
415
|
+
if (options.lock) {
|
|
416
|
+
// The lock is persisted on rules.current_lock
|
|
417
|
+
await rules.lock(session);
|
|
418
|
+
}
|
|
217
419
|
return rules;
|
|
218
420
|
}
|
|
219
|
-
|
|
421
|
+
/**
|
|
422
|
+
* Stop top-level and embedded processing work before creating a replacement stream.
|
|
423
|
+
*
|
|
424
|
+
* Use in the same transaction that the replacement stream is created.
|
|
425
|
+
*/
|
|
426
|
+
async stopExistingProcessingWork(session) {
|
|
427
|
+
await this.db.sync_rules.updateMany({
|
|
428
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
429
|
+
'sync_configs.state': storage.SyncRuleState.PROCESSING
|
|
430
|
+
}, {
|
|
431
|
+
$set: {
|
|
432
|
+
'sync_configs.$[config].state': storage.SyncRuleState.STOP
|
|
433
|
+
}
|
|
434
|
+
}, {
|
|
435
|
+
session,
|
|
436
|
+
arrayFilters: [{ 'config.state': storage.SyncRuleState.PROCESSING }]
|
|
437
|
+
});
|
|
438
|
+
await this.db.sync_rules.updateMany({
|
|
439
|
+
state: storage.SyncRuleState.PROCESSING
|
|
440
|
+
}, stopReplicationStreamPipeline(), { session });
|
|
441
|
+
}
|
|
442
|
+
async getActiveSyncConfig() {
|
|
443
|
+
return this.getActiveSyncConfigInternal();
|
|
444
|
+
}
|
|
445
|
+
async getActiveSyncConfigInternal(session) {
|
|
220
446
|
const doc = await this.db.sync_rules.findOne({
|
|
221
447
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
222
|
-
}, { sort: { _id: -1 }, limit: 1 });
|
|
223
|
-
return this.
|
|
448
|
+
}, { session, sort: { _id: -1 }, limit: 1 });
|
|
449
|
+
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED], { cacheActiveStorage: session == null }, session);
|
|
224
450
|
}
|
|
225
|
-
async
|
|
451
|
+
async replicationStreamFromDoc(doc, stateFilter, session) {
|
|
226
452
|
if (doc == null) {
|
|
227
453
|
return null;
|
|
228
454
|
}
|
|
229
455
|
const storageConfig = getMongoStorageConfig(doc.storage_version ?? LEGACY_STORAGE_VERSION);
|
|
230
456
|
if (storageConfig.incrementalReprocessing) {
|
|
231
457
|
const v3 = doc;
|
|
232
|
-
const
|
|
233
|
-
if (
|
|
458
|
+
const matching = v3.sync_configs.filter((c) => stateFilter.includes(c.state));
|
|
459
|
+
if (matching.length == 0) {
|
|
234
460
|
return null;
|
|
235
461
|
}
|
|
236
462
|
// TODO: cache the config. It could specifically help for the main replication loop
|
|
237
463
|
// that checks for active replication streams.
|
|
238
464
|
// It is not a major bottleneck though, since it only runs once every couple of seconds at most.
|
|
239
465
|
const db = this.db.versioned(storageConfig);
|
|
240
|
-
const
|
|
241
|
-
|
|
466
|
+
const syncConfigDocs = await db.syncConfigDefinitions
|
|
467
|
+
.find({
|
|
468
|
+
_id: { $in: matching.map((config) => config._id) }
|
|
469
|
+
}, { session })
|
|
470
|
+
.toArray();
|
|
471
|
+
if (syncConfigDocs.length == 0) {
|
|
242
472
|
return null;
|
|
243
473
|
}
|
|
244
|
-
return new
|
|
474
|
+
return new MongoPersistedReplicationStream(this.db, v3, syncConfigDocs);
|
|
245
475
|
}
|
|
246
|
-
return new
|
|
476
|
+
return new MongoPersistedReplicationStream(this.db, doc);
|
|
477
|
+
}
|
|
478
|
+
async getDeployingSyncConfig() {
|
|
479
|
+
return this.getDeployingSyncConfigInternal();
|
|
247
480
|
}
|
|
248
|
-
async
|
|
481
|
+
async getDeployingSyncConfigInternal(session) {
|
|
249
482
|
const doc = await this.db.sync_rules.findOne({
|
|
250
|
-
state: storage.SyncRuleState.PROCESSING
|
|
251
|
-
}, { sort: { _id: -1 }, limit: 1 });
|
|
252
|
-
return this.
|
|
483
|
+
$or: [{ state: storage.SyncRuleState.PROCESSING }, { 'sync_configs.state': storage.SyncRuleState.PROCESSING }]
|
|
484
|
+
}, { session, sort: { _id: -1 }, limit: 1 });
|
|
485
|
+
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.PROCESSING], {}, session);
|
|
253
486
|
}
|
|
254
|
-
async
|
|
487
|
+
async getReplicatingReplicationStreams() {
|
|
255
488
|
const docs = await this.db.sync_rules
|
|
256
489
|
.find({
|
|
257
490
|
state: { $in: [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE] }
|
|
258
491
|
})
|
|
259
492
|
.toArray();
|
|
260
493
|
return (await Promise.all(docs.map((doc) => {
|
|
261
|
-
return this.
|
|
494
|
+
return this.replicationStreamFromDoc(doc, [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE]);
|
|
262
495
|
}))).filter((r) => r != null);
|
|
263
496
|
}
|
|
264
|
-
async
|
|
497
|
+
async getStoppedReplicationStreams() {
|
|
265
498
|
const docs = await this.db.sync_rules
|
|
266
499
|
.find({
|
|
267
500
|
state: storage.SyncRuleState.STOP
|
|
268
501
|
})
|
|
269
502
|
.toArray();
|
|
270
503
|
return (await Promise.all(docs.map((doc) => {
|
|
271
|
-
return this.
|
|
504
|
+
return this.replicationStreamFromDoc(doc, [storage.SyncRuleState.STOP]);
|
|
272
505
|
}))).filter((d) => d != null);
|
|
273
506
|
}
|
|
274
|
-
async
|
|
275
|
-
const
|
|
276
|
-
if (
|
|
507
|
+
async resolvedSyncConfigFromDoc(doc, stateFilter, options = {}, session) {
|
|
508
|
+
const stream = await this.replicationStreamFromDoc(doc, stateFilter, session);
|
|
509
|
+
if (stream == null) {
|
|
277
510
|
return null;
|
|
278
511
|
}
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
512
|
+
const content = stream.syncConfigContent[0];
|
|
513
|
+
const thisFactory = this;
|
|
514
|
+
return {
|
|
515
|
+
content,
|
|
516
|
+
replicationStream: stream,
|
|
517
|
+
get storage() {
|
|
518
|
+
// It is important that this instance is cached.
|
|
519
|
+
// Not for the instance construction itself, but to ensure that internal caches on the instance
|
|
520
|
+
// are re-used properly.
|
|
521
|
+
if (options.cacheActiveStorage &&
|
|
522
|
+
thisFactory.activeStorageCache?.replicationStream.replicationJobId == stream.replicationJobId) {
|
|
523
|
+
return thisFactory.activeStorageCache;
|
|
524
|
+
}
|
|
525
|
+
const instance = thisFactory.getInstance(stream);
|
|
526
|
+
if (options.cacheActiveStorage) {
|
|
527
|
+
thisFactory.activeStorageCache = instance;
|
|
528
|
+
}
|
|
529
|
+
return instance;
|
|
530
|
+
}
|
|
531
|
+
};
|
|
290
532
|
}
|
|
291
533
|
async getStorageMetrics() {
|
|
292
534
|
const ignoreNotExisting = (e) => {
|
|
293
|
-
if (lib_mongo.
|
|
535
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(e)) {
|
|
294
536
|
// Collection doesn't exist - return 0
|
|
295
537
|
return [{ storageStats: { size: 0 } }];
|
|
296
538
|
}
|
|
@@ -298,8 +540,6 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
298
540
|
return Promise.reject(e);
|
|
299
541
|
}
|
|
300
542
|
};
|
|
301
|
-
// For now, we get storage metrics over all v1 and v3 collections.
|
|
302
|
-
// In the future, we may split these metrics to report separately for active replication streams versus processing streams.
|
|
303
543
|
const aggregateStaticCollection = async (collection) => {
|
|
304
544
|
// We check whether the collection exists before getting the statistics. This avoids repeated
|
|
305
545
|
// errors in the MongoDB logs if the collection hasn't been created yet.
|
|
@@ -320,7 +560,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
320
560
|
.catch(ignoreNotExisting);
|
|
321
561
|
};
|
|
322
562
|
const operations_aggregate = await aggregateStaticCollection(this.db.bucket_data);
|
|
323
|
-
const
|
|
563
|
+
const v3OperationCollections = await this.db.listBucketDataCollectionsV3();
|
|
564
|
+
const v3_operation_aggregates = await Promise.all(v3OperationCollections.map((collection) => collection
|
|
324
565
|
.aggregate([
|
|
325
566
|
{
|
|
326
567
|
$collStats: {
|
|
@@ -331,7 +572,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
331
572
|
.toArray()
|
|
332
573
|
.catch(ignoreNotExisting)));
|
|
333
574
|
const parameters_aggregate = await aggregateStaticCollection(this.db.bucket_parameters);
|
|
334
|
-
const
|
|
575
|
+
const v3ParameterCollections = await this.db.listAllParameterIndexCollectionsV3();
|
|
576
|
+
const v3_parameter_aggregates = await Promise.all(v3ParameterCollections.map((collection) => collection
|
|
335
577
|
.aggregate([
|
|
336
578
|
{
|
|
337
579
|
$collStats: {
|
|
@@ -342,7 +584,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
342
584
|
.toArray()
|
|
343
585
|
.catch(ignoreNotExisting)));
|
|
344
586
|
const v1_source_record_aggregate = await aggregateStaticCollection(this.db.current_data);
|
|
345
|
-
const
|
|
587
|
+
const v3SourceRecordCollections = await this.db.listAllSourceRecordCollectionsV3();
|
|
588
|
+
const source_record_aggregates = await Promise.all(v3SourceRecordCollections.map((collection) => collection
|
|
346
589
|
.aggregate([
|
|
347
590
|
{
|
|
348
591
|
$collStats: {
|
|
@@ -352,13 +595,84 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
352
595
|
])
|
|
353
596
|
.toArray()
|
|
354
597
|
.catch(ignoreNotExisting)));
|
|
598
|
+
const v3StorageConfig = getMongoStorageConfig(storage.STORAGE_VERSION_3);
|
|
599
|
+
const v3Db = this.db.versioned(v3StorageConfig);
|
|
600
|
+
const objectStorageDefinitionUsage = await ObjectStorageUsage.readAllDefinitionUsage(v3Db);
|
|
601
|
+
const v3StreamDocs = (await this.db.sync_rules
|
|
602
|
+
.find({ storage_version: { $gte: storage.STORAGE_VERSION_3 } }, { projection: { _id: 1, sync_configs: 1 } })
|
|
603
|
+
.toArray());
|
|
604
|
+
const collectionSizes = new Map();
|
|
605
|
+
const addCollectionSize = (collection, aggregate, prefix, sizes) => {
|
|
606
|
+
const match = collection.collectionName.match(new RegExp(`^${prefix}(\\d+)_(.+)$`));
|
|
607
|
+
if (match == null) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
sizes.set(`${prefix}${match[1]}:${match[2]}`, Number(aggregate[0]?.storageStats?.size ?? 0));
|
|
611
|
+
};
|
|
612
|
+
v3OperationCollections.forEach((collection, index) => addCollectionSize(collection, v3_operation_aggregates[index], 'bucket_data_', collectionSizes));
|
|
613
|
+
v3ParameterCollections.forEach((collection, index) => addCollectionSize(collection, v3_parameter_aggregates[index], 'parameter_index_', collectionSizes));
|
|
614
|
+
v3SourceRecordCollections.forEach((collection, index) => addCollectionSize(collection, source_record_aggregates[index], 'source_records_', collectionSizes));
|
|
615
|
+
const syncConfigIds = v3StreamDocs.flatMap((stream) => (stream.sync_configs ?? []).map((config) => config._id));
|
|
616
|
+
const syncConfigDefinitions = syncConfigIds.length == 0
|
|
617
|
+
? []
|
|
618
|
+
: await v3Db.syncConfigDefinitions
|
|
619
|
+
.find({ _id: { $in: syncConfigIds } }, { projection: { _id: 1, rule_mapping: 1, version_label: 1 } })
|
|
620
|
+
.toArray();
|
|
621
|
+
const syncConfigDefinitionsById = new Map(syncConfigDefinitions.map((definition) => [definition._id.toHexString(), definition]));
|
|
622
|
+
const sourceTablesByStream = new Map();
|
|
623
|
+
await Promise.all(v3StreamDocs.map(async (stream) => {
|
|
624
|
+
const sourceTables = await v3Db
|
|
625
|
+
.sourceTables(stream._id)
|
|
626
|
+
.find({}, { projection: { _id: 1, bucket_data_source_ids: 1, parameter_lookup_source_ids: 1 } })
|
|
627
|
+
.toArray();
|
|
628
|
+
sourceTablesByStream.set(stream._id, sourceTables);
|
|
629
|
+
}));
|
|
630
|
+
const objectStorageSizeByDefinition = new Map();
|
|
631
|
+
for (const usage of objectStorageDefinitionUsage) {
|
|
632
|
+
objectStorageSizeByDefinition.set(`${usage.replication_stream_id}:${usage.definition_id}`, Number(usage.active_bytes));
|
|
633
|
+
}
|
|
634
|
+
const sumCollectionSizes = (prefix, streamId, ids) => [...ids].reduce((total, id) => total + (collectionSizes.get(`${prefix}${streamId}:${id}`) ?? 0), 0);
|
|
635
|
+
const syncConfigMetrics = [];
|
|
636
|
+
for (const stream of v3StreamDocs) {
|
|
637
|
+
const sourceTables = sourceTablesByStream.get(stream._id) ?? [];
|
|
638
|
+
for (const syncConfig of stream.sync_configs ?? []) {
|
|
639
|
+
const definition = syncConfigDefinitionsById.get(syncConfig._id.toHexString());
|
|
640
|
+
if (definition == null) {
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
const mapping = SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(definition.rule_mapping);
|
|
644
|
+
const bucketDefinitionIds = mapping.allBucketDefinitionIds();
|
|
645
|
+
const parameterIndexIds = mapping.allParameterIndexIds();
|
|
646
|
+
const bucketDefinitionIdSet = new Set(bucketDefinitionIds);
|
|
647
|
+
const parameterIndexIdSet = new Set(parameterIndexIds);
|
|
648
|
+
const replicationSize = sourceTables.reduce((total, sourceTable) => {
|
|
649
|
+
if (!(sourceTable.bucket_data_source_ids ?? []).some((id) => bucketDefinitionIdSet.has(id)) &&
|
|
650
|
+
!(sourceTable.parameter_lookup_source_ids ?? []).some((id) => parameterIndexIdSet.has(id))) {
|
|
651
|
+
return total;
|
|
652
|
+
}
|
|
653
|
+
return total + (collectionSizes.get(`source_records_${stream._id}:${sourceTable._id.toHexString()}`) ?? 0);
|
|
654
|
+
}, 0);
|
|
655
|
+
syncConfigMetrics.push({
|
|
656
|
+
sync_config_id: syncConfig._id.toHexString(),
|
|
657
|
+
sync_config_state: String(syncConfig.state),
|
|
658
|
+
version_label: definition.version_label,
|
|
659
|
+
attributed_bucket_data_bytes: sumCollectionSizes('bucket_data_', stream._id, bucketDefinitionIdSet),
|
|
660
|
+
attributed_parameter_indexes_bytes: sumCollectionSizes('parameter_index_', stream._id, parameterIndexIdSet),
|
|
661
|
+
attributed_source_records_bytes: replicationSize,
|
|
662
|
+
attributed_object_storage_bytes: [...bucketDefinitionIdSet].reduce((total, definitionId) => total + (objectStorageSizeByDefinition.get(`${stream._id}:${definitionId}`) ?? 0), 0)
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const totalObjectStorageSize = objectStorageDefinitionUsage.reduce((total, usage) => total + usage.active_bytes, 0n);
|
|
355
667
|
return {
|
|
356
668
|
operations_size_bytes: Number(operations_aggregate[0].storageStats.size) +
|
|
357
669
|
v3_operation_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
|
|
358
670
|
parameters_size_bytes: Number(parameters_aggregate[0].storageStats.size) +
|
|
359
671
|
v3_parameter_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
|
|
360
672
|
replication_size_bytes: Number(v1_source_record_aggregate[0]?.storageStats?.size ?? 0) +
|
|
361
|
-
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0)
|
|
673
|
+
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0),
|
|
674
|
+
object_storage_size_bytes: Number(totalObjectStorageSize),
|
|
675
|
+
sync_config_metrics: syncConfigMetrics
|
|
362
676
|
};
|
|
363
677
|
}
|
|
364
678
|
async getPowerSyncInstanceId() {
|