@powersync/service-module-mongodb-storage 0.18.3 → 0.20.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 +53 -0
- 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 +5 -1
- package/dist/storage/MongoBucketStorage.js +5 -1
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +3 -0
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +15 -19
- package/dist/storage/implementation/MongoCompactor.js +68 -260
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +26 -3
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +10 -4
- package/dist/storage/implementation/MongoSyncBucketStorage.js +13 -12
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +2 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +146 -34
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/common/PersistedBatch.d.ts +6 -0
- package/dist/storage/implementation/common/PersistedBatch.js +15 -8
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +20 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +4 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +58 -84
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +11 -2
- package/dist/storage/implementation/v1/MongoCompactorV1.js +250 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +19 -11
- 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/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/MongoBucketBatchV3.js +35 -23
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +1 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +59 -103
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -2
- package/dist/storage/implementation/v3/MongoCompactorV3.js +354 -219
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +5 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +12 -11
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +5 -3
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +108 -106
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +5 -2
- package/dist/storage/implementation/v3/PersistedBatchV3.js +53 -15
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +2 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +3 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +10 -1
- package/dist/storage/implementation/v3/bucket-format.js +28 -9
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- package/dist/storage/implementation/v3/chunking.js +3 -1
- package/dist/storage/implementation/v3/chunking.js.map +1 -1
- package/dist/storage/implementation/v3/models.d.ts +43 -2
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +21 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +44 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +30 -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 +70 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +146 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +42 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +285 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/source-table-utils.d.ts +9 -1
- package/dist/storage/implementation/v3/source-table-utils.js +20 -21
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
- package/dist/types/types.d.ts +35 -0
- package/dist/types/types.js +35 -1
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.js +3 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +8 -1
- package/dist/utils/util.js +125 -2
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -7
- package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
- package/src/storage/MongoBucketStorage.ts +10 -2
- package/src/storage/implementation/MongoBucketBatch.ts +4 -0
- package/src/storage/implementation/MongoCompactor.ts +90 -314
- package/src/storage/implementation/MongoStorageProvider.ts +32 -3
- package/src/storage/implementation/MongoSyncBucketStorage.ts +23 -22
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +164 -37
- package/src/storage/implementation/common/PersistedBatch.ts +22 -10
- package/src/storage/implementation/models.ts +20 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +76 -96
- package/src/storage/implementation/v1/MongoCompactorV1.ts +292 -8
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +53 -40
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
- package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +40 -23
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +75 -105
- package/src/storage/implementation/v3/MongoCompactorV3.ts +456 -258
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +21 -19
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +122 -126
- package/src/storage/implementation/v3/PersistedBatchV3.ts +69 -16
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +5 -0
- package/src/storage/implementation/v3/bucket-format.ts +36 -9
- package/src/storage/implementation/v3/chunking.ts +3 -1
- package/src/storage/implementation/v3/models.ts +45 -1
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +54 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +29 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +171 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +343 -0
- package/src/storage/implementation/v3/source-table-utils.ts +43 -35
- package/src/types/types.ts +42 -1
- package/src/utils/test-utils.ts +3 -1
- package/src/utils/util.ts +182 -4
- package/test/src/__snapshots__/storage_sync.test.ts.snap +1 -583
- package/test/src/cleanup-stopped-sync-configs.test.ts +24 -1
- package/test/src/helpers/MemoryObjectStorage.ts +53 -0
- package/test/src/helpers/s3TestFactory.ts +69 -0
- package/test/src/object_storage_lifecycle.test.ts +59 -0
- package/test/src/setup.ts +6 -1
- package/test/src/storage.test.ts +216 -2
- package/test/src/storage_compacting.test.ts +176 -187
- package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
- package/test/src/storage_s3_checksums.test.ts +191 -0
- package/test/src/storage_s3_compaction_lifecycle.test.ts +417 -0
- package/test/src/storage_s3_reading.test.ts +526 -0
- package/test/src/storage_s3_writing.test.ts +135 -0
- package/test/src/storage_sync.test.ts +124 -5
- package/tsconfig.tsbuildinfo +1 -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/SingleBucketStoreV3.d.ts +0 -18
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -84
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
- package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -108
package/src/utils/util.ts
CHANGED
|
@@ -1,14 +1,56 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
|
-
import { ReplicationAbortedError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
|
+
import { Logger, ReplicationAbortedError, ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
4
4
|
import { storage, utils } from '@powersync/service-core';
|
|
5
5
|
import * as bson from 'bson';
|
|
6
6
|
import * as crypto from 'crypto';
|
|
7
7
|
import * as timers from 'node:timers/promises';
|
|
8
8
|
import * as uuid from 'uuid';
|
|
9
9
|
import { BucketDataDoc } from '../storage/implementation/common/BucketDataDoc.js';
|
|
10
|
+
import { DEFAULT_CLEAR_BATCH_THROTTLE_RATE } from '../types/types.js';
|
|
10
11
|
|
|
11
|
-
|
|
12
|
+
/**
|
|
13
|
+
* Default and max batch size for clearing.
|
|
14
|
+
*/
|
|
15
|
+
const CLEAR_BATCH_SIZE = 10_000;
|
|
16
|
+
|
|
17
|
+
/**
|
|
18
|
+
* Minimum batch size for clearing when a batch takes too long.
|
|
19
|
+
*/
|
|
20
|
+
const CLEAR_MIN_BATCH_SIZE = 100;
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* We target batch duration to stay between the min and max. If the druation is outside this range,
|
|
24
|
+
* we increase or decrease the batch size.
|
|
25
|
+
*
|
|
26
|
+
* MONGO_CLEAR_OPERATION_TIMEOUT_MS controls the absolute max of individual find and delete operations,
|
|
27
|
+
* but we try to avoid hitting that.
|
|
28
|
+
*/
|
|
29
|
+
const CLEAR_BATCH_MIN_DURATION_MS = 500;
|
|
30
|
+
const CLEAR_BATCH_MAX_DURATION_MS = 2000;
|
|
31
|
+
|
|
32
|
+
type ClearCollectionFilter<T extends mongo.Document> = mongo.Filter<T> & {
|
|
33
|
+
_id: mongo.FilterOperators<mongo.InferIdType<T>>;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
function throwIfClearAborted(signal?: AbortSignal): void {
|
|
37
|
+
if (signal?.aborted) {
|
|
38
|
+
throw new ReplicationAbortedError('Aborted clearing data', signal.reason);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
async function waitWithSignal(delayMs: number, signal: AbortSignal | undefined, abortMessage: string): Promise<void> {
|
|
43
|
+
try {
|
|
44
|
+
await timers.setTimeout(delayMs, undefined, { signal });
|
|
45
|
+
} catch (error) {
|
|
46
|
+
if (signal?.aborted) {
|
|
47
|
+
throw new ReplicationAbortedError(abortMessage, signal.reason);
|
|
48
|
+
}
|
|
49
|
+
throw error;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export function idPrefixFilter<T>(prefix: Partial<T>, rest: (keyof T)[]): mongo.FilterOperators<T> {
|
|
12
54
|
let filter = {
|
|
13
55
|
$gte: {
|
|
14
56
|
...prefix
|
|
@@ -149,16 +191,152 @@ export async function retryOnMongoMaxTimeMSExpired<T>(
|
|
|
149
191
|
try {
|
|
150
192
|
return await operation();
|
|
151
193
|
} catch (e) {
|
|
152
|
-
if (!lib_mongo.
|
|
194
|
+
if (!lib_mongo.isMaxTimeMSExpiredError(e)) {
|
|
153
195
|
throw e;
|
|
154
196
|
}
|
|
155
197
|
retryCount += 1;
|
|
156
198
|
options.onRetry?.(retryCount);
|
|
157
|
-
await
|
|
199
|
+
await waitWithSignal(options.retryDelayMs, options.signal, options.abortMessage ?? 'Aborted MongoDB operation');
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
async function clearCollectionInBatches(
|
|
205
|
+
logger: Logger,
|
|
206
|
+
label: string,
|
|
207
|
+
findAndDeleteBatch: (batchSize: number) => Promise<number>,
|
|
208
|
+
signal?: AbortSignal,
|
|
209
|
+
throttleRate = DEFAULT_CLEAR_BATCH_THROTTLE_RATE
|
|
210
|
+
): Promise<number> {
|
|
211
|
+
let batchSize = CLEAR_BATCH_SIZE;
|
|
212
|
+
let deletedCount = 0;
|
|
213
|
+
while (true) {
|
|
214
|
+
throwIfClearAborted(signal);
|
|
215
|
+
try {
|
|
216
|
+
const batchStartedAt = performance.now();
|
|
217
|
+
const foundBatchSize = await findAndDeleteBatch(batchSize);
|
|
218
|
+
const batchDurationMs = performance.now() - batchStartedAt;
|
|
219
|
+
deletedCount += foundBatchSize;
|
|
220
|
+
if (foundBatchSize > 0) {
|
|
221
|
+
logger.info(
|
|
222
|
+
`Cleared batch of ${label} (${foundBatchSize} documents) in ${Math.round(batchDurationMs)}ms, continuing...`
|
|
223
|
+
);
|
|
224
|
+
}
|
|
225
|
+
if (foundBatchSize < batchSize) {
|
|
226
|
+
return deletedCount;
|
|
227
|
+
}
|
|
228
|
+
if (batchDurationMs > CLEAR_BATCH_MAX_DURATION_MS) {
|
|
229
|
+
batchSize = Math.max(CLEAR_MIN_BATCH_SIZE, Math.floor(batchSize / 2));
|
|
230
|
+
} else if (batchDurationMs < CLEAR_BATCH_MIN_DURATION_MS) {
|
|
231
|
+
batchSize = Math.min(CLEAR_BATCH_SIZE, batchSize * 2);
|
|
232
|
+
}
|
|
233
|
+
await waitWithSignal(batchDurationMs * throttleRate, signal, 'Aborted clearing data');
|
|
234
|
+
} catch (error) {
|
|
235
|
+
if (!lib_mongo.isTimeoutError(error) || batchSize == CLEAR_MIN_BATCH_SIZE) {
|
|
236
|
+
throw error;
|
|
237
|
+
}
|
|
238
|
+
const nextBatchSize = Math.max(CLEAR_MIN_BATCH_SIZE, Math.floor(batchSize / 2));
|
|
239
|
+
logger.info(
|
|
240
|
+
`Clearing batch of ${label} timed out with batch size ${batchSize}, retrying find and delete with ${nextBatchSize}...`
|
|
241
|
+
);
|
|
242
|
+
batchSize = nextBatchSize;
|
|
243
|
+
|
|
244
|
+
// Pause for at least the equivalent of one query timeout before retrying, since the cause may be high database load.
|
|
245
|
+
const pauseDuration = Math.max(throttleRate, 1.0) * lib_mongo.MONGO_CLEAR_OPERATION_TIMEOUT_MS;
|
|
246
|
+
await waitWithSignal(pauseDuration, signal, 'Aborted clearing data');
|
|
158
247
|
}
|
|
159
248
|
}
|
|
160
249
|
}
|
|
161
250
|
|
|
251
|
+
export async function clearCollectionInIdRanges<T extends mongo.Document>(
|
|
252
|
+
logger: Logger,
|
|
253
|
+
label: string,
|
|
254
|
+
collection: mongo.Collection<T>,
|
|
255
|
+
filter: ClearCollectionFilter<T>,
|
|
256
|
+
signal?: AbortSignal,
|
|
257
|
+
throttleRate = DEFAULT_CLEAR_BATCH_THROTTLE_RATE
|
|
258
|
+
): Promise<number> {
|
|
259
|
+
return clearCollectionInBatches(
|
|
260
|
+
logger,
|
|
261
|
+
label,
|
|
262
|
+
async (batchSize) => {
|
|
263
|
+
const queryOptions: mongo.FindOptions<T> = {
|
|
264
|
+
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS,
|
|
265
|
+
projection: { _id: 1 }
|
|
266
|
+
};
|
|
267
|
+
const [batchEnd] = await collection
|
|
268
|
+
.find(filter, queryOptions)
|
|
269
|
+
.sort({ _id: 1 })
|
|
270
|
+
.skip(batchSize)
|
|
271
|
+
.limit(1)
|
|
272
|
+
.toArray();
|
|
273
|
+
throwIfClearAborted(signal);
|
|
274
|
+
|
|
275
|
+
if (batchEnd == null) {
|
|
276
|
+
// We're on the last batch
|
|
277
|
+
const result = await collection.deleteMany(filter, {
|
|
278
|
+
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
|
|
279
|
+
});
|
|
280
|
+
return result.deletedCount;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
const idRange: mongo.FilterOperators<mongo.InferIdType<T>> = {
|
|
284
|
+
...filter._id,
|
|
285
|
+
$lt: batchEnd._id
|
|
286
|
+
};
|
|
287
|
+
await collection.deleteMany(
|
|
288
|
+
{
|
|
289
|
+
...filter,
|
|
290
|
+
_id: idRange
|
|
291
|
+
} as mongo.Filter<T>,
|
|
292
|
+
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
293
|
+
);
|
|
294
|
+
return batchSize;
|
|
295
|
+
},
|
|
296
|
+
signal,
|
|
297
|
+
throttleRate
|
|
298
|
+
);
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
export async function clearCollectionInIdBatches<T extends mongo.Document>(
|
|
302
|
+
logger: Logger,
|
|
303
|
+
label: string,
|
|
304
|
+
collection: mongo.Collection<T>,
|
|
305
|
+
filter: mongo.Filter<T>,
|
|
306
|
+
signal?: AbortSignal,
|
|
307
|
+
throttleRate = DEFAULT_CLEAR_BATCH_THROTTLE_RATE
|
|
308
|
+
): Promise<number> {
|
|
309
|
+
return clearCollectionInBatches(
|
|
310
|
+
logger,
|
|
311
|
+
label,
|
|
312
|
+
async (batchSize) => {
|
|
313
|
+
const documents = await collection
|
|
314
|
+
.find(filter, {
|
|
315
|
+
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS,
|
|
316
|
+
projection: { _id: 1 }
|
|
317
|
+
})
|
|
318
|
+
.limit(batchSize)
|
|
319
|
+
.toArray();
|
|
320
|
+
throwIfClearAborted(signal);
|
|
321
|
+
|
|
322
|
+
if (documents.length === 0) {
|
|
323
|
+
return 0;
|
|
324
|
+
}
|
|
325
|
+
await collection.deleteMany(
|
|
326
|
+
{
|
|
327
|
+
_id: {
|
|
328
|
+
$in: documents.map((document) => document._id)
|
|
329
|
+
}
|
|
330
|
+
} as mongo.Filter<T>,
|
|
331
|
+
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
332
|
+
);
|
|
333
|
+
return documents.length;
|
|
334
|
+
},
|
|
335
|
+
signal,
|
|
336
|
+
throttleRate
|
|
337
|
+
);
|
|
338
|
+
}
|
|
339
|
+
|
|
162
340
|
export const createPaginatedConnectionQuery = async <T extends mongo.Document>(
|
|
163
341
|
query: mongo.Filter<T>,
|
|
164
342
|
collection: mongo.Collection<T>,
|