@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,20 +1,100 @@
|
|
|
1
|
-
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
-
import {
|
|
1
|
+
import { mongo, MONGO_OPERATION_TIMEOUT_MS } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { ReplicationAssertionError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import {
|
|
4
|
+
addChecksums,
|
|
5
|
+
CompactInitialReplicationResults,
|
|
6
|
+
InternalOpId,
|
|
7
|
+
isPartialChecksum,
|
|
8
|
+
storage,
|
|
9
|
+
SyncRuleState,
|
|
10
|
+
utils
|
|
11
|
+
} from '@powersync/service-core';
|
|
4
12
|
import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
5
|
-
import {
|
|
13
|
+
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
6
14
|
import { BucketStateDocumentBase, LEGACY_BUCKET_DATA_DEFINITION_ID } from '../models.js';
|
|
7
|
-
import {
|
|
8
|
-
import {
|
|
15
|
+
import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
|
|
16
|
+
import { cacheKey } from '../OperationBatch.js';
|
|
17
|
+
import { BucketDataDocumentV1, BucketStateDocumentV1 } from './models.js';
|
|
9
18
|
import type { MongoSyncBucketStorageV1 } from './MongoSyncBucketStorageV1.js';
|
|
10
19
|
import { SingleBucketStoreV1 } from './SingleBucketStoreV1.js';
|
|
11
20
|
import { VersionedPowerSyncMongoV1 } from './VersionedPowerSyncMongoV1.js';
|
|
12
21
|
|
|
22
|
+
type CompactClearProperties = 'op' | 'checksum' | 'target_op';
|
|
23
|
+
|
|
24
|
+
const DEFAULT_MIN_BUCKET_CHANGES = 10;
|
|
25
|
+
const DEFAULT_MIN_CHANGE_RATIO = 0.1;
|
|
26
|
+
const DIRTY_BUCKET_SCAN_BATCH_SIZE = 2_000;
|
|
27
|
+
|
|
28
|
+
interface CurrentBucketState {
|
|
29
|
+
bucket: string;
|
|
30
|
+
definitionId: BucketDefinitionId;
|
|
31
|
+
seen: Map<string, InternalOpId>;
|
|
32
|
+
trackingSize: number;
|
|
33
|
+
lastNotPut: InternalOpId | null;
|
|
34
|
+
opsSincePut: number;
|
|
35
|
+
checksum: number;
|
|
36
|
+
opCount: number;
|
|
37
|
+
opBytes: number;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
interface DirtyBucket {
|
|
41
|
+
bucket: string;
|
|
42
|
+
definitionId: BucketDefinitionId | null;
|
|
43
|
+
estimatedCount: number;
|
|
44
|
+
dirtyRatio?: number;
|
|
45
|
+
}
|
|
46
|
+
|
|
13
47
|
export class MongoCompactorV1 extends MongoCompactor {
|
|
14
48
|
// Override types to the more specific ones
|
|
15
49
|
declare protected readonly db: VersionedPowerSyncMongoV1;
|
|
16
50
|
declare protected readonly storage: MongoSyncBucketStorageV1;
|
|
17
51
|
|
|
52
|
+
private updates: mongo.AnyBulkWriteOperation<BucketDataDocumentV1>[] = [];
|
|
53
|
+
private bucketStateUpdates: mongo.AnyBulkWriteOperation<BucketStateDocumentBase>[] = [];
|
|
54
|
+
private readonly minBucketChanges: number;
|
|
55
|
+
private readonly minChangeRatio: number;
|
|
56
|
+
private readonly maxOpId: bigint;
|
|
57
|
+
|
|
58
|
+
constructor(
|
|
59
|
+
bucketStorage: MongoSyncBucketStorageV1,
|
|
60
|
+
db: VersionedPowerSyncMongoV1,
|
|
61
|
+
private options: MongoCompactOptions
|
|
62
|
+
) {
|
|
63
|
+
super(bucketStorage, db, options);
|
|
64
|
+
this.minBucketChanges = options.minBucketChanges ?? DEFAULT_MIN_BUCKET_CHANGES;
|
|
65
|
+
this.minChangeRatio = options.minChangeRatio ?? DEFAULT_MIN_CHANGE_RATIO;
|
|
66
|
+
this.maxOpId = options.maxOpId ?? 0n;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Compact buckets by converting operations into MOVE and/or CLEAR operations.
|
|
71
|
+
*
|
|
72
|
+
* See /docs/storage/compacting-operations.md for details.
|
|
73
|
+
*/
|
|
74
|
+
override async compact(): Promise<number> {
|
|
75
|
+
if (this.options?.incrementalOnly) {
|
|
76
|
+
// Not supported for V1
|
|
77
|
+
this.logger.info('Incremental compacting is not supported on MongoDB storage V1/V2');
|
|
78
|
+
return 0;
|
|
79
|
+
} else if (this.storage.replicationStream.state != SyncRuleState.ACTIVE) {
|
|
80
|
+
this.logger.info(`Skipping compacting of replication stream in ${this.storage.replicationStream.state} state.`);
|
|
81
|
+
return 0;
|
|
82
|
+
}
|
|
83
|
+
await this.deleteOldCheckpointRequests();
|
|
84
|
+
|
|
85
|
+
if (this.buckets) {
|
|
86
|
+
for (const bucket of this.buckets) {
|
|
87
|
+
// We can make this more efficient later on by iterating through the buckets in a single query.
|
|
88
|
+
// That makes batching more tricky, so we leave for later.
|
|
89
|
+
await this.compactSingleBucketRetried(bucket);
|
|
90
|
+
}
|
|
91
|
+
} else {
|
|
92
|
+
await this.compactDirtyBuckets();
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
return this.compactedBucketCount;
|
|
96
|
+
}
|
|
97
|
+
|
|
18
98
|
public async *dirtyBucketBatches(options: {
|
|
19
99
|
minBucketChanges: number;
|
|
20
100
|
minChangeRatio: number;
|
|
@@ -29,8 +109,7 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
29
109
|
this.db.bucketStateV1,
|
|
30
110
|
{ g: this.group_id, b: new mongo.MinKey() as any },
|
|
31
111
|
{ g: this.group_id, b: new mongo.MaxKey() as any },
|
|
32
|
-
options
|
|
33
|
-
() => null
|
|
112
|
+
options
|
|
34
113
|
);
|
|
35
114
|
}
|
|
36
115
|
|
|
@@ -40,14 +119,225 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
40
119
|
}
|
|
41
120
|
// Unlike dirtyBucketBatches, this path is resumable after restart because populateChecksums resets
|
|
42
121
|
// estimate_since_compact as it progresses.
|
|
43
|
-
return this.dirtyBucketBatchForChecksumsForCollection(
|
|
44
|
-
this.
|
|
45
|
-
{
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
122
|
+
return this.dirtyBucketBatchForChecksumsForCollection({
|
|
123
|
+
'_id.g': this.group_id,
|
|
124
|
+
'estimate_since_compact.count': { $gte: options.minBucketChanges }
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
private async *dirtyBucketBatchesForCollection<TBucketState extends BucketStateDocumentBase>(
|
|
129
|
+
collection: mongo.Collection<TBucketState>,
|
|
130
|
+
lastId: TBucketState['_id'],
|
|
131
|
+
maxId: TBucketState['_id'],
|
|
132
|
+
options: { minBucketChanges: number; minChangeRatio: number }
|
|
133
|
+
): AsyncGenerator<DirtyBucket[]> {
|
|
134
|
+
// Paginate through the bucket state collection using cursor-based scanning.
|
|
135
|
+
while (true) {
|
|
136
|
+
// To avoid timeouts from too many buckets not meeting the minimum-change
|
|
137
|
+
// criteria, scan a fixed batch and only return matching buckets.
|
|
138
|
+
const [result] = await collection
|
|
139
|
+
.aggregate<{ buckets: TBucketState[]; cursor: Pick<TBucketState, '_id'>[] }>(
|
|
140
|
+
[
|
|
141
|
+
{ $match: { _id: { $gt: lastId, $lt: maxId } } },
|
|
142
|
+
{ $sort: { _id: 1 } },
|
|
143
|
+
// Scan a fixed number of docs each query so sparse matches don't block progress.
|
|
144
|
+
{ $limit: DIRTY_BUCKET_SCAN_BATCH_SIZE },
|
|
145
|
+
{
|
|
146
|
+
$facet: {
|
|
147
|
+
buckets: [
|
|
148
|
+
{ $match: { 'estimate_since_compact.count': { $gte: options.minBucketChanges } } },
|
|
149
|
+
{ $project: { _id: 1, estimate_since_compact: 1, compacted_state: 1 } }
|
|
150
|
+
],
|
|
151
|
+
cursor: [{ $sort: { _id: -1 } }, { $limit: 1 }, { $project: { _id: 1 } }]
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
],
|
|
155
|
+
{ maxTimeMS: MONGO_OPERATION_TIMEOUT_MS }
|
|
156
|
+
)
|
|
157
|
+
.toArray();
|
|
158
|
+
|
|
159
|
+
const cursor = result?.cursor?.[0];
|
|
160
|
+
if (cursor == null) {
|
|
161
|
+
break;
|
|
162
|
+
}
|
|
163
|
+
lastId = cursor._id;
|
|
164
|
+
|
|
165
|
+
const dirtyBuckets = (result?.buckets ?? []).map((bucketState) => {
|
|
166
|
+
// BigInt precision is not needed here since this is only an estimate.
|
|
167
|
+
const updatedCount = bucketState.estimate_since_compact?.count ?? 0;
|
|
168
|
+
const totalCount = (bucketState.compacted_state?.count ?? 0) + updatedCount;
|
|
169
|
+
const updatedBytes = Number(bucketState.estimate_since_compact?.bytes ?? 0);
|
|
170
|
+
const totalBytes = Number(bucketState.compacted_state?.bytes ?? 0) + updatedBytes;
|
|
171
|
+
return {
|
|
172
|
+
bucket: bucketState._id.b,
|
|
173
|
+
definitionId: null,
|
|
174
|
+
estimatedCount: totalCount,
|
|
175
|
+
dirtyRatio: Math.max(
|
|
176
|
+
totalCount > 0 ? updatedCount / totalCount : 0,
|
|
177
|
+
totalBytes > 0 ? updatedBytes / totalBytes : 0
|
|
178
|
+
)
|
|
179
|
+
};
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
yield dirtyBuckets.filter(
|
|
183
|
+
(bucket) => bucket.estimatedCount >= options.minBucketChanges && bucket.dirtyRatio >= options.minChangeRatio
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
private async dirtyBucketBatchForChecksumsForCollection(
|
|
189
|
+
filter: mongo.Filter<BucketStateDocumentV1>
|
|
190
|
+
): Promise<DirtyBucket[]> {
|
|
191
|
+
const dirtyBuckets = await this.db.bucketStateV1
|
|
192
|
+
.find(filter, {
|
|
193
|
+
projection: { _id: 1, estimate_since_compact: 1, compacted_state: 1 },
|
|
194
|
+
sort: { 'estimate_since_compact.count': -1 },
|
|
195
|
+
limit: 200,
|
|
196
|
+
maxTimeMS: MONGO_OPERATION_TIMEOUT_MS
|
|
197
|
+
})
|
|
198
|
+
.toArray();
|
|
199
|
+
|
|
200
|
+
return dirtyBuckets.map((bucket) => ({
|
|
201
|
+
bucket: bucket._id.b,
|
|
202
|
+
definitionId: null,
|
|
203
|
+
estimatedCount: Number(bucket.estimate_since_compact!.count) + Number(bucket.compacted_state?.count ?? 0)
|
|
204
|
+
}));
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
private async compactSingleBucketRetried(bucket: string, _definitionId: BucketDefinitionId | null = null) {
|
|
208
|
+
await this.retryCompaction(bucket, () => this.compactSingleBucket(bucket));
|
|
209
|
+
this.compactedBucketCount++;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
private async compactDirtyBuckets() {
|
|
213
|
+
for await (const buckets of this.dirtyBucketBatches({
|
|
214
|
+
minBucketChanges: this.minBucketChanges,
|
|
215
|
+
minChangeRatio: this.minChangeRatio
|
|
216
|
+
})) {
|
|
217
|
+
this.signal?.throwIfAborted();
|
|
218
|
+
for (const { bucket, definitionId } of buckets) {
|
|
219
|
+
await this.compactSingleBucketRetried(bucket, definitionId);
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Subset of compact, only populating checksums where relevant.
|
|
226
|
+
*/
|
|
227
|
+
async populateChecksums(options: { minBucketChanges: number }): Promise<CompactInitialReplicationResults> {
|
|
228
|
+
let count = 0;
|
|
229
|
+
// Paginate through dirty buckets in batches until no more buckets meet the criteria.
|
|
230
|
+
while (true) {
|
|
231
|
+
this.signal?.throwIfAborted();
|
|
232
|
+
const buckets = await this.dirtyBucketBatchForChecksums(options);
|
|
233
|
+
if (buckets.length == 0) {
|
|
234
|
+
break;
|
|
235
|
+
}
|
|
236
|
+
this.signal?.throwIfAborted();
|
|
237
|
+
|
|
238
|
+
const start = Date.now();
|
|
239
|
+
// Filter batch by estimated bucket size, to reduce possibility of timeouts.
|
|
240
|
+
const checkBuckets: typeof buckets = [];
|
|
241
|
+
let totalCountEstimate = 0;
|
|
242
|
+
for (const bucket of buckets) {
|
|
243
|
+
checkBuckets.push(bucket);
|
|
244
|
+
totalCountEstimate += bucket.estimatedCount;
|
|
245
|
+
if (totalCountEstimate > 50_000) {
|
|
246
|
+
break;
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
this.logger.info(
|
|
250
|
+
`Calculating checksums for batch of ${buckets.length} buckets, estimated count of ${totalCountEstimate}`
|
|
251
|
+
);
|
|
252
|
+
await this.updateChecksumsBatch(checkBuckets);
|
|
253
|
+
this.logger.info(`Updated checksums for batch of ${checkBuckets.length} buckets in ${Date.now() - start}ms`);
|
|
254
|
+
count += checkBuckets.length;
|
|
255
|
+
}
|
|
256
|
+
return { buckets: count };
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
private collectBucketStateUpdate(
|
|
260
|
+
state: CurrentBucketState,
|
|
261
|
+
compactedOpId: bigint
|
|
262
|
+
): mongo.AnyBulkWriteOperation<BucketStateDocumentBase> {
|
|
263
|
+
if (state.opCount < 0) {
|
|
264
|
+
throw new ServiceAssertionError(
|
|
265
|
+
`Invalid opCount: ${state.opCount} checksum ${state.checksum} opsSincePut: ${state.opsSincePut} maxOpId: ${this.maxOpId}`
|
|
266
|
+
);
|
|
267
|
+
}
|
|
268
|
+
return {
|
|
269
|
+
updateOne: {
|
|
270
|
+
filter: this.bucketStateFilter(state.bucket),
|
|
271
|
+
update: {
|
|
272
|
+
$set: {
|
|
273
|
+
compacted_state: {
|
|
274
|
+
op_id: compactedOpId,
|
|
275
|
+
count: state.opCount,
|
|
276
|
+
checksum: BigInt(state.checksum),
|
|
277
|
+
bytes: state.opBytes
|
|
278
|
+
},
|
|
279
|
+
estimate_since_compact: {
|
|
280
|
+
// There could have been a whole bunch of new operations added to the bucket while compacting,
|
|
281
|
+
// which we don't currently cater for. We could potentially query for that, but that adds overhead.
|
|
282
|
+
count: 0,
|
|
283
|
+
bytes: 0
|
|
284
|
+
}
|
|
285
|
+
}
|
|
286
|
+
} satisfies mongo.UpdateFilter<BucketStateDocumentBase>,
|
|
287
|
+
// We generally expect this to have been created before.
|
|
288
|
+
// We don't create new ones here, to avoid issues with the unique index on bucket_updates.
|
|
289
|
+
upsert: false
|
|
290
|
+
}
|
|
291
|
+
};
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
private updateBucketChecksums(state: CurrentBucketState, compactedOpId: bigint) {
|
|
295
|
+
this.bucketStateUpdates.push(this.collectBucketStateUpdate(state, compactedOpId));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
private async flushBucketStateUpdates() {
|
|
299
|
+
if (this.bucketStateUpdates.length > 0) {
|
|
300
|
+
this.logger.info(`Updating ${this.bucketStateUpdates.length} bucket states`);
|
|
301
|
+
await this.writeBucketStateUpdates();
|
|
302
|
+
this.bucketStateUpdates = [];
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
private async updateChecksumsBatch(buckets: Pick<DirtyBucket, 'bucket' | 'definitionId'>[]) {
|
|
307
|
+
const checksums = await this.computeChecksumsForBuckets(buckets);
|
|
308
|
+
const definitionIdByBucket = new Map(buckets.map((bucket) => [bucket.bucket, bucket.definitionId]));
|
|
309
|
+
|
|
310
|
+
for (const bucketChecksum of checksums.values()) {
|
|
311
|
+
if (isPartialChecksum(bucketChecksum)) {
|
|
312
|
+
// Should never happen since we don't specify `start`.
|
|
313
|
+
throw new ServiceAssertionError(`Full checksum expected, got ${JSON.stringify(bucketChecksum)}`);
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
this.bucketStateUpdates.push({
|
|
317
|
+
updateOne: {
|
|
318
|
+
filter: this.bucketStateFilter(bucketChecksum.bucket),
|
|
319
|
+
update: {
|
|
320
|
+
$set: {
|
|
321
|
+
compacted_state: {
|
|
322
|
+
op_id: this.maxOpId,
|
|
323
|
+
count: bucketChecksum.count,
|
|
324
|
+
checksum: BigInt(bucketChecksum.checksum),
|
|
325
|
+
bytes: null
|
|
326
|
+
},
|
|
327
|
+
estimate_since_compact: {
|
|
328
|
+
count: 0,
|
|
329
|
+
bytes: 0
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} satisfies mongo.UpdateFilter<BucketStateDocumentBase>,
|
|
333
|
+
// We don't create new ones here - it gets tricky to get the last_op right with the unique index on
|
|
334
|
+
// bucket_updates.
|
|
335
|
+
upsert: false
|
|
336
|
+
}
|
|
337
|
+
});
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
await this.flushBucketStateUpdates();
|
|
51
341
|
}
|
|
52
342
|
|
|
53
343
|
protected async writeBucketStateUpdates(): Promise<void> {
|
|
@@ -64,14 +354,12 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
64
354
|
buckets.map(({ bucket }) => ({
|
|
65
355
|
bucket,
|
|
66
356
|
end: this.maxOpId
|
|
67
|
-
}))
|
|
357
|
+
})),
|
|
358
|
+
{ readOptions: { readConcern: 'snapshot' } }
|
|
68
359
|
);
|
|
69
360
|
}
|
|
70
361
|
|
|
71
|
-
|
|
72
|
-
bucket: string,
|
|
73
|
-
_definitionId: BucketDefinitionId | null
|
|
74
|
-
): mongo.Filter<BucketStateDocumentBase> {
|
|
362
|
+
private bucketStateFilter(bucket: string): mongo.Filter<BucketStateDocumentBase> {
|
|
75
363
|
return {
|
|
76
364
|
_id: {
|
|
77
365
|
g: this.group_id,
|
|
@@ -80,14 +368,295 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
80
368
|
};
|
|
81
369
|
}
|
|
82
370
|
|
|
83
|
-
|
|
84
|
-
bucket: string,
|
|
85
|
-
_definitionId: BucketDefinitionId | null
|
|
86
|
-
): Promise<SingleBucketStore | null> {
|
|
371
|
+
private getBucketDataContext(bucket: string): SingleBucketStoreV1 {
|
|
87
372
|
return new SingleBucketStoreV1(this.db, {
|
|
88
373
|
replicationStreamId: this.group_id,
|
|
89
374
|
definitionId: LEGACY_BUCKET_DATA_DEFINITION_ID,
|
|
90
375
|
bucket
|
|
91
376
|
});
|
|
92
377
|
}
|
|
378
|
+
|
|
379
|
+
protected async compactSingleBucket(bucket: string) {
|
|
380
|
+
// Do not carry queued writes from a failed attempt into the rescan.
|
|
381
|
+
this.updates = [];
|
|
382
|
+
this.bucketStateUpdates = [];
|
|
383
|
+
|
|
384
|
+
const idLimitBytes = this.idLimitBytes;
|
|
385
|
+
const bucketContext = this.getBucketDataContext(bucket);
|
|
386
|
+
const currentState: CurrentBucketState = {
|
|
387
|
+
bucket,
|
|
388
|
+
definitionId: bucketContext.key.definitionId,
|
|
389
|
+
seen: new Map(),
|
|
390
|
+
trackingSize: 0,
|
|
391
|
+
lastNotPut: null,
|
|
392
|
+
opsSincePut: 0,
|
|
393
|
+
checksum: 0,
|
|
394
|
+
opCount: 0,
|
|
395
|
+
opBytes: 0
|
|
396
|
+
};
|
|
397
|
+
|
|
398
|
+
// Constant lower bound.
|
|
399
|
+
const lowerBound = bucketContext.minId;
|
|
400
|
+
// Upper bound is adjusted for each batch.
|
|
401
|
+
let upperBound = bucketContext.maxId;
|
|
402
|
+
|
|
403
|
+
// Paginate through bucket data in batches to avoid cursor timeouts.
|
|
404
|
+
while (true) {
|
|
405
|
+
this.signal?.throwIfAborted();
|
|
406
|
+
|
|
407
|
+
// Query one batch at a time, to avoid cursor timeouts.
|
|
408
|
+
const pipeline = [
|
|
409
|
+
{
|
|
410
|
+
$match: {
|
|
411
|
+
_id: {
|
|
412
|
+
$gte: lowerBound,
|
|
413
|
+
$lt: upperBound
|
|
414
|
+
}
|
|
415
|
+
}
|
|
416
|
+
},
|
|
417
|
+
{ $sort: { _id: -1 } },
|
|
418
|
+
{ $limit: this.moveBatchQueryLimit },
|
|
419
|
+
{
|
|
420
|
+
$project: {
|
|
421
|
+
_id: 1,
|
|
422
|
+
op: 1,
|
|
423
|
+
table: 1,
|
|
424
|
+
row_id: 1,
|
|
425
|
+
source_table: 1,
|
|
426
|
+
source_key: 1,
|
|
427
|
+
checksum: 1,
|
|
428
|
+
size: { $bsonSize: '$$ROOT' }
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
];
|
|
432
|
+
|
|
433
|
+
const cursor = bucketContext.collection.aggregate<BucketDataDocumentV1 & { size: number | bigint }>(pipeline, {
|
|
434
|
+
// batchSize is 1 more than limit to auto-close the cursor.
|
|
435
|
+
// See https://github.com/mongodb/node-mongodb-native/pull/4580
|
|
436
|
+
batchSize: this.moveBatchQueryLimit + 1
|
|
437
|
+
});
|
|
438
|
+
// We don't limit to a single batch here, since that often causes MongoDB to scan through more than it returns.
|
|
439
|
+
// Instead, we load up to the limit.
|
|
440
|
+
const rawBatch = await cursor.toArray();
|
|
441
|
+
const batch = rawBatch.map((document) => {
|
|
442
|
+
const { size, ...rest } = document;
|
|
443
|
+
return {
|
|
444
|
+
doc: bucketContext.fromPersistedDocument(rest),
|
|
445
|
+
size
|
|
446
|
+
};
|
|
447
|
+
});
|
|
448
|
+
|
|
449
|
+
if (batch.length == 0) {
|
|
450
|
+
// We've reached the end.
|
|
451
|
+
break;
|
|
452
|
+
}
|
|
453
|
+
|
|
454
|
+
// Reuse the exact collection _id value from Mongo for the next bound.
|
|
455
|
+
upperBound = rawBatch[rawBatch.length - 1]._id;
|
|
456
|
+
|
|
457
|
+
for (const { doc, size } of batch) {
|
|
458
|
+
if (doc.o > this.maxOpId) {
|
|
459
|
+
continue;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
currentState.checksum = addChecksums(currentState.checksum, Number(doc.checksum));
|
|
463
|
+
currentState.opCount += 1;
|
|
464
|
+
|
|
465
|
+
let isPersistentPut = doc.op == 'PUT';
|
|
466
|
+
|
|
467
|
+
currentState.opBytes += Number(size);
|
|
468
|
+
if (doc.op == 'REMOVE' || doc.op == 'PUT') {
|
|
469
|
+
const key = `${doc.table}/${doc.row_id}/${cacheKey(doc.source_table!, doc.source_key!)}`;
|
|
470
|
+
const targetOp = currentState.seen.get(key);
|
|
471
|
+
if (targetOp) {
|
|
472
|
+
// Will convert to MOVE, so don't count as PUT.
|
|
473
|
+
isPersistentPut = false;
|
|
474
|
+
|
|
475
|
+
this.updates.push({
|
|
476
|
+
updateOne: {
|
|
477
|
+
filter: { _id: bucketContext.docId(doc.o) },
|
|
478
|
+
update: {
|
|
479
|
+
$set: {
|
|
480
|
+
op: 'MOVE',
|
|
481
|
+
target_op: targetOp
|
|
482
|
+
},
|
|
483
|
+
$unset: {
|
|
484
|
+
source_table: 1,
|
|
485
|
+
source_key: 1,
|
|
486
|
+
subkey: 1,
|
|
487
|
+
table: 1,
|
|
488
|
+
row_id: 1,
|
|
489
|
+
data: 1
|
|
490
|
+
}
|
|
491
|
+
} satisfies mongo.UpdateFilter<BucketDataDocumentV1>
|
|
492
|
+
}
|
|
493
|
+
});
|
|
494
|
+
|
|
495
|
+
// TODO: better estimate for this.
|
|
496
|
+
currentState.opBytes += 200 - Number(size);
|
|
497
|
+
} else if (currentState.trackingSize < idLimitBytes) {
|
|
498
|
+
// flatstr reduces the memory usage by flattening the string.
|
|
499
|
+
currentState.seen.set(utils.flatstr(key), doc.o);
|
|
500
|
+
// length + 16 for the string
|
|
501
|
+
// 24 for the bigint
|
|
502
|
+
// 50 for map overhead
|
|
503
|
+
// 50 for additional overhead
|
|
504
|
+
currentState.trackingSize += key.length + 140;
|
|
505
|
+
}
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
if (isPersistentPut) {
|
|
509
|
+
currentState.lastNotPut = null;
|
|
510
|
+
currentState.opsSincePut = 0;
|
|
511
|
+
} else if (doc.op != 'CLEAR') {
|
|
512
|
+
if (currentState.lastNotPut == null) {
|
|
513
|
+
currentState.lastNotPut = doc.o;
|
|
514
|
+
}
|
|
515
|
+
currentState.opsSincePut += 1;
|
|
516
|
+
}
|
|
517
|
+
|
|
518
|
+
if (this.updates.length + this.bucketStateUpdates.length >= this.moveBatchLimit) {
|
|
519
|
+
await this.flush(bucketContext);
|
|
520
|
+
}
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
this.logger.info(`Processed batch of length ${batch.length} current bucket: ${bucket}`);
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
// Free memory before clearing the bucket.
|
|
527
|
+
currentState.seen.clear();
|
|
528
|
+
if (currentState.lastNotPut != null && currentState.opsSincePut >= 1) {
|
|
529
|
+
this.logger.info(
|
|
530
|
+
`Inserting CLEAR at ${this.group_id}:${bucket}:${currentState.lastNotPut} to remove ${currentState.opsSincePut} operations`
|
|
531
|
+
);
|
|
532
|
+
// Need flush() before clear().
|
|
533
|
+
await this.flush(bucketContext);
|
|
534
|
+
await this.clearBucket(currentState, bucketContext);
|
|
535
|
+
}
|
|
536
|
+
|
|
537
|
+
// Do this after clearBucket so we have accurate counts.
|
|
538
|
+
this.updateBucketChecksums(currentState, this.maxOpId);
|
|
539
|
+
// Need another flush after updateBucketChecksums().
|
|
540
|
+
await this.flush(bucketContext);
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
private async flush(bucketContext: SingleBucketStoreV1) {
|
|
544
|
+
if (this.updates.length > 0) {
|
|
545
|
+
this.logger.info(`Compacting ${this.updates.length} ops`);
|
|
546
|
+
await bucketContext.collection.bulkWrite(this.updates, {
|
|
547
|
+
// Order is not important. Since checksums are not affected, these operations can happen in any order,
|
|
548
|
+
// and it's fine if the operations are partially applied. Each individual operation is atomic.
|
|
549
|
+
ordered: false
|
|
550
|
+
});
|
|
551
|
+
this.updates = [];
|
|
552
|
+
}
|
|
553
|
+
|
|
554
|
+
await this.flushBucketStateUpdates();
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
/**
|
|
558
|
+
* Perform a CLEAR compact for a bucket.
|
|
559
|
+
*
|
|
560
|
+
* @param currentState tracks the last non-PUT op, which will be converted to CLEAR.
|
|
561
|
+
*/
|
|
562
|
+
private async clearBucket(currentState: CurrentBucketState, bucketContext: SingleBucketStoreV1) {
|
|
563
|
+
const clearOp = currentState.lastNotPut!;
|
|
564
|
+
|
|
565
|
+
const opFilter = {
|
|
566
|
+
_id: {
|
|
567
|
+
$gte: bucketContext.minId,
|
|
568
|
+
$lte: bucketContext.docId(clearOp)
|
|
569
|
+
}
|
|
570
|
+
};
|
|
571
|
+
|
|
572
|
+
const session = this.db.client.startSession();
|
|
573
|
+
try {
|
|
574
|
+
let done = false;
|
|
575
|
+
while (!done) {
|
|
576
|
+
this.signal?.throwIfAborted();
|
|
577
|
+
let opCountDiff = 0;
|
|
578
|
+
// Do the CLEAR operation in batches, with each batch a separate transaction.
|
|
579
|
+
// The state after each batch is fully consistent.
|
|
580
|
+
// We need a transaction per batch to make sure checksums stay consistent.
|
|
581
|
+
await session.withTransaction(
|
|
582
|
+
async () => {
|
|
583
|
+
const query = bucketContext.collection.find<Pick<BucketDataDocumentV1, '_id' | CompactClearProperties>>(
|
|
584
|
+
opFilter,
|
|
585
|
+
{
|
|
586
|
+
session,
|
|
587
|
+
sort: { _id: 1 },
|
|
588
|
+
projection: {
|
|
589
|
+
_id: 1,
|
|
590
|
+
op: 1,
|
|
591
|
+
checksum: 1,
|
|
592
|
+
target_op: 1
|
|
593
|
+
},
|
|
594
|
+
limit: this.clearBatchLimit
|
|
595
|
+
}
|
|
596
|
+
);
|
|
597
|
+
let checksum = 0;
|
|
598
|
+
let lastOp: Pick<BucketDataDoc, 'o' | CompactClearProperties> | null = null;
|
|
599
|
+
let targetOp: bigint | null = null;
|
|
600
|
+
let gotAnOp = false;
|
|
601
|
+
let numberOfOpsToClear = 0;
|
|
602
|
+
for await (const rawOp of query.stream()) {
|
|
603
|
+
const op = bucketContext.fromPartialPersistedDocument(rawOp);
|
|
604
|
+
|
|
605
|
+
if (op.op == 'MOVE' || op.op == 'REMOVE' || op.op == 'CLEAR') {
|
|
606
|
+
checksum = utils.addChecksums(checksum, Number(op.checksum));
|
|
607
|
+
lastOp = op;
|
|
608
|
+
numberOfOpsToClear += 1;
|
|
609
|
+
if (op.op != 'CLEAR') {
|
|
610
|
+
gotAnOp = true;
|
|
611
|
+
}
|
|
612
|
+
if (op.target_op != null && (targetOp == null || op.target_op > targetOp)) {
|
|
613
|
+
targetOp = op.target_op;
|
|
614
|
+
}
|
|
615
|
+
} else {
|
|
616
|
+
throw new ReplicationAssertionError(`Unexpected ${op.op} operation at ${this.formatBucketDataKey(op)}`);
|
|
617
|
+
}
|
|
618
|
+
}
|
|
619
|
+
if (!gotAnOp) {
|
|
620
|
+
done = true;
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
this.logger.info(`Flushing CLEAR for ${numberOfOpsToClear} ops at ${lastOp?.o}`);
|
|
625
|
+
await bucketContext.collection.deleteMany(
|
|
626
|
+
{
|
|
627
|
+
_id: {
|
|
628
|
+
$gte: bucketContext.minId,
|
|
629
|
+
$lte: bucketContext.docId(lastOp!.o)
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
{ session }
|
|
633
|
+
);
|
|
634
|
+
|
|
635
|
+
const op = bucketContext.toPersistedDocument({
|
|
636
|
+
o: lastOp!.o,
|
|
637
|
+
op: 'CLEAR',
|
|
638
|
+
checksum: BigInt(checksum),
|
|
639
|
+
data: null,
|
|
640
|
+
target_op: targetOp
|
|
641
|
+
});
|
|
642
|
+
await bucketContext.collection.insertOne(op, { session });
|
|
643
|
+
|
|
644
|
+
opCountDiff = -numberOfOpsToClear + 1;
|
|
645
|
+
},
|
|
646
|
+
{
|
|
647
|
+
writeConcern: { w: 'majority' },
|
|
648
|
+
readConcern: { level: 'snapshot' }
|
|
649
|
+
}
|
|
650
|
+
);
|
|
651
|
+
// Update outside the transaction, since the transaction can be retried multiple times.
|
|
652
|
+
currentState.opCount += opCountDiff;
|
|
653
|
+
}
|
|
654
|
+
} finally {
|
|
655
|
+
await session.endSession();
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
|
|
659
|
+
private formatBucketDataKey(doc: Pick<BucketDataDoc, 'bucketKey' | 'o'>) {
|
|
660
|
+
return `${doc.bucketKey.replicationStreamId}:${doc.bucketKey.bucket}:${doc.o}`;
|
|
661
|
+
}
|
|
93
662
|
}
|