@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,5 +1,5 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { BucketDefinitionId, ParameterIndexId } from '@powersync/service-sync-rules';
|
|
2
|
+
import { BucketDefinitionId, EventDefinitionId, ParameterIndexId } from '@powersync/service-sync-rules';
|
|
3
3
|
import { BaseVersionedPowerSyncMongo } from '../common/VersionedPowerSyncMongoBase.js';
|
|
4
4
|
import { CommonSourceTableDocument } from '../models.js';
|
|
5
5
|
import {
|
|
@@ -7,58 +7,134 @@ import {
|
|
|
7
7
|
BucketParameterDocumentV3,
|
|
8
8
|
BucketStateDocumentV3,
|
|
9
9
|
CurrentDataDocumentV3,
|
|
10
|
+
CustomCheckpointRequestDocumentV3,
|
|
11
|
+
ObjectStorageDeletionMarker,
|
|
12
|
+
ObjectStorageUsageDocument,
|
|
10
13
|
SourceTableDocumentV3,
|
|
11
14
|
SyncConfigDefinition
|
|
12
15
|
} from './models.js';
|
|
13
16
|
|
|
17
|
+
export const OBJECT_STORAGE_USAGE_COLLECTION = 'object_storage_usage';
|
|
18
|
+
export type CustomCheckpointRequestCollectionSpecifier = {
|
|
19
|
+
replicationStreamId: number;
|
|
20
|
+
eventId: EventDefinitionId;
|
|
21
|
+
};
|
|
22
|
+
|
|
14
23
|
export class VersionedPowerSyncMongoV3 extends BaseVersionedPowerSyncMongo {
|
|
24
|
+
constructor(
|
|
25
|
+
upstream: ConstructorParameters<typeof BaseVersionedPowerSyncMongo>[0],
|
|
26
|
+
storageConfig: ConstructorParameters<typeof BaseVersionedPowerSyncMongo>[1]
|
|
27
|
+
) {
|
|
28
|
+
super(upstream, storageConfig);
|
|
29
|
+
}
|
|
30
|
+
|
|
15
31
|
get syncConfigDefinitions() {
|
|
16
32
|
return this.db.collection<SyncConfigDefinition>('sync_config');
|
|
17
33
|
}
|
|
18
34
|
|
|
19
|
-
|
|
35
|
+
sourceRecords(replicationStreamId: number, sourceTableId: mongo.ObjectId): mongo.Collection<CurrentDataDocumentV3> {
|
|
20
36
|
const collectionName = this.sourceRecordsCollectionName(replicationStreamId, sourceTableId);
|
|
21
37
|
return this.db.collection<CurrentDataDocumentV3>(collectionName);
|
|
22
38
|
}
|
|
23
39
|
|
|
24
|
-
async
|
|
40
|
+
async listSourceRecordCollections(replicationStreamId: number): Promise<mongo.Collection<CurrentDataDocumentV3>[]> {
|
|
25
41
|
return this.listCollectionsByPrefix<CurrentDataDocumentV3>(`source_records_${replicationStreamId}_`);
|
|
26
42
|
}
|
|
27
43
|
|
|
28
|
-
async initializeSourceRecordsCollection(
|
|
29
|
-
|
|
44
|
+
async initializeSourceRecordsCollection(
|
|
45
|
+
replicationStreamId: number,
|
|
46
|
+
sourceTableId: mongo.ObjectId,
|
|
47
|
+
session?: mongo.ClientSession
|
|
48
|
+
) {
|
|
49
|
+
await this.sourceRecords(replicationStreamId, sourceTableId).createIndex(
|
|
30
50
|
{
|
|
31
51
|
pending_delete: 1
|
|
32
52
|
},
|
|
33
53
|
{
|
|
34
54
|
partialFilterExpression: { pending_delete: { $exists: true } },
|
|
35
|
-
name: 'pending_delete'
|
|
55
|
+
name: 'pending_delete',
|
|
56
|
+
session
|
|
36
57
|
}
|
|
37
58
|
);
|
|
38
59
|
}
|
|
39
60
|
|
|
40
61
|
commonSourceTables(replicationStreamId: number): mongo.Collection<CommonSourceTableDocument> {
|
|
41
|
-
return this.
|
|
62
|
+
return this.sourceTables(replicationStreamId) as any as mongo.Collection<CommonSourceTableDocument>;
|
|
42
63
|
}
|
|
43
64
|
|
|
44
|
-
|
|
65
|
+
bucketState(replicationStreamId: number): mongo.Collection<BucketStateDocumentV3> {
|
|
45
66
|
return this.db.collection(`bucket_state_${replicationStreamId}`);
|
|
46
67
|
}
|
|
47
68
|
|
|
48
|
-
|
|
49
|
-
replicationStreamId: number,
|
|
50
|
-
indexId: ParameterIndexId
|
|
51
|
-
): mongo.Collection<BucketParameterDocumentV3> {
|
|
69
|
+
parameterIndex(replicationStreamId: number, indexId: ParameterIndexId): mongo.Collection<BucketParameterDocumentV3> {
|
|
52
70
|
return this.db.collection(`parameter_index_${replicationStreamId}_${indexId}`);
|
|
53
71
|
}
|
|
54
72
|
|
|
55
|
-
|
|
73
|
+
sourceTables(replicationStreamId: number): mongo.Collection<SourceTableDocumentV3> {
|
|
56
74
|
return this.db.collection<SourceTableDocumentV3>(this.sourceTableCollectionName(replicationStreamId));
|
|
57
75
|
}
|
|
58
76
|
|
|
77
|
+
bucketData(replicationStreamId: number, definitionId: BucketDefinitionId): mongo.Collection<BucketDataDocumentV3> {
|
|
78
|
+
return this.db.collection<BucketDataDocumentV3>(`bucket_data_${replicationStreamId}_${definitionId}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
pendingObjectStorageDeletes(replicationStreamId: number): mongo.Collection<ObjectStorageDeletionMarker> {
|
|
82
|
+
return this.db.collection<ObjectStorageDeletionMarker>(`pending_object_storage_deletes_${replicationStreamId}`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
get objectStorageUsage(): mongo.Collection<ObjectStorageUsageDocument> {
|
|
86
|
+
return this.db.collection<ObjectStorageUsageDocument>(OBJECT_STORAGE_USAGE_COLLECTION);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
customCheckpointRequests(options: CustomCheckpointRequestCollectionSpecifier) {
|
|
90
|
+
const collectionName = this.customCheckpointRequestCollectionName(options);
|
|
91
|
+
return this.db.collection<CustomCheckpointRequestDocumentV3>(collectionName);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async initializeCustomCheckpointRequestsCollection(options: CustomCheckpointRequestCollectionSpecifier) {
|
|
95
|
+
// createIndexes also creates the collection on first use. Callers invoke
|
|
96
|
+
// this lazily before the transaction which writes the first checkpoint.
|
|
97
|
+
await this.customCheckpointRequests(options).createIndexes([
|
|
98
|
+
{
|
|
99
|
+
key: { user_id: 1 },
|
|
100
|
+
name: 'user_unique',
|
|
101
|
+
unique: true
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
// Find checkpoint changes over an operation-id range.
|
|
105
|
+
key: { op_id: 1 },
|
|
106
|
+
name: 'op_id'
|
|
107
|
+
},
|
|
108
|
+
{
|
|
109
|
+
// Limit retention scans to temporary client-requested checkpoints.
|
|
110
|
+
key: { checkpoint_requested_at: 1 },
|
|
111
|
+
name: 'checkpoint_requested_at',
|
|
112
|
+
partialFilterExpression: { checkpoint_requested_at: { $exists: true } }
|
|
113
|
+
}
|
|
114
|
+
]);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
listCustomCheckpointRequestCollections(replicationStreamId: number) {
|
|
118
|
+
return this.upstream.listCustomCheckpointRequestCollections(replicationStreamId);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
listBucketDataCollections(replicationStreamId: number) {
|
|
122
|
+
return this.listCollectionsByPrefix(`bucket_data_${replicationStreamId}_`);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async listParameterIndexCollections(replicationStreamId: number) {
|
|
126
|
+
const prefix = `parameter_index_${replicationStreamId}_`;
|
|
127
|
+
const collections = await this.listCollectionsByPrefix(prefix);
|
|
128
|
+
|
|
129
|
+
return collections.map((collection) => ({
|
|
130
|
+
collection,
|
|
131
|
+
indexId: collection.collectionName.slice(prefix.length)
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
|
|
59
135
|
async initializeStreamStorage(replicationStreamId: number) {
|
|
60
|
-
const sourceTables = this.
|
|
61
|
-
const bucketState = this.
|
|
136
|
+
const sourceTables = this.sourceTables(replicationStreamId);
|
|
137
|
+
const bucketState = this.bucketState(replicationStreamId);
|
|
62
138
|
await sourceTables.createIndex(
|
|
63
139
|
{
|
|
64
140
|
connection_id: 1,
|
|
@@ -85,33 +161,18 @@ export class VersionedPowerSyncMongoV3 extends BaseVersionedPowerSyncMongo {
|
|
|
85
161
|
},
|
|
86
162
|
{ name: 'bucket_updates', unique: true }
|
|
87
163
|
);
|
|
164
|
+
// V3 workers claim only buckets that have reached their scheduled check
|
|
165
|
+
// time, keeping scheduled scans proportional to pending work.
|
|
88
166
|
await bucketState.createIndex(
|
|
167
|
+
{ next_compact_check: 1 },
|
|
89
168
|
{
|
|
90
|
-
'
|
|
91
|
-
|
|
92
|
-
|
|
169
|
+
name: 'next_compact_check',
|
|
170
|
+
partialFilterExpression: { next_compact_check: { $exists: true } }
|
|
171
|
+
}
|
|
93
172
|
);
|
|
94
173
|
}
|
|
95
174
|
|
|
96
|
-
|
|
97
|
-
return this.
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
listBucketDataCollectionsV3(replicationStreamId: number) {
|
|
101
|
-
return this.upstream.listBucketDataCollectionsV3(replicationStreamId);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
async listParameterIndexCollectionsV3(
|
|
105
|
-
replicationStreamId: number
|
|
106
|
-
): Promise<{ collection: mongo.Collection<BucketParameterDocumentV3>; indexId: ParameterIndexId }[]> {
|
|
107
|
-
const prefix = `parameter_index_${replicationStreamId}_`;
|
|
108
|
-
const collections = await this.db.listCollections({ name: new RegExp(`^${prefix}`) }, { nameOnly: true }).toArray();
|
|
109
|
-
|
|
110
|
-
return collections
|
|
111
|
-
.filter((collection) => collection.name.startsWith(prefix))
|
|
112
|
-
.map((collection) => ({
|
|
113
|
-
collection: this.db.collection<BucketParameterDocumentV3>(collection.name),
|
|
114
|
-
indexId: collection.name.slice(prefix.length)
|
|
115
|
-
}));
|
|
175
|
+
customCheckpointRequestCollectionName(options: CustomCheckpointRequestCollectionSpecifier) {
|
|
176
|
+
return this.upstream.customCheckpointRequestCollectionName(options);
|
|
116
177
|
}
|
|
117
178
|
}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
2
|
+
import { bson, InternalOpId } from '@powersync/service-core';
|
|
3
|
+
import { BucketDataDoc, BucketKey } from '../common/BucketDataDoc.js';
|
|
4
|
+
import { BucketDataDocumentV3, BucketOperation } from './models.js';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Serialize a non-empty list of operations in strictly ascending `o` order.
|
|
8
|
+
* Preserving that order establishes the {@link BucketDataDocumentV3} range
|
|
9
|
+
* invariants used by metadata-only queries.
|
|
10
|
+
*/
|
|
11
|
+
export function serializeBucketData(
|
|
12
|
+
bucket: string,
|
|
13
|
+
operations: BucketDataDoc[],
|
|
14
|
+
options?: { targetOp?: InternalOpId | null }
|
|
15
|
+
): BucketDataDocumentV3 {
|
|
16
|
+
const minOp = operations[0].o;
|
|
17
|
+
const maxOp = operations[operations.length - 1].o;
|
|
18
|
+
|
|
19
|
+
let totalChecksum = 0n;
|
|
20
|
+
let maxTargetOp: InternalOpId | null = options?.targetOp ?? null;
|
|
21
|
+
let hasClearOp = false;
|
|
22
|
+
|
|
23
|
+
const ops: BucketOperation[] = operations.map((op) => {
|
|
24
|
+
totalChecksum += op.checksum;
|
|
25
|
+
|
|
26
|
+
if (op.op == 'CLEAR') {
|
|
27
|
+
hasClearOp = true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
o: op.o,
|
|
32
|
+
op: op.op,
|
|
33
|
+
source_table: op.source_table,
|
|
34
|
+
source_key: op.source_key,
|
|
35
|
+
subkey: op.subkey,
|
|
36
|
+
table: op.table,
|
|
37
|
+
row_id: op.row_id,
|
|
38
|
+
checksum: op.checksum,
|
|
39
|
+
data: op.data
|
|
40
|
+
};
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const size = bson.calculateObjectSize(ops);
|
|
44
|
+
|
|
45
|
+
return {
|
|
46
|
+
_id: {
|
|
47
|
+
b: bucket,
|
|
48
|
+
o: maxOp
|
|
49
|
+
},
|
|
50
|
+
min_op: minOp,
|
|
51
|
+
checksum: totalChecksum,
|
|
52
|
+
count: operations.length,
|
|
53
|
+
size,
|
|
54
|
+
target_op: maxTargetOp,
|
|
55
|
+
...(hasClearOp ? { has_clear_op: true as const } : {}),
|
|
56
|
+
ops
|
|
57
|
+
};
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export function* loadBucketDataDocument(
|
|
61
|
+
context: Pick<BucketKey, 'replicationStreamId' | 'definitionId'>,
|
|
62
|
+
doc: BucketDataDocumentV3
|
|
63
|
+
): Generator<BucketDataDoc> {
|
|
64
|
+
const { _id, ops } = doc;
|
|
65
|
+
if (!ops) {
|
|
66
|
+
throw new ServiceAssertionError(
|
|
67
|
+
`Missing ops array on BucketDataDocumentV3 at _id.o=${_id.o}. Callers must patch doc.ops from S3 before calling this function.`
|
|
68
|
+
);
|
|
69
|
+
}
|
|
70
|
+
const bucketKey = {
|
|
71
|
+
...context,
|
|
72
|
+
bucket: _id.b
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
for (const op of ops) {
|
|
76
|
+
yield {
|
|
77
|
+
bucketKey,
|
|
78
|
+
o: op.o,
|
|
79
|
+
op: op.op,
|
|
80
|
+
source_table: op.source_table,
|
|
81
|
+
source_key: op.source_key,
|
|
82
|
+
subkey: op.subkey,
|
|
83
|
+
table: op.table,
|
|
84
|
+
row_id: op.row_id,
|
|
85
|
+
checksum: op.checksum,
|
|
86
|
+
data: op.data
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function maxOpId(a: InternalOpId | null | undefined, b: InternalOpId | null | undefined): InternalOpId | null {
|
|
92
|
+
if (a == null) {
|
|
93
|
+
return b ?? null;
|
|
94
|
+
}
|
|
95
|
+
if (b == null) {
|
|
96
|
+
return a ?? null;
|
|
97
|
+
}
|
|
98
|
+
return a > b ? a : b;
|
|
99
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { bson } from '@powersync/service-core';
|
|
2
|
+
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
3
|
+
|
|
4
|
+
export const DEFAULT_MAX_DOC_SIZE_BYTES = 1024 * 1024; // 1MB
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Split an array of bucket data operations into chunks where each chunk's
|
|
8
|
+
* total data size stays close to `maxDocSizeBytes`.
|
|
9
|
+
*
|
|
10
|
+
* The threshold is a target, not a hard ceiling — a single operation whose
|
|
11
|
+
* `data` field exceeds the limit is placed in its own chunk and is NOT split.
|
|
12
|
+
*/
|
|
13
|
+
export function chunkBucketData(
|
|
14
|
+
operations: BucketDataDoc[],
|
|
15
|
+
maxDocSizeBytes = DEFAULT_MAX_DOC_SIZE_BYTES
|
|
16
|
+
): BucketDataDoc[][] {
|
|
17
|
+
const chunks: BucketDataDoc[][] = [];
|
|
18
|
+
let currentChunk: BucketDataDoc[] = [];
|
|
19
|
+
let currentSize = 0;
|
|
20
|
+
|
|
21
|
+
for (const op of operations) {
|
|
22
|
+
// Add 5 to account for array index up to 4 digits (9999) plus null byte.
|
|
23
|
+
const opSize = bson.calculateObjectSize(op) + 5;
|
|
24
|
+
|
|
25
|
+
if (currentSize + opSize > maxDocSizeBytes && currentChunk.length > 0) {
|
|
26
|
+
chunks.push(currentChunk);
|
|
27
|
+
currentChunk = [];
|
|
28
|
+
currentSize = 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
currentChunk.push(op);
|
|
32
|
+
currentSize += opSize;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
if (currentChunk.length > 0) {
|
|
36
|
+
chunks.push(currentChunk);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return chunks;
|
|
40
|
+
}
|
|
@@ -0,0 +1,320 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { addChecksums, InternalOpId } from '@powersync/service-core';
|
|
4
|
+
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
5
|
+
import { AVAILABLE_LEASE_EXPR, CompactionLease } from './CompactionLease.js';
|
|
6
|
+
import { BucketDataDocumentV3, BucketStateDocumentV3 } from './models.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* Perform chunk compaction if at least this many chunks have been added since
|
|
10
|
+
* the latest compaction.
|
|
11
|
+
*
|
|
12
|
+
* A lower value increases compaction frequency and can increase chunk rewrites
|
|
13
|
+
* and object-storage operations. A higher value leaves more small chunks for
|
|
14
|
+
* longer, which can hurt sync performance.
|
|
15
|
+
*/
|
|
16
|
+
const MERGE_CHUNKS_THRESHOLD = 8;
|
|
17
|
+
|
|
18
|
+
export const FULL_COMPACT_RESCHEDULE_MARGIN_MS = 60 * 1000;
|
|
19
|
+
|
|
20
|
+
export interface PendingCompactionGroup {
|
|
21
|
+
/**
|
|
22
|
+
* Input documents are ordered from oldest to newest, matching `ops`.
|
|
23
|
+
* Keeping the inputs intact lets unchanged singletons retain their object.
|
|
24
|
+
*/
|
|
25
|
+
inputs: BucketDataDocumentV3[];
|
|
26
|
+
ops: BucketDataDoc[];
|
|
27
|
+
changed: boolean;
|
|
28
|
+
targetOp: InternalOpId | null;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface CompactIntervalConfig {
|
|
32
|
+
readonly minCompactChunkIntervalMs: number;
|
|
33
|
+
readonly minCompactFullIntervalMs: number;
|
|
34
|
+
readonly maxCompactFullIntervalMs: number;
|
|
35
|
+
}
|
|
36
|
+
export interface CompactTargetConfig {
|
|
37
|
+
readonly maxOpIdCap: InternalOpId | undefined;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
export enum CompactionKind {
|
|
41
|
+
Full = 'full',
|
|
42
|
+
Chunks = 'chunks'
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
export interface CompactionDecision {
|
|
46
|
+
kind: CompactionKind | null;
|
|
47
|
+
nextCompactCheck: mongo.Document;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export interface ScheduledCompactionOptions {
|
|
51
|
+
/** Process checks scheduled this far after the captured job start. */
|
|
52
|
+
dueAheadMs?: number;
|
|
53
|
+
/** Used by initial replication, which must not run a full compact. */
|
|
54
|
+
forceKind?: CompactionKind;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export class CompactionContext {
|
|
58
|
+
constructor(
|
|
59
|
+
readonly lease: CompactionLease,
|
|
60
|
+
readonly kind: CompactionKind,
|
|
61
|
+
readonly decision: CompactionDecision,
|
|
62
|
+
readonly rescheduleNotBefore: Date | undefined,
|
|
63
|
+
readonly targetOp: InternalOpId
|
|
64
|
+
) {}
|
|
65
|
+
|
|
66
|
+
get state() {
|
|
67
|
+
return this.lease.state;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
get startedAt() {
|
|
71
|
+
return this.lease.startedAt;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
get lastOp() {
|
|
75
|
+
return this.lease.lastOp;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export interface BucketStats {
|
|
80
|
+
count: number;
|
|
81
|
+
bytes: bigint;
|
|
82
|
+
chunks: number;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/** Bucket stats read from bucket-data documents, including their checksum. */
|
|
86
|
+
export interface BucketStatsWithChecksum extends BucketStats {
|
|
87
|
+
checksum: number;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export interface CompactionResult {
|
|
91
|
+
/** Metadata cached at compacted_state.op_id. */
|
|
92
|
+
compactedState: BucketStatsWithChecksum;
|
|
93
|
+
/** Complete bucket metadata through the op head captured at claim time. */
|
|
94
|
+
bucketStats: BucketStats;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
export function bucketStats(state: BucketStateDocumentV3): BucketStats {
|
|
98
|
+
return {
|
|
99
|
+
count: state.bucket_stats.count,
|
|
100
|
+
bytes: state.bucket_stats.bytes,
|
|
101
|
+
chunks: state.bucket_stats.chunks
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
export function emptyBucketStats(): BucketStatsWithChecksum {
|
|
106
|
+
return { count: 0, bytes: 0n, chunks: 0, checksum: 0 };
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
export function statsForDocument(
|
|
110
|
+
document: Pick<BucketDataDocumentV3, 'count' | 'size' | 'checksum'>
|
|
111
|
+
): BucketStatsWithChecksum {
|
|
112
|
+
return {
|
|
113
|
+
count: document.count,
|
|
114
|
+
bytes: BigInt(document.size),
|
|
115
|
+
chunks: 1,
|
|
116
|
+
checksum: addChecksums(0, Number(document.checksum))
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
export function statsForDocuments(
|
|
121
|
+
documents: Iterable<Pick<BucketDataDocumentV3, 'count' | 'size' | 'checksum'>>
|
|
122
|
+
): BucketStatsWithChecksum {
|
|
123
|
+
let result = emptyBucketStats();
|
|
124
|
+
for (const document of documents) {
|
|
125
|
+
result = combineAdjacentStats(result, statsForDocument(document));
|
|
126
|
+
}
|
|
127
|
+
return result;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** A scheduled bucket always has writes awaiting a full compact. */
|
|
131
|
+
export function firstUncompactedWrite(state: BucketStateDocumentV3): Date {
|
|
132
|
+
if (state.first_uncompacted_write == null) {
|
|
133
|
+
throw new ReplicationAssertionError(`Scheduled V3 bucket ${state._id.b} has no first uncompacted write`);
|
|
134
|
+
}
|
|
135
|
+
return state.first_uncompacted_write;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
export function combineChunkStats(
|
|
139
|
+
previous: NonNullable<BucketStateDocumentV3['compacted_state']>,
|
|
140
|
+
compactedTail: BucketStatsWithChecksum,
|
|
141
|
+
overlappingCompactedChunk: BucketStatsWithChecksum
|
|
142
|
+
): BucketStatsWithChecksum {
|
|
143
|
+
return {
|
|
144
|
+
count: previous.count - overlappingCompactedChunk.count + compactedTail.count,
|
|
145
|
+
bytes: previous.bytes - overlappingCompactedChunk.bytes + compactedTail.bytes,
|
|
146
|
+
chunks: previous.chunks - 1 + compactedTail.chunks,
|
|
147
|
+
checksum: addChecksums(
|
|
148
|
+
addChecksums(Number(previous.checksum), -overlappingCompactedChunk.checksum),
|
|
149
|
+
compactedTail.checksum
|
|
150
|
+
)
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
export function combineAdjacentStats(
|
|
155
|
+
first: BucketStatsWithChecksum,
|
|
156
|
+
second: BucketStatsWithChecksum
|
|
157
|
+
): BucketStatsWithChecksum {
|
|
158
|
+
return {
|
|
159
|
+
count: first.count + second.count,
|
|
160
|
+
bytes: first.bytes + second.bytes,
|
|
161
|
+
chunks: first.chunks + second.chunks,
|
|
162
|
+
checksum: addChecksums(first.checksum, second.checksum)
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
export function applyCompactionDelta(
|
|
167
|
+
total: BucketStats,
|
|
168
|
+
before: BucketStatsWithChecksum,
|
|
169
|
+
after: BucketStatsWithChecksum
|
|
170
|
+
): BucketStats {
|
|
171
|
+
return {
|
|
172
|
+
count: total.count - before.count + after.count,
|
|
173
|
+
bytes: total.bytes - before.bytes + after.bytes,
|
|
174
|
+
chunks: total.chunks - before.chunks + after.chunks
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/** Replace one stored range in an accumulated statistics snapshot. */
|
|
179
|
+
export function applyStatsReplacement(
|
|
180
|
+
total: BucketStatsWithChecksum,
|
|
181
|
+
before: BucketStatsWithChecksum,
|
|
182
|
+
after: BucketStatsWithChecksum
|
|
183
|
+
): BucketStatsWithChecksum {
|
|
184
|
+
return {
|
|
185
|
+
...applyCompactionDelta(total, before, after),
|
|
186
|
+
checksum: addChecksums(addChecksums(total.checksum, -before.checksum), after.checksum)
|
|
187
|
+
};
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/**
|
|
191
|
+
* This checks that the bucket state hasn't changed by a concurrent write since we checked.
|
|
192
|
+
*
|
|
193
|
+
* If it has changed, we'll re-check in the next batch.
|
|
194
|
+
*/
|
|
195
|
+
export function unclaimedSnapshotFilter(state: BucketStateDocumentV3): mongo.Filter<BucketStateDocumentV3> {
|
|
196
|
+
return {
|
|
197
|
+
_id: state._id,
|
|
198
|
+
last_op: state.last_op,
|
|
199
|
+
next_compact_check: state.next_compact_check,
|
|
200
|
+
first_uncompacted_write: state.first_uncompacted_write ?? { $exists: false },
|
|
201
|
+
bucket_stats: state.bucket_stats,
|
|
202
|
+
compacted_state: state.compacted_state ?? { $exists: false },
|
|
203
|
+
last_full_compact: state.last_full_compact ?? { $exists: false },
|
|
204
|
+
...AVAILABLE_LEASE_EXPR
|
|
205
|
+
};
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Calculate the earliest full compaction time from the first uncompacted
|
|
210
|
+
* write, bounded by the maximum retention interval.
|
|
211
|
+
*/
|
|
212
|
+
export function fullCompactionCheckAt(state: BucketStateDocumentV3, config: CompactIntervalConfig): Date {
|
|
213
|
+
const firstWrite = firstUncompactedWrite(state);
|
|
214
|
+
const stats = bucketStats(state);
|
|
215
|
+
const lastFull = state.last_full_compact;
|
|
216
|
+
|
|
217
|
+
// The number of operations since the last full compact.
|
|
218
|
+
// We may make this more specific in the future, to track new updates and deletes only, ignoring
|
|
219
|
+
// full new inserts, but that requires more granular tracking when replicating.
|
|
220
|
+
const uncompactedCount = lastFull == null ? stats.count : Math.max(0, stats.count - lastFull.count);
|
|
221
|
+
const compactedRows = lastFull?.puts ?? 0;
|
|
222
|
+
|
|
223
|
+
// If no full compact has ever been performed: ratio = 1, compact after minCompactFullIntervalMs.
|
|
224
|
+
// If every row has been updated or deleted exactly once since the last full compact: ratio = 0.5, compact after minCompactFullIntervalMs * 2.
|
|
225
|
+
// If 10% of rows has been updated since the last full compact, compact after minCompactFullIntervalMs * 11.
|
|
226
|
+
// If every row has been updated multiple times, the ratio tends closer to 1 again.
|
|
227
|
+
const ratio = uncompactedCount == 0 ? 0 : uncompactedCount / (compactedRows + uncompactedCount);
|
|
228
|
+
const fullIntervalMs = ratio == 0 ? config.maxCompactFullIntervalMs : config.minCompactFullIntervalMs / ratio;
|
|
229
|
+
return new Date(firstWrite.getTime() + Math.min(fullIntervalMs, config.maxCompactFullIntervalMs));
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
export function chooseCompactionKind(
|
|
233
|
+
state: BucketStateDocumentV3,
|
|
234
|
+
now: Date,
|
|
235
|
+
config: CompactIntervalConfig
|
|
236
|
+
): CompactionDecision {
|
|
237
|
+
// For chunk compaction, we consider the number of chunks added.
|
|
238
|
+
// Right now, we trigger a compact if the interval has passed and at least a threshold of chunks were added.
|
|
239
|
+
// A future policy could also use bytes per chunk or records per chunk to
|
|
240
|
+
// decide when to compact.
|
|
241
|
+
const fullCheckAt = fullCompactionCheckAt(state, config);
|
|
242
|
+
// Schedule a little late so a worker using a slightly earlier clock does
|
|
243
|
+
// not wake before the exact full-compaction condition is true.
|
|
244
|
+
const fullCheckWithMargin = new Date(fullCheckAt.getTime() + FULL_COMPACT_RESCHEDULE_MARGIN_MS);
|
|
245
|
+
const compacted = state.compacted_state;
|
|
246
|
+
const chunksSinceCompact = Math.max(0, state.bucket_stats.chunks - (compacted?.chunks ?? 0));
|
|
247
|
+
const shouldCompactChunks = chunksSinceCompact >= MERGE_CHUNKS_THRESHOLD;
|
|
248
|
+
const canCheckChunks =
|
|
249
|
+
compacted == null || now.getTime() - compacted.at.getTime() >= config.minCompactChunkIntervalMs;
|
|
250
|
+
// Too few new chunks cannot make chunk compaction eligible. Do not poll this
|
|
251
|
+
// bucket at the chunk-compaction interval; only wake it for its full-compact check.
|
|
252
|
+
const nextCompactCheck: mongo.Document = !shouldCompactChunks
|
|
253
|
+
? fullCheckWithMargin
|
|
254
|
+
: {
|
|
255
|
+
$min: [
|
|
256
|
+
fullCheckWithMargin,
|
|
257
|
+
{ $dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: config.minCompactChunkIntervalMs } }
|
|
258
|
+
]
|
|
259
|
+
};
|
|
260
|
+
let kind: CompactionKind | null = null;
|
|
261
|
+
if (now >= fullCheckAt) {
|
|
262
|
+
kind = CompactionKind.Full;
|
|
263
|
+
} else if (canCheckChunks && shouldCompactChunks) {
|
|
264
|
+
kind = CompactionKind.Chunks;
|
|
265
|
+
}
|
|
266
|
+
return {
|
|
267
|
+
kind,
|
|
268
|
+
nextCompactCheck
|
|
269
|
+
};
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
export function forcedCompactionKind(
|
|
273
|
+
state: BucketStateDocumentV3,
|
|
274
|
+
forceKind: CompactionKind | undefined,
|
|
275
|
+
config: CompactTargetConfig
|
|
276
|
+
): CompactionKind | null {
|
|
277
|
+
if (forceKind == null) {
|
|
278
|
+
return null;
|
|
279
|
+
}
|
|
280
|
+
const maxOpId = config.maxOpIdCap == null || state.last_op < config.maxOpIdCap ? state.last_op : config.maxOpIdCap;
|
|
281
|
+
return state.compacted_state == null || state.compacted_state.op_id < maxOpId ? forceKind : null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
/**
|
|
285
|
+
* Read one bounded prefix from a compaction cursor.
|
|
286
|
+
*
|
|
287
|
+
* The document that would cross the byte limit is deliberately not returned:
|
|
288
|
+
* pagination resumes past the last returned `_id`, so that document remains
|
|
289
|
+
* eligible for the next query. The first document is always accepted to
|
|
290
|
+
* ensure progress when a single document exceeds the configured byte limit.
|
|
291
|
+
*
|
|
292
|
+
* `hasMore` is conservative when the document limit is reached. An extra empty
|
|
293
|
+
* query is preferable to exhausting the cursor just to determine whether the
|
|
294
|
+
* limited MongoDB query contained another document.
|
|
295
|
+
*/
|
|
296
|
+
export async function readCompactionBatch(
|
|
297
|
+
cursor: mongo.AggregationCursor<BucketDataDocumentV3>,
|
|
298
|
+
options: { byteLimit: number; documentLimit: number }
|
|
299
|
+
): Promise<{ documents: BucketDataDocumentV3[]; hasMore: boolean }> {
|
|
300
|
+
const documents: BucketDataDocumentV3[] = [];
|
|
301
|
+
let cumulativeBytes = 0;
|
|
302
|
+
|
|
303
|
+
try {
|
|
304
|
+
for await (const document of cursor) {
|
|
305
|
+
if (documents.length > 0 && cumulativeBytes + document.size > options.byteLimit) {
|
|
306
|
+
return { documents, hasMore: true };
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
documents.push(document);
|
|
310
|
+
cumulativeBytes += document.size;
|
|
311
|
+
|
|
312
|
+
if (documents.length >= options.documentLimit) {
|
|
313
|
+
return { documents, hasMore: true };
|
|
314
|
+
}
|
|
315
|
+
}
|
|
316
|
+
return { documents, hasMore: false };
|
|
317
|
+
} finally {
|
|
318
|
+
await cursor.close();
|
|
319
|
+
}
|
|
320
|
+
}
|