@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.
Files changed (145) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
  3. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
  4. package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
  5. package/dist/storage/MongoBucketStorage.d.ts +5 -1
  6. package/dist/storage/MongoBucketStorage.js +5 -1
  7. package/dist/storage/MongoBucketStorage.js.map +1 -1
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +3 -0
  9. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  10. package/dist/storage/implementation/MongoCompactor.d.ts +15 -19
  11. package/dist/storage/implementation/MongoCompactor.js +68 -260
  12. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  13. package/dist/storage/implementation/MongoStorageProvider.js +26 -3
  14. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  15. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +10 -4
  16. package/dist/storage/implementation/MongoSyncBucketStorage.js +13 -12
  17. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  18. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +2 -1
  19. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +146 -34
  20. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  21. package/dist/storage/implementation/common/PersistedBatch.d.ts +6 -0
  22. package/dist/storage/implementation/common/PersistedBatch.js +15 -8
  23. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  24. package/dist/storage/implementation/models.d.ts +20 -0
  25. package/dist/storage/implementation/models.js.map +1 -1
  26. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +4 -0
  27. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +58 -84
  28. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  29. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +11 -2
  30. package/dist/storage/implementation/v1/MongoCompactorV1.js +250 -1
  31. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  32. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +19 -11
  33. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  34. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
  35. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
  36. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
  37. package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
  38. package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
  39. package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
  40. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +35 -23
  41. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  42. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +1 -0
  43. package/dist/storage/implementation/v3/MongoChecksumsV3.js +59 -103
  44. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  45. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -2
  46. package/dist/storage/implementation/v3/MongoCompactorV3.js +354 -219
  47. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  48. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +5 -0
  49. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +12 -11
  50. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
  51. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +5 -3
  52. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +108 -106
  53. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  54. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +5 -2
  55. package/dist/storage/implementation/v3/PersistedBatchV3.js +53 -15
  56. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  57. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +2 -1
  58. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +3 -0
  59. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  60. package/dist/storage/implementation/v3/bucket-format.d.ts +10 -1
  61. package/dist/storage/implementation/v3/bucket-format.js +28 -9
  62. package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
  63. package/dist/storage/implementation/v3/chunking.js +3 -1
  64. package/dist/storage/implementation/v3/chunking.js.map +1 -1
  65. package/dist/storage/implementation/v3/models.d.ts +43 -2
  66. package/dist/storage/implementation/v3/models.js.map +1 -1
  67. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +21 -0
  68. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +44 -0
  69. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
  70. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +30 -0
  71. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
  72. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
  73. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +70 -0
  74. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +146 -0
  75. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
  76. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +42 -0
  77. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +285 -0
  78. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
  79. package/dist/storage/implementation/v3/source-table-utils.d.ts +9 -1
  80. package/dist/storage/implementation/v3/source-table-utils.js +20 -21
  81. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
  82. package/dist/types/types.d.ts +35 -0
  83. package/dist/types/types.js +35 -1
  84. package/dist/types/types.js.map +1 -1
  85. package/dist/utils/test-utils.js +3 -1
  86. package/dist/utils/test-utils.js.map +1 -1
  87. package/dist/utils/util.d.ts +8 -1
  88. package/dist/utils/util.js +125 -2
  89. package/dist/utils/util.js.map +1 -1
  90. package/package.json +10 -7
  91. package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
  92. package/src/storage/MongoBucketStorage.ts +10 -2
  93. package/src/storage/implementation/MongoBucketBatch.ts +4 -0
  94. package/src/storage/implementation/MongoCompactor.ts +90 -314
  95. package/src/storage/implementation/MongoStorageProvider.ts +32 -3
  96. package/src/storage/implementation/MongoSyncBucketStorage.ts +23 -22
  97. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +164 -37
  98. package/src/storage/implementation/common/PersistedBatch.ts +22 -10
  99. package/src/storage/implementation/models.ts +20 -0
  100. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +76 -96
  101. package/src/storage/implementation/v1/MongoCompactorV1.ts +292 -8
  102. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +53 -40
  103. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
  104. package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
  105. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +40 -23
  106. package/src/storage/implementation/v3/MongoChecksumsV3.ts +75 -105
  107. package/src/storage/implementation/v3/MongoCompactorV3.ts +456 -258
  108. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +21 -19
  109. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +122 -126
  110. package/src/storage/implementation/v3/PersistedBatchV3.ts +69 -16
  111. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +5 -0
  112. package/src/storage/implementation/v3/bucket-format.ts +36 -9
  113. package/src/storage/implementation/v3/chunking.ts +3 -1
  114. package/src/storage/implementation/v3/models.ts +45 -1
  115. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +54 -0
  116. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +29 -0
  117. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +171 -0
  118. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +343 -0
  119. package/src/storage/implementation/v3/source-table-utils.ts +43 -35
  120. package/src/types/types.ts +42 -1
  121. package/src/utils/test-utils.ts +3 -1
  122. package/src/utils/util.ts +182 -4
  123. package/test/src/__snapshots__/storage_sync.test.ts.snap +1 -583
  124. package/test/src/cleanup-stopped-sync-configs.test.ts +24 -1
  125. package/test/src/helpers/MemoryObjectStorage.ts +53 -0
  126. package/test/src/helpers/s3TestFactory.ts +69 -0
  127. package/test/src/object_storage_lifecycle.test.ts +59 -0
  128. package/test/src/setup.ts +6 -1
  129. package/test/src/storage.test.ts +216 -2
  130. package/test/src/storage_compacting.test.ts +176 -187
  131. package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
  132. package/test/src/storage_s3_checksums.test.ts +191 -0
  133. package/test/src/storage_s3_compaction_lifecycle.test.ts +417 -0
  134. package/test/src/storage_s3_reading.test.ts +526 -0
  135. package/test/src/storage_s3_writing.test.ts +135 -0
  136. package/test/src/storage_sync.test.ts +124 -5
  137. package/tsconfig.tsbuildinfo +1 -1
  138. package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
  139. package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
  140. package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
  141. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
  142. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -84
  143. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
  144. package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
  145. 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
- export function idPrefixFilter<T>(prefix: Partial<T>, rest: (keyof T)[]): mongo.Condition<T> {
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.isMongoServerError(e) || e.codeName !== 'MaxTimeMSExpired') {
194
+ if (!lib_mongo.isMaxTimeMSExpiredError(e)) {
153
195
  throw e;
154
196
  }
155
197
  retryCount += 1;
156
198
  options.onRetry?.(retryCount);
157
- await timers.setTimeout(options.retryDelayMs);
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>,