@powersync/service-module-mongodb-storage 0.20.0 → 0.22.0
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 +62 -0
- package/dist/storage/MongoBucketStorage.d.ts +36 -3
- package/dist/storage/MongoBucketStorage.js +372 -207
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +17 -5
- package/dist/storage/implementation/MongoBucketBatch.js +44 -17
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
- package/dist/storage/implementation/MongoCompactor.js +27 -238
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
- package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +7 -3
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
- package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
- package/dist/storage/implementation/MongoWriteBatch.js +149 -0
- package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
- 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 +22 -2
- package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
- package/dist/storage/implementation/common/PersistedBatch.js +49 -6
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +9 -1
- package/dist/storage/implementation/db.js +27 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +31 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +2 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +33 -4
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
- package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
- 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 +18 -11
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
- package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -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/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 +10 -2
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +100 -26
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +82 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +698 -159
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
- 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 +14 -8
- package/dist/storage/implementation/v3/PersistedBatchV3.js +76 -42
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.js +2 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- 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 +84 -4
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +6 -6
- package/dist/storage/implementation/v3/source-table-utils.js +30 -37
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
- package/dist/storage/storage-index.d.ts +2 -1
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +28 -1
- package/dist/types/types.js +28 -7
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.js +1 -0
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.js +3 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -9
- package/src/storage/MongoBucketStorage.ts +507 -269
- package/src/storage/implementation/MongoBucketBatch.ts +67 -24
- package/src/storage/implementation/MongoCompactor.ts +35 -346
- package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
- package/src/storage/implementation/MongoStorageProvider.ts +15 -3
- package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
- package/src/storage/implementation/MongoWriteBatch.ts +172 -0
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +66 -10
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
- package/src/storage/implementation/db.ts +35 -2
- package/src/storage/implementation/models.ts +34 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +46 -6
- package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
- package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
- package/src/storage/implementation/v1/models.ts +1 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +138 -34
- package/src/storage/implementation/v3/MongoCompactorV3.ts +958 -225
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +93 -48
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
- package/src/storage/implementation/v3/bucket-format.ts +2 -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 +91 -3
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
- package/src/storage/implementation/v3/source-table-utils.ts +39 -41
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +42 -7
- package/src/utils/test-utils.ts +1 -0
- package/src/utils/util.ts +3 -1
- package/test/src/__snapshots__/storage.test.ts.snap +25 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
- package/test/src/chunk_compaction_config.test.ts +38 -0
- package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +14 -6
- package/test/src/object_storage_usage.test.ts +267 -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/storage.test.ts +273 -22
- package/test/src/storage_compacting.test.ts +968 -246
- package/test/src/storage_s3_checksums.test.ts +3 -4
- package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
- package/test/src/storage_s3_reading.test.ts +290 -2
- package/test/src/storage_s3_writing.test.ts +1 -0
- package/test/src/storage_sync.test.ts +336 -41
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import {
|
|
2
|
+
import {
|
|
3
|
+
EventDefinitionId,
|
|
4
|
+
HydratedEventDescriptor,
|
|
5
|
+
HydratedSyncConfig,
|
|
6
|
+
SqliteRow,
|
|
7
|
+
SqliteValue
|
|
8
|
+
} from '@powersync/service-sync-rules';
|
|
3
9
|
import * as bson from 'bson';
|
|
4
10
|
|
|
5
11
|
import {
|
|
@@ -30,9 +36,10 @@ import type { VersionedPowerSyncMongo } from './db.js';
|
|
|
30
36
|
import { MAX_ROW_SIZE } from './MongoBucketBatchShared.js';
|
|
31
37
|
import { MongoIdSequence } from './MongoIdSequence.js';
|
|
32
38
|
import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
|
|
33
|
-
import {
|
|
39
|
+
import { MongoWriteBatch } from './MongoWriteBatch.js';
|
|
34
40
|
import { OperationBatch, RecordOperation } from './OperationBatch.js';
|
|
35
41
|
import { ObjectStorage } from './v3/object-storage/ObjectStorage.js';
|
|
42
|
+
import { createObjectStorageUsageWriterId } from './v3/object-storage/ObjectStorageUsage.js';
|
|
36
43
|
|
|
37
44
|
// Currently, we can only have a single flush() at a time, since it locks the op_id sequence.
|
|
38
45
|
// While the MongoDB transaction retry mechanism handles this okay, using an in-process Mutex
|
|
@@ -72,6 +79,10 @@ export interface MongoBucketBatchOptions {
|
|
|
72
79
|
|
|
73
80
|
logger: Logger;
|
|
74
81
|
tracer?: PerformanceTracer<'storage' | 'evaluate'>;
|
|
82
|
+
/**
|
|
83
|
+
* Aborts in-flight object storage uploads when replication stops.
|
|
84
|
+
*/
|
|
85
|
+
signal?: AbortSignal;
|
|
75
86
|
|
|
76
87
|
objectStorage?: ObjectStorage;
|
|
77
88
|
inlineThresholdBytes?: number;
|
|
@@ -104,9 +115,10 @@ export abstract class MongoBucketBatch
|
|
|
104
115
|
private readonly storeCurrentData: boolean;
|
|
105
116
|
public readonly skipExistingRows: boolean;
|
|
106
117
|
protected readonly mapping: BucketDefinitionMapping;
|
|
118
|
+
protected readonly objectStorageUsageWriterId = createObjectStorageUsageWriterId();
|
|
107
119
|
|
|
108
120
|
private batch: OperationBatch | null = null;
|
|
109
|
-
|
|
121
|
+
protected write_checkpoint_batch: storage.CustomWriteCheckpointOptions[] = [];
|
|
110
122
|
private markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
|
|
111
123
|
private hooks: storage.StorageHooks | undefined;
|
|
112
124
|
private clearedError = false;
|
|
@@ -185,6 +197,11 @@ export abstract class MongoBucketBatch
|
|
|
185
197
|
no_checkpoint_before_lsn?: string
|
|
186
198
|
): Promise<storage.SourceTable[]>;
|
|
187
199
|
|
|
200
|
+
protected abstract batchCreateCustomWriteCheckpoints(session: mongo.ClientSession, opId: InternalOpId): Promise<void>;
|
|
201
|
+
|
|
202
|
+
/** Perform any version-specific setup required before writing custom checkpoints. */
|
|
203
|
+
protected async prepareCustomWriteCheckpoints(): Promise<void> {}
|
|
204
|
+
|
|
188
205
|
async flush(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null> {
|
|
189
206
|
let result: storage.FlushedResult | null = null;
|
|
190
207
|
// One flush may be split over multiple transactions.
|
|
@@ -207,6 +224,10 @@ export abstract class MongoBucketBatch
|
|
|
207
224
|
using _ = this.tracer.span('storage', 'flush');
|
|
208
225
|
|
|
209
226
|
await this.hooks?.beforeBatchFlush?.(this);
|
|
227
|
+
if (this.write_checkpoint_batch.length > 0) {
|
|
228
|
+
// Collection/index creation cannot run inside the replication transaction.
|
|
229
|
+
await this.prepareCustomWriteCheckpoints();
|
|
230
|
+
}
|
|
210
231
|
|
|
211
232
|
await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
|
|
212
233
|
clearedError = false;
|
|
@@ -218,13 +239,15 @@ export abstract class MongoBucketBatch
|
|
|
218
239
|
|
|
219
240
|
if (this.write_checkpoint_batch.length > 0) {
|
|
220
241
|
this.logger.info(`Writing ${this.write_checkpoint_batch.length} custom write checkpoints`);
|
|
221
|
-
await batchCreateCustomWriteCheckpoints(
|
|
222
|
-
this.write_checkpoint_batch = [];
|
|
242
|
+
await this.batchCreateCustomWriteCheckpoints(session, opSeq.next());
|
|
223
243
|
}
|
|
224
244
|
|
|
225
245
|
last_op = opSeq.last();
|
|
226
246
|
});
|
|
227
247
|
|
|
248
|
+
// Keep checkpoints available if the transaction retries after writing them.
|
|
249
|
+
this.write_checkpoint_batch = [];
|
|
250
|
+
|
|
228
251
|
if (clearedError) {
|
|
229
252
|
this.clearedError = true;
|
|
230
253
|
}
|
|
@@ -686,7 +709,9 @@ export abstract class MongoBucketBatch
|
|
|
686
709
|
|
|
687
710
|
await callback(session, opSeq);
|
|
688
711
|
|
|
689
|
-
|
|
712
|
+
const writes = this.db.createWriteBatch(session, { ordered: false });
|
|
713
|
+
writes.updateOne(
|
|
714
|
+
this.db.op_id_sequence,
|
|
690
715
|
{
|
|
691
716
|
_id: 'main'
|
|
692
717
|
},
|
|
@@ -694,13 +719,11 @@ export abstract class MongoBucketBatch
|
|
|
694
719
|
$set: {
|
|
695
720
|
op_id: opSeq.last()
|
|
696
721
|
}
|
|
697
|
-
},
|
|
698
|
-
{
|
|
699
|
-
session
|
|
700
722
|
}
|
|
701
723
|
);
|
|
702
724
|
|
|
703
|
-
|
|
725
|
+
writes.updateOne(
|
|
726
|
+
this.db.sync_rules,
|
|
704
727
|
{
|
|
705
728
|
_id: this.replicationStreamId
|
|
706
729
|
},
|
|
@@ -708,13 +731,13 @@ export abstract class MongoBucketBatch
|
|
|
708
731
|
$set: {
|
|
709
732
|
last_keepalive_ts: new Date()
|
|
710
733
|
}
|
|
711
|
-
}
|
|
712
|
-
{ session }
|
|
734
|
+
}
|
|
713
735
|
);
|
|
714
736
|
|
|
715
737
|
// Allow subclasses to persist additional flush-time state in the same transaction
|
|
716
738
|
// (e.g. v3 advances the stream-level last_persisted_op).
|
|
717
|
-
|
|
739
|
+
this.onReplicationTransactionFlush(writes, opSeq.last());
|
|
740
|
+
await writes.execute();
|
|
718
741
|
// We don't notify checkpoint here - we don't make any checkpoint updates directly
|
|
719
742
|
});
|
|
720
743
|
}
|
|
@@ -725,7 +748,7 @@ export abstract class MongoBucketBatch
|
|
|
725
748
|
* The base implementation does nothing; v3 storage overrides this to `$max` the stream-level
|
|
726
749
|
* `last_persisted_op` durably within the same transaction.
|
|
727
750
|
*/
|
|
728
|
-
protected
|
|
751
|
+
protected onReplicationTransactionFlush(_writes: MongoWriteBatch, _lastOp: InternalOpId): void {
|
|
729
752
|
// No-op by default (v1 behaviour unchanged).
|
|
730
753
|
}
|
|
731
754
|
|
|
@@ -762,11 +785,12 @@ export abstract class MongoBucketBatch
|
|
|
762
785
|
async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
|
|
763
786
|
const { after, before, sourceTable, tag } = record;
|
|
764
787
|
const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
|
|
765
|
-
//
|
|
766
|
-
// SourceTables
|
|
767
|
-
//
|
|
788
|
+
// V3 source tables own disjoint event-definition ids for each physical table. Multiple
|
|
789
|
+
// SourceTables may evaluate different events, but each definition is evaluated through
|
|
790
|
+
// at most one record for this row change.
|
|
791
|
+
// Legacy storage leaves eventDefinitionIds undefined and selects by table ref.
|
|
768
792
|
if (sourceTable.syncEvent) {
|
|
769
|
-
for (const event of this.getTableEvents(sourceTable)) {
|
|
793
|
+
for (const { event, eventId } of this.getTableEvents(sourceTable)) {
|
|
770
794
|
this.iterateListeners((cb) =>
|
|
771
795
|
cb.replicationEvent?.({
|
|
772
796
|
batch: this,
|
|
@@ -776,7 +800,8 @@ export abstract class MongoBucketBatch
|
|
|
776
800
|
after: after && utils.isCompleteRow(storeCurrentData, after) ? after : undefined,
|
|
777
801
|
before: before && utils.isCompleteRow(storeCurrentData, before) ? before : undefined
|
|
778
802
|
},
|
|
779
|
-
event
|
|
803
|
+
event,
|
|
804
|
+
event_id: eventId
|
|
780
805
|
})
|
|
781
806
|
);
|
|
782
807
|
}
|
|
@@ -934,11 +959,29 @@ export abstract class MongoBucketBatch
|
|
|
934
959
|
}
|
|
935
960
|
|
|
936
961
|
/**
|
|
937
|
-
* Gets relevant {@link
|
|
962
|
+
* Gets relevant {@link HydratedEventDescriptor}s for the given {@link SourceTable}
|
|
938
963
|
*/
|
|
939
|
-
protected getTableEvents(
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
964
|
+
protected getTableEvents(
|
|
965
|
+
table: storage.SourceTable
|
|
966
|
+
): { event: HydratedEventDescriptor; eventId?: EventDefinitionId }[] {
|
|
967
|
+
// V3 storage assigns event-definition ids to each source table, so membership is authoritative.
|
|
968
|
+
// Iterate the table's distinct ids and resolve each through the stream's deduped event map, so a
|
|
969
|
+
// definition reused across configs has one evaluator for that id. The evaluator may still return
|
|
970
|
+
// multiple payloads from matching queries. Legacy storage leaves this undefined and selects by table ref.
|
|
971
|
+
if (table.eventDefinitionIds != null) {
|
|
972
|
+
const eventById = this.options.parsedSyncConfig.eventById;
|
|
973
|
+
const events: { event: HydratedEventDescriptor; eventId: EventDefinitionId }[] = [];
|
|
974
|
+
for (const id of table.eventDefinitionIds) {
|
|
975
|
+
const event = eventById.get(id);
|
|
976
|
+
if (event != null && event.tableTriggersEvent(table.ref)) {
|
|
977
|
+
events.push({ event, eventId: id });
|
|
978
|
+
}
|
|
979
|
+
}
|
|
980
|
+
return events;
|
|
981
|
+
}
|
|
982
|
+
|
|
983
|
+
return this.sync_rules.eventDescriptors
|
|
984
|
+
.filter((event) => event.tableTriggersEvent(table.ref))
|
|
985
|
+
.map((event) => ({ event }));
|
|
943
986
|
}
|
|
944
987
|
}
|
|
@@ -1,64 +1,27 @@
|
|
|
1
1
|
import * as timers from 'node:timers/promises';
|
|
2
2
|
|
|
3
|
-
import { isMongoServerError, mongo
|
|
4
|
-
import {
|
|
5
|
-
|
|
6
|
-
Logger,
|
|
7
|
-
ReplicationAssertionError,
|
|
8
|
-
ServiceAssertionError
|
|
9
|
-
} from '@powersync/lib-services-framework';
|
|
10
|
-
import { InternalOpId, isPartialChecksum, PopulateChecksumCacheResults, storage } from '@powersync/service-core';
|
|
11
|
-
import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
3
|
+
import { isMongoServerError, mongo } from '@powersync/lib-service-mongodb';
|
|
4
|
+
import { logger as defaultLogger, Logger, ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
5
|
+
import { storage } from '@powersync/service-core';
|
|
12
6
|
|
|
13
7
|
import { BucketKey } from './common/BucketDataDoc.js';
|
|
14
8
|
import type { VersionedPowerSyncMongo } from './db.js';
|
|
15
|
-
import { BucketStateDocumentBase } from './models.js';
|
|
16
9
|
import type { MongoSyncBucketStorage } from './MongoSyncBucketStorage.js';
|
|
17
10
|
import { isRetryableObjectStorageError } from './v3/object-storage/ObjectStorage.js';
|
|
18
11
|
|
|
19
|
-
export interface
|
|
20
|
-
/** Bucket name */
|
|
21
|
-
bucket: string;
|
|
22
|
-
definitionId: BucketDefinitionId;
|
|
12
|
+
export interface MongoCompactOptions extends storage.CompactOptions {
|
|
23
13
|
/**
|
|
24
|
-
*
|
|
14
|
+
* Only merge adjacent V3 bucket-data chunks. This is used after initial
|
|
15
|
+
* replication, where reading every operation would defeat the purpose of
|
|
16
|
+
* the lightweight pass.
|
|
25
17
|
*/
|
|
26
|
-
|
|
27
|
-
/**
|
|
28
|
-
* Estimated memory usage of the seen Map.
|
|
29
|
-
*/
|
|
30
|
-
trackingSize: number;
|
|
31
|
-
/**
|
|
32
|
-
* Last (lowest) seen op_id that is not a PUT.
|
|
33
|
-
*/
|
|
34
|
-
lastNotPut: InternalOpId | null;
|
|
35
|
-
/**
|
|
36
|
-
* Number of REMOVE/MOVE operations seen since lastNotPut.
|
|
37
|
-
*/
|
|
38
|
-
opsSincePut: number;
|
|
39
|
-
/**
|
|
40
|
-
* Incrementally-updated checksum, up to maxOpId.
|
|
41
|
-
*/
|
|
42
|
-
checksum: number;
|
|
43
|
-
/**
|
|
44
|
-
* Op count for the checksum.
|
|
45
|
-
*/
|
|
46
|
-
opCount: number;
|
|
47
|
-
/**
|
|
48
|
-
* Byte size of ops covered by the checksum.
|
|
49
|
-
*/
|
|
50
|
-
opBytes: number;
|
|
18
|
+
compactChunksOnly?: boolean;
|
|
51
19
|
}
|
|
52
20
|
|
|
53
|
-
export interface MongoCompactOptions extends storage.CompactOptions {}
|
|
54
|
-
|
|
55
21
|
const DEFAULT_CLEAR_BATCH_LIMIT = 5000;
|
|
56
22
|
const DEFAULT_MOVE_BATCH_LIMIT = 2000;
|
|
57
23
|
const DEFAULT_MOVE_BATCH_QUERY_LIMIT = 10_000;
|
|
58
24
|
const DEFAULT_MOVE_BATCH_BYTE_LIMIT = 16 * 1024 * 1024;
|
|
59
|
-
const DEFAULT_MIN_BUCKET_CHANGES = 10;
|
|
60
|
-
const DEFAULT_MIN_CHANGE_RATIO = 0.1;
|
|
61
|
-
const DIRTY_BUCKET_SCAN_BATCH_SIZE = 2_000;
|
|
62
25
|
/** This default is primarily for tests. */
|
|
63
26
|
const DEFAULT_MEMORY_LIMIT_MB = 64;
|
|
64
27
|
const COMPACTION_RETRY_LIMIT = 3;
|
|
@@ -71,28 +34,30 @@ export class ConcurrentCompactionError extends Error {
|
|
|
71
34
|
}
|
|
72
35
|
}
|
|
73
36
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
37
|
+
/**
|
|
38
|
+
* A worker whose bucket lease has been replaced must stop immediately. Unlike
|
|
39
|
+
* a transactional replacement conflict, retrying it with the same compactor
|
|
40
|
+
* instance would still use the stale lease and could race the new owner.
|
|
41
|
+
*/
|
|
42
|
+
export class CompactionLeaseLostError extends ConcurrentCompactionError {
|
|
43
|
+
constructor(message: string) {
|
|
44
|
+
super(message);
|
|
45
|
+
this.name = 'CompactionLeaseLostError';
|
|
46
|
+
}
|
|
79
47
|
}
|
|
80
48
|
|
|
81
49
|
export abstract class MongoCompactor {
|
|
82
|
-
protected bucketStateUpdates: mongo.AnyBulkWriteOperation<BucketStateDocumentBase>[] = [];
|
|
83
|
-
|
|
84
50
|
protected readonly idLimitBytes: number;
|
|
85
51
|
protected readonly moveBatchLimit: number;
|
|
86
52
|
protected readonly moveBatchQueryLimit: number;
|
|
87
53
|
protected readonly moveBatchByteLimit: number;
|
|
88
54
|
protected readonly clearBatchLimit: number;
|
|
89
|
-
protected readonly minBucketChanges: number;
|
|
90
|
-
protected readonly minChangeRatio: number;
|
|
91
|
-
protected readonly maxOpId: bigint;
|
|
92
55
|
protected readonly buckets: string[] | undefined;
|
|
93
56
|
protected readonly deleteCheckpointRequestsBefore: Date | undefined;
|
|
94
57
|
protected readonly signal?: AbortSignal;
|
|
95
58
|
protected readonly group_id: number;
|
|
59
|
+
protected readonly compactChunksOnly: boolean;
|
|
60
|
+
protected compactedBucketCount = 0;
|
|
96
61
|
|
|
97
62
|
protected readonly logger: Logger;
|
|
98
63
|
|
|
@@ -110,35 +75,16 @@ export abstract class MongoCompactor {
|
|
|
110
75
|
if (this.clearBatchLimit < 2) {
|
|
111
76
|
throw new ReplicationAssertionError('clearBatchLimit must be >= 2');
|
|
112
77
|
}
|
|
113
|
-
this.minBucketChanges = options.minBucketChanges ?? DEFAULT_MIN_BUCKET_CHANGES;
|
|
114
|
-
this.minChangeRatio = options.minChangeRatio ?? DEFAULT_MIN_CHANGE_RATIO;
|
|
115
|
-
this.maxOpId = options.maxOpId ?? 0n;
|
|
116
78
|
this.buckets = options.compactBuckets;
|
|
117
79
|
this.deleteCheckpointRequestsBefore = options.deleteCheckpointRequestsBefore;
|
|
118
80
|
this.signal = options.signal;
|
|
81
|
+
this.compactChunksOnly = options.compactChunksOnly ?? false;
|
|
119
82
|
this.logger = options.logger ?? defaultLogger;
|
|
120
83
|
}
|
|
121
84
|
|
|
122
|
-
|
|
123
|
-
* Compact buckets by converting operations into MOVE and/or CLEAR operations.
|
|
124
|
-
*
|
|
125
|
-
* See /docs/storage/compacting-operations.md for details.
|
|
126
|
-
*/
|
|
127
|
-
async compact() {
|
|
128
|
-
await this.deleteOldCheckpointRequests();
|
|
129
|
-
|
|
130
|
-
if (this.buckets) {
|
|
131
|
-
for (const bucket of this.buckets) {
|
|
132
|
-
// We can make this more efficient later on by iterating through the buckets in a single query.
|
|
133
|
-
// That makes batching more tricky, so we leave for later.
|
|
134
|
-
await this.compactSingleBucketRetried(bucket);
|
|
135
|
-
}
|
|
136
|
-
} else {
|
|
137
|
-
await this.compactDirtyBuckets();
|
|
138
|
-
}
|
|
139
|
-
}
|
|
85
|
+
abstract compact(): Promise<number>;
|
|
140
86
|
|
|
141
|
-
|
|
87
|
+
protected async deleteOldCheckpointRequests() {
|
|
142
88
|
if (this.deleteCheckpointRequestsBefore == null) {
|
|
143
89
|
return;
|
|
144
90
|
}
|
|
@@ -152,180 +98,17 @@ export abstract class MongoCompactor {
|
|
|
152
98
|
checkpoint_requested_at: { $exists: true, $lt: this.deleteCheckpointRequestsBefore },
|
|
153
99
|
processed_at_lsn: { $ne: null }
|
|
154
100
|
});
|
|
155
|
-
await this.
|
|
156
|
-
checkpoint_requested_at: { $exists: true, $lt: this.deleteCheckpointRequestsBefore }
|
|
157
|
-
});
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
/**
|
|
161
|
-
* Subset of compact, only populating checksums where relevant.
|
|
162
|
-
*/
|
|
163
|
-
async populateChecksums(options: { minBucketChanges: number }): Promise<PopulateChecksumCacheResults> {
|
|
164
|
-
let count = 0;
|
|
165
|
-
// Paginate through dirty buckets in batches until no more buckets meet the criteria.
|
|
166
|
-
while (true) {
|
|
167
|
-
this.signal?.throwIfAborted();
|
|
168
|
-
const buckets = await this.dirtyBucketBatchForChecksums(options);
|
|
169
|
-
if (buckets.length == 0) {
|
|
170
|
-
break;
|
|
171
|
-
}
|
|
172
|
-
this.signal?.throwIfAborted();
|
|
173
|
-
|
|
174
|
-
const start = Date.now();
|
|
175
|
-
// Filter batch by estimated bucket size, to reduce possibility of timeouts.
|
|
176
|
-
const checkBuckets: typeof buckets = [];
|
|
177
|
-
let totalCountEstimate = 0;
|
|
178
|
-
for (const bucket of buckets) {
|
|
179
|
-
checkBuckets.push(bucket);
|
|
180
|
-
totalCountEstimate += bucket.estimatedCount;
|
|
181
|
-
if (totalCountEstimate > 50_000) {
|
|
182
|
-
break;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
this.logger.info(
|
|
186
|
-
`Calculating checksums for batch of ${buckets.length} buckets, estimated count of ${totalCountEstimate}`
|
|
187
|
-
);
|
|
188
|
-
await this.updateChecksumsBatch(checkBuckets);
|
|
189
|
-
this.logger.info(`Updated checksums for batch of ${checkBuckets.length} buckets in ${Date.now() - start}ms`);
|
|
190
|
-
count += checkBuckets.length;
|
|
191
|
-
}
|
|
192
|
-
return { buckets: count };
|
|
101
|
+
await this.deleteOldCustomCheckpointRequests();
|
|
193
102
|
}
|
|
194
103
|
|
|
195
|
-
protected async
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
maxId: TCollectionBucketState['_id'],
|
|
199
|
-
options: {
|
|
200
|
-
minBucketChanges: number;
|
|
201
|
-
minChangeRatio: number;
|
|
202
|
-
},
|
|
203
|
-
getDefinitionId: (state: TCollectionBucketState) => BucketDefinitionId | null
|
|
204
|
-
): AsyncGenerator<DirtyBucket[]> {
|
|
205
|
-
// Paginate through the bucket state collection using cursor-based scanning.
|
|
206
|
-
while (true) {
|
|
207
|
-
// To avoid timeouts from too many buckets not meeting the minBucketChanges criteria, use an aggregation pipeline
|
|
208
|
-
// to scan a fixed batch of buckets at a time, but only return buckets that meet the criteria.
|
|
209
|
-
const [result] = await collection
|
|
210
|
-
.aggregate<{
|
|
211
|
-
buckets: TCollectionBucketState[];
|
|
212
|
-
cursor: Pick<TCollectionBucketState, '_id'>[];
|
|
213
|
-
}>(
|
|
214
|
-
[
|
|
215
|
-
{
|
|
216
|
-
$match: {
|
|
217
|
-
_id: { $gt: lastId, $lt: maxId }
|
|
218
|
-
}
|
|
219
|
-
},
|
|
220
|
-
{
|
|
221
|
-
$sort: { _id: 1 }
|
|
222
|
-
},
|
|
223
|
-
{
|
|
224
|
-
// Scan a fixed number of docs each query so sparse matches don't block progress.
|
|
225
|
-
$limit: DIRTY_BUCKET_SCAN_BATCH_SIZE
|
|
226
|
-
},
|
|
227
|
-
{
|
|
228
|
-
$facet: {
|
|
229
|
-
buckets: [
|
|
230
|
-
{
|
|
231
|
-
$match: {
|
|
232
|
-
'estimate_since_compact.count': { $gte: options.minBucketChanges }
|
|
233
|
-
}
|
|
234
|
-
},
|
|
235
|
-
{
|
|
236
|
-
$project: {
|
|
237
|
-
_id: 1,
|
|
238
|
-
estimate_since_compact: 1,
|
|
239
|
-
compacted_state: 1
|
|
240
|
-
}
|
|
241
|
-
}
|
|
242
|
-
],
|
|
243
|
-
// This is used for the next query.
|
|
244
|
-
cursor: [{ $sort: { _id: -1 } }, { $limit: 1 }, { $project: { _id: 1 } }]
|
|
245
|
-
}
|
|
246
|
-
}
|
|
247
|
-
],
|
|
248
|
-
{ maxTimeMS: MONGO_OPERATION_TIMEOUT_MS }
|
|
249
|
-
)
|
|
250
|
-
.toArray();
|
|
251
|
-
|
|
252
|
-
const cursor = result?.cursor?.[0];
|
|
253
|
-
if (cursor == null) {
|
|
254
|
-
break;
|
|
255
|
-
}
|
|
256
|
-
lastId = cursor._id;
|
|
257
|
-
|
|
258
|
-
const mapped = (result?.buckets ?? []).map((bucketState) => {
|
|
259
|
-
// The numbers, specifically the bytes, could be a bigint. Convert to Number to allow calculating ratios.
|
|
260
|
-
// BigInt precision is not needed here since this is only an estimate.
|
|
261
|
-
const updatedCount = bucketState.estimate_since_compact?.count ?? 0;
|
|
262
|
-
const totalCount = (bucketState.compacted_state?.count ?? 0) + updatedCount;
|
|
263
|
-
const updatedBytes = Number(bucketState.estimate_since_compact?.bytes ?? 0);
|
|
264
|
-
const totalBytes = Number(bucketState.compacted_state?.bytes ?? 0) + updatedBytes;
|
|
265
|
-
const dirtyChangeNumber = totalCount > 0 ? updatedCount / totalCount : 0;
|
|
266
|
-
const dirtyChangeBytes = totalBytes > 0 ? updatedBytes / totalBytes : 0;
|
|
267
|
-
return {
|
|
268
|
-
bucket: bucketState._id.b,
|
|
269
|
-
definitionId: getDefinitionId(bucketState),
|
|
270
|
-
estimatedCount: totalCount,
|
|
271
|
-
dirtyRatio: Math.max(dirtyChangeNumber, dirtyChangeBytes)
|
|
272
|
-
};
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
yield mapped.filter(
|
|
276
|
-
(bucket) => bucket.estimatedCount >= options.minBucketChanges && bucket.dirtyRatio >= options.minChangeRatio
|
|
277
|
-
);
|
|
104
|
+
protected async deleteOldCustomCheckpointRequests() {
|
|
105
|
+
if (this.deleteCheckpointRequestsBefore == null) {
|
|
106
|
+
return;
|
|
278
107
|
}
|
|
279
|
-
}
|
|
280
|
-
|
|
281
|
-
protected async dirtyBucketBatchForChecksumsForCollection<TBucketState extends BucketStateDocumentBase>(
|
|
282
|
-
collection: mongo.Collection<TBucketState>,
|
|
283
|
-
filter: mongo.Filter<TBucketState>,
|
|
284
|
-
getDefinitionId: (state: mongo.WithId<TBucketState>) => BucketDefinitionId | null
|
|
285
|
-
): Promise<DirtyBucket[]> {
|
|
286
|
-
const dirtyBuckets = await collection
|
|
287
|
-
.find(filter, {
|
|
288
|
-
projection: {
|
|
289
|
-
_id: 1,
|
|
290
|
-
estimate_since_compact: 1,
|
|
291
|
-
compacted_state: 1
|
|
292
|
-
},
|
|
293
|
-
sort: {
|
|
294
|
-
'estimate_since_compact.count': -1
|
|
295
|
-
},
|
|
296
|
-
limit: 200,
|
|
297
|
-
maxTimeMS: MONGO_OPERATION_TIMEOUT_MS
|
|
298
|
-
})
|
|
299
|
-
.toArray();
|
|
300
|
-
|
|
301
|
-
return dirtyBuckets.map((bucket) => ({
|
|
302
|
-
bucket: bucket._id.b,
|
|
303
|
-
definitionId: getDefinitionId(bucket),
|
|
304
|
-
estimatedCount: Number(bucket.estimate_since_compact!.count) + Number(bucket.compacted_state?.count ?? 0)
|
|
305
|
-
}));
|
|
306
|
-
}
|
|
307
108
|
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
}): AsyncGenerator<DirtyBucket[]>;
|
|
312
|
-
|
|
313
|
-
public abstract dirtyBucketBatchForChecksums(options: { minBucketChanges: number }): Promise<DirtyBucket[]>;
|
|
314
|
-
|
|
315
|
-
protected async compactDirtyBuckets() {
|
|
316
|
-
for await (const buckets of this.dirtyBucketBatches({
|
|
317
|
-
minBucketChanges: this.minBucketChanges,
|
|
318
|
-
minChangeRatio: this.minChangeRatio
|
|
319
|
-
})) {
|
|
320
|
-
this.signal?.throwIfAborted();
|
|
321
|
-
if (buckets.length == 0) {
|
|
322
|
-
continue;
|
|
323
|
-
}
|
|
324
|
-
|
|
325
|
-
for (const { bucket, definitionId } of buckets) {
|
|
326
|
-
await this.compactSingleBucketRetried(bucket, definitionId);
|
|
327
|
-
}
|
|
328
|
-
}
|
|
109
|
+
await this.db.custom_write_checkpoints.deleteMany({
|
|
110
|
+
checkpoint_requested_at: { $exists: true, $lt: this.deleteCheckpointRequestsBefore }
|
|
111
|
+
});
|
|
329
112
|
}
|
|
330
113
|
|
|
331
114
|
/**
|
|
@@ -336,14 +119,12 @@ export abstract class MongoCompactor {
|
|
|
336
119
|
* storage paths are also prepared with lifecycle markers, so a retry can
|
|
337
120
|
* safely overwrite or eventually clean up uploads from the failed attempt.
|
|
338
121
|
*/
|
|
339
|
-
protected async
|
|
122
|
+
protected async retryCompaction(bucket: string, compact: () => Promise<void>) {
|
|
340
123
|
let retryCount = 0;
|
|
341
124
|
while (true) {
|
|
342
125
|
this.signal?.throwIfAborted();
|
|
343
|
-
// Do not carry queued bucket state writes from a failed attempt into the rescan.
|
|
344
|
-
this.bucketStateUpdates = [];
|
|
345
126
|
try {
|
|
346
|
-
await
|
|
127
|
+
await compact();
|
|
347
128
|
return;
|
|
348
129
|
} catch (e) {
|
|
349
130
|
if (this.signal?.aborted) {
|
|
@@ -372,101 +153,6 @@ export abstract class MongoCompactor {
|
|
|
372
153
|
}
|
|
373
154
|
}
|
|
374
155
|
}
|
|
375
|
-
|
|
376
|
-
protected abstract compactSingleBucket(bucket: string, definitionId?: BucketDefinitionId | null): Promise<void>;
|
|
377
|
-
|
|
378
|
-
protected collectBucketStateUpdates(
|
|
379
|
-
state: CurrentBucketState,
|
|
380
|
-
compactedOpId: InternalOpId
|
|
381
|
-
): mongo.AnyBulkWriteOperation<BucketStateDocumentBase> {
|
|
382
|
-
if (state.opCount < 0) {
|
|
383
|
-
throw new ServiceAssertionError(
|
|
384
|
-
`Invalid opCount: ${state.opCount} checksum ${state.checksum} opsSincePut: ${state.opsSincePut} maxOpId: ${this.maxOpId}`
|
|
385
|
-
);
|
|
386
|
-
}
|
|
387
|
-
return {
|
|
388
|
-
updateOne: {
|
|
389
|
-
filter: this.bucketStateFilter(state.bucket, state.definitionId),
|
|
390
|
-
update: {
|
|
391
|
-
$set: {
|
|
392
|
-
compacted_state: {
|
|
393
|
-
op_id: compactedOpId,
|
|
394
|
-
count: state.opCount,
|
|
395
|
-
checksum: BigInt(state.checksum),
|
|
396
|
-
bytes: state.opBytes
|
|
397
|
-
},
|
|
398
|
-
estimate_since_compact: {
|
|
399
|
-
// There could have been a whole bunch of new operations added to the bucket while compacting,
|
|
400
|
-
// which we don't currently cater for. We could potentially query for that, but that adds overhead.
|
|
401
|
-
count: 0,
|
|
402
|
-
bytes: 0
|
|
403
|
-
}
|
|
404
|
-
}
|
|
405
|
-
} satisfies mongo.UpdateFilter<BucketStateDocumentBase>,
|
|
406
|
-
// We generally expect this to have been created before.
|
|
407
|
-
// We don't create new ones here, to avoid issues with the unique index on bucket_updates.
|
|
408
|
-
upsert: false
|
|
409
|
-
}
|
|
410
|
-
};
|
|
411
|
-
}
|
|
412
|
-
|
|
413
|
-
protected updateBucketChecksums(state: CurrentBucketState, compactedOpId: InternalOpId) {
|
|
414
|
-
this.bucketStateUpdates.push(this.collectBucketStateUpdates(state, compactedOpId));
|
|
415
|
-
}
|
|
416
|
-
|
|
417
|
-
protected async flushBucketStateUpdates() {
|
|
418
|
-
if (this.bucketStateUpdates.length > 0) {
|
|
419
|
-
this.logger.info(`Updating ${this.bucketStateUpdates.length} bucket states`);
|
|
420
|
-
await this.writeBucketStateUpdates();
|
|
421
|
-
this.bucketStateUpdates = [];
|
|
422
|
-
}
|
|
423
|
-
}
|
|
424
|
-
|
|
425
|
-
protected async updateChecksumsBatch(buckets: Pick<DirtyBucket, 'bucket' | 'definitionId'>[]) {
|
|
426
|
-
const checksums = await this.computeChecksumsForBuckets(buckets);
|
|
427
|
-
const definitionIdByBucket = new Map(buckets.map((bucket) => [bucket.bucket, bucket.definitionId]));
|
|
428
|
-
|
|
429
|
-
for (const bucketChecksum of checksums.values()) {
|
|
430
|
-
if (isPartialChecksum(bucketChecksum)) {
|
|
431
|
-
// Should never happen since we don't specify `start`.
|
|
432
|
-
throw new ServiceAssertionError(`Full checksum expected, got ${JSON.stringify(bucketChecksum)}`);
|
|
433
|
-
}
|
|
434
|
-
|
|
435
|
-
this.bucketStateUpdates.push({
|
|
436
|
-
updateOne: {
|
|
437
|
-
filter: this.bucketStateFilter(
|
|
438
|
-
bucketChecksum.bucket,
|
|
439
|
-
definitionIdByBucket.get(bucketChecksum.bucket) ?? null
|
|
440
|
-
),
|
|
441
|
-
update: {
|
|
442
|
-
$set: {
|
|
443
|
-
compacted_state: {
|
|
444
|
-
op_id: this.maxOpId,
|
|
445
|
-
count: bucketChecksum.count,
|
|
446
|
-
checksum: BigInt(bucketChecksum.checksum),
|
|
447
|
-
bytes: null
|
|
448
|
-
},
|
|
449
|
-
estimate_since_compact: {
|
|
450
|
-
count: 0,
|
|
451
|
-
bytes: 0
|
|
452
|
-
}
|
|
453
|
-
}
|
|
454
|
-
} satisfies mongo.UpdateFilter<BucketStateDocumentBase>,
|
|
455
|
-
// We don't create new ones here - it gets tricky to get the last_op right with the unique index on
|
|
456
|
-
// bucket_updates.
|
|
457
|
-
upsert: false
|
|
458
|
-
}
|
|
459
|
-
});
|
|
460
|
-
}
|
|
461
|
-
|
|
462
|
-
await this.flushBucketStateUpdates();
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
protected abstract writeBucketStateUpdates(): Promise<void>;
|
|
466
|
-
protected abstract computeChecksumsForBuckets(
|
|
467
|
-
buckets: Pick<DirtyBucket, 'bucket' | 'definitionId'>[]
|
|
468
|
-
): Promise<storage.PartialChecksumMap>;
|
|
469
|
-
protected abstract bucketStateFilter(bucket: string, definitionId: BucketDefinitionId | null): mongo.Document;
|
|
470
156
|
}
|
|
471
157
|
|
|
472
158
|
export interface BucketDataCollectionContext<TBucketData extends mongo.Document> {
|
|
@@ -475,6 +161,9 @@ export interface BucketDataCollectionContext<TBucketData extends mongo.Document>
|
|
|
475
161
|
}
|
|
476
162
|
|
|
477
163
|
function compactionRetryReason(error: unknown): string | null {
|
|
164
|
+
if (error instanceof CompactionLeaseLostError) {
|
|
165
|
+
return null;
|
|
166
|
+
}
|
|
478
167
|
if (error instanceof ConcurrentCompactionError) {
|
|
479
168
|
return 'concurrent compaction';
|
|
480
169
|
}
|