@powersync/service-module-mongodb-storage 0.20.0 → 0.21.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 +45 -0
- package/dist/storage/MongoBucketStorage.d.ts +31 -3
- package/dist/storage/MongoBucketStorage.js +365 -206
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +15 -4
- package/dist/storage/implementation/MongoBucketBatch.js +35 -9
- 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 +3 -1
- 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/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +7 -28
- 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 +27 -0
- package/dist/storage/implementation/common/PersistedBatch.js +43 -2
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +4 -1
- package/dist/storage/implementation/db.js +14 -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 +22 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +36 -5
- package/dist/storage/implementation/v1/MongoCompactorV1.js +229 -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 +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/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 +8 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +90 -19
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +77 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +585 -141
- 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 +2 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +52 -11
- 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 +8 -4
- 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 +3 -5
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +10 -2
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- 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 +37 -12
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +177 -23
- 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 +23 -1
- package/dist/types/types.js +16 -7
- package/dist/types/types.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 +495 -268
- package/src/storage/implementation/MongoBucketBatch.ts +54 -14
- 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 +3 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +12 -44
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +54 -2
- package/src/storage/implementation/db.ts +21 -2
- package/src/storage/implementation/models.ts +34 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +31 -0
- package/src/storage/implementation/v1/MongoCompactorV1.ts +303 -19
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
- 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 +124 -22
- package/src/storage/implementation/v3/MongoCompactorV3.ts +744 -190
- 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 +57 -12
- 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 +16 -8
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +15 -4
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +257 -27
- package/src/storage/implementation/v3/source-table-utils.ts +39 -41
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +24 -7
- 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/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 +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/storage.test.ts +273 -22
- package/test/src/storage_compacting.test.ts +714 -244
- 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 +243 -1
- 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,9 @@ 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 { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
|
|
34
39
|
import { OperationBatch, RecordOperation } from './OperationBatch.js';
|
|
35
40
|
import { ObjectStorage } from './v3/object-storage/ObjectStorage.js';
|
|
41
|
+
import { createObjectStorageUsageWriterId } from './v3/object-storage/ObjectStorageUsage.js';
|
|
36
42
|
|
|
37
43
|
// Currently, we can only have a single flush() at a time, since it locks the op_id sequence.
|
|
38
44
|
// While the MongoDB transaction retry mechanism handles this okay, using an in-process Mutex
|
|
@@ -72,6 +78,10 @@ export interface MongoBucketBatchOptions {
|
|
|
72
78
|
|
|
73
79
|
logger: Logger;
|
|
74
80
|
tracer?: PerformanceTracer<'storage' | 'evaluate'>;
|
|
81
|
+
/**
|
|
82
|
+
* Aborts in-flight object storage uploads when replication stops.
|
|
83
|
+
*/
|
|
84
|
+
signal?: AbortSignal;
|
|
75
85
|
|
|
76
86
|
objectStorage?: ObjectStorage;
|
|
77
87
|
inlineThresholdBytes?: number;
|
|
@@ -104,9 +114,10 @@ export abstract class MongoBucketBatch
|
|
|
104
114
|
private readonly storeCurrentData: boolean;
|
|
105
115
|
public readonly skipExistingRows: boolean;
|
|
106
116
|
protected readonly mapping: BucketDefinitionMapping;
|
|
117
|
+
protected readonly objectStorageUsageWriterId = createObjectStorageUsageWriterId();
|
|
107
118
|
|
|
108
119
|
private batch: OperationBatch | null = null;
|
|
109
|
-
|
|
120
|
+
protected write_checkpoint_batch: storage.CustomWriteCheckpointOptions[] = [];
|
|
110
121
|
private markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
|
|
111
122
|
private hooks: storage.StorageHooks | undefined;
|
|
112
123
|
private clearedError = false;
|
|
@@ -185,6 +196,11 @@ export abstract class MongoBucketBatch
|
|
|
185
196
|
no_checkpoint_before_lsn?: string
|
|
186
197
|
): Promise<storage.SourceTable[]>;
|
|
187
198
|
|
|
199
|
+
protected abstract batchCreateCustomWriteCheckpoints(session: mongo.ClientSession, opId: InternalOpId): Promise<void>;
|
|
200
|
+
|
|
201
|
+
/** Perform any version-specific setup required before writing custom checkpoints. */
|
|
202
|
+
protected async prepareCustomWriteCheckpoints(): Promise<void> {}
|
|
203
|
+
|
|
188
204
|
async flush(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null> {
|
|
189
205
|
let result: storage.FlushedResult | null = null;
|
|
190
206
|
// One flush may be split over multiple transactions.
|
|
@@ -207,6 +223,10 @@ export abstract class MongoBucketBatch
|
|
|
207
223
|
using _ = this.tracer.span('storage', 'flush');
|
|
208
224
|
|
|
209
225
|
await this.hooks?.beforeBatchFlush?.(this);
|
|
226
|
+
if (this.write_checkpoint_batch.length > 0) {
|
|
227
|
+
// Collection/index creation cannot run inside the replication transaction.
|
|
228
|
+
await this.prepareCustomWriteCheckpoints();
|
|
229
|
+
}
|
|
210
230
|
|
|
211
231
|
await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
|
|
212
232
|
clearedError = false;
|
|
@@ -218,7 +238,7 @@ export abstract class MongoBucketBatch
|
|
|
218
238
|
|
|
219
239
|
if (this.write_checkpoint_batch.length > 0) {
|
|
220
240
|
this.logger.info(`Writing ${this.write_checkpoint_batch.length} custom write checkpoints`);
|
|
221
|
-
await batchCreateCustomWriteCheckpoints(
|
|
241
|
+
await this.batchCreateCustomWriteCheckpoints(session, opSeq.next());
|
|
222
242
|
this.write_checkpoint_batch = [];
|
|
223
243
|
}
|
|
224
244
|
|
|
@@ -762,11 +782,12 @@ export abstract class MongoBucketBatch
|
|
|
762
782
|
async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
|
|
763
783
|
const { after, before, sourceTable, tag } = record;
|
|
764
784
|
const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
|
|
765
|
-
//
|
|
766
|
-
// SourceTables
|
|
767
|
-
//
|
|
785
|
+
// V3 source tables own disjoint event-definition ids for each physical table. Multiple
|
|
786
|
+
// SourceTables may evaluate different events, but each definition is evaluated through
|
|
787
|
+
// at most one record for this row change.
|
|
788
|
+
// Legacy storage leaves eventDefinitionIds undefined and selects by table ref.
|
|
768
789
|
if (sourceTable.syncEvent) {
|
|
769
|
-
for (const event of this.getTableEvents(sourceTable)) {
|
|
790
|
+
for (const { event, eventId } of this.getTableEvents(sourceTable)) {
|
|
770
791
|
this.iterateListeners((cb) =>
|
|
771
792
|
cb.replicationEvent?.({
|
|
772
793
|
batch: this,
|
|
@@ -776,7 +797,8 @@ export abstract class MongoBucketBatch
|
|
|
776
797
|
after: after && utils.isCompleteRow(storeCurrentData, after) ? after : undefined,
|
|
777
798
|
before: before && utils.isCompleteRow(storeCurrentData, before) ? before : undefined
|
|
778
799
|
},
|
|
779
|
-
event
|
|
800
|
+
event,
|
|
801
|
+
event_id: eventId
|
|
780
802
|
})
|
|
781
803
|
);
|
|
782
804
|
}
|
|
@@ -934,11 +956,29 @@ export abstract class MongoBucketBatch
|
|
|
934
956
|
}
|
|
935
957
|
|
|
936
958
|
/**
|
|
937
|
-
* Gets relevant {@link
|
|
959
|
+
* Gets relevant {@link HydratedEventDescriptor}s for the given {@link SourceTable}
|
|
938
960
|
*/
|
|
939
|
-
protected getTableEvents(
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
961
|
+
protected getTableEvents(
|
|
962
|
+
table: storage.SourceTable
|
|
963
|
+
): { event: HydratedEventDescriptor; eventId?: EventDefinitionId }[] {
|
|
964
|
+
// V3 storage assigns event-definition ids to each source table, so membership is authoritative.
|
|
965
|
+
// Iterate the table's distinct ids and resolve each through the stream's deduped event map, so a
|
|
966
|
+
// definition reused across configs has one evaluator for that id. The evaluator may still return
|
|
967
|
+
// multiple payloads from matching queries. Legacy storage leaves this undefined and selects by table ref.
|
|
968
|
+
if (table.eventDefinitionIds != null) {
|
|
969
|
+
const eventById = this.options.parsedSyncConfig.eventById;
|
|
970
|
+
const events: { event: HydratedEventDescriptor; eventId: EventDefinitionId }[] = [];
|
|
971
|
+
for (const id of table.eventDefinitionIds) {
|
|
972
|
+
const event = eventById.get(id);
|
|
973
|
+
if (event != null && event.tableTriggersEvent(table.ref)) {
|
|
974
|
+
events.push({ event, eventId: id });
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
return events;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
return this.sync_rules.eventDescriptors
|
|
981
|
+
.filter((event) => event.tableTriggersEvent(table.ref))
|
|
982
|
+
.map((event) => ({ event }));
|
|
943
983
|
}
|
|
944
984
|
}
|
|
@@ -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
|
}
|