@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,39 +1,70 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GetIntanceOptions,
|
|
3
|
+
LEGACY_STORAGE_VERSION,
|
|
4
|
+
SingleSyncConfigBucketDefinitionMapping,
|
|
5
|
+
storage
|
|
6
|
+
} from '@powersync/service-core';
|
|
2
7
|
|
|
3
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
DO_NOT_LOG,
|
|
10
|
+
ErrorCode,
|
|
11
|
+
logger,
|
|
12
|
+
ReplicationAssertionError,
|
|
13
|
+
ServiceError
|
|
14
|
+
} from '@powersync/lib-services-framework';
|
|
4
15
|
import { v4 as uuid } from 'uuid';
|
|
5
16
|
|
|
6
17
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
7
18
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
8
19
|
|
|
20
|
+
import {
|
|
21
|
+
describeIncrementalSyncConfigUpdate,
|
|
22
|
+
formatIncrementalSyncConfigUpdateLog,
|
|
23
|
+
isCompatible
|
|
24
|
+
} from '@powersync/service-core';
|
|
9
25
|
import { ObjectId } from 'bson';
|
|
10
|
-
import {
|
|
11
|
-
import {
|
|
26
|
+
import { DEFAULT_CLEAR_BATCH_THROTTLE_RATE } from '../types/types.js';
|
|
27
|
+
import { generateReplicationStreamName } from '../utils/util.js';
|
|
12
28
|
import type { MongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
13
29
|
import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
14
30
|
import { PowerSyncMongo } from './implementation/db.js';
|
|
15
31
|
import { getMongoStorageConfig, StorageConfig, SyncRuleDocumentBase } from './implementation/models.js';
|
|
16
32
|
import { MongoChecksumOptions } from './implementation/MongoChecksums.js';
|
|
17
|
-
import {
|
|
18
|
-
|
|
19
|
-
MongoPersistedSyncRulesContentV3
|
|
20
|
-
} from './implementation/MongoPersistedSyncRulesContent.js';
|
|
21
|
-
import { syncRuleStateUpdatePipeline } from './implementation/SyncRuleStateUpdate.js';
|
|
33
|
+
import { MongoPersistedReplicationStream } from './implementation/MongoPersistedReplicationStream.js';
|
|
34
|
+
import { stopReplicationStreamPipeline } from './implementation/SyncRuleStateUpdate.js';
|
|
22
35
|
import { SyncRuleDocumentV1 } from './implementation/v1/models.js';
|
|
36
|
+
import { ObjectStorage } from './implementation/v3/object-storage/ObjectStorage.js';
|
|
37
|
+
import { ObjectStorageUsage } from './implementation/v3/object-storage/ObjectStorageUsage.js';
|
|
23
38
|
import { VersionedPowerSyncMongoV3 } from './implementation/v3/VersionedPowerSyncMongoV3.js';
|
|
24
|
-
import { ReplicationStreamDocumentV3, SyncConfigDefinition } from './storage-index.js';
|
|
39
|
+
import { ReplicationStreamDocumentV3, SyncConfigDefinition, SyncRuleConfigStateV3 } from './storage-index.js';
|
|
25
40
|
|
|
26
41
|
export interface MongoBucketStorageOptions {
|
|
27
|
-
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'
|
|
42
|
+
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
|
|
43
|
+
objectStorage?: ObjectStorage;
|
|
44
|
+
inlineThresholdBytes?: number;
|
|
45
|
+
/**
|
|
46
|
+
* Prefix for replication stream name and Postgres logical replication slot name.
|
|
47
|
+
*/
|
|
48
|
+
replicationStreamNamePrefix: string;
|
|
49
|
+
readPreference?: mongo.ReadPreference;
|
|
50
|
+
checksumCacheTtlMs?: number;
|
|
51
|
+
clearBatchThrottleRate?: number;
|
|
52
|
+
defaultStorageVersion?: number;
|
|
53
|
+
/**
|
|
54
|
+
* Reuse a compatible active replication stream by appending a new sync config.
|
|
55
|
+
*
|
|
56
|
+
* This currently requires source replication support. MongoDB sources can process multiple
|
|
57
|
+
* sync configs in one replication stream, but other source connectors still expect a single
|
|
58
|
+
* sync config per stream.
|
|
59
|
+
*/
|
|
60
|
+
supportsMultipleSyncConfigs?: boolean;
|
|
28
61
|
}
|
|
29
62
|
|
|
30
63
|
export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
31
64
|
[DO_NOT_LOG] = true;
|
|
32
65
|
|
|
33
66
|
private readonly client: mongo.MongoClient;
|
|
34
|
-
|
|
35
|
-
// TODO: This is still Postgres specific and needs to be reworked
|
|
36
|
-
public readonly slot_name_prefix: string;
|
|
67
|
+
public readonly replicationStreamNamePrefix: string;
|
|
37
68
|
|
|
38
69
|
private activeStorageCache: MongoSyncBucketStorage | undefined;
|
|
39
70
|
|
|
@@ -41,51 +72,58 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
41
72
|
|
|
42
73
|
constructor(
|
|
43
74
|
db: PowerSyncMongo,
|
|
44
|
-
options:
|
|
45
|
-
slot_name_prefix: string;
|
|
46
|
-
},
|
|
47
|
-
private internalOptions?: MongoBucketStorageOptions
|
|
75
|
+
private options: MongoBucketStorageOptions
|
|
48
76
|
) {
|
|
49
77
|
super();
|
|
50
78
|
this.client = db.client;
|
|
51
79
|
this.db = db;
|
|
52
|
-
this.
|
|
53
|
-
this.slot_name_prefix = options.slot_name_prefix;
|
|
80
|
+
this.replicationStreamNamePrefix = options.replicationStreamNamePrefix;
|
|
54
81
|
}
|
|
55
82
|
|
|
56
83
|
async [Symbol.asyncDispose]() {
|
|
57
84
|
// No-op
|
|
58
85
|
}
|
|
59
86
|
|
|
60
|
-
getInstance(
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
87
|
+
getInstance(
|
|
88
|
+
replicationStream: storage.PersistedReplicationStream,
|
|
89
|
+
options?: GetIntanceOptions
|
|
90
|
+
): MongoSyncBucketStorage {
|
|
91
|
+
if (!(replicationStream instanceof MongoPersistedReplicationStream)) {
|
|
92
|
+
throw new Error(`Expected MongoPersistedReplicationStream`);
|
|
93
|
+
}
|
|
94
|
+
let { replicationStreamId, replicationStreamName } = replicationStream;
|
|
95
|
+
if ((typeof replicationStreamId as any) == 'bigint') {
|
|
96
|
+
replicationStreamId = Number(replicationStreamId);
|
|
64
97
|
}
|
|
65
|
-
const storageConfig =
|
|
66
|
-
const
|
|
98
|
+
const storageConfig = replicationStream.getStorageConfig();
|
|
99
|
+
const syncRuleStorage = createMongoSyncBucketStorage(
|
|
67
100
|
this,
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
101
|
+
replicationStreamId,
|
|
102
|
+
replicationStream,
|
|
103
|
+
replicationStreamName,
|
|
71
104
|
undefined,
|
|
72
105
|
{
|
|
73
|
-
|
|
74
|
-
|
|
106
|
+
checksumOptions: this.options.checksumOptions,
|
|
107
|
+
readPreference: this.options.readPreference,
|
|
108
|
+
checksumCacheTtlMs: this.options.checksumCacheTtlMs,
|
|
109
|
+
storageConfig,
|
|
110
|
+
objectStorage: this.options.objectStorage,
|
|
111
|
+
inlineThresholdBytes: this.options.inlineThresholdBytes,
|
|
112
|
+
clearBatchThrottleRate: this.options.clearBatchThrottleRate ?? DEFAULT_CLEAR_BATCH_THROTTLE_RATE
|
|
75
113
|
}
|
|
76
114
|
);
|
|
77
115
|
if (!options?.skipLifecycleHooks) {
|
|
78
|
-
this.iterateListeners((cb) => cb.syncStorageCreated?.(
|
|
116
|
+
this.iterateListeners((cb) => cb.syncStorageCreated?.(syncRuleStorage));
|
|
79
117
|
}
|
|
80
118
|
|
|
81
|
-
|
|
119
|
+
syncRuleStorage.registerListener({
|
|
82
120
|
batchStarted: (batch) => {
|
|
83
121
|
batch.registerListener({
|
|
84
122
|
replicationEvent: (payload) => this.iterateListeners((cb) => cb.replicationEvent?.(payload))
|
|
85
123
|
});
|
|
86
124
|
}
|
|
87
125
|
});
|
|
88
|
-
return
|
|
126
|
+
return syncRuleStorage;
|
|
89
127
|
}
|
|
90
128
|
|
|
91
129
|
async getSystemIdentifier(): Promise<storage.BucketStorageSystemIdentifier> {
|
|
@@ -105,228 +143,527 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
105
143
|
};
|
|
106
144
|
}
|
|
107
145
|
|
|
108
|
-
async restartReplication(
|
|
109
|
-
|
|
110
|
-
|
|
146
|
+
async restartReplication(replicationStreamId: number) {
|
|
147
|
+
await this.withTransaction(async (session) => {
|
|
148
|
+
const next = await this.getDeployingSyncConfigInternal(session);
|
|
149
|
+
const active = await this.getActiveSyncConfigInternal(session);
|
|
150
|
+
|
|
151
|
+
if (next != null && next.content.replicationStreamId == replicationStreamId) {
|
|
152
|
+
// We need to redo the "next" replication stream.
|
|
153
|
+
// This creates a new stream, and stops the existing PROCESSING one.
|
|
154
|
+
await this.updateSyncRulesInTransaction(
|
|
155
|
+
next.content.asUpdateOptions({ forceNewReplicationStream: true }),
|
|
156
|
+
session
|
|
157
|
+
);
|
|
158
|
+
const sharedActiveStream = active?.content.replicationStreamId == replicationStreamId;
|
|
159
|
+
if (sharedActiveStream) {
|
|
160
|
+
// The same stream was also used for the ACTIVE config. Transition that to ERRORED
|
|
161
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
162
|
+
}
|
|
163
|
+
} else if (next == null && active?.content.replicationStreamId == replicationStreamId) {
|
|
164
|
+
// Slot removed for "active" replication stream, while there is no "next" one.
|
|
165
|
+
await this.updateSyncRulesInTransaction(
|
|
166
|
+
active.content.asUpdateOptions({ forceNewReplicationStream: true }),
|
|
167
|
+
session
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
// In this case we keep the old one as active for clients, so that that existing clients
|
|
171
|
+
// can still get the latest data while we replicate the new ones.
|
|
172
|
+
// It will however not replicate anymore.
|
|
173
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
174
|
+
} else if (next != null && active?.content.replicationStreamId == replicationStreamId) {
|
|
175
|
+
// Already have next replication stream, but need to stop replicating the active one.
|
|
176
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
177
|
+
} else {
|
|
178
|
+
// replicationStreamId does not match the ACTIVE or PROCESSING streams - no-op.
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
await this.db.notifyCheckpoint();
|
|
182
|
+
}
|
|
111
183
|
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
184
|
+
/**
|
|
185
|
+
* Keep an active config readable after its replication stream is invalidated.
|
|
186
|
+
*
|
|
187
|
+
* Incremental V3 streams can retain stopped historical configs and embed a deploying config.
|
|
188
|
+
* Read-side storage requires exactly one ACTIVE or ERRORED config, so only the active config
|
|
189
|
+
* transitions to ERRORED; every other embedded config remains or becomes STOP.
|
|
190
|
+
*/
|
|
191
|
+
private async errorActiveStreamForReplacement(active: storage.ResolvedSyncConfig, session: mongo.ClientSession) {
|
|
192
|
+
const activeSyncConfigId = active.content.syncConfigId;
|
|
193
|
+
if (activeSyncConfigId == null) {
|
|
194
|
+
// V1 documents
|
|
195
|
+
const result = await this.db.sync_rules.updateOne(
|
|
117
196
|
{
|
|
118
|
-
_id:
|
|
119
|
-
state: storage.SyncRuleState.
|
|
197
|
+
_id: active.content.replicationStreamId,
|
|
198
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
120
199
|
},
|
|
121
|
-
syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP)
|
|
122
|
-
);
|
|
123
|
-
await this.db.notifyCheckpoint();
|
|
124
|
-
} else if (next == null && active?.id == sync_rules_group_id) {
|
|
125
|
-
// Slot removed for "active" replication stream, while there is no "next" one.
|
|
126
|
-
await this.updateSyncRules(active.asUpdateOptions());
|
|
127
|
-
|
|
128
|
-
// In this case we keep the old one as active for clients, so that that existing clients
|
|
129
|
-
// can still get the latest data while we replicate the new ones.
|
|
130
|
-
// It will however not replicate anymore.
|
|
131
|
-
|
|
132
|
-
await this.db.sync_rules.updateOne(
|
|
133
200
|
{
|
|
134
|
-
|
|
135
|
-
|
|
201
|
+
$set: {
|
|
202
|
+
state: storage.SyncRuleState.ERRORED
|
|
203
|
+
}
|
|
136
204
|
},
|
|
137
|
-
|
|
205
|
+
{ session }
|
|
138
206
|
);
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
207
|
+
if (result.matchedCount != 1) {
|
|
208
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
209
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
210
|
+
throw new ReplicationAssertionError(
|
|
211
|
+
`Active replication stream ${active.content.replicationStreamId} changed during restart`
|
|
212
|
+
);
|
|
213
|
+
}
|
|
214
|
+
return;
|
|
215
|
+
}
|
|
142
216
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
},
|
|
148
|
-
|
|
217
|
+
const activeConfigObjectId = new ObjectId(activeSyncConfigId);
|
|
218
|
+
const result = await this.db.sync_rules.updateOne(
|
|
219
|
+
{
|
|
220
|
+
_id: active.content.replicationStreamId,
|
|
221
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
222
|
+
sync_configs: {
|
|
223
|
+
// Confirm that this is still the ACTIVE/ERRORED one.
|
|
224
|
+
$elemMatch: {
|
|
225
|
+
_id: activeConfigObjectId,
|
|
226
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
},
|
|
230
|
+
{
|
|
231
|
+
$set: {
|
|
232
|
+
state: storage.SyncRuleState.ERRORED,
|
|
233
|
+
// There must be exactly one config with an ERRORED state: This will be used to serve
|
|
234
|
+
// clients until there is a new ACTIVE one.
|
|
235
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.ERRORED,
|
|
236
|
+
// PROCESSING configs must stop. Historical STOP configs remain unchanged.
|
|
237
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.STOP
|
|
238
|
+
}
|
|
239
|
+
},
|
|
240
|
+
{
|
|
241
|
+
session,
|
|
242
|
+
arrayFilters: [
|
|
243
|
+
{
|
|
244
|
+
'activeConfig._id': activeConfigObjectId,
|
|
245
|
+
'activeConfig.state': { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
246
|
+
},
|
|
247
|
+
{
|
|
248
|
+
'processingConfig._id': { $ne: activeConfigObjectId },
|
|
249
|
+
'processingConfig.state': storage.SyncRuleState.PROCESSING
|
|
250
|
+
}
|
|
251
|
+
]
|
|
252
|
+
}
|
|
253
|
+
);
|
|
254
|
+
if (result.matchedCount != 1) {
|
|
255
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
256
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
257
|
+
throw new ReplicationAssertionError(
|
|
258
|
+
`Active replication stream ${active.content.replicationStreamId} changed during restart`
|
|
149
259
|
);
|
|
150
|
-
await this.db.notifyCheckpoint();
|
|
151
260
|
}
|
|
152
261
|
}
|
|
153
262
|
|
|
154
|
-
private async
|
|
263
|
+
private async updateSyncRulesInTransaction(options: storage.UpdateSyncRulesOptions, session: mongo.ClientSession) {
|
|
264
|
+
const storageVersion =
|
|
265
|
+
options.storageVersion ??
|
|
266
|
+
options.config.parsed.config.storageVersion ??
|
|
267
|
+
this.options.defaultStorageVersion ??
|
|
268
|
+
storage.CURRENT_STORAGE_VERSION;
|
|
269
|
+
const storageConfig = getMongoStorageConfig(storageVersion);
|
|
270
|
+
if (storageConfig.incrementalReprocessing) {
|
|
271
|
+
return this.updateSyncConfigV3InTransaction(options, storageVersion, storageConfig, session);
|
|
272
|
+
}
|
|
273
|
+
return this.updateSyncConfigV1InTransaction(options, storageVersion, session);
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
/**
|
|
277
|
+
* Update to a V3 sync config.
|
|
278
|
+
*
|
|
279
|
+
* Does support cases where existing replication streams are on V1.
|
|
280
|
+
*/
|
|
281
|
+
private async updateSyncConfigV3InTransaction(
|
|
155
282
|
options: storage.UpdateSyncRulesOptions,
|
|
156
283
|
storageVersion: number,
|
|
157
|
-
storageConfig: StorageConfig
|
|
158
|
-
|
|
159
|
-
|
|
284
|
+
storageConfig: StorageConfig,
|
|
285
|
+
session: mongo.ClientSession
|
|
286
|
+
): Promise<MongoPersistedReplicationStream> {
|
|
160
287
|
const versioned = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
|
|
161
288
|
|
|
162
|
-
const
|
|
289
|
+
const active = await this.db.sync_rules.findOne<ReplicationStreamDocumentV3>(
|
|
290
|
+
{
|
|
291
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
292
|
+
storage_version: storageVersion
|
|
293
|
+
},
|
|
294
|
+
{ session, sort: { _id: -1 }, limit: 1 }
|
|
295
|
+
);
|
|
296
|
+
if (active != null) {
|
|
297
|
+
const existingConfigDocs = await this.loadSyncConfigDefinitions(versioned, active, session);
|
|
298
|
+
|
|
299
|
+
if (
|
|
300
|
+
!options.forceNewReplicationStream &&
|
|
301
|
+
this.options.supportsMultipleSyncConfigs &&
|
|
302
|
+
isCompatible(
|
|
303
|
+
existingConfigDocs.map((d) => d.serialized_plan ?? null),
|
|
304
|
+
options.config,
|
|
305
|
+
logger
|
|
306
|
+
)
|
|
307
|
+
) {
|
|
308
|
+
logger.info(`Using incremental reprocessing`);
|
|
309
|
+
await this.stopExistingProcessingWork(session);
|
|
310
|
+
return await this.appendSyncConfigToStream({
|
|
311
|
+
versioned,
|
|
312
|
+
existing: active,
|
|
313
|
+
existingConfigDocs,
|
|
314
|
+
options,
|
|
315
|
+
storageVersion,
|
|
316
|
+
session
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
await this.stopExistingProcessingWork(session);
|
|
322
|
+
|
|
323
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
|
|
324
|
+
{
|
|
325
|
+
_id: 'sync_rules'
|
|
326
|
+
},
|
|
327
|
+
{
|
|
328
|
+
$inc: {
|
|
329
|
+
op_id: 1n
|
|
330
|
+
}
|
|
331
|
+
},
|
|
332
|
+
{
|
|
333
|
+
upsert: true,
|
|
334
|
+
returnDocument: 'after',
|
|
335
|
+
session
|
|
336
|
+
}
|
|
337
|
+
);
|
|
338
|
+
|
|
339
|
+
const id = Number(id_doc!.op_id);
|
|
340
|
+
const replicationStreamName = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
341
|
+
|
|
342
|
+
const mapping =
|
|
343
|
+
options.config.plan == null
|
|
344
|
+
? // For legacy sync rules and streams, use the parsed config directly to create a mapping
|
|
345
|
+
SingleSyncConfigBucketDefinitionMapping.fromParsedSyncConfig(options.config.parsed)
|
|
346
|
+
: // For new sync streams, always use the serialized version
|
|
347
|
+
SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingFromSerializedPlans(
|
|
348
|
+
[],
|
|
349
|
+
options.config.plan.plan,
|
|
350
|
+
[]
|
|
351
|
+
);
|
|
352
|
+
|
|
353
|
+
const syncConfigDoc: SyncConfigDefinition = {
|
|
354
|
+
_id: new ObjectId(),
|
|
355
|
+
replication_stream_id: id,
|
|
356
|
+
created_at: new Date(),
|
|
357
|
+
storage_version: storageVersion,
|
|
358
|
+
content: options.config.yaml,
|
|
359
|
+
version_label: options.version_label,
|
|
360
|
+
serialized_plan: options.config.plan,
|
|
361
|
+
rule_mapping: mapping.serialize()
|
|
362
|
+
};
|
|
363
|
+
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
364
|
+
|
|
365
|
+
const doc: ReplicationStreamDocumentV3 = {
|
|
366
|
+
_id: id,
|
|
367
|
+
storage_version: storageVersion,
|
|
368
|
+
sync_configs: [
|
|
369
|
+
{
|
|
370
|
+
_id: syncConfigDoc._id,
|
|
371
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
372
|
+
last_checkpoint: null,
|
|
373
|
+
last_checkpoint_lsn: null,
|
|
374
|
+
no_checkpoint_before: null,
|
|
375
|
+
snapshot_done: false
|
|
376
|
+
}
|
|
377
|
+
],
|
|
378
|
+
snapshot_lsn: undefined,
|
|
379
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
380
|
+
slot_name: replicationStreamName,
|
|
381
|
+
last_checkpoint_ts: null,
|
|
382
|
+
last_fatal_error: null,
|
|
383
|
+
last_fatal_error_ts: null,
|
|
384
|
+
last_keepalive_ts: null
|
|
385
|
+
};
|
|
386
|
+
|
|
387
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
388
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc, [syncConfigDoc]);
|
|
389
|
+
if (options.lock) {
|
|
390
|
+
// We only lock when creating a new stream - otherwise we'll likely get lock contention
|
|
391
|
+
// from an existing job on the stream, which would fail the entire transaction.
|
|
392
|
+
// The lock is persisted on rules.current_lock
|
|
393
|
+
await rules.lock(session);
|
|
394
|
+
}
|
|
395
|
+
return rules;
|
|
396
|
+
}
|
|
397
|
+
|
|
398
|
+
private async loadSyncConfigDefinitions(
|
|
399
|
+
versioned: VersionedPowerSyncMongoV3,
|
|
400
|
+
existing: ReplicationStreamDocumentV3,
|
|
401
|
+
session: mongo.ClientSession
|
|
402
|
+
) {
|
|
403
|
+
const activeConfigIds = existing.sync_configs
|
|
404
|
+
.filter((config) => config.state == storage.SyncRuleState.ACTIVE)
|
|
405
|
+
.map((config) => config._id);
|
|
163
406
|
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
await this.db.sync_rules.updateMany(
|
|
407
|
+
return versioned.syncConfigDefinitions
|
|
408
|
+
.find(
|
|
167
409
|
{
|
|
168
|
-
|
|
410
|
+
_id: { $in: activeConfigIds }
|
|
169
411
|
},
|
|
170
|
-
syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
|
|
171
412
|
{ session }
|
|
172
|
-
)
|
|
413
|
+
)
|
|
414
|
+
.toArray();
|
|
415
|
+
}
|
|
173
416
|
|
|
174
|
-
|
|
417
|
+
/**
|
|
418
|
+
* Load _all_ definition mappings for a replication stream - used as a base to generate new ids.
|
|
419
|
+
*/
|
|
420
|
+
private async loadHistoricalSyncConfigRuleMappings(
|
|
421
|
+
versioned: VersionedPowerSyncMongoV3,
|
|
422
|
+
replicationStreamId: number,
|
|
423
|
+
session: mongo.ClientSession
|
|
424
|
+
) {
|
|
425
|
+
return versioned.syncConfigDefinitions
|
|
426
|
+
.find(
|
|
175
427
|
{
|
|
176
|
-
|
|
428
|
+
replication_stream_id: replicationStreamId
|
|
177
429
|
},
|
|
178
430
|
{
|
|
179
|
-
|
|
180
|
-
|
|
431
|
+
session,
|
|
432
|
+
projection: {
|
|
433
|
+
rule_mapping: 1
|
|
181
434
|
}
|
|
435
|
+
}
|
|
436
|
+
)
|
|
437
|
+
.toArray();
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
private logIncrementalDefinitionChanges(changes: ReturnType<typeof describeIncrementalSyncConfigUpdate>) {
|
|
441
|
+
logger.info(`Incremental reprocessing sync config update:\n${formatIncrementalSyncConfigUpdateLog(changes)}`);
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
private async appendSyncConfigToStream(options: {
|
|
445
|
+
versioned: VersionedPowerSyncMongoV3;
|
|
446
|
+
existing: ReplicationStreamDocumentV3;
|
|
447
|
+
existingConfigDocs: SyncConfigDefinition[];
|
|
448
|
+
options: storage.UpdateSyncRulesOptions;
|
|
449
|
+
storageVersion: number;
|
|
450
|
+
session: mongo.ClientSession;
|
|
451
|
+
}): Promise<MongoPersistedReplicationStream> {
|
|
452
|
+
const { versioned, existing, existingConfigDocs, options: updateOptions, storageVersion, session } = options;
|
|
453
|
+
const compatibleConfigs = existingConfigDocs.map((doc) => ({
|
|
454
|
+
plan: doc.serialized_plan!.plan,
|
|
455
|
+
mapping: SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping)
|
|
456
|
+
}));
|
|
457
|
+
const historicalRuleMappings = await this.loadHistoricalSyncConfigRuleMappings(versioned, existing._id, session);
|
|
458
|
+
const reservedMappings = historicalRuleMappings.map((doc) =>
|
|
459
|
+
SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping)
|
|
460
|
+
);
|
|
461
|
+
const mappingResult = SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingWithChanges(
|
|
462
|
+
compatibleConfigs,
|
|
463
|
+
updateOptions.config.plan!.plan,
|
|
464
|
+
reservedMappings
|
|
465
|
+
);
|
|
466
|
+
const mapping = mappingResult.mapping;
|
|
467
|
+
this.logIncrementalDefinitionChanges(
|
|
468
|
+
describeIncrementalSyncConfigUpdate({
|
|
469
|
+
activeMappings: existingConfigDocs.map((doc) =>
|
|
470
|
+
SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(doc.rule_mapping)
|
|
471
|
+
),
|
|
472
|
+
newMapping: mapping,
|
|
473
|
+
newSyncConfig: updateOptions.config.parsed,
|
|
474
|
+
mappingChanges: mappingResult.changes
|
|
475
|
+
})
|
|
476
|
+
);
|
|
477
|
+
|
|
478
|
+
const syncConfigDoc: SyncConfigDefinition = {
|
|
479
|
+
_id: new ObjectId(),
|
|
480
|
+
replication_stream_id: existing._id,
|
|
481
|
+
created_at: new Date(),
|
|
482
|
+
storage_version: storageVersion,
|
|
483
|
+
content: updateOptions.config.yaml,
|
|
484
|
+
version_label: updateOptions.version_label,
|
|
485
|
+
serialized_plan: updateOptions.config.plan,
|
|
486
|
+
rule_mapping: mapping.serialize()
|
|
487
|
+
};
|
|
488
|
+
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
489
|
+
const syncConfigState: SyncRuleConfigStateV3 = {
|
|
490
|
+
_id: syncConfigDoc._id,
|
|
491
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
492
|
+
last_checkpoint: null,
|
|
493
|
+
last_checkpoint_lsn: null,
|
|
494
|
+
no_checkpoint_before: null,
|
|
495
|
+
snapshot_done: false
|
|
496
|
+
};
|
|
497
|
+
|
|
498
|
+
await this.db.sync_rules.updateOne(
|
|
499
|
+
{ _id: existing._id },
|
|
500
|
+
{
|
|
501
|
+
$push: {
|
|
502
|
+
sync_configs: syncConfigState
|
|
182
503
|
},
|
|
183
|
-
{
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
session
|
|
504
|
+
$set: {
|
|
505
|
+
last_fatal_error: null,
|
|
506
|
+
last_fatal_error_ts: null
|
|
187
507
|
}
|
|
188
|
-
|
|
508
|
+
},
|
|
509
|
+
{ session }
|
|
510
|
+
);
|
|
511
|
+
const syncConfigStates = [
|
|
512
|
+
...existing.sync_configs.filter((config) => config.state == storage.SyncRuleState.ACTIVE),
|
|
513
|
+
syncConfigState
|
|
514
|
+
];
|
|
515
|
+
const stream = new MongoPersistedReplicationStream(
|
|
516
|
+
this.db,
|
|
517
|
+
{
|
|
518
|
+
...existing,
|
|
519
|
+
sync_configs: syncConfigStates
|
|
520
|
+
},
|
|
521
|
+
[...existingConfigDocs, syncConfigDoc]
|
|
522
|
+
);
|
|
523
|
+
// The stream already exists, so an active replication job may already hold the stream lock.
|
|
524
|
+
// Deployment only persists the appended sync config; replication job locking is handled by
|
|
525
|
+
// the replicator.
|
|
526
|
+
return stream;
|
|
527
|
+
}
|
|
189
528
|
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
const syncConfigDoc: SyncConfigDefinition = {
|
|
196
|
-
_id: new ObjectId(),
|
|
197
|
-
replication_stream_id: id,
|
|
198
|
-
created_at: new Date(),
|
|
199
|
-
storage_version: storageVersion,
|
|
200
|
-
content: options.config.yaml,
|
|
201
|
-
serialized_plan: options.config.plan,
|
|
202
|
-
rule_mapping: mapping.serialize()
|
|
203
|
-
};
|
|
204
|
-
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
205
|
-
|
|
206
|
-
const doc: ReplicationStreamDocumentV3 = {
|
|
207
|
-
_id: id,
|
|
208
|
-
storage_version: storageVersion,
|
|
209
|
-
sync_configs: [
|
|
210
|
-
{
|
|
211
|
-
_id: syncConfigDoc._id,
|
|
212
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
213
|
-
keepalive_op: null,
|
|
214
|
-
last_checkpoint: null,
|
|
215
|
-
last_checkpoint_lsn: null,
|
|
216
|
-
no_checkpoint_before: null,
|
|
217
|
-
snapshot_done: false
|
|
218
|
-
}
|
|
219
|
-
],
|
|
220
|
-
snapshot_lsn: undefined,
|
|
221
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
222
|
-
slot_name: slot_name,
|
|
223
|
-
last_checkpoint_ts: null,
|
|
224
|
-
last_fatal_error: null,
|
|
225
|
-
last_fatal_error_ts: null,
|
|
226
|
-
last_keepalive_ts: null
|
|
227
|
-
};
|
|
228
|
-
|
|
229
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
230
|
-
await this.db.notifyCheckpoint();
|
|
231
|
-
rules = new MongoPersistedSyncRulesContentV3(this.db, doc, syncConfigDoc);
|
|
232
|
-
if (options.lock) {
|
|
233
|
-
// The lock is persisted on rules.current_lock
|
|
234
|
-
await rules.lock(session);
|
|
235
|
-
}
|
|
529
|
+
async updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<MongoPersistedReplicationStream> {
|
|
530
|
+
let rules: MongoPersistedReplicationStream | undefined;
|
|
531
|
+
await this.withTransaction(async (session) => {
|
|
532
|
+
rules = await this.updateSyncRulesInTransaction(options, session);
|
|
236
533
|
});
|
|
237
|
-
|
|
534
|
+
await this.db.notifyCheckpoint();
|
|
238
535
|
return rules!;
|
|
239
536
|
}
|
|
240
537
|
|
|
241
|
-
async
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
const storageVersion =
|
|
245
|
-
options.storageVersion ?? options.config.parsed.config.storageVersion ?? storage.CURRENT_STORAGE_VERSION;
|
|
538
|
+
private async withTransaction<T>(callback: (session: mongo.ClientSession) => Promise<T>): Promise<T> {
|
|
539
|
+
return this.client.withSession((session) => session.withTransaction(callback));
|
|
540
|
+
}
|
|
246
541
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
542
|
+
/**
|
|
543
|
+
* Update to a V1 sync config.
|
|
544
|
+
*
|
|
545
|
+
* Does support cases where existing replication streams are on V3.
|
|
546
|
+
*/
|
|
547
|
+
private async updateSyncConfigV1InTransaction(
|
|
548
|
+
options: storage.UpdateSyncRulesOptions,
|
|
549
|
+
storageVersion: number,
|
|
550
|
+
session: mongo.ClientSession
|
|
551
|
+
): Promise<MongoPersistedReplicationStream> {
|
|
552
|
+
await this.stopExistingProcessingWork(session);
|
|
251
553
|
|
|
252
|
-
|
|
554
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
|
|
555
|
+
{
|
|
556
|
+
_id: 'sync_rules'
|
|
557
|
+
},
|
|
558
|
+
{
|
|
559
|
+
$inc: {
|
|
560
|
+
op_id: 1n
|
|
561
|
+
}
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
upsert: true,
|
|
565
|
+
returnDocument: 'after',
|
|
566
|
+
session
|
|
567
|
+
}
|
|
568
|
+
);
|
|
253
569
|
|
|
254
|
-
const
|
|
570
|
+
const id = Number(id_doc!.op_id);
|
|
571
|
+
const slot_name = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
572
|
+
|
|
573
|
+
// All V1 replication streams share both the `main` op id sequence and the `bucket_parameters`
|
|
574
|
+
// collection, so every parameter entry this stream writes gets an op id above the current
|
|
575
|
+
// head. Seeding the parameter compaction cursor with that head keeps the stream's first
|
|
576
|
+
// compaction from scanning other streams' history, which would otherwise be repeated for
|
|
577
|
+
// every new deployment. A concurrent replication flush can only advance the head after this
|
|
578
|
+
// read, which makes the seed conservative, never too high.
|
|
579
|
+
const opSequence = await this.db.op_id_sequence.findOne({ _id: 'main' }, { session });
|
|
580
|
+
|
|
581
|
+
const doc: SyncRuleDocumentV1 = {
|
|
582
|
+
_id: id,
|
|
583
|
+
storage_version: storageVersion,
|
|
584
|
+
content: options.config.yaml,
|
|
585
|
+
version_label: options.version_label,
|
|
586
|
+
serialized_plan: options.config.plan,
|
|
587
|
+
last_checkpoint: null,
|
|
588
|
+
last_checkpoint_lsn: null,
|
|
589
|
+
no_checkpoint_before: null,
|
|
590
|
+
keepalive_op: null,
|
|
591
|
+
snapshot_done: false,
|
|
592
|
+
snapshot_lsn: undefined,
|
|
593
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
594
|
+
slot_name: slot_name,
|
|
595
|
+
last_checkpoint_ts: null,
|
|
596
|
+
last_fatal_error: null,
|
|
597
|
+
last_fatal_error_ts: null,
|
|
598
|
+
last_keepalive_ts: null,
|
|
599
|
+
parameter_compaction: { compacted_before: opSequence?.op_id ?? 0n }
|
|
600
|
+
};
|
|
255
601
|
|
|
256
|
-
await
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
);
|
|
602
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
603
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc);
|
|
604
|
+
if (options.lock) {
|
|
605
|
+
// The lock is persisted on rules.current_lock
|
|
606
|
+
await rules.lock(session);
|
|
607
|
+
}
|
|
608
|
+
return rules;
|
|
609
|
+
}
|
|
265
610
|
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
},
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
611
|
+
/**
|
|
612
|
+
* Stop top-level and embedded processing work before creating a replacement stream.
|
|
613
|
+
*
|
|
614
|
+
* Use in the same transaction that the replacement stream is created.
|
|
615
|
+
*/
|
|
616
|
+
private async stopExistingProcessingWork(session: mongo.ClientSession) {
|
|
617
|
+
await this.db.sync_rules.updateMany(
|
|
618
|
+
{
|
|
619
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
620
|
+
'sync_configs.state': storage.SyncRuleState.PROCESSING
|
|
621
|
+
},
|
|
622
|
+
{
|
|
623
|
+
$set: {
|
|
624
|
+
'sync_configs.$[config].state': storage.SyncRuleState.STOP
|
|
279
625
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
const doc: SyncRuleDocumentV1 = {
|
|
286
|
-
_id: id,
|
|
287
|
-
storage_version: storageVersion,
|
|
288
|
-
content: options.config.yaml,
|
|
289
|
-
serialized_plan: options.config.plan,
|
|
290
|
-
last_checkpoint: null,
|
|
291
|
-
last_checkpoint_lsn: null,
|
|
292
|
-
no_checkpoint_before: null,
|
|
293
|
-
keepalive_op: null,
|
|
294
|
-
snapshot_done: false,
|
|
295
|
-
snapshot_lsn: undefined,
|
|
296
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
297
|
-
slot_name: slot_name,
|
|
298
|
-
last_checkpoint_ts: null,
|
|
299
|
-
last_fatal_error: null,
|
|
300
|
-
last_fatal_error_ts: null,
|
|
301
|
-
last_keepalive_ts: null
|
|
302
|
-
};
|
|
303
|
-
|
|
304
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
305
|
-
await this.db.notifyCheckpoint();
|
|
306
|
-
rules = new MongoPersistedSyncRulesContentV1(this.db, doc);
|
|
307
|
-
if (options.lock) {
|
|
308
|
-
// The lock is persisted on rules.current_lock
|
|
309
|
-
await rules.lock(session);
|
|
626
|
+
},
|
|
627
|
+
{
|
|
628
|
+
session,
|
|
629
|
+
arrayFilters: [{ 'config.state': storage.SyncRuleState.PROCESSING }]
|
|
310
630
|
}
|
|
311
|
-
|
|
631
|
+
);
|
|
312
632
|
|
|
313
|
-
|
|
633
|
+
await this.db.sync_rules.updateMany(
|
|
634
|
+
{
|
|
635
|
+
state: storage.SyncRuleState.PROCESSING
|
|
636
|
+
},
|
|
637
|
+
stopReplicationStreamPipeline(),
|
|
638
|
+
{ session }
|
|
639
|
+
);
|
|
314
640
|
}
|
|
315
641
|
|
|
316
|
-
async
|
|
317
|
-
|
|
318
|
-
|
|
642
|
+
async getActiveSyncConfig(): Promise<storage.ResolvedSyncConfig | null> {
|
|
643
|
+
return this.getActiveSyncConfigInternal();
|
|
644
|
+
}
|
|
645
|
+
|
|
646
|
+
private async getActiveSyncConfigInternal(session?: mongo.ClientSession): Promise<storage.ResolvedSyncConfig | null> {
|
|
319
647
|
const doc = await this.db.sync_rules.findOne(
|
|
320
648
|
{
|
|
321
649
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
322
650
|
},
|
|
323
|
-
{ sort: { _id: -1 }, limit: 1 }
|
|
651
|
+
{ session, sort: { _id: -1 }, limit: 1 }
|
|
324
652
|
);
|
|
325
653
|
|
|
326
|
-
return this.
|
|
654
|
+
return this.resolvedSyncConfigFromDoc(
|
|
655
|
+
doc,
|
|
656
|
+
[storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED],
|
|
657
|
+
{ cacheActiveStorage: session == null },
|
|
658
|
+
session
|
|
659
|
+
);
|
|
327
660
|
}
|
|
328
661
|
|
|
329
|
-
private async
|
|
662
|
+
private async replicationStreamFromDoc(
|
|
663
|
+
doc: SyncRuleDocumentBase | null,
|
|
664
|
+
stateFilter: storage.SyncRuleState[],
|
|
665
|
+
session?: mongo.ClientSession
|
|
666
|
+
) {
|
|
330
667
|
if (doc == null) {
|
|
331
668
|
return null;
|
|
332
669
|
}
|
|
@@ -334,8 +671,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
334
671
|
|
|
335
672
|
if (storageConfig.incrementalReprocessing) {
|
|
336
673
|
const v3 = doc as ReplicationStreamDocumentV3;
|
|
337
|
-
const
|
|
338
|
-
if (
|
|
674
|
+
const matching = v3.sync_configs.filter((c) => stateFilter.includes(c.state));
|
|
675
|
+
if (matching.length == 0) {
|
|
339
676
|
return null;
|
|
340
677
|
}
|
|
341
678
|
|
|
@@ -343,28 +680,42 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
343
680
|
// that checks for active replication streams.
|
|
344
681
|
// It is not a major bottleneck though, since it only runs once every couple of seconds at most.
|
|
345
682
|
const db = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
|
|
346
|
-
const
|
|
347
|
-
|
|
683
|
+
const syncConfigDocs = await db.syncConfigDefinitions
|
|
684
|
+
.find(
|
|
685
|
+
{
|
|
686
|
+
_id: { $in: matching.map((config) => config._id) }
|
|
687
|
+
},
|
|
688
|
+
{ session }
|
|
689
|
+
)
|
|
690
|
+
.toArray();
|
|
691
|
+
|
|
692
|
+
if (syncConfigDocs.length == 0) {
|
|
348
693
|
return null;
|
|
349
694
|
}
|
|
350
|
-
return new
|
|
695
|
+
return new MongoPersistedReplicationStream(this.db, v3, syncConfigDocs);
|
|
351
696
|
}
|
|
352
697
|
|
|
353
|
-
return new
|
|
698
|
+
return new MongoPersistedReplicationStream(this.db, doc as SyncRuleDocumentV1);
|
|
354
699
|
}
|
|
355
700
|
|
|
356
|
-
async
|
|
701
|
+
async getDeployingSyncConfig(): Promise<storage.ResolvedSyncConfig | null> {
|
|
702
|
+
return this.getDeployingSyncConfigInternal();
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
private async getDeployingSyncConfigInternal(
|
|
706
|
+
session?: mongo.ClientSession
|
|
707
|
+
): Promise<storage.ResolvedSyncConfig | null> {
|
|
357
708
|
const doc = await this.db.sync_rules.findOne(
|
|
358
709
|
{
|
|
359
|
-
state: storage.SyncRuleState.PROCESSING
|
|
710
|
+
$or: [{ state: storage.SyncRuleState.PROCESSING }, { 'sync_configs.state': storage.SyncRuleState.PROCESSING }]
|
|
360
711
|
},
|
|
361
|
-
{ sort: { _id: -1 }, limit: 1 }
|
|
712
|
+
{ session, sort: { _id: -1 }, limit: 1 }
|
|
362
713
|
);
|
|
363
714
|
|
|
364
|
-
return this.
|
|
715
|
+
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.PROCESSING], {}, session);
|
|
365
716
|
}
|
|
366
717
|
|
|
367
|
-
async
|
|
718
|
+
async getReplicatingReplicationStreams(): Promise<storage.PersistedReplicationStream[]> {
|
|
368
719
|
const docs = await this.db.sync_rules
|
|
369
720
|
.find({
|
|
370
721
|
state: { $in: [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE] }
|
|
@@ -374,13 +725,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
374
725
|
return (
|
|
375
726
|
await Promise.all(
|
|
376
727
|
docs.map((doc) => {
|
|
377
|
-
return this.
|
|
728
|
+
return this.replicationStreamFromDoc(doc, [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE]);
|
|
378
729
|
})
|
|
379
730
|
)
|
|
380
731
|
).filter((r) => r != null);
|
|
381
732
|
}
|
|
382
733
|
|
|
383
|
-
async
|
|
734
|
+
async getStoppedReplicationStreams(): Promise<storage.PersistedReplicationStream[]> {
|
|
384
735
|
const docs = await this.db.sync_rules
|
|
385
736
|
.find({
|
|
386
737
|
state: storage.SyncRuleState.STOP
|
|
@@ -390,33 +741,52 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
390
741
|
return (
|
|
391
742
|
await Promise.all(
|
|
392
743
|
docs.map((doc) => {
|
|
393
|
-
return this.
|
|
744
|
+
return this.replicationStreamFromDoc(doc, [storage.SyncRuleState.STOP]);
|
|
394
745
|
})
|
|
395
746
|
)
|
|
396
747
|
).filter((d) => d != null);
|
|
397
748
|
}
|
|
398
749
|
|
|
399
|
-
async
|
|
400
|
-
|
|
401
|
-
|
|
750
|
+
private async resolvedSyncConfigFromDoc(
|
|
751
|
+
doc: SyncRuleDocumentBase | null,
|
|
752
|
+
stateFilter: storage.SyncRuleState[],
|
|
753
|
+
options: { cacheActiveStorage?: boolean } = {},
|
|
754
|
+
session?: mongo.ClientSession
|
|
755
|
+
): Promise<storage.ResolvedSyncConfig | null> {
|
|
756
|
+
const stream = await this.replicationStreamFromDoc(doc, stateFilter, session);
|
|
757
|
+
if (stream == null) {
|
|
402
758
|
return null;
|
|
403
759
|
}
|
|
404
760
|
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
761
|
+
const content = stream.syncConfigContent[0];
|
|
762
|
+
const thisFactory = this;
|
|
763
|
+
|
|
764
|
+
return {
|
|
765
|
+
content,
|
|
766
|
+
replicationStream: stream,
|
|
767
|
+
get storage() {
|
|
768
|
+
// It is important that this instance is cached.
|
|
769
|
+
// Not for the instance construction itself, but to ensure that internal caches on the instance
|
|
770
|
+
// are re-used properly.
|
|
771
|
+
if (
|
|
772
|
+
options.cacheActiveStorage &&
|
|
773
|
+
thisFactory.activeStorageCache?.replicationStream.replicationJobId == stream.replicationJobId
|
|
774
|
+
) {
|
|
775
|
+
return thisFactory.activeStorageCache;
|
|
776
|
+
}
|
|
777
|
+
|
|
778
|
+
const instance = thisFactory.getInstance(stream);
|
|
779
|
+
if (options.cacheActiveStorage) {
|
|
780
|
+
thisFactory.activeStorageCache = instance;
|
|
781
|
+
}
|
|
782
|
+
return instance;
|
|
783
|
+
}
|
|
784
|
+
};
|
|
415
785
|
}
|
|
416
786
|
|
|
417
787
|
async getStorageMetrics(): Promise<storage.StorageMetrics> {
|
|
418
788
|
const ignoreNotExisting = (e: unknown) => {
|
|
419
|
-
if (lib_mongo.
|
|
789
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(e)) {
|
|
420
790
|
// Collection doesn't exist - return 0
|
|
421
791
|
return [{ storageStats: { size: 0 } }];
|
|
422
792
|
} else {
|
|
@@ -424,9 +794,6 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
424
794
|
}
|
|
425
795
|
};
|
|
426
796
|
|
|
427
|
-
// For now, we get storage metrics over all v1 and v3 collections.
|
|
428
|
-
// In the future, we may split these metrics to report separately for active replication streams versus processing streams.
|
|
429
|
-
|
|
430
797
|
const aggregateStaticCollection = async <T extends mongo.Document>(collection: mongo.Collection<T>) => {
|
|
431
798
|
// We check whether the collection exists before getting the statistics. This avoids repeated
|
|
432
799
|
// errors in the MongoDB logs if the collection hasn't been created yet.
|
|
@@ -450,8 +817,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
450
817
|
};
|
|
451
818
|
|
|
452
819
|
const operations_aggregate = await aggregateStaticCollection(this.db.bucket_data);
|
|
820
|
+
const v3OperationCollections = await this.db.listBucketDataCollectionsV3();
|
|
453
821
|
const v3_operation_aggregates = await Promise.all(
|
|
454
|
-
|
|
822
|
+
v3OperationCollections.map((collection) =>
|
|
455
823
|
collection
|
|
456
824
|
.aggregate([
|
|
457
825
|
{
|
|
@@ -467,8 +835,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
467
835
|
|
|
468
836
|
const parameters_aggregate = await aggregateStaticCollection(this.db.bucket_parameters);
|
|
469
837
|
|
|
838
|
+
const v3ParameterCollections = await this.db.listAllParameterIndexCollectionsV3();
|
|
470
839
|
const v3_parameter_aggregates = await Promise.all(
|
|
471
|
-
|
|
840
|
+
v3ParameterCollections.map((collection) =>
|
|
472
841
|
collection
|
|
473
842
|
.aggregate([
|
|
474
843
|
{
|
|
@@ -484,8 +853,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
484
853
|
|
|
485
854
|
const v1_source_record_aggregate = await aggregateStaticCollection(this.db.current_data);
|
|
486
855
|
|
|
856
|
+
const v3SourceRecordCollections = await this.db.listAllSourceRecordCollectionsV3();
|
|
487
857
|
const source_record_aggregates = await Promise.all(
|
|
488
|
-
|
|
858
|
+
v3SourceRecordCollections.map((collection) =>
|
|
489
859
|
collection
|
|
490
860
|
.aggregate([
|
|
491
861
|
{
|
|
@@ -498,6 +868,120 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
498
868
|
.catch(ignoreNotExisting)
|
|
499
869
|
)
|
|
500
870
|
);
|
|
871
|
+
|
|
872
|
+
const v3StorageConfig = getMongoStorageConfig(storage.STORAGE_VERSION_3) as StorageConfig & {
|
|
873
|
+
incrementalReprocessing: true;
|
|
874
|
+
};
|
|
875
|
+
const v3Db = this.db.versioned(v3StorageConfig) as VersionedPowerSyncMongoV3;
|
|
876
|
+
const objectStorageDefinitionUsage = await ObjectStorageUsage.readAllDefinitionUsage(v3Db);
|
|
877
|
+
|
|
878
|
+
const v3StreamDocs = (await this.db.sync_rules
|
|
879
|
+
.find({ storage_version: { $gte: storage.STORAGE_VERSION_3 } }, { projection: { _id: 1, sync_configs: 1 } })
|
|
880
|
+
.toArray()) as unknown as Pick<ReplicationStreamDocumentV3, '_id' | 'sync_configs'>[];
|
|
881
|
+
|
|
882
|
+
const collectionSizes = new Map<string, number>();
|
|
883
|
+
const addCollectionSize = (
|
|
884
|
+
collection: { collectionName: string },
|
|
885
|
+
aggregate: { storageStats?: { size?: number | bigint } }[],
|
|
886
|
+
prefix: string,
|
|
887
|
+
sizes: Map<string, number>
|
|
888
|
+
) => {
|
|
889
|
+
const match = collection.collectionName.match(new RegExp(`^${prefix}(\\d+)_(.+)$`));
|
|
890
|
+
if (match == null) {
|
|
891
|
+
return;
|
|
892
|
+
}
|
|
893
|
+
sizes.set(`${prefix}${match[1]}:${match[2]}`, Number(aggregate[0]?.storageStats?.size ?? 0));
|
|
894
|
+
};
|
|
895
|
+
|
|
896
|
+
v3OperationCollections.forEach((collection, index) =>
|
|
897
|
+
addCollectionSize(collection, v3_operation_aggregates[index], 'bucket_data_', collectionSizes)
|
|
898
|
+
);
|
|
899
|
+
v3ParameterCollections.forEach((collection, index) =>
|
|
900
|
+
addCollectionSize(collection, v3_parameter_aggregates[index], 'parameter_index_', collectionSizes)
|
|
901
|
+
);
|
|
902
|
+
v3SourceRecordCollections.forEach((collection, index) =>
|
|
903
|
+
addCollectionSize(collection, source_record_aggregates[index], 'source_records_', collectionSizes)
|
|
904
|
+
);
|
|
905
|
+
|
|
906
|
+
const syncConfigIds = v3StreamDocs.flatMap((stream) => (stream.sync_configs ?? []).map((config) => config._id));
|
|
907
|
+
const syncConfigDefinitions =
|
|
908
|
+
syncConfigIds.length == 0
|
|
909
|
+
? []
|
|
910
|
+
: await v3Db.syncConfigDefinitions
|
|
911
|
+
.find({ _id: { $in: syncConfigIds } }, { projection: { _id: 1, rule_mapping: 1, version_label: 1 } })
|
|
912
|
+
.toArray();
|
|
913
|
+
const syncConfigDefinitionsById = new Map(
|
|
914
|
+
syncConfigDefinitions.map((definition) => [definition._id.toHexString(), definition])
|
|
915
|
+
);
|
|
916
|
+
|
|
917
|
+
const sourceTablesByStream = new Map<
|
|
918
|
+
number,
|
|
919
|
+
{ _id: ObjectId; bucket_data_source_ids: string[]; parameter_lookup_source_ids: string[] }[]
|
|
920
|
+
>();
|
|
921
|
+
await Promise.all(
|
|
922
|
+
v3StreamDocs.map(async (stream) => {
|
|
923
|
+
const sourceTables = await v3Db
|
|
924
|
+
.sourceTables(stream._id)
|
|
925
|
+
.find({}, { projection: { _id: 1, bucket_data_source_ids: 1, parameter_lookup_source_ids: 1 } })
|
|
926
|
+
.toArray();
|
|
927
|
+
sourceTablesByStream.set(stream._id, sourceTables);
|
|
928
|
+
})
|
|
929
|
+
);
|
|
930
|
+
|
|
931
|
+
const objectStorageSizeByDefinition = new Map<string, number>();
|
|
932
|
+
for (const usage of objectStorageDefinitionUsage) {
|
|
933
|
+
objectStorageSizeByDefinition.set(
|
|
934
|
+
`${usage.replication_stream_id}:${usage.definition_id}`,
|
|
935
|
+
Number(usage.active_bytes)
|
|
936
|
+
);
|
|
937
|
+
}
|
|
938
|
+
|
|
939
|
+
const sumCollectionSizes = (prefix: string, streamId: number, ids: ReadonlySet<string>) =>
|
|
940
|
+
[...ids].reduce((total, id) => total + (collectionSizes.get(`${prefix}${streamId}:${id}`) ?? 0), 0);
|
|
941
|
+
|
|
942
|
+
const syncConfigMetrics: storage.StorageSyncConfigMetrics[] = [];
|
|
943
|
+
for (const stream of v3StreamDocs) {
|
|
944
|
+
const sourceTables = sourceTablesByStream.get(stream._id) ?? [];
|
|
945
|
+
for (const syncConfig of stream.sync_configs ?? []) {
|
|
946
|
+
const definition = syncConfigDefinitionsById.get(syncConfig._id.toHexString());
|
|
947
|
+
if (definition == null) {
|
|
948
|
+
continue;
|
|
949
|
+
}
|
|
950
|
+
|
|
951
|
+
const mapping = SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(definition.rule_mapping);
|
|
952
|
+
const bucketDefinitionIds = mapping.allBucketDefinitionIds();
|
|
953
|
+
const parameterIndexIds = mapping.allParameterIndexIds();
|
|
954
|
+
const bucketDefinitionIdSet = new Set(bucketDefinitionIds);
|
|
955
|
+
const parameterIndexIdSet = new Set(parameterIndexIds);
|
|
956
|
+
const replicationSize = sourceTables.reduce((total, sourceTable) => {
|
|
957
|
+
if (
|
|
958
|
+
!(sourceTable.bucket_data_source_ids ?? []).some((id) => bucketDefinitionIdSet.has(id)) &&
|
|
959
|
+
!(sourceTable.parameter_lookup_source_ids ?? []).some((id) => parameterIndexIdSet.has(id))
|
|
960
|
+
) {
|
|
961
|
+
return total;
|
|
962
|
+
}
|
|
963
|
+
return total + (collectionSizes.get(`source_records_${stream._id}:${sourceTable._id.toHexString()}`) ?? 0);
|
|
964
|
+
}, 0);
|
|
965
|
+
|
|
966
|
+
syncConfigMetrics.push({
|
|
967
|
+
sync_config_id: syncConfig._id.toHexString(),
|
|
968
|
+
sync_config_state: String(syncConfig.state),
|
|
969
|
+
version_label: definition.version_label,
|
|
970
|
+
attributed_bucket_data_bytes: sumCollectionSizes('bucket_data_', stream._id, bucketDefinitionIdSet),
|
|
971
|
+
attributed_parameter_indexes_bytes: sumCollectionSizes('parameter_index_', stream._id, parameterIndexIdSet),
|
|
972
|
+
attributed_source_records_bytes: replicationSize,
|
|
973
|
+
attributed_object_storage_bytes: [...bucketDefinitionIdSet].reduce(
|
|
974
|
+
(total, definitionId) => total + (objectStorageSizeByDefinition.get(`${stream._id}:${definitionId}`) ?? 0),
|
|
975
|
+
0
|
|
976
|
+
)
|
|
977
|
+
});
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
|
|
981
|
+
const totalObjectStorageSize = objectStorageDefinitionUsage.reduce(
|
|
982
|
+
(total, usage) => total + usage.active_bytes,
|
|
983
|
+
0n
|
|
984
|
+
);
|
|
501
985
|
return {
|
|
502
986
|
operations_size_bytes:
|
|
503
987
|
Number(operations_aggregate[0].storageStats.size) +
|
|
@@ -507,7 +991,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
507
991
|
v3_parameter_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
|
|
508
992
|
replication_size_bytes:
|
|
509
993
|
Number(v1_source_record_aggregate[0]?.storageStats?.size ?? 0) +
|
|
510
|
-
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0)
|
|
994
|
+
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0),
|
|
995
|
+
object_storage_size_bytes: Number(totalObjectStorageSize),
|
|
996
|
+
sync_config_metrics: syncConfigMetrics
|
|
511
997
|
};
|
|
512
998
|
}
|
|
513
999
|
|