@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,38 +1,77 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
-
import { internalToExternalOpId,
|
|
3
|
+
import { internalToExternalOpId, ParameterSetLimitExceededError, storage } from '@powersync/service-core';
|
|
4
4
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
5
5
|
import * as bson from 'bson';
|
|
6
6
|
import { mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../../utils/util.js';
|
|
7
|
-
import { MongoPersistedSyncRulesContentV3 } from '../MongoPersistedSyncRulesContent.js';
|
|
8
7
|
import { MongoSyncBucketStorage } from '../MongoSyncBucketStorage.js';
|
|
9
|
-
import {
|
|
8
|
+
import { loadBucketDataDocument, maxOpId } from './bucket-format.js';
|
|
9
|
+
import { deserializeParameterLookup, serializeParameterLookup } from './models.js';
|
|
10
10
|
import { MongoBucketBatchV3 } from './MongoBucketBatchV3.js';
|
|
11
11
|
import { MongoChecksumsV3 } from './MongoChecksumsV3.js';
|
|
12
12
|
import { MongoCompactorV3 } from './MongoCompactorV3.js';
|
|
13
13
|
import { MongoParameterCompactorV3 } from './MongoParameterCompactorV3.js';
|
|
14
|
-
import {
|
|
14
|
+
import { MongoStoppedSyncConfigCleanup } from './MongoStoppedSyncConfigCleanup.js';
|
|
15
|
+
import { MongoWriteCheckpointAPIV3 } from './MongoWriteCheckpointAPIV3.js';
|
|
16
|
+
import { hydrateBucketDataDocuments } from './object-storage/BucketDataObjectStorage.js';
|
|
17
|
+
import { ObjectStorageLifecycle } from './object-storage/ObjectStorageLifecycle.js';
|
|
18
|
+
import { ObjectStorageUsage } from './object-storage/ObjectStorageUsage.js';
|
|
19
|
+
const BUCKET_DATA_FETCH_BATCH_LIMIT_BYTES = 16 * 1024 * 1024;
|
|
20
|
+
/**
|
|
21
|
+
* Keep the documents hydrated for one sync response within a bounded payload or slightly higher.
|
|
22
|
+
*
|
|
23
|
+
* This deserializes on-demand, so no deserialization is performed for discarded data.
|
|
24
|
+
*/
|
|
25
|
+
function cutBucketDataBatch(rawDocuments) {
|
|
26
|
+
let cumulativeBytes = 0;
|
|
27
|
+
let documents = [];
|
|
28
|
+
for (const raw of rawDocuments) {
|
|
29
|
+
const doc = bson.deserialize(raw, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS);
|
|
30
|
+
documents.push(doc);
|
|
31
|
+
cumulativeBytes += doc.size;
|
|
32
|
+
if (cumulativeBytes > BUCKET_DATA_FETCH_BATCH_LIMIT_BYTES) {
|
|
33
|
+
return {
|
|
34
|
+
documents,
|
|
35
|
+
wasCut: documents.length < rawDocuments.length
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
return { documents, wasCut: false };
|
|
40
|
+
}
|
|
41
|
+
function extractRowsFromDocument(doc, context, bucketStart, endOpId) {
|
|
42
|
+
const rows = [];
|
|
43
|
+
for (const row of loadBucketDataDocument(context, doc)) {
|
|
44
|
+
// In theory a binary search could be faster than a linear scan to find the start.
|
|
45
|
+
// In practice, most cases should not filter out anything here.
|
|
46
|
+
if (row.o <= bucketStart) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
if (row.o > endOpId) {
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
rows.push(row);
|
|
53
|
+
}
|
|
54
|
+
return rows;
|
|
55
|
+
}
|
|
15
56
|
export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
if (!(sync_rules instanceof MongoPersistedSyncRulesContentV3)) {
|
|
57
|
+
constructor(factory, replicationStreamId, replicationStream, replicationStreamName, writeCheckpointMode, options) {
|
|
58
|
+
super(factory, replicationStreamId, replicationStream, replicationStreamName, writeCheckpointMode, options);
|
|
59
|
+
if (replicationStream.syncConfigIds.length == 0) {
|
|
20
60
|
throw new ServiceAssertionError('Missing sync config id for storage v3');
|
|
21
61
|
}
|
|
22
|
-
this.syncRulesV3 = sync_rules;
|
|
23
62
|
}
|
|
24
|
-
get
|
|
25
|
-
return this.
|
|
63
|
+
get syncConfigIds() {
|
|
64
|
+
return this.replicationStream.syncConfigIds;
|
|
26
65
|
}
|
|
27
66
|
get syncRulesCollection() {
|
|
28
67
|
return this.db.sync_rules;
|
|
29
68
|
}
|
|
30
69
|
syncConfigMatch(extra = {}) {
|
|
31
70
|
return {
|
|
32
|
-
_id: this.
|
|
71
|
+
_id: this.replicationStreamId,
|
|
33
72
|
sync_configs: {
|
|
34
73
|
$elemMatch: {
|
|
35
|
-
_id: this.
|
|
74
|
+
_id: { $in: this.syncConfigIds },
|
|
36
75
|
...extra
|
|
37
76
|
}
|
|
38
77
|
}
|
|
@@ -41,39 +80,25 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
41
80
|
syncConfigProjection(extra = {}) {
|
|
42
81
|
return {
|
|
43
82
|
...extra,
|
|
44
|
-
sync_configs:
|
|
45
|
-
$elemMatch: {
|
|
46
|
-
_id: this.syncConfigId
|
|
47
|
-
}
|
|
48
|
-
}
|
|
83
|
+
sync_configs: 1
|
|
49
84
|
};
|
|
50
85
|
}
|
|
51
|
-
|
|
52
|
-
return
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* For now, we only support a single sync config per replication stream.
|
|
56
|
-
*
|
|
57
|
-
* In the future we'll add support for multiple.
|
|
58
|
-
*/
|
|
59
|
-
selectedSyncConfig(doc) {
|
|
60
|
-
return doc?.sync_configs?.[0] ?? null;
|
|
86
|
+
selectedSyncConfigs(doc) {
|
|
87
|
+
return doc?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
61
88
|
}
|
|
62
89
|
async initializeVersionStorage() {
|
|
63
|
-
const
|
|
64
|
-
for (
|
|
65
|
-
const collection = this.db.
|
|
66
|
-
await this.db.db
|
|
67
|
-
.createCollection(collection, { clusteredIndex: { name: '_id', unique: true, key: { _id: 1 } } })
|
|
68
|
-
.catch((error) => {
|
|
90
|
+
const storageIds = this.storageIds;
|
|
91
|
+
for (const source of storageIds.bucketDefinitionIds) {
|
|
92
|
+
const collection = this.db.bucketData(this.replicationStreamId, source).collectionName;
|
|
93
|
+
await this.db.db.createCollection(collection, {}).catch((error) => {
|
|
69
94
|
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceExists') {
|
|
70
95
|
return;
|
|
71
96
|
}
|
|
72
97
|
throw error;
|
|
73
98
|
});
|
|
74
99
|
}
|
|
75
|
-
for (
|
|
76
|
-
await this.db.
|
|
100
|
+
for (const indexId of storageIds.parameterIndexIds) {
|
|
101
|
+
await this.db.parameterIndex(this.replicationStreamId, indexId).createIndex({
|
|
77
102
|
lookup: 1,
|
|
78
103
|
key: 1,
|
|
79
104
|
_id: -1
|
|
@@ -83,53 +108,85 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
83
108
|
}
|
|
84
109
|
}
|
|
85
110
|
createMongoChecksums(options) {
|
|
86
|
-
return new MongoChecksumsV3(this.db, this.
|
|
111
|
+
return new MongoChecksumsV3(this.db, this.replicationStreamId, {
|
|
87
112
|
...options.checksumOptions,
|
|
113
|
+
checksumCacheTtlMs: options.checksumCacheTtlMs,
|
|
88
114
|
storageConfig: options?.storageConfig,
|
|
89
|
-
|
|
115
|
+
syncConfigMapping: () => this.singleSyncConfigMapping()
|
|
116
|
+
});
|
|
117
|
+
}
|
|
118
|
+
createWriteCheckpointAPI(options) {
|
|
119
|
+
return new MongoWriteCheckpointAPIV3({
|
|
120
|
+
...options,
|
|
121
|
+
db: this.db,
|
|
122
|
+
syncConfigMapping: () => this.singleSyncConfigMapping()
|
|
90
123
|
});
|
|
91
124
|
}
|
|
92
125
|
createMongoCompactor(options) {
|
|
93
126
|
return new MongoCompactorV3(this, this.db, options);
|
|
94
127
|
}
|
|
95
|
-
|
|
96
|
-
|
|
128
|
+
async compactInitialReplication(options) {
|
|
129
|
+
this.logger.info(`Compacting chunks after initial replication...`);
|
|
130
|
+
const start = Date.now();
|
|
131
|
+
const maxOpId = options.maxOpId ?? (await this.fetchPersistedOpHead()) ?? undefined;
|
|
132
|
+
const compactedBuckets = await this.createMongoCompactor({
|
|
133
|
+
...options,
|
|
134
|
+
maxOpId,
|
|
135
|
+
compactChunksOnly: true,
|
|
136
|
+
logger: this.logger
|
|
137
|
+
}).compact();
|
|
138
|
+
this.logger.info(`Compacted chunks after initial replication in ${(Date.now() - start) / 1000}s`);
|
|
139
|
+
return { buckets: compactedBuckets };
|
|
97
140
|
}
|
|
98
|
-
|
|
99
|
-
return new
|
|
141
|
+
createMongoParameterCompactor(checkpoint, options) {
|
|
142
|
+
return new MongoParameterCompactorV3(this.db, this.replicationStreamId, checkpoint, options);
|
|
143
|
+
}
|
|
144
|
+
async fetchPersistedOpHead() {
|
|
145
|
+
const doc = await this.syncRulesCollection.findOne({ _id: this.replicationStreamId }, { projection: { last_persisted_op: 1 } });
|
|
146
|
+
return doc?.last_persisted_op == null ? null : BigInt(doc.last_persisted_op);
|
|
147
|
+
}
|
|
148
|
+
async createWriterImpl(options) {
|
|
149
|
+
const batchOptions = this.writerBatchOptions(options);
|
|
150
|
+
const doc = await this.syncRulesCollection.findOne({ _id: this.replicationStreamId }, { projection: { resume_lsn: 1 } });
|
|
151
|
+
return new MongoBucketBatchV3({
|
|
152
|
+
...batchOptions,
|
|
153
|
+
// The stream-level replication position - per-config checkpoint LSNs are consistency
|
|
154
|
+
// markers and do not affect where replication resumes.
|
|
155
|
+
resumeFromLsn: doc?.resume_lsn ?? null,
|
|
156
|
+
syncConfigIds: this.syncConfigIds
|
|
157
|
+
});
|
|
100
158
|
}
|
|
101
159
|
async fetchCheckpointState(session) {
|
|
102
160
|
const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch({
|
|
103
161
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
104
162
|
}), {
|
|
105
163
|
session,
|
|
106
|
-
|
|
164
|
+
// The invalidation fence must be read in the same snapshot as the checkpoint.
|
|
165
|
+
projection: this.syncConfigProjection({ 'parameter_compaction.checkpoint_changes_invalid_before': 1 })
|
|
107
166
|
});
|
|
108
|
-
|
|
109
|
-
|
|
167
|
+
// Checkpoints are served from the single active config. A PROCESSING config in the same
|
|
168
|
+
// stream (incremental reprocessing) does not affect checkpoints until it is activated.
|
|
169
|
+
const syncConfigs = this.selectedSyncConfigs(doc).filter((config) => [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED].includes(config.state));
|
|
170
|
+
if (syncConfigs.length > 1) {
|
|
171
|
+
// Activation atomically replaces the previous active config, so this cannot happen unless
|
|
172
|
+
// the stored state is corrupt.
|
|
173
|
+
throw new ServiceAssertionError(`Expected a single active sync config, got ${syncConfigs.map((config) => config._id.toHexString()).join(', ')}`);
|
|
174
|
+
}
|
|
175
|
+
const syncConfig = syncConfigs[0];
|
|
176
|
+
if (syncConfig == null || !syncConfig.snapshot_done) {
|
|
110
177
|
return null;
|
|
111
178
|
}
|
|
112
179
|
return {
|
|
113
180
|
checkpoint: syncConfig.last_checkpoint ?? 0n,
|
|
114
|
-
lsn: syncConfig.last_checkpoint_lsn ?? null
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
|
|
119
|
-
projection: this.syncConfigProjection({ snapshot_lsn: 1 })
|
|
120
|
-
});
|
|
121
|
-
const syncConfig = this.selectedSyncConfig(doc);
|
|
122
|
-
const checkpointLsn = syncConfig?.last_checkpoint_lsn ?? null;
|
|
123
|
-
return {
|
|
124
|
-
lastCheckpointLsn: checkpointLsn,
|
|
125
|
-
resumeFromLsn: maxLsn(checkpointLsn, doc?.snapshot_lsn),
|
|
126
|
-
keepaliveOp: syncConfig?.keepalive_op ?? null,
|
|
127
|
-
syncConfigId: this.syncConfigId
|
|
181
|
+
lsn: syncConfig.last_checkpoint_lsn ?? null,
|
|
182
|
+
// Stream-level state: shared by all sync configs. Defaults to 0n for streams that have
|
|
183
|
+
// never been compacted.
|
|
184
|
+
parameterChangesInvalidBefore: doc?.parameter_compaction?.checkpoint_changes_invalid_before ?? 0n
|
|
128
185
|
};
|
|
129
186
|
}
|
|
130
187
|
async terminateSyncRuleState() {
|
|
131
188
|
await this.db.sync_rules.updateOne({
|
|
132
|
-
_id: this.
|
|
189
|
+
_id: this.replicationStreamId
|
|
133
190
|
}, {
|
|
134
191
|
$set: {
|
|
135
192
|
state: storage.SyncRuleState.TERMINATED,
|
|
@@ -140,43 +197,70 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
140
197
|
}
|
|
141
198
|
async getStatusImpl() {
|
|
142
199
|
const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
|
|
143
|
-
projection: this.syncConfigProjection({
|
|
200
|
+
projection: this.syncConfigProjection({ resume_lsn: 1 })
|
|
144
201
|
});
|
|
145
|
-
const
|
|
146
|
-
if (doc == null ||
|
|
202
|
+
const syncConfigs = this.selectedSyncConfigs(doc);
|
|
203
|
+
if (doc == null || syncConfigs.length == 0) {
|
|
147
204
|
throw new ServiceAssertionError('Cannot find replication stream status');
|
|
148
205
|
}
|
|
149
206
|
return {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
checkpoint_lsn: syncConfig.last_checkpoint_lsn ?? null,
|
|
154
|
-
keepalive_op: syncConfig.keepalive_op ?? null
|
|
207
|
+
snapshotDone: syncConfigs.every((config) => config.snapshot_done ?? false) &&
|
|
208
|
+
syncConfigs.every((config) => config.last_checkpoint_lsn != null),
|
|
209
|
+
resumeLsn: doc.resume_lsn ?? null
|
|
155
210
|
};
|
|
156
211
|
}
|
|
157
212
|
async clearSyncRuleState() {
|
|
158
|
-
|
|
213
|
+
// Clearing resets the entire replication stream (bucket data and the op sequence), so reset
|
|
214
|
+
// the checkpoint state for _all_ embedded sync configs, not only the ones selected for this
|
|
215
|
+
// storage instance. This maintains the invariant that no config has a last_checkpoint past
|
|
216
|
+
// the stream-level last_persisted_op.
|
|
217
|
+
await this.syncRulesCollection.updateOne({ _id: this.replicationStreamId }, {
|
|
159
218
|
$set: {
|
|
160
219
|
persisted_lsn: null,
|
|
161
|
-
'sync_configs.$[
|
|
162
|
-
'sync_configs.$[
|
|
163
|
-
'sync_configs.$[
|
|
164
|
-
'sync_configs.$[
|
|
165
|
-
'sync_configs.$[config].keepalive_op': null
|
|
220
|
+
'sync_configs.$[].snapshot_done': false,
|
|
221
|
+
'sync_configs.$[].last_checkpoint_lsn': null,
|
|
222
|
+
'sync_configs.$[].last_checkpoint': null,
|
|
223
|
+
'sync_configs.$[].no_checkpoint_before': null
|
|
166
224
|
},
|
|
167
225
|
$unset: {
|
|
168
|
-
|
|
226
|
+
resume_lsn: 1,
|
|
227
|
+
last_persisted_op: 1,
|
|
228
|
+
parameter_compaction: 1
|
|
169
229
|
}
|
|
170
230
|
}, {
|
|
171
|
-
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
|
|
172
|
-
arrayFilters: this.syncConfigArrayFilters()
|
|
231
|
+
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
|
|
173
232
|
});
|
|
174
233
|
}
|
|
234
|
+
/**
|
|
235
|
+
* The persisted mapping of the single sync config that read operations are served from.
|
|
236
|
+
*
|
|
237
|
+
* Reads always operate on a single sync config: read-path storage instances are
|
|
238
|
+
* constructed for the active sync config only (see MongoBucketStorage.getActiveSyncConfig),
|
|
239
|
+
* and checkpoints are served from the single active config (see fetchCheckpointState).
|
|
240
|
+
* Within a single sync config, unique names are the persistence key of its rule mapping,
|
|
241
|
+
* so its name-keyed {@link SingleSyncConfigBucketDefinitionMapping} resolves sources from
|
|
242
|
+
* any parse of that config unambiguously - no parsed-set identity is required.
|
|
243
|
+
*
|
|
244
|
+
* Throws on storage instances with multiple sync configs (replication-side instances),
|
|
245
|
+
* which must not serve reads.
|
|
246
|
+
*/
|
|
247
|
+
singleSyncConfigMapping() {
|
|
248
|
+
const content = this.replicationStream.syncConfigContent;
|
|
249
|
+
if (content.length != 1) {
|
|
250
|
+
throw new ServiceAssertionError(`Read operations require a storage instance with a single sync config, got ${content.length}`);
|
|
251
|
+
}
|
|
252
|
+
return content[0].mapping;
|
|
253
|
+
}
|
|
175
254
|
get versionContext() {
|
|
255
|
+
const self = this;
|
|
176
256
|
return {
|
|
177
257
|
db: this.db,
|
|
178
|
-
|
|
179
|
-
|
|
258
|
+
objectStorage: this.objectStorage,
|
|
259
|
+
replicationStreamId: this.replicationStreamId,
|
|
260
|
+
readPreference: this.readPreference,
|
|
261
|
+
get mapping() {
|
|
262
|
+
return self.singleSyncConfigMapping();
|
|
263
|
+
}
|
|
180
264
|
};
|
|
181
265
|
}
|
|
182
266
|
getParameterSetsImpl(checkpoint, lookups, limit) {
|
|
@@ -185,27 +269,42 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
185
269
|
getBucketDataBatchImpl(checkpoint, dataBuckets, options) {
|
|
186
270
|
return getBucketDataBatchV3(this.versionContext, checkpoint, dataBuckets, options);
|
|
187
271
|
}
|
|
188
|
-
async clearBucketData(
|
|
189
|
-
for (const collection of await this.db.
|
|
272
|
+
async clearBucketData(signal) {
|
|
273
|
+
for (const collection of await this.db.listBucketDataCollections(this.replicationStreamId)) {
|
|
190
274
|
await collection.drop();
|
|
191
275
|
}
|
|
276
|
+
const usage = new ObjectStorageUsage(this.db, this.replicationStreamId);
|
|
277
|
+
await this.db.client.withSession((session) => session.withTransaction(() => usage.removeStream(session)));
|
|
278
|
+
if (this.objectStorage) {
|
|
279
|
+
const lifecycle = new ObjectStorageLifecycle(this.db, this.replicationStreamId, this.objectStorage);
|
|
280
|
+
await lifecycle.deletePrefix(lifecycle.streamPrefix(), { signal });
|
|
281
|
+
}
|
|
282
|
+
await this.db
|
|
283
|
+
.pendingObjectStorageDeletes(this.replicationStreamId)
|
|
284
|
+
.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
|
|
285
|
+
.catch((error) => {
|
|
286
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
|
|
287
|
+
return;
|
|
288
|
+
}
|
|
289
|
+
throw error;
|
|
290
|
+
});
|
|
192
291
|
}
|
|
193
292
|
async clearParameterIndexes(_signal) {
|
|
194
|
-
for (const collection of await this.db.
|
|
293
|
+
for (const collection of await this.db.listParameterIndexCollections(this.replicationStreamId)) {
|
|
195
294
|
await collection.collection.drop();
|
|
196
295
|
}
|
|
197
296
|
}
|
|
198
297
|
async clearSourceRecords(_signal) {
|
|
199
|
-
for (const collection of await this.db.
|
|
298
|
+
for (const collection of await this.db.listSourceRecordCollections(this.replicationStreamId)) {
|
|
200
299
|
await collection.drop();
|
|
201
300
|
}
|
|
202
301
|
}
|
|
203
302
|
async clearBucketState(_signal) {
|
|
204
303
|
await this.db
|
|
205
|
-
.
|
|
304
|
+
.bucketState(this.replicationStreamId)
|
|
206
305
|
.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
|
|
207
306
|
.catch((error) => {
|
|
208
|
-
if (lib_mongo.
|
|
307
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
|
|
209
308
|
return;
|
|
210
309
|
}
|
|
211
310
|
throw error;
|
|
@@ -213,15 +312,38 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
213
312
|
}
|
|
214
313
|
async clearSourceTables(_signal) {
|
|
215
314
|
await this.db
|
|
216
|
-
.
|
|
315
|
+
.sourceTables(this.replicationStreamId)
|
|
217
316
|
.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
|
|
218
317
|
.catch((error) => {
|
|
219
|
-
if (lib_mongo.
|
|
318
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
|
|
220
319
|
return;
|
|
221
320
|
}
|
|
222
321
|
throw error;
|
|
223
322
|
});
|
|
224
323
|
}
|
|
324
|
+
async clearCustomCheckpointRequests(signal) {
|
|
325
|
+
for (const collection of await this.db.listCustomCheckpointRequestCollections(this.replicationStreamId)) {
|
|
326
|
+
signal?.throwIfAborted();
|
|
327
|
+
await collection.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }).catch((error) => {
|
|
328
|
+
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
throw error;
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
async cleanupStoppedSyncConfigs(options) {
|
|
336
|
+
return new MongoStoppedSyncConfigCleanup({
|
|
337
|
+
db: this.db,
|
|
338
|
+
replicationStreamId: this.replicationStreamId,
|
|
339
|
+
signal: options.signal,
|
|
340
|
+
logger: options.logger ?? this.logger,
|
|
341
|
+
defaultSchema: options.defaultSchema,
|
|
342
|
+
sourceConnectionTag: options.sourceConnectionTag,
|
|
343
|
+
objectStorage: this.objectStorage,
|
|
344
|
+
clearBatchThrottleRate: this.clearBatchThrottleRate
|
|
345
|
+
}).run();
|
|
346
|
+
}
|
|
225
347
|
getDataBucketChangesImpl(options) {
|
|
226
348
|
return getDataBucketChangesV3(this.versionContext, options);
|
|
227
349
|
}
|
|
@@ -234,8 +356,8 @@ export async function getParameterSetsV3(ctx, checkpoint, lookups, limit) {
|
|
|
234
356
|
setSessionSnapshotTime(session, checkpoint.snapshotTime);
|
|
235
357
|
const buildLookupPipeline = (lookup, index) => {
|
|
236
358
|
const indexId = lookup.indexId;
|
|
237
|
-
const collection = ctx.db.
|
|
238
|
-
const lookupFilter =
|
|
359
|
+
const collection = ctx.db.parameterIndex(ctx.replicationStreamId, indexId);
|
|
360
|
+
const lookupFilter = serializeParameterLookup(lookup);
|
|
239
361
|
return {
|
|
240
362
|
collection,
|
|
241
363
|
pipeline: [
|
|
@@ -313,13 +435,24 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
|
|
|
313
435
|
if (dataBuckets.length == 0) {
|
|
314
436
|
return;
|
|
315
437
|
}
|
|
316
|
-
if (checkpoint == null) {
|
|
438
|
+
if (checkpoint.checkpoint == null) {
|
|
317
439
|
throw new Error('checkpoint is null');
|
|
318
440
|
}
|
|
441
|
+
const readPreference = options?.requestHint == 'bulk' ? ctx.readPreference : undefined;
|
|
442
|
+
const readConcern = ctx.readPreference == null ? undefined : 'majority';
|
|
443
|
+
const session = readPreference == null || checkpoint.snapshotTime == null
|
|
444
|
+
? undefined
|
|
445
|
+
: ctx.db.client.startSession({ causalConsistency: true });
|
|
446
|
+
await using _ = { [Symbol.asyncDispose]: async () => session?.endSession() };
|
|
447
|
+
if (session != null) {
|
|
448
|
+
session.advanceOperationTime(checkpoint.snapshotTime);
|
|
449
|
+
session.advanceClusterTime(checkpoint.clusterTime);
|
|
450
|
+
}
|
|
319
451
|
const batchLimit = options?.limit ?? storage.DEFAULT_DOCUMENT_BATCH_LIMIT;
|
|
320
452
|
const chunkSizeLimitBytes = options?.chunkLimitBytes ?? storage.DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
|
|
321
|
-
const end = checkpoint;
|
|
322
|
-
|
|
453
|
+
const end = checkpoint.checkpoint;
|
|
454
|
+
// Group requests by definition, so that we can query each definition's bucket data in a single query.
|
|
455
|
+
// We only return the results of a single query per batch.
|
|
323
456
|
const requestsByDefinition = new Map();
|
|
324
457
|
for (const request of dataBuckets) {
|
|
325
458
|
const definitionId = ctx.mapping.bucketSourceId(request.source);
|
|
@@ -328,49 +461,67 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
|
|
|
328
461
|
requestsByDefinition.set(definitionId, requests);
|
|
329
462
|
}
|
|
330
463
|
const definitionGroups = Array.from(requestsByDefinition.entries());
|
|
331
|
-
for (
|
|
332
|
-
const [definitionId, requests] = definitionGroups[groupIndex];
|
|
464
|
+
for (const [groupIndex, [definitionId, requests]] of definitionGroups.entries()) {
|
|
333
465
|
const hasLaterDefinitionGroups = groupIndex < definitionGroups.length - 1;
|
|
334
466
|
const bucketMap = new Map(requests.map((request) => [request.bucket, request.start]));
|
|
335
467
|
const filters = Array.from(bucketMap.entries()).map(([bucket, start]) => ({
|
|
336
468
|
_id: {
|
|
337
|
-
$gt: {
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
b: bucket,
|
|
343
|
-
o: end
|
|
344
|
-
}
|
|
345
|
-
}
|
|
469
|
+
$gt: { b: bucket, o: start },
|
|
470
|
+
$lte: { b: bucket, o: new bson.MaxKey() }
|
|
471
|
+
},
|
|
472
|
+
min_op: { $lte: end }
|
|
473
|
+
// MongoDB Filter<T> doesn't accept compound _id ranges or dotted field paths in its type.
|
|
346
474
|
}));
|
|
347
|
-
const
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
475
|
+
const collection = ctx.db.bucketData(ctx.replicationStreamId, definitionId);
|
|
476
|
+
// MongoDB Filter<T> doesn't accept the $or operator in its type.
|
|
477
|
+
const filter = { $or: filters };
|
|
478
|
+
const context = { replicationStreamId: ctx.replicationStreamId, definitionId };
|
|
479
|
+
const cursorOptions = { limit: batchLimit, batchSize: batchLimit + 1 };
|
|
480
|
+
// raw: true returns Buffers, but the driver typing doesn't reflect that
|
|
481
|
+
// without an explicit cast to FindCursor<Buffer>.
|
|
482
|
+
const cursor = collection.find(filter, {
|
|
483
|
+
session,
|
|
484
|
+
readPreference,
|
|
485
|
+
readConcern,
|
|
351
486
|
sort: { _id: 1 },
|
|
352
|
-
limit: remainingLimit,
|
|
353
|
-
batchSize: remainingLimit + 1,
|
|
354
487
|
raw: true,
|
|
355
|
-
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
|
|
488
|
+
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS,
|
|
489
|
+
...cursorOptions
|
|
356
490
|
});
|
|
357
|
-
let { data, hasMore
|
|
491
|
+
let { data: rawData, hasMore } = await readSingleBatch(cursor).catch((e) => {
|
|
358
492
|
throw lib_mongo.mapQueryError(e, 'while reading bucket data');
|
|
359
493
|
});
|
|
360
|
-
if (
|
|
361
|
-
|
|
494
|
+
if (rawData.length >= cursorOptions.limit) {
|
|
495
|
+
hasMore = true;
|
|
362
496
|
}
|
|
363
|
-
|
|
364
|
-
|
|
497
|
+
// Deserialize the raw documents and cut the batch to a bounded size. Any data not
|
|
498
|
+
// making the cut will be read in the next round, using a fresh query.
|
|
499
|
+
const cutBatch = cutBucketDataBatch(rawData);
|
|
500
|
+
const docs = cutBatch.documents;
|
|
501
|
+
if (cutBatch.wasCut) {
|
|
502
|
+
hasMore = true;
|
|
365
503
|
}
|
|
366
|
-
|
|
367
|
-
|
|
504
|
+
// Hydrate any operations from object storage.
|
|
505
|
+
// In the future we can do this in a more pipelined fashion, but for now we hydrate
|
|
506
|
+
// the entire batch at once.
|
|
507
|
+
await hydrateBucketDataDocuments(docs, ctx.objectStorage, { signal: options?.signal, tracer: options?.tracer });
|
|
508
|
+
let currentChunkSizeBytes = 0;
|
|
368
509
|
let currentChunk = null;
|
|
369
510
|
let targetOp = null;
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
511
|
+
let seenBuckets = new Set();
|
|
512
|
+
const batchHasMore = hasMore;
|
|
513
|
+
for (const doc of docs) {
|
|
514
|
+
const bucket = doc._id.b;
|
|
515
|
+
seenBuckets.add(bucket);
|
|
516
|
+
const bucketStart = bucketMap.get(bucket);
|
|
517
|
+
if (bucketStart == null) {
|
|
518
|
+
throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
|
|
519
|
+
}
|
|
520
|
+
// Reached a new bucket or size limit: yield the current chunk and start a new one.
|
|
521
|
+
if (currentChunk == null ||
|
|
522
|
+
currentChunk.bucket != bucket ||
|
|
523
|
+
currentChunkSizeBytes >= chunkSizeLimitBytes ||
|
|
524
|
+
currentChunk.data.length >= batchLimit) {
|
|
374
525
|
let start = undefined;
|
|
375
526
|
if (currentChunk != null) {
|
|
376
527
|
if (currentChunk.bucket == bucket) {
|
|
@@ -379,16 +530,12 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
|
|
|
379
530
|
}
|
|
380
531
|
const yieldChunk = currentChunk;
|
|
381
532
|
currentChunk = null;
|
|
382
|
-
|
|
383
|
-
yield { chunkData: yieldChunk, targetOp
|
|
533
|
+
currentChunkSizeBytes = 0;
|
|
534
|
+
yield { chunkData: yieldChunk, targetOp };
|
|
384
535
|
targetOp = null;
|
|
385
536
|
}
|
|
386
537
|
if (start == null) {
|
|
387
|
-
|
|
388
|
-
if (startOpId == null) {
|
|
389
|
-
throw new Error(`data for unexpected bucket: ${bucket}`);
|
|
390
|
-
}
|
|
391
|
-
start = internalToExternalOpId(startOpId);
|
|
538
|
+
start = internalToExternalOpId(bucketStart);
|
|
392
539
|
}
|
|
393
540
|
currentChunk = {
|
|
394
541
|
bucket,
|
|
@@ -398,28 +545,52 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
|
|
|
398
545
|
next_after: start
|
|
399
546
|
};
|
|
400
547
|
}
|
|
401
|
-
const
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
currentChunk.next_after = entry.op_id;
|
|
407
|
-
chunkSizeBytes += rawData.byteLength;
|
|
548
|
+
const rows = extractRowsFromDocument(doc, context, bucketStart, end);
|
|
549
|
+
currentChunk.data.push(...rows.map(mapOpEntry));
|
|
550
|
+
currentChunk.next_after = currentChunk.data.at(-1)?.op_id ?? internalToExternalOpId(end);
|
|
551
|
+
targetOp = maxOpId(targetOp, doc.target_op);
|
|
552
|
+
currentChunkSizeBytes += doc.size;
|
|
408
553
|
}
|
|
409
554
|
if (currentChunk != null) {
|
|
410
555
|
const yieldChunk = currentChunk;
|
|
411
|
-
|
|
412
|
-
|
|
556
|
+
// The last chunk may contain more data that was cut in this batch.
|
|
557
|
+
yieldChunk.has_more = batchHasMore;
|
|
558
|
+
yield { chunkData: yieldChunk, targetOp };
|
|
559
|
+
}
|
|
560
|
+
if (!batchHasMore) {
|
|
561
|
+
for (const bucket of bucketMap.keys()) {
|
|
562
|
+
if (!seenBuckets.has(bucket)) {
|
|
563
|
+
// We processed everything for this definition group, but this bucket had no data in the batch.
|
|
564
|
+
// Yield an empty chunk to indicate that it is complete.
|
|
565
|
+
// This prevents re-querying the same bucket in the next batch.
|
|
566
|
+
yield {
|
|
567
|
+
chunkData: {
|
|
568
|
+
bucket,
|
|
569
|
+
after: internalToExternalOpId(bucketMap.get(bucket)),
|
|
570
|
+
has_more: false,
|
|
571
|
+
data: [],
|
|
572
|
+
next_after: internalToExternalOpId(end)
|
|
573
|
+
},
|
|
574
|
+
targetOp
|
|
575
|
+
};
|
|
576
|
+
}
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
if (currentChunk != null) {
|
|
580
|
+
// We yielded data in this group (aside from empty buckets).
|
|
581
|
+
// Return to the caller to allow them to process it before continuing to the next group.
|
|
582
|
+
yield { hasMore: batchHasMore || hasLaterDefinitionGroups };
|
|
583
|
+
break;
|
|
413
584
|
}
|
|
414
|
-
|
|
415
|
-
|
|
585
|
+
else {
|
|
586
|
+
// No data in this definition group - continue in the next group.
|
|
416
587
|
}
|
|
417
588
|
}
|
|
418
589
|
}
|
|
419
590
|
export async function getDataBucketChangesV3(ctx, options) {
|
|
420
591
|
const limit = 1000;
|
|
421
592
|
const bucketStateUpdates = await ctx.db
|
|
422
|
-
.
|
|
593
|
+
.bucketState(ctx.replicationStreamId)
|
|
423
594
|
.aggregate([
|
|
424
595
|
{
|
|
425
596
|
$match: {
|
|
@@ -449,12 +620,20 @@ export async function getDataBucketChangesV3(ctx, options) {
|
|
|
449
620
|
updatedDataBuckets: invalidateDataBuckets ? new Set() : new Set(buckets)
|
|
450
621
|
};
|
|
451
622
|
}
|
|
623
|
+
/**
|
|
624
|
+
* Query the parameter entries changed between the two checkpoints, to determine which parameter
|
|
625
|
+
* lookups need to be re-evaluated.
|
|
626
|
+
*
|
|
627
|
+
* This runs at the next checkpoint's snapshot, so it still sees entries that parameter compaction
|
|
628
|
+
* deleted after that snapshot. Compaction that deleted entries before the snapshot is covered by
|
|
629
|
+
* the invalidation fence, checked before we get here.
|
|
630
|
+
*/
|
|
452
631
|
export async function getParameterBucketChangesV3(ctx, options) {
|
|
453
632
|
const limit = 1000;
|
|
454
633
|
const indexIds = ctx.mapping.allParameterIndexIds();
|
|
455
634
|
const collections = indexIds.map((indexId) => ({
|
|
456
635
|
indexId,
|
|
457
|
-
collection: ctx.db.
|
|
636
|
+
collection: ctx.db.parameterIndex(ctx.replicationStreamId, indexId)
|
|
458
637
|
}));
|
|
459
638
|
if (collections.length == 0) {
|
|
460
639
|
return {
|
|
@@ -478,31 +657,44 @@ export async function getParameterBucketChangesV3(ctx, options) {
|
|
|
478
657
|
}
|
|
479
658
|
];
|
|
480
659
|
const [firstCollection, ...remainingCollections] = collections;
|
|
481
|
-
const parameterUpdates = await
|
|
482
|
-
.
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
660
|
+
const parameterUpdates = await ctx.db.client.withSession({ snapshot: true }, async (session) => {
|
|
661
|
+
setSessionSnapshotTime(session, options.nextCheckpoint.snapshotTime);
|
|
662
|
+
return await firstCollection.collection
|
|
663
|
+
.aggregate([
|
|
664
|
+
...pipelineForCollection(firstCollection.indexId),
|
|
665
|
+
...remainingCollections.map((collection) => {
|
|
666
|
+
return {
|
|
667
|
+
$unionWith: {
|
|
668
|
+
coll: collection.collection.collectionName,
|
|
669
|
+
pipeline: pipelineForCollection(collection.indexId)
|
|
670
|
+
}
|
|
671
|
+
};
|
|
672
|
+
}),
|
|
673
|
+
{
|
|
674
|
+
$limit: limit + 1
|
|
675
|
+
}
|
|
676
|
+
], {
|
|
677
|
+
session,
|
|
678
|
+
readConcern: 'snapshot',
|
|
679
|
+
batchSize: limit + 2,
|
|
680
|
+
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
|
|
681
|
+
})
|
|
682
|
+
.toArray()
|
|
683
|
+
.catch((e) => {
|
|
684
|
+
// Includes the case where the checkpoint snapshot has expired. Degrading to
|
|
685
|
+
// invalidateParameterBuckets would be safe in itself - it reads nothing - but it gains
|
|
686
|
+
// nothing: the caller responds to that by re-evaluating the parameter queries at this
|
|
687
|
+
// same snapshot, which fails too. The checkpoint has to be refetched instead, which is
|
|
688
|
+
// what the existing sync retry behavior does.
|
|
689
|
+
throw lib_mongo.mapQueryError(e, 'while querying parameter changes');
|
|
690
|
+
});
|
|
691
|
+
});
|
|
500
692
|
const invalidateParameterUpdates = parameterUpdates.length > limit;
|
|
501
693
|
return {
|
|
502
694
|
invalidateParameterBuckets: invalidateParameterUpdates,
|
|
503
695
|
updatedParameterLookups: invalidateParameterUpdates
|
|
504
696
|
? new Set()
|
|
505
|
-
: new Set(parameterUpdates.map((p) => JSONBig.stringify(
|
|
697
|
+
: new Set(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup, p.indexId))))
|
|
506
698
|
};
|
|
507
699
|
}
|
|
508
700
|
//# sourceMappingURL=MongoSyncBucketStorageV3.js.map
|