@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,44 +1,100 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
+
import { ReplicationAssertionError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
3
|
import { storage } from '@powersync/service-core';
|
|
4
4
|
import * as bson from 'bson';
|
|
5
5
|
import { mongoTableId } from '../../../utils/util.js';
|
|
6
|
-
import {
|
|
6
|
+
import { canCheckpointState } from '../CheckpointState.js';
|
|
7
7
|
import { MongoBucketBatch } from '../MongoBucketBatch.js';
|
|
8
|
-
import {
|
|
8
|
+
import { stopReplicationStreamPipeline } from '../SyncRuleStateUpdate.js';
|
|
9
9
|
import { PersistedBatchV3 } from './PersistedBatchV3.js';
|
|
10
10
|
import { SourceRecordStoreV3 } from './SourceRecordStoreV3.js';
|
|
11
|
-
|
|
12
|
-
return left.length == right.length && left.every((value, index) => value == right[index]);
|
|
13
|
-
}
|
|
11
|
+
import { createNewSourceTable, overlappingSourceTableFilter, planSourceTableReconciliation, sourceTableDesiredResolution, sourceTableFromDocument } from './source-table-utils.js';
|
|
14
12
|
export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
15
13
|
store;
|
|
16
|
-
|
|
14
|
+
/** Avoid repeating idempotent createIndexes calls for every checkpoint flush. */
|
|
15
|
+
initializedCustomCheckpointEventIds = new Set();
|
|
16
|
+
// Used only to validate supplied checkpoint ids; it does not initialize collections.
|
|
17
|
+
knownEventIds;
|
|
18
|
+
syncConfigIds;
|
|
17
19
|
needsActivationV3 = true;
|
|
18
20
|
lastWaitingLogThrottledV3 = 0;
|
|
19
21
|
constructor(options) {
|
|
20
22
|
super(options);
|
|
21
|
-
|
|
23
|
+
const syncConfigIds = options.syncConfigIds ?? [];
|
|
24
|
+
if (syncConfigIds.length == 0) {
|
|
22
25
|
throw new ReplicationAssertionError('Missing sync config id for v3 batch');
|
|
23
26
|
}
|
|
24
|
-
this.
|
|
25
|
-
this.
|
|
27
|
+
this.syncConfigIds = syncConfigIds;
|
|
28
|
+
this.knownEventIds = new Set(this.mapping.allEventDefinitionIds());
|
|
29
|
+
this.store = new SourceRecordStoreV3(this.db, this.replicationStreamId, this.mapping);
|
|
26
30
|
}
|
|
27
31
|
createPersistedBatch(writtenSize) {
|
|
28
|
-
return new PersistedBatchV3(this.db, this.
|
|
29
|
-
logger: this.logger
|
|
32
|
+
return new PersistedBatchV3(this.db, this.replicationStreamId, this.mapping, writtenSize, {
|
|
33
|
+
logger: this.logger,
|
|
34
|
+
objectStorage: this.options.objectStorage,
|
|
35
|
+
inlineThresholdBytes: this.options.inlineThresholdBytes,
|
|
36
|
+
signal: this.options.signal,
|
|
37
|
+
objectStorageUsageWriterId: this.objectStorageUsageWriterId
|
|
30
38
|
});
|
|
31
39
|
}
|
|
32
40
|
get sourceRecordStore() {
|
|
33
41
|
return this.store;
|
|
34
42
|
}
|
|
43
|
+
async onReplicationTransactionFlush(session, lastOp) {
|
|
44
|
+
// Durably advance the stream-level head of persisted ops within the flush transaction.
|
|
45
|
+
// This ensures a checkpoint created later (even by an empty commit, or by a freshly-appended
|
|
46
|
+
// config that replicates nothing) covers all ops persisted before a potential crash.
|
|
47
|
+
await this.db.sync_rules.updateOne({
|
|
48
|
+
_id: this.replicationStreamId
|
|
49
|
+
}, {
|
|
50
|
+
$max: {
|
|
51
|
+
last_persisted_op: lastOp
|
|
52
|
+
}
|
|
53
|
+
}, { session });
|
|
54
|
+
}
|
|
55
|
+
selectedSyncConfigObjectIds(syncConfigIds) {
|
|
56
|
+
const selectedIds = new Set(this.syncConfigIds.map((id) => id.toHexString()));
|
|
57
|
+
return syncConfigIds
|
|
58
|
+
.filter((id) => selectedIds.has(id) && bson.ObjectId.isValid(id))
|
|
59
|
+
.map((id) => new bson.ObjectId(id));
|
|
60
|
+
}
|
|
61
|
+
relevantSyncConfigIds(table) {
|
|
62
|
+
const bucketDataSourceIds = table.bucketDataSources.map((source) => this.mapping.bucketSourceId(source));
|
|
63
|
+
const parameterLookupSourceIds = table.parameterLookupSources.map((source) => this.mapping.parameterLookupId(source));
|
|
64
|
+
return this.selectedSyncConfigObjectIds(this.mapping.syncConfigIdsForSourceTable(this.syncConfigIds.map((id) => id.toHexString()), table.ref, bucketDataSourceIds, parameterLookupSourceIds, [...table.eventDefinitionIds]));
|
|
65
|
+
}
|
|
66
|
+
relevantSyncConfigIdsForTables(tables) {
|
|
67
|
+
const ids = new Map();
|
|
68
|
+
for (const table of tables) {
|
|
69
|
+
for (const id of this.relevantSyncConfigIds(table)) {
|
|
70
|
+
ids.set(id.toHexString(), id);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
return [...ids.values()];
|
|
74
|
+
}
|
|
75
|
+
snapshotBlockingSourceTablesFilter() {
|
|
76
|
+
const clauses = this.syncConfigIds.flatMap((syncConfigId) => {
|
|
77
|
+
const filter = this.mapping.snapshotBlockingSourceTablesFilter(syncConfigId.toHexString());
|
|
78
|
+
return filter.$or ?? [];
|
|
79
|
+
});
|
|
80
|
+
if (clauses.length == 0) {
|
|
81
|
+
return {
|
|
82
|
+
snapshot_done: false,
|
|
83
|
+
_id: { $exists: false }
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return {
|
|
87
|
+
snapshot_done: false,
|
|
88
|
+
$or: clauses
|
|
89
|
+
};
|
|
90
|
+
}
|
|
35
91
|
async cleanupDroppedSourceTables(sourceTables) {
|
|
36
92
|
for (const table of sourceTables) {
|
|
37
93
|
await this.db
|
|
38
|
-
.
|
|
94
|
+
.sourceRecords(this.replicationStreamId, mongoTableId(table.id))
|
|
39
95
|
.drop()
|
|
40
96
|
.catch((error) => {
|
|
41
|
-
if (lib_mongo.
|
|
97
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
|
|
42
98
|
return;
|
|
43
99
|
}
|
|
44
100
|
throw error;
|
|
@@ -46,179 +102,96 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
46
102
|
}
|
|
47
103
|
}
|
|
48
104
|
async resolveTables(options) {
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
105
|
+
// The test-only override is a whole parsed set, so the sync rules and the mapping
|
|
106
|
+
// used below always come from the same parse.
|
|
107
|
+
const parsedOverride = options.parsedSyncConfig;
|
|
108
|
+
const syncConfig = parsedOverride?.hydratedSyncConfig ?? this.sync_rules;
|
|
109
|
+
const mapping = parsedOverride?.mapping ?? this.mapping;
|
|
110
|
+
const eventById = (parsedOverride ?? this.options.parsedSyncConfig).eventById;
|
|
52
111
|
const { connection_id, source } = options;
|
|
53
|
-
const
|
|
54
|
-
const
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
112
|
+
const reconcile = options.reconcileSourceTables ?? storage.defaultSourceTableReconciler;
|
|
113
|
+
const identity = {
|
|
114
|
+
schema: source.schema,
|
|
115
|
+
name: source.name,
|
|
116
|
+
objectId: source.objectId,
|
|
117
|
+
replicaIdColumns: source.replicaIdColumns.map((column) => ({
|
|
118
|
+
name: column.name,
|
|
119
|
+
type: column.type,
|
|
120
|
+
type_oid: column.typeId
|
|
121
|
+
}))
|
|
122
|
+
};
|
|
59
123
|
let result = null;
|
|
60
|
-
const
|
|
61
|
-
await
|
|
62
|
-
|
|
63
|
-
const
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
124
|
+
const session = this.db.client.startSession();
|
|
125
|
+
await using _ = { [Symbol.asyncDispose]: () => session.endSession() };
|
|
126
|
+
await session.withTransaction(async () => {
|
|
127
|
+
const col = this.db.sourceTables(this.replicationStreamId);
|
|
128
|
+
// Find records that overlap by name or relation id.
|
|
129
|
+
const candidateDocs = await col
|
|
130
|
+
.find(overlappingSourceTableFilter(connection_id, identity), { session })
|
|
131
|
+
.toArray();
|
|
132
|
+
const candidateTables = candidateDocs.map((doc) => sourceTableFromDocument(doc, source.connectionTag, syncConfig, mapping, eventById));
|
|
133
|
+
const candidates = candidateTables.map((table) => table.clone());
|
|
134
|
+
const resolution = await reconcile({ source, candidates });
|
|
135
|
+
storage.validateSourceTableCandidateResolution(candidates, resolution);
|
|
136
|
+
// Persist metadata from the reconciler without mutating the queried documents.
|
|
137
|
+
for (const { id, sourceMetadata } of storage.diffSourceTableUpdates(candidateTables, resolution)) {
|
|
138
|
+
await col.updateOne({ _id: mongoTableId(id) }, { $set: { source_metadata: sourceMetadata } }, { session });
|
|
71
139
|
}
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
if (!sameStringArray(doc.bucket_data_source_ids, bucketDataSourceIds) ||
|
|
96
|
-
!sameStringArray(doc.parameter_lookup_source_ids, parameterLookupSourceIds)) {
|
|
97
|
-
await col.updateOne({ _id: doc._id }, {
|
|
98
|
-
$set: {
|
|
99
|
-
bucket_data_source_ids: bucketDataSourceIds,
|
|
100
|
-
parameter_lookup_source_ids: parameterLookupSourceIds
|
|
101
|
-
}
|
|
102
|
-
}, { session });
|
|
103
|
-
}
|
|
104
|
-
if (coversDesiredMembership || coversEventOnlyTable) {
|
|
105
|
-
if (coversEventOnlyTable) {
|
|
106
|
-
retainedEventOnlyTable = true;
|
|
140
|
+
const context = {
|
|
141
|
+
connectionId: connection_id,
|
|
142
|
+
connectionTag: source.connectionTag,
|
|
143
|
+
identity,
|
|
144
|
+
storeCurrentData: source.sendsCompleteRows !== true,
|
|
145
|
+
syncConfig,
|
|
146
|
+
mapping,
|
|
147
|
+
desired: sourceTableDesiredResolution(syncConfig, source, mapping, eventById),
|
|
148
|
+
sourceCompatibleTables: resolution.compatibleTables,
|
|
149
|
+
newTableSourceMetadata: resolution.newTableValues.sourceMetadata
|
|
150
|
+
};
|
|
151
|
+
// Plan record reuse, membership changes, creation, and removal.
|
|
152
|
+
const plan = planSourceTableReconciliation(candidateDocs, context);
|
|
153
|
+
// Persist narrowing for incomplete snapshots only. Snapshot-complete docs keep stale
|
|
154
|
+
// coverage ids so compatible future configs can reuse already-snapshotted data.
|
|
155
|
+
// Narrowing occurs after removing a sync config, meaning we don't process those
|
|
156
|
+
// definitions anymore.
|
|
157
|
+
for (const update of plan.narrowingUpdates) {
|
|
158
|
+
await col.updateOne({ _id: update.id }, {
|
|
159
|
+
$set: {
|
|
160
|
+
bucket_data_source_ids: update.memberships.bucketDataSourceIds,
|
|
161
|
+
parameter_lookup_source_ids: update.memberships.parameterLookupSourceIds,
|
|
162
|
+
event_definition_ids: update.memberships.eventDefinitionIds
|
|
107
163
|
}
|
|
108
|
-
|
|
109
|
-
tables.push(this.sourceTableFromDocument({
|
|
110
|
-
...doc,
|
|
111
|
-
bucket_data_source_ids: bucketDataSourceIds,
|
|
112
|
-
parameter_lookup_source_ids: parameterLookupSourceIds
|
|
113
|
-
}, connectionTag, syncRules, {
|
|
114
|
-
bucketDataSources: bucketDataSourceIds.map((id) => bucketSourceById.get(id)),
|
|
115
|
-
parameterLookupSources: parameterLookupSourceIds.map((id) => parameterLookupSourceById.get(id))
|
|
116
|
-
}));
|
|
117
|
-
}
|
|
164
|
+
}, { session });
|
|
118
165
|
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (
|
|
166
|
+
// Any desired membership not covered by an existing doc gets a new source table.
|
|
167
|
+
// That table snapshots only the uncovered memberships.
|
|
168
|
+
if (plan.newTableMemberships != null) {
|
|
122
169
|
const id = options.idGenerator ? options.idGenerator() : new bson.ObjectId();
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
replicaIdColumns,
|
|
128
|
-
snapshotComplete: false,
|
|
129
|
-
bucketDataSources: uncoveredBucketIds.map((id) => bucketSourceById.get(id)),
|
|
130
|
-
parameterLookupSources: uncoveredLookupIds.map((id) => parameterLookupSourceById.get(id))
|
|
131
|
-
});
|
|
132
|
-
sourceTable.syncData = uncoveredBucketIds.length > 0;
|
|
133
|
-
sourceTable.syncParameters = uncoveredLookupIds.length > 0;
|
|
134
|
-
sourceTable.syncEvent = triggersEvent;
|
|
135
|
-
const createDoc = {
|
|
136
|
-
_id: id,
|
|
137
|
-
connection_id,
|
|
138
|
-
relation_id: objectId,
|
|
139
|
-
schema_name: schema,
|
|
140
|
-
table_name: name,
|
|
141
|
-
replica_id_columns: null,
|
|
142
|
-
replica_id_columns2: normalizedReplicaIdColumns,
|
|
143
|
-
snapshot_done: false,
|
|
144
|
-
snapshot_status: undefined,
|
|
145
|
-
bucket_data_source_ids: uncoveredBucketIds,
|
|
146
|
-
parameter_lookup_source_ids: uncoveredLookupIds
|
|
147
|
-
};
|
|
148
|
-
await col.insertOne(createDoc, { session });
|
|
149
|
-
initializeSourceRecordsFor.push(createDoc._id);
|
|
150
|
-
retainedDocIds.push(createDoc._id);
|
|
151
|
-
tables.push(sourceTable);
|
|
170
|
+
const { doc, table } = createNewSourceTable(id, plan.newTableMemberships, context);
|
|
171
|
+
await col.insertOne(doc, { session });
|
|
172
|
+
await this.db.initializeSourceRecordsCollection(this.replicationStreamId, doc._id, session);
|
|
173
|
+
plan.tables.push(table);
|
|
152
174
|
}
|
|
153
|
-
const conflictFilter = [{ schema_name: schema, table_name: name }];
|
|
154
|
-
if (objectId != null) {
|
|
155
|
-
conflictFilter.push({ relation_id: objectId });
|
|
156
|
-
}
|
|
157
|
-
const dropTables = await col
|
|
158
|
-
.find({
|
|
159
|
-
connection_id,
|
|
160
|
-
_id: { $nin: retainedDocIds },
|
|
161
|
-
$or: conflictFilter
|
|
162
|
-
}, { session })
|
|
163
|
-
.toArray();
|
|
164
175
|
result = {
|
|
165
|
-
tables,
|
|
166
|
-
dropTables:
|
|
176
|
+
tables: plan.tables,
|
|
177
|
+
dropTables: plan.dropDocs.map((doc) => sourceTableFromDocument(doc, context.connectionTag, syncConfig, mapping, eventById))
|
|
167
178
|
};
|
|
168
179
|
});
|
|
169
|
-
for (const sourceTableId of initializeSourceRecordsFor) {
|
|
170
|
-
await this.db.initializeSourceRecordsCollection(this.group_id, sourceTableId);
|
|
171
|
-
}
|
|
172
180
|
return result;
|
|
173
181
|
}
|
|
174
|
-
sourceTableFromDocument(doc, connectionTag, syncRules, memberships) {
|
|
175
|
-
const resolvedMemberships = memberships ?? this.sourceTableMembershipsFromDocument(doc, syncRules);
|
|
176
|
-
const table = new storage.SourceTable({
|
|
177
|
-
id: doc._id,
|
|
178
|
-
ref: {
|
|
179
|
-
connectionTag,
|
|
180
|
-
schema: doc.schema_name,
|
|
181
|
-
name: doc.table_name
|
|
182
|
-
},
|
|
183
|
-
objectId: doc.relation_id,
|
|
184
|
-
replicaIdColumns: doc.replica_id_columns2.map((c) => ({ name: c.name, typeId: c.type_oid, type: c.type })),
|
|
185
|
-
snapshotComplete: doc.snapshot_done ?? true,
|
|
186
|
-
bucketDataSources: resolvedMemberships.bucketDataSources,
|
|
187
|
-
parameterLookupSources: resolvedMemberships.parameterLookupSources
|
|
188
|
-
});
|
|
189
|
-
table.syncData = table.bucketDataSources.length > 0;
|
|
190
|
-
table.syncParameters = table.parameterLookupSources.length > 0;
|
|
191
|
-
table.syncEvent = syncRules.tableTriggersEvent(table.ref);
|
|
192
|
-
table.snapshotStatus =
|
|
193
|
-
doc.snapshot_status == null
|
|
194
|
-
? undefined
|
|
195
|
-
: {
|
|
196
|
-
lastKey: doc.snapshot_status.last_key?.buffer ?? null,
|
|
197
|
-
totalEstimatedCount: doc.snapshot_status.total_estimated_count,
|
|
198
|
-
replicatedCount: doc.snapshot_status.replicated_count
|
|
199
|
-
};
|
|
200
|
-
return table;
|
|
201
|
-
}
|
|
202
|
-
sourceTableMembershipsFromDocument(doc, syncRules) {
|
|
203
|
-
const bucketDataSourceIds = new Set(doc.bucket_data_source_ids);
|
|
204
|
-
const parameterLookupSourceIds = new Set(doc.parameter_lookup_source_ids);
|
|
205
|
-
return {
|
|
206
|
-
bucketDataSources: syncRules.bucketDataSources.filter((source) => bucketDataSourceIds.has(this.mapping.bucketSourceId(source))),
|
|
207
|
-
parameterLookupSources: syncRules.bucketParameterLookupSources.filter((source) => parameterLookupSourceIds.has(this.mapping.parameterLookupId(source)))
|
|
208
|
-
};
|
|
209
|
-
}
|
|
210
182
|
async getSourceTableStatus(table) {
|
|
211
183
|
const doc = (await this.db
|
|
212
|
-
.
|
|
184
|
+
.sourceTables(this.replicationStreamId)
|
|
213
185
|
.findOne({ _id: mongoTableId(table.id) }, { session: this.session }));
|
|
214
186
|
if (doc == null) {
|
|
215
187
|
return null;
|
|
216
188
|
}
|
|
217
|
-
return
|
|
189
|
+
return sourceTableFromDocument(doc, table.ref.connectionTag, this.sync_rules, this.mapping, this.options.parsedSyncConfig.eventById);
|
|
218
190
|
}
|
|
219
191
|
async commit(lsn, options) {
|
|
220
192
|
const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
|
|
221
193
|
await this.flush(options);
|
|
194
|
+
using _ = this.tracer.span('storage', 'commit');
|
|
222
195
|
const now = new Date();
|
|
223
196
|
await this.db.write_checkpoints.updateMany({
|
|
224
197
|
processed_at_lsn: null,
|
|
@@ -230,94 +203,145 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
230
203
|
}, {
|
|
231
204
|
session: this.session
|
|
232
205
|
});
|
|
233
|
-
const preUpdateDocument = await this.db.sync_rules.findOne({
|
|
234
|
-
_id: this.
|
|
235
|
-
'sync_configs._id': this.
|
|
206
|
+
const preUpdateDocument = (await this.db.sync_rules.findOne({
|
|
207
|
+
_id: this.replicationStreamId,
|
|
208
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
236
209
|
}, {
|
|
237
210
|
session: this.session,
|
|
238
211
|
projection: {
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
$elemMatch: {
|
|
242
|
-
_id: this.syncConfigId
|
|
243
|
-
}
|
|
244
|
-
}
|
|
212
|
+
sync_configs: 1,
|
|
213
|
+
last_persisted_op: 1
|
|
245
214
|
}
|
|
246
|
-
});
|
|
247
|
-
const
|
|
248
|
-
if (
|
|
249
|
-
throw new ReplicationAssertionError(`Failed to update checkpoint - no matching sync_config for _id: ${this.
|
|
215
|
+
}));
|
|
216
|
+
const states = preUpdateDocument?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
217
|
+
if (states.length == 0) {
|
|
218
|
+
throw new ReplicationAssertionError(`Failed to update checkpoint - no matching sync_config for _id: ${this.replicationStreamId}/${this.syncConfigIds
|
|
219
|
+
.map((id) => id.toHexString())
|
|
220
|
+
.join(',')}`);
|
|
250
221
|
}
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
persistedOp: this.persisted_op,
|
|
259
|
-
createEmptyCheckpoints
|
|
260
|
-
});
|
|
261
|
-
const updateSet = {
|
|
262
|
-
last_keepalive_ts: now,
|
|
263
|
-
last_fatal_error: null,
|
|
264
|
-
last_fatal_error_ts: null,
|
|
265
|
-
'sync_configs.$[config].keepalive_op': checkpointState.newKeepaliveOp,
|
|
266
|
-
'sync_configs.$[config].last_checkpoint': checkpointState.newLastCheckpoint
|
|
267
|
-
};
|
|
268
|
-
if (checkpointState.checkpointCreated) {
|
|
269
|
-
updateSet['sync_configs.$[config].last_checkpoint_lsn'] = lsn;
|
|
270
|
-
updateSet['snapshot_lsn'] = null;
|
|
271
|
-
updateSet['last_checkpoint_ts'] = now;
|
|
222
|
+
// The replication job / MongoBucketBatch must be constructed with all replicating (PROCESSING / ACTIVE)
|
|
223
|
+
// sync configs in the stream, otherwise we'll get inconsistencies. Configs in other states (e.g. STOP)
|
|
224
|
+
// remain embedded in the document and are ignored here.
|
|
225
|
+
const missingSyncConfig = preUpdateDocument.sync_configs.find((config) => [storage.SyncRuleState.PROCESSING, storage.SyncRuleState.ACTIVE].includes(config.state) &&
|
|
226
|
+
!this.syncConfigIds.some((id) => id.equals(config._id)));
|
|
227
|
+
if (missingSyncConfig != null) {
|
|
228
|
+
throw new ReplicationAssertionError(`Replication job not configured for sync config ${missingSyncConfig._id}`);
|
|
272
229
|
}
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
230
|
+
// Effective head of the stream's op sequence.
|
|
231
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and this
|
|
232
|
+
// read uses the same session, so it covers all ops persisted by this batch.
|
|
233
|
+
const newCheckpoint = preUpdateDocument?.last_persisted_op == null ? 0n : BigInt(preUpdateDocument.last_persisted_op);
|
|
234
|
+
let checkpointBlocked = false;
|
|
235
|
+
let checkpointCreated = false;
|
|
236
|
+
let checkpointLogState = null;
|
|
237
|
+
const unblockedConfigIds = [];
|
|
238
|
+
for (const state of states) {
|
|
239
|
+
if (state.last_checkpoint != null && state.last_checkpoint > newCheckpoint) {
|
|
240
|
+
// last_persisted_op is $max-advanced durably in the same transaction as every flush, and
|
|
241
|
+
// checkpoints are only ever created at that head, so a checkpoint past the head means the
|
|
242
|
+
// op sequence or the stored state is corrupt.
|
|
243
|
+
throw new ReplicationAssertionError(`Invariant violation: sync config ${state._id} has last_checkpoint ${state.last_checkpoint} > stream head ${newCheckpoint}`);
|
|
244
|
+
}
|
|
245
|
+
const canCheckpoint = canCheckpointState(lsn, {
|
|
246
|
+
snapshotDone: state.snapshot_done === true,
|
|
247
|
+
lastCheckpointLsn: state.last_checkpoint_lsn,
|
|
248
|
+
noCheckpointBefore: state.no_checkpoint_before
|
|
249
|
+
});
|
|
250
|
+
if (!canCheckpoint) {
|
|
251
|
+
checkpointBlocked = true;
|
|
252
|
+
// Log the first blocked config's state.
|
|
253
|
+
checkpointLogState ??= {
|
|
285
254
|
snapshot_done: state.snapshot_done,
|
|
286
255
|
last_checkpoint_lsn: state.last_checkpoint_lsn,
|
|
287
256
|
no_checkpoint_before: state.no_checkpoint_before
|
|
288
|
-
}
|
|
257
|
+
};
|
|
258
|
+
continue;
|
|
259
|
+
}
|
|
260
|
+
checkpointCreated ||= createEmptyCheckpoints || state.last_checkpoint !== newCheckpoint;
|
|
261
|
+
unblockedConfigIds.push(state._id);
|
|
262
|
+
}
|
|
263
|
+
// Every commit advances the stream's resume position: commit() flushes first, so all
|
|
264
|
+
// source changes up to this lsn have been persisted, even when checkpoints are blocked.
|
|
265
|
+
// In the future we could also advance this on flush, when the connector provides the
|
|
266
|
+
// current position (see setResumeLsn, which connectors may already call after flushing).
|
|
267
|
+
const resumeLsnUpdate = { resume_lsn: lsn };
|
|
268
|
+
if (unblockedConfigIds.length > 0) {
|
|
269
|
+
// All unblocked configs get the SAME new value, so we apply it with a single updateOne
|
|
270
|
+
// (single-document atomicity).
|
|
271
|
+
const updateSet = {
|
|
272
|
+
last_keepalive_ts: now,
|
|
273
|
+
last_fatal_error: null,
|
|
274
|
+
last_fatal_error_ts: null
|
|
275
|
+
};
|
|
276
|
+
// Only advance checkpoint fields when an actual (non-empty) checkpoint is created, matching
|
|
277
|
+
// the previous per-config / v1 behaviour.
|
|
278
|
+
if (checkpointCreated) {
|
|
279
|
+
updateSet['sync_configs.$[config].last_checkpoint'] = newCheckpoint;
|
|
280
|
+
updateSet['sync_configs.$[config].last_checkpoint_lsn'] = lsn;
|
|
281
|
+
updateSet['last_checkpoint_ts'] = now;
|
|
282
|
+
}
|
|
283
|
+
await this.db.sync_rules.updateOne({
|
|
284
|
+
_id: this.replicationStreamId,
|
|
285
|
+
'sync_configs._id': { $in: unblockedConfigIds }
|
|
286
|
+
}, { $set: updateSet, $max: resumeLsnUpdate }, {
|
|
287
|
+
session: this.session,
|
|
288
|
+
arrayFilters: checkpointCreated ? [{ 'config._id': { $in: unblockedConfigIds } }] : undefined
|
|
289
|
+
});
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
// All selected configs are blocked - only update keepalive/error tracking and the
|
|
293
|
+
// resume position.
|
|
294
|
+
await this.db.sync_rules.updateOne({
|
|
295
|
+
_id: this.replicationStreamId
|
|
296
|
+
}, {
|
|
297
|
+
$set: {
|
|
298
|
+
last_keepalive_ts: now,
|
|
299
|
+
last_fatal_error: null,
|
|
300
|
+
last_fatal_error_ts: null
|
|
301
|
+
},
|
|
302
|
+
$max: resumeLsnUpdate
|
|
303
|
+
}, { session: this.session });
|
|
304
|
+
}
|
|
305
|
+
if (checkpointBlocked) {
|
|
306
|
+
if (Date.now() - this.lastWaitingLogThrottledV3 > 5_000) {
|
|
307
|
+
this.logger.info(`Waiting before creating checkpoint, currently at ${lsn}. Current state: ${JSON.stringify(checkpointLogState)}`);
|
|
289
308
|
this.lastWaitingLogThrottledV3 = Date.now();
|
|
290
309
|
}
|
|
291
310
|
}
|
|
292
311
|
else {
|
|
293
|
-
if (
|
|
294
|
-
this.logger.debug(`Created checkpoint at ${lsn}
|
|
312
|
+
if (checkpointCreated) {
|
|
313
|
+
this.logger.debug(`Created checkpoint at ${lsn}`);
|
|
295
314
|
}
|
|
296
315
|
await this.autoActivateV3(lsn);
|
|
316
|
+
// All configs are now checkpointed at newCheckpoint (the stream head).
|
|
317
|
+
await this.sourceRecordStore.postCommitCleanup(newCheckpoint, this.logger);
|
|
318
|
+
}
|
|
319
|
+
if (checkpointCreated) {
|
|
297
320
|
await this.db.notifyCheckpoint();
|
|
298
|
-
this.persisted_op = null;
|
|
299
|
-
this.last_checkpoint_lsn = lsn;
|
|
300
|
-
if (checkpointState.newLastCheckpoint != null) {
|
|
301
|
-
await this.sourceRecordStore.postCommitCleanup(checkpointState.newLastCheckpoint, this.logger);
|
|
302
|
-
}
|
|
303
321
|
}
|
|
304
322
|
return {
|
|
305
|
-
checkpointBlocked
|
|
306
|
-
checkpointCreated
|
|
323
|
+
checkpointBlocked,
|
|
324
|
+
checkpointCreated
|
|
307
325
|
};
|
|
308
326
|
}
|
|
309
327
|
async keepalive(lsn) {
|
|
310
328
|
return await this.commit(lsn, { createEmptyCheckpoints: true });
|
|
311
329
|
}
|
|
312
330
|
async setResumeLsn(lsn) {
|
|
331
|
+
using _ = this.tracer.span('storage', 'set_resume_lsn');
|
|
313
332
|
await this.db.sync_rules.updateOne({
|
|
314
|
-
_id: this.
|
|
315
|
-
'sync_configs._id': this.syncConfigId
|
|
333
|
+
_id: this.replicationStreamId
|
|
316
334
|
}, {
|
|
317
335
|
$set: {
|
|
318
|
-
|
|
336
|
+
resume_lsn: lsn
|
|
319
337
|
}
|
|
320
|
-
}, {
|
|
338
|
+
}, {
|
|
339
|
+
session: this.session,
|
|
340
|
+
// Losing occasional resume LSN is fine. That may mean reprocessing
|
|
341
|
+
// some source changes in some edge cases, which is not an issue since
|
|
342
|
+
// changes are processed in an idempotent way.
|
|
343
|
+
writeConcern: { w: 1 }
|
|
344
|
+
});
|
|
321
345
|
}
|
|
322
346
|
async autoActivateV3(lsn) {
|
|
323
347
|
if (!this.needsActivationV3) {
|
|
@@ -325,30 +349,32 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
325
349
|
}
|
|
326
350
|
const session = this.session;
|
|
327
351
|
let activated = false;
|
|
352
|
+
let needsFutureActivationCheck = true;
|
|
328
353
|
await session.withTransaction(async () => {
|
|
354
|
+
// Reset on transaction retries.
|
|
355
|
+
needsFutureActivationCheck = true;
|
|
356
|
+
activated = false;
|
|
329
357
|
const doc = await this.db.sync_rules.findOne({
|
|
330
|
-
_id: this.
|
|
331
|
-
'sync_configs._id': this.
|
|
358
|
+
_id: this.replicationStreamId,
|
|
359
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
332
360
|
}, {
|
|
333
361
|
session,
|
|
334
362
|
projection: {
|
|
335
363
|
state: 1,
|
|
336
|
-
sync_configs:
|
|
337
|
-
$elemMatch: {
|
|
338
|
-
_id: this.syncConfigId
|
|
339
|
-
}
|
|
340
|
-
}
|
|
364
|
+
sync_configs: 1
|
|
341
365
|
}
|
|
342
366
|
});
|
|
343
|
-
const
|
|
344
|
-
if (doc
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
367
|
+
const states = doc?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
368
|
+
if (doc == null || states.length == 0) {
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
const processingStates = states.filter((state) => state.state == storage.SyncRuleState.PROCESSING);
|
|
372
|
+
if (doc.state == storage.SyncRuleState.PROCESSING &&
|
|
373
|
+
processingStates.length == states.length &&
|
|
374
|
+
states.every((state) => state.snapshot_done && state.last_checkpoint != null)) {
|
|
349
375
|
await this.db.sync_rules.updateOne({
|
|
350
|
-
_id: this.
|
|
351
|
-
'sync_configs._id': this.
|
|
376
|
+
_id: this.replicationStreamId,
|
|
377
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
352
378
|
}, {
|
|
353
379
|
$set: {
|
|
354
380
|
state: storage.SyncRuleState.ACTIVE,
|
|
@@ -356,16 +382,39 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
356
382
|
}
|
|
357
383
|
}, {
|
|
358
384
|
session,
|
|
359
|
-
arrayFilters: [{ 'config._id': this.
|
|
385
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
360
386
|
});
|
|
361
387
|
await this.db.sync_rules.updateMany({
|
|
362
|
-
_id: { $ne: this.
|
|
388
|
+
_id: { $ne: this.replicationStreamId },
|
|
363
389
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
364
|
-
},
|
|
390
|
+
}, stopReplicationStreamPipeline(), { session });
|
|
391
|
+
activated = true;
|
|
392
|
+
}
|
|
393
|
+
else if (doc.state == storage.SyncRuleState.ACTIVE &&
|
|
394
|
+
processingStates.length > 0 &&
|
|
395
|
+
processingStates.every((state) => state.snapshot_done && state.last_checkpoint != null)) {
|
|
396
|
+
await this.db.sync_rules.updateOne({
|
|
397
|
+
_id: this.replicationStreamId,
|
|
398
|
+
'sync_configs._id': { $in: processingStates.map((state) => state._id) }
|
|
399
|
+
}, {
|
|
400
|
+
$set: {
|
|
401
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.STOP,
|
|
402
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.ACTIVE
|
|
403
|
+
}
|
|
404
|
+
}, {
|
|
405
|
+
session,
|
|
406
|
+
arrayFilters: [
|
|
407
|
+
{ 'activeConfig.state': storage.SyncRuleState.ACTIVE },
|
|
408
|
+
{ 'processingConfig._id': { $in: processingStates.map((state) => state._id) } }
|
|
409
|
+
]
|
|
410
|
+
});
|
|
365
411
|
activated = true;
|
|
366
412
|
}
|
|
367
|
-
else if (doc
|
|
368
|
-
|
|
413
|
+
else if (doc.state != storage.SyncRuleState.PROCESSING && doc.state != storage.SyncRuleState.ACTIVE) {
|
|
414
|
+
needsFutureActivationCheck = false;
|
|
415
|
+
}
|
|
416
|
+
else if (doc.state == storage.SyncRuleState.ACTIVE && processingStates.length == 0) {
|
|
417
|
+
needsFutureActivationCheck = false;
|
|
369
418
|
}
|
|
370
419
|
});
|
|
371
420
|
if (activated) {
|
|
@@ -373,11 +422,14 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
373
422
|
await this.db.notifyCheckpoint();
|
|
374
423
|
this.needsActivationV3 = false;
|
|
375
424
|
}
|
|
425
|
+
else if (!needsFutureActivationCheck) {
|
|
426
|
+
this.needsActivationV3 = false;
|
|
427
|
+
}
|
|
376
428
|
}
|
|
377
429
|
async markAllSnapshotDone(no_checkpoint_before_lsn) {
|
|
378
430
|
await this.db.sync_rules.updateOne({
|
|
379
|
-
_id: this.
|
|
380
|
-
'sync_configs._id': this.
|
|
431
|
+
_id: this.replicationStreamId,
|
|
432
|
+
'sync_configs._id': { $in: this.syncConfigIds }
|
|
381
433
|
}, {
|
|
382
434
|
$set: {
|
|
383
435
|
'sync_configs.$[config].snapshot_done': true,
|
|
@@ -388,18 +440,22 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
388
440
|
}
|
|
389
441
|
}, {
|
|
390
442
|
session: this.session,
|
|
391
|
-
arrayFilters: [{ 'config._id': this.
|
|
443
|
+
arrayFilters: [{ 'config._id': { $in: this.syncConfigIds } }]
|
|
392
444
|
});
|
|
393
445
|
}
|
|
394
446
|
async markSnapshotDone(no_checkpoint_before_lsn, options) {
|
|
395
447
|
await this.withTransaction(async () => {
|
|
396
448
|
// Protect against race conditions
|
|
397
|
-
const
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
449
|
+
const blockingTables = await this.db
|
|
450
|
+
.sourceTables(this.replicationStreamId)
|
|
451
|
+
.find(this.snapshotBlockingSourceTablesFilter(), {
|
|
452
|
+
session: this.session,
|
|
453
|
+
projection: { schema_name: 1, table_name: 1 }
|
|
454
|
+
})
|
|
455
|
+
.toArray();
|
|
456
|
+
if (blockingTables.length > 0) {
|
|
401
457
|
if (options?.throwOnConflict ?? true) {
|
|
402
|
-
throw new ReplicationAssertionError(`Cannot mark snapshot done while
|
|
458
|
+
throw new ReplicationAssertionError(`Cannot mark snapshot done while source tables still require snapshotting. Tables: ${blockingTables.map((t) => `${t.schema_name}.${t.table_name}`).join(', ')}`);
|
|
403
459
|
}
|
|
404
460
|
else {
|
|
405
461
|
return;
|
|
@@ -408,24 +464,29 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
408
464
|
await this.markAllSnapshotDone(no_checkpoint_before_lsn);
|
|
409
465
|
});
|
|
410
466
|
}
|
|
411
|
-
async markTableSnapshotRequired(
|
|
467
|
+
async markTableSnapshotRequired(table) {
|
|
468
|
+
const syncConfigIds = this.relevantSyncConfigIds(table);
|
|
469
|
+
if (syncConfigIds.length == 0) {
|
|
470
|
+
return;
|
|
471
|
+
}
|
|
412
472
|
await this.db.sync_rules.updateOne({
|
|
413
|
-
_id: this.
|
|
414
|
-
'sync_configs._id':
|
|
473
|
+
_id: this.replicationStreamId,
|
|
474
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
415
475
|
}, {
|
|
416
476
|
$set: {
|
|
417
477
|
'sync_configs.$[config].snapshot_done': false
|
|
418
478
|
}
|
|
419
479
|
}, {
|
|
420
480
|
session: this.session,
|
|
421
|
-
arrayFilters: [{ 'config._id':
|
|
481
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
422
482
|
});
|
|
423
483
|
}
|
|
424
484
|
async markTableSnapshotDone(tables, no_checkpoint_before_lsn) {
|
|
425
485
|
const session = this.session;
|
|
426
486
|
const ids = tables.map((table) => mongoTableId(table.id));
|
|
487
|
+
const syncConfigIds = this.relevantSyncConfigIdsForTables(tables);
|
|
427
488
|
await this.withTransaction(async () => {
|
|
428
|
-
await this.db.
|
|
489
|
+
await this.db.sourceTables(this.replicationStreamId).updateMany({ _id: { $in: ids } }, {
|
|
429
490
|
$set: {
|
|
430
491
|
snapshot_done: true
|
|
431
492
|
},
|
|
@@ -433,10 +494,10 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
433
494
|
snapshot_status: 1
|
|
434
495
|
}
|
|
435
496
|
}, { session });
|
|
436
|
-
if (no_checkpoint_before_lsn != null) {
|
|
497
|
+
if (no_checkpoint_before_lsn != null && syncConfigIds.length > 0) {
|
|
437
498
|
await this.db.sync_rules.updateOne({
|
|
438
|
-
_id: this.
|
|
439
|
-
'sync_configs._id':
|
|
499
|
+
_id: this.replicationStreamId,
|
|
500
|
+
'sync_configs._id': { $in: syncConfigIds }
|
|
440
501
|
}, {
|
|
441
502
|
$set: {
|
|
442
503
|
last_keepalive_ts: new Date()
|
|
@@ -446,7 +507,8 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
446
507
|
}
|
|
447
508
|
}, {
|
|
448
509
|
session: this.session,
|
|
449
|
-
|
|
510
|
+
// Only set for sync configs that use this table
|
|
511
|
+
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
450
512
|
});
|
|
451
513
|
}
|
|
452
514
|
});
|
|
@@ -456,5 +518,74 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
456
518
|
return copy;
|
|
457
519
|
});
|
|
458
520
|
}
|
|
521
|
+
async batchCreateCustomWriteCheckpoints(session, opId) {
|
|
522
|
+
if (this.write_checkpoint_batch.length == 0) {
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const checkpointsByEvent = Map.groupBy(this.write_checkpoint_batch, (checkpoint) => this.validateCustomCheckpointEventId(checkpoint.event_id));
|
|
526
|
+
for (const [eventId, checkpoints] of checkpointsByEvent) {
|
|
527
|
+
await this.batchCreateEventCustomWriteCheckpoints(session, opId, eventId, checkpoints);
|
|
528
|
+
}
|
|
529
|
+
}
|
|
530
|
+
async prepareCustomWriteCheckpoints() {
|
|
531
|
+
// Most events never produce custom checkpoints, so create collections lazily
|
|
532
|
+
// only for the event ids present in this batch. This hook runs before the
|
|
533
|
+
// replication transaction because MongoDB cannot create indexes within it.
|
|
534
|
+
const eventIds = new Set(this.write_checkpoint_batch.map((checkpoint) => this.validateCustomCheckpointEventId(checkpoint.event_id)));
|
|
535
|
+
for (const eventId of eventIds) {
|
|
536
|
+
if (this.initializedCustomCheckpointEventIds.has(eventId)) {
|
|
537
|
+
continue;
|
|
538
|
+
}
|
|
539
|
+
await this.db.initializeCustomCheckpointRequestsCollection({
|
|
540
|
+
replicationStreamId: this.replicationStreamId,
|
|
541
|
+
eventId
|
|
542
|
+
});
|
|
543
|
+
this.initializedCustomCheckpointEventIds.add(eventId);
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
validateCustomCheckpointEventId(eventId) {
|
|
547
|
+
if (eventId == null) {
|
|
548
|
+
throw new ServiceAssertionError('V3 custom checkpoints require an event definition id');
|
|
549
|
+
}
|
|
550
|
+
if (!this.knownEventIds.has(eventId)) {
|
|
551
|
+
throw new ServiceAssertionError(`Unknown custom checkpoint event definition ${eventId}`);
|
|
552
|
+
}
|
|
553
|
+
return eventId;
|
|
554
|
+
}
|
|
555
|
+
async batchCreateEventCustomWriteCheckpoints(session, opId, eventId, checkpoints) {
|
|
556
|
+
await this.db
|
|
557
|
+
.customCheckpointRequests({
|
|
558
|
+
eventId,
|
|
559
|
+
replicationStreamId: this.replicationStreamId
|
|
560
|
+
})
|
|
561
|
+
.bulkWrite(checkpoints.map((checkpointOptions) => {
|
|
562
|
+
const set = {
|
|
563
|
+
user_id: checkpointOptions.user_id,
|
|
564
|
+
checkpoint: checkpointOptions.checkpoint,
|
|
565
|
+
op_id: opId
|
|
566
|
+
};
|
|
567
|
+
if (checkpointOptions.checkpoint_requested_at != null) {
|
|
568
|
+
set.checkpoint_requested_at = checkpointOptions.checkpoint_requested_at;
|
|
569
|
+
}
|
|
570
|
+
return {
|
|
571
|
+
updateOne: {
|
|
572
|
+
filter: {
|
|
573
|
+
user_id: checkpointOptions.user_id
|
|
574
|
+
},
|
|
575
|
+
update: {
|
|
576
|
+
$set: set,
|
|
577
|
+
...(checkpointOptions.checkpoint_requested_at == null
|
|
578
|
+
? {
|
|
579
|
+
$unset: {
|
|
580
|
+
checkpoint_requested_at: 1
|
|
581
|
+
}
|
|
582
|
+
}
|
|
583
|
+
: {})
|
|
584
|
+
},
|
|
585
|
+
upsert: true
|
|
586
|
+
}
|
|
587
|
+
};
|
|
588
|
+
}), { session });
|
|
589
|
+
}
|
|
459
590
|
}
|
|
460
591
|
//# sourceMappingURL=MongoBucketBatchV3.js.map
|