@powersync/service-module-mongodb-storage 0.0.0-dev-20260225160713 → 0.0.0-dev-20260511080634

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 (213) hide show
  1. package/CHANGELOG.md +119 -6
  2. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +1 -1
  3. package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -1
  4. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +3 -3
  5. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  6. package/dist/migrations/db/migrations/1770213298299-storage-version.js.map +1 -1
  7. package/dist/storage/MongoBucketStorage.d.ts +5 -3
  8. package/dist/storage/MongoBucketStorage.js +53 -26
  9. package/dist/storage/MongoBucketStorage.js.map +1 -1
  10. package/dist/storage/MongoReportStorage.js.map +1 -1
  11. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +17 -0
  12. package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
  13. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
  14. package/dist/storage/implementation/MongoBucketBatch.d.ts +25 -21
  15. package/dist/storage/implementation/MongoBucketBatch.js +256 -210
  16. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  17. package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
  18. package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
  19. package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
  20. package/dist/storage/implementation/MongoChecksums.d.ts +30 -19
  21. package/dist/storage/implementation/MongoChecksums.js +13 -71
  22. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  23. package/dist/storage/implementation/MongoCompactor.d.ts +99 -53
  24. package/dist/storage/implementation/MongoCompactor.js +265 -303
  25. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  26. package/dist/storage/implementation/MongoParameterCompactor.d.ts +12 -7
  27. package/dist/storage/implementation/MongoParameterCompactor.js +24 -9
  28. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  29. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
  30. package/dist/storage/implementation/MongoPersistedSyncRules.js +64 -0
  31. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
  32. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +3 -0
  33. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +11 -7
  34. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
  35. package/dist/storage/implementation/MongoStorageProvider.js +1 -1
  36. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  37. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +54 -30
  38. package/dist/storage/implementation/MongoSyncBucketStorage.js +118 -472
  39. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  40. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +7 -5
  41. package/dist/storage/implementation/MongoSyncRulesLock.js +12 -10
  42. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  43. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +4 -4
  44. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
  45. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  46. package/dist/storage/implementation/OperationBatch.js +3 -2
  47. package/dist/storage/implementation/OperationBatch.js.map +1 -1
  48. package/dist/storage/implementation/common/BucketDataDoc.d.ts +35 -0
  49. package/dist/storage/implementation/common/BucketDataDoc.js +2 -0
  50. package/dist/storage/implementation/common/BucketDataDoc.js.map +1 -0
  51. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +13 -0
  52. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +2 -0
  53. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +1 -0
  54. package/dist/storage/implementation/common/PersistedBatch.d.ts +108 -0
  55. package/dist/storage/implementation/common/PersistedBatch.js +237 -0
  56. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -0
  57. package/dist/storage/implementation/common/SingleBucketStore.d.ts +54 -0
  58. package/dist/storage/implementation/common/SingleBucketStore.js +3 -0
  59. package/dist/storage/implementation/common/SingleBucketStore.js.map +1 -0
  60. package/dist/storage/implementation/common/SourceRecordStore.d.ts +36 -0
  61. package/dist/storage/implementation/common/SourceRecordStore.js +2 -0
  62. package/dist/storage/implementation/common/SourceRecordStore.js.map +1 -0
  63. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +27 -0
  64. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +57 -0
  65. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -0
  66. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +7 -0
  67. package/dist/storage/implementation/createMongoSyncBucketStorage.js +9 -0
  68. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -0
  69. package/dist/storage/implementation/db.d.ts +38 -4
  70. package/dist/storage/implementation/db.js +80 -1
  71. package/dist/storage/implementation/db.js.map +1 -1
  72. package/dist/storage/implementation/models.d.ts +66 -25
  73. package/dist/storage/implementation/models.js +22 -2
  74. package/dist/storage/implementation/models.js.map +1 -1
  75. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +13 -0
  76. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +22 -0
  77. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -0
  78. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +12 -0
  79. package/dist/storage/implementation/v1/MongoChecksumsV1.js +56 -0
  80. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -0
  81. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +23 -0
  82. package/dist/storage/implementation/v1/MongoCompactorV1.js +52 -0
  83. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -0
  84. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +9 -0
  85. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -0
  86. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -0
  87. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +41 -0
  88. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +269 -0
  89. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
  90. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +26 -0
  91. package/dist/storage/implementation/v1/PersistedBatchV1.js +183 -0
  92. package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -0
  93. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +18 -0
  94. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +57 -0
  95. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -0
  96. package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +19 -0
  97. package/dist/storage/implementation/v1/SourceRecordStoreV1.js +105 -0
  98. package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -0
  99. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +12 -0
  100. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +20 -0
  101. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -0
  102. package/dist/storage/implementation/v1/models.d.ts +34 -0
  103. package/dist/storage/implementation/v1/models.js +37 -0
  104. package/dist/storage/implementation/v1/models.js.map +1 -0
  105. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +13 -0
  106. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +34 -0
  107. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -0
  108. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +15 -0
  109. package/dist/storage/implementation/v3/MongoChecksumsV3.js +84 -0
  110. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -0
  111. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -0
  112. package/dist/storage/implementation/v3/MongoCompactorV3.js +68 -0
  113. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -0
  114. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +9 -0
  115. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +18 -0
  116. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
  117. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +5 -0
  118. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +9 -0
  119. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -0
  120. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +41 -0
  121. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +404 -0
  122. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
  123. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +29 -0
  124. package/dist/storage/implementation/v3/PersistedBatchV3.js +259 -0
  125. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -0
  126. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +18 -0
  127. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +48 -0
  128. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -0
  129. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +22 -0
  130. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +164 -0
  131. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -0
  132. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +21 -0
  133. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +71 -0
  134. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
  135. package/dist/storage/implementation/v3/models.d.ts +43 -0
  136. package/dist/storage/implementation/v3/models.js +34 -0
  137. package/dist/storage/implementation/v3/models.js.map +1 -0
  138. package/dist/storage/storage-index.d.ts +8 -5
  139. package/dist/storage/storage-index.js +8 -5
  140. package/dist/storage/storage-index.js.map +1 -1
  141. package/dist/utils/test-utils.d.ts +4 -1
  142. package/dist/utils/test-utils.js +15 -12
  143. package/dist/utils/test-utils.js.map +1 -1
  144. package/dist/utils/util.d.ts +13 -5
  145. package/dist/utils/util.js +40 -5
  146. package/dist/utils/util.js.map +1 -1
  147. package/package.json +9 -9
  148. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
  149. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
  150. package/src/migrations/db/migrations/1770213298299-storage-version.ts +1 -1
  151. package/src/storage/MongoBucketStorage.ts +111 -55
  152. package/src/storage/MongoReportStorage.ts +2 -2
  153. package/src/storage/implementation/BucketDefinitionMapping.ts +72 -0
  154. package/src/storage/implementation/MongoBucketBatch.ts +322 -270
  155. package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
  156. package/src/storage/implementation/MongoChecksums.ts +54 -75
  157. package/src/storage/implementation/MongoCompactor.ts +411 -412
  158. package/src/storage/implementation/MongoParameterCompactor.ts +52 -26
  159. package/src/storage/implementation/MongoPersistedSyncRules.ts +76 -0
  160. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +19 -10
  161. package/src/storage/implementation/MongoStorageProvider.ts +1 -1
  162. package/src/storage/implementation/MongoSyncBucketStorage.ts +206 -466
  163. package/src/storage/implementation/MongoSyncRulesLock.ts +14 -16
  164. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +7 -5
  165. package/src/storage/implementation/OperationBatch.ts +3 -2
  166. package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
  167. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
  168. package/src/storage/implementation/common/PersistedBatch.ts +364 -0
  169. package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
  170. package/src/storage/implementation/common/SourceRecordStore.ts +49 -0
  171. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
  172. package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
  173. package/src/storage/implementation/db.ts +115 -8
  174. package/src/storage/implementation/models.ts +88 -28
  175. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +32 -0
  176. package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
  177. package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
  178. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
  179. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +432 -0
  180. package/src/storage/implementation/v1/PersistedBatchV1.ts +230 -0
  181. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
  182. package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
  183. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
  184. package/src/storage/implementation/v1/models.ts +84 -0
  185. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +44 -0
  186. package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
  187. package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
  188. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
  189. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +12 -0
  190. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +545 -0
  191. package/src/storage/implementation/v3/PersistedBatchV3.ts +318 -0
  192. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
  193. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
  194. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +112 -0
  195. package/src/storage/implementation/v3/models.ts +96 -0
  196. package/src/storage/storage-index.ts +8 -5
  197. package/src/utils/test-utils.ts +15 -12
  198. package/src/utils/util.ts +53 -9
  199. package/test/src/__snapshots__/storage.test.ts.snap +201 -0
  200. package/test/src/__snapshots__/storage_compacting.test.ts.snap +17 -0
  201. package/test/src/__snapshots__/storage_sync.test.ts.snap +1393 -16
  202. package/test/src/connection-report-storage.test.ts +3 -3
  203. package/test/src/setup.ts +1 -1
  204. package/test/src/storage.test.ts +10 -8
  205. package/test/src/storage_compacting.test.ts +146 -46
  206. package/test/src/storage_sync.test.ts +404 -56
  207. package/test/src/util.ts +3 -3
  208. package/test/tsconfig.json +0 -1
  209. package/tsconfig.tsbuildinfo +1 -1
  210. package/dist/storage/implementation/PersistedBatch.d.ts +0 -64
  211. package/dist/storage/implementation/PersistedBatch.js +0 -323
  212. package/dist/storage/implementation/PersistedBatch.js.map +0 -1
  213. package/src/storage/implementation/PersistedBatch.ts +0 -401
@@ -1,5 +1,5 @@
1
- import { mongo, MONGO_OPERATION_TIMEOUT_MS } from '@powersync/lib-service-mongodb';
2
- import { logger, ReplicationAssertionError, ServiceAssertionError } from '@powersync/lib-services-framework';
1
+ import { isMongoServerError, MONGO_OPERATION_TIMEOUT_MS } from '@powersync/lib-service-mongodb';
2
+ import { logger as defaultLogger, ReplicationAssertionError, ServiceAssertionError } from '@powersync/lib-services-framework';
3
3
  import { addChecksums, isPartialChecksum, utils } from '@powersync/service-core';
4
4
  import { cacheKey } from './OperationBatch.js';
5
5
  const DEFAULT_CLEAR_BATCH_LIMIT = 5000;
@@ -25,19 +25,21 @@ export class MongoCompactor {
25
25
  buckets;
26
26
  signal;
27
27
  group_id;
28
+ logger;
28
29
  constructor(storage, db, options) {
29
30
  this.storage = storage;
30
31
  this.db = db;
31
32
  this.group_id = storage.group_id;
32
- this.idLimitBytes = (options?.memoryLimitMB ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
33
- this.moveBatchLimit = options?.moveBatchLimit ?? DEFAULT_MOVE_BATCH_LIMIT;
34
- this.moveBatchQueryLimit = options?.moveBatchQueryLimit ?? DEFAULT_MOVE_BATCH_QUERY_LIMIT;
35
- this.clearBatchLimit = options?.clearBatchLimit ?? DEFAULT_CLEAR_BATCH_LIMIT;
36
- this.minBucketChanges = options?.minBucketChanges ?? DEFAULT_MIN_BUCKET_CHANGES;
37
- this.minChangeRatio = options?.minChangeRatio ?? DEFAULT_MIN_CHANGE_RATIO;
38
- this.maxOpId = options?.maxOpId ?? 0n;
39
- this.buckets = options?.compactBuckets;
40
- this.signal = options?.signal;
33
+ this.idLimitBytes = (options.memoryLimitMB ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
34
+ this.moveBatchLimit = options.moveBatchLimit ?? DEFAULT_MOVE_BATCH_LIMIT;
35
+ this.moveBatchQueryLimit = options.moveBatchQueryLimit ?? DEFAULT_MOVE_BATCH_QUERY_LIMIT;
36
+ this.clearBatchLimit = options.clearBatchLimit ?? DEFAULT_CLEAR_BATCH_LIMIT;
37
+ this.minBucketChanges = options.minBucketChanges ?? DEFAULT_MIN_BUCKET_CHANGES;
38
+ this.minChangeRatio = options.minChangeRatio ?? DEFAULT_MIN_CHANGE_RATIO;
39
+ this.maxOpId = options.maxOpId ?? 0n;
40
+ this.buckets = options.compactBuckets;
41
+ this.signal = options.signal;
42
+ this.logger = options.logger ?? defaultLogger;
41
43
  }
42
44
  /**
43
45
  * Compact buckets by converting operations into MOVE and/or CLEAR operations.
@@ -46,37 +48,178 @@ export class MongoCompactor {
46
48
  */
47
49
  async compact() {
48
50
  if (this.buckets) {
49
- for (let bucket of this.buckets) {
50
- // We can make this more efficient later on by iterating
51
- // through the buckets in a single query.
51
+ for (const bucket of this.buckets) {
52
+ // We can make this more efficient later on by iterating through the buckets in a single query.
52
53
  // That makes batching more tricky, so we leave for later.
53
- await this.compactSingleBucket(bucket);
54
+ await this.compactSingleBucketRetried(bucket);
54
55
  }
55
56
  }
56
57
  else {
57
58
  await this.compactDirtyBuckets();
58
59
  }
59
60
  }
61
+ /**
62
+ * Subset of compact, only populating checksums where relevant.
63
+ */
64
+ async populateChecksums(options) {
65
+ let count = 0;
66
+ while (true) {
67
+ this.signal?.throwIfAborted();
68
+ const buckets = await this.dirtyBucketBatchForChecksums(options);
69
+ if (buckets.length == 0) {
70
+ break;
71
+ }
72
+ this.signal?.throwIfAborted();
73
+ const start = Date.now();
74
+ // Filter batch by estimated bucket size, to reduce possibility of timeouts.
75
+ const checkBuckets = [];
76
+ let totalCountEstimate = 0;
77
+ for (const bucket of buckets) {
78
+ checkBuckets.push(bucket);
79
+ totalCountEstimate += bucket.estimatedCount;
80
+ if (totalCountEstimate > 50_000) {
81
+ break;
82
+ }
83
+ }
84
+ this.logger.info(`Calculating checksums for batch of ${buckets.length} buckets, estimated count of ${totalCountEstimate}`);
85
+ await this.updateChecksumsBatch(checkBuckets);
86
+ this.logger.info(`Updated checksums for batch of ${checkBuckets.length} buckets in ${Date.now() - start}ms`);
87
+ count += checkBuckets.length;
88
+ }
89
+ return { buckets: count };
90
+ }
91
+ async *dirtyBucketBatchesForCollection(collection, lastId, maxId, options, getDefinitionId) {
92
+ while (true) {
93
+ // To avoid timeouts from too many buckets not meeting the minBucketChanges criteria, use an aggregation pipeline
94
+ // to scan a fixed batch of buckets at a time, but only return buckets that meet the criteria.
95
+ const [result] = await collection
96
+ .aggregate([
97
+ {
98
+ $match: {
99
+ _id: { $gt: lastId, $lt: maxId }
100
+ }
101
+ },
102
+ {
103
+ $sort: { _id: 1 }
104
+ },
105
+ {
106
+ // Scan a fixed number of docs each query so sparse matches don't block progress.
107
+ $limit: DIRTY_BUCKET_SCAN_BATCH_SIZE
108
+ },
109
+ {
110
+ $facet: {
111
+ buckets: [
112
+ {
113
+ $match: {
114
+ 'estimate_since_compact.count': { $gte: options.minBucketChanges }
115
+ }
116
+ },
117
+ {
118
+ $project: {
119
+ _id: 1,
120
+ estimate_since_compact: 1,
121
+ compacted_state: 1
122
+ }
123
+ }
124
+ ],
125
+ // This is used for the next query.
126
+ cursor: [{ $sort: { _id: -1 } }, { $limit: 1 }, { $project: { _id: 1 } }]
127
+ }
128
+ }
129
+ ], { maxTimeMS: MONGO_OPERATION_TIMEOUT_MS })
130
+ .toArray();
131
+ const cursor = result?.cursor?.[0];
132
+ if (cursor == null) {
133
+ break;
134
+ }
135
+ lastId = cursor._id;
136
+ const mapped = (result?.buckets ?? []).map((bucketState) => {
137
+ // The numbers, specifically the bytes, could be a bigint. Convert to Number to allow calculating ratios.
138
+ // BigInt precision is not needed here since this is only an estimate.
139
+ const updatedCount = bucketState.estimate_since_compact?.count ?? 0;
140
+ const totalCount = (bucketState.compacted_state?.count ?? 0) + updatedCount;
141
+ const updatedBytes = Number(bucketState.estimate_since_compact?.bytes ?? 0);
142
+ const totalBytes = Number(bucketState.compacted_state?.bytes ?? 0) + updatedBytes;
143
+ const dirtyChangeNumber = totalCount > 0 ? updatedCount / totalCount : 0;
144
+ const dirtyChangeBytes = totalBytes > 0 ? updatedBytes / totalBytes : 0;
145
+ return {
146
+ bucket: bucketState._id.b,
147
+ definitionId: getDefinitionId(bucketState),
148
+ estimatedCount: totalCount,
149
+ dirtyRatio: Math.max(dirtyChangeNumber, dirtyChangeBytes)
150
+ };
151
+ });
152
+ yield mapped.filter((bucket) => bucket.estimatedCount >= options.minBucketChanges && bucket.dirtyRatio >= options.minChangeRatio);
153
+ }
154
+ }
155
+ async dirtyBucketBatchForChecksumsForCollection(collection, filter, getDefinitionId) {
156
+ const dirtyBuckets = await collection
157
+ .find(filter, {
158
+ projection: {
159
+ _id: 1,
160
+ estimate_since_compact: 1,
161
+ compacted_state: 1
162
+ },
163
+ sort: {
164
+ 'estimate_since_compact.count': -1
165
+ },
166
+ limit: 200,
167
+ maxTimeMS: MONGO_OPERATION_TIMEOUT_MS
168
+ })
169
+ .toArray();
170
+ return dirtyBuckets.map((bucket) => ({
171
+ bucket: bucket._id.b,
172
+ definitionId: getDefinitionId(bucket),
173
+ estimatedCount: Number(bucket.estimate_since_compact.count) + Number(bucket.compacted_state?.count ?? 0)
174
+ }));
175
+ }
60
176
  async compactDirtyBuckets() {
61
- for await (let buckets of this.dirtyBucketBatches({
177
+ for await (const buckets of this.dirtyBucketBatches({
62
178
  minBucketChanges: this.minBucketChanges,
63
179
  minChangeRatio: this.minChangeRatio
64
180
  })) {
65
- if (this.signal?.aborted) {
66
- break;
67
- }
181
+ this.signal?.throwIfAborted();
68
182
  if (buckets.length == 0) {
69
183
  continue;
70
184
  }
71
- for (let { bucket } of buckets) {
72
- await this.compactSingleBucket(bucket);
185
+ for (const { bucket, definitionId } of buckets) {
186
+ await this.compactSingleBucketRetried(bucket, definitionId);
187
+ }
188
+ }
189
+ }
190
+ /**
191
+ * Compaction for a single bucket, with retries on failure.
192
+ *
193
+ * This covers against occasional network or other database errors during a long compact job.
194
+ */
195
+ async compactSingleBucketRetried(bucket, definitionId = null) {
196
+ let retryCount = 0;
197
+ while (true) {
198
+ try {
199
+ await this.compactSingleBucket(bucket, definitionId);
200
+ break;
201
+ }
202
+ catch (e) {
203
+ if (retryCount < 3 && isMongoServerError(e)) {
204
+ this.logger.warn(`Error compacting bucket ${bucket}, retrying...`, e);
205
+ retryCount++;
206
+ await new Promise((resolve) => setTimeout(resolve, 1000 * retryCount));
207
+ }
208
+ else {
209
+ throw e;
210
+ }
73
211
  }
74
212
  }
75
213
  }
76
- async compactSingleBucket(bucket) {
214
+ async compactSingleBucket(bucket, definitionId = null) {
77
215
  const idLimitBytes = this.idLimitBytes;
78
- let currentState = {
216
+ const bucketContext = await this.getBucketDataContext(bucket, definitionId);
217
+ if (bucketContext == null) {
218
+ return;
219
+ }
220
+ const currentState = {
79
221
  bucket,
222
+ definitionId: bucketContext.key.definitionId,
80
223
  seen: new Map(),
81
224
  trackingSize: 0,
82
225
  lastNotPut: null,
@@ -85,27 +228,24 @@ export class MongoCompactor {
85
228
  opCount: 0,
86
229
  opBytes: 0
87
230
  };
88
- // Constant lower bound
89
- const lowerBound = {
90
- g: this.group_id,
91
- b: bucket,
92
- o: new mongo.MinKey()
93
- };
94
- // Upper bound is adjusted for each batch
95
- let upperBound = {
96
- g: this.group_id,
97
- b: bucket,
98
- o: new mongo.MaxKey()
99
- };
100
- while (!this.signal?.aborted) {
101
- // Query one batch at a time, to avoid cursor timeouts
102
- const cursor = this.db.bucket_data.aggregate([
231
+ // Constant lower bound.
232
+ const lowerBound = bucketContext.minId;
233
+ // Upper bound is adjusted for each batch.
234
+ let upperBound = bucketContext.maxId;
235
+ while (true) {
236
+ this.signal?.throwIfAborted();
237
+ // Query one batch at a time, to avoid cursor timeouts.
238
+ const pipeline = [
103
239
  {
104
240
  $match: {
105
241
  _id: {
106
242
  $gte: lowerBound,
107
243
  $lt: upperBound
108
- }
244
+ },
245
+ // Workaround for a clustered collection bug where the $lt operator may include upperBound.
246
+ // Technically only needed for storage V3.
247
+ // https://jira.mongodb.org/browse/SERVER-121822
248
+ '_id.o': { $lt: upperBound.o }
109
249
  }
110
250
  },
111
251
  { $sort: { _id: -1 } },
@@ -122,39 +262,45 @@ export class MongoCompactor {
122
262
  size: { $bsonSize: '$$ROOT' }
123
263
  }
124
264
  }
125
- ], {
265
+ ];
266
+ const cursor = bucketContext.collection.aggregate(pipeline, {
126
267
  // batchSize is 1 more than limit to auto-close the cursor.
127
268
  // See https://github.com/mongodb/node-mongodb-native/pull/4580
128
269
  batchSize: this.moveBatchQueryLimit + 1
129
270
  });
130
271
  // We don't limit to a single batch here, since that often causes MongoDB to scan through more than it returns.
131
272
  // Instead, we load up to the limit.
132
- const batch = await cursor.toArray();
273
+ const rawBatch = await cursor.toArray();
274
+ const batch = rawBatch.map((document) => {
275
+ const { size, ...rest } = document;
276
+ return {
277
+ doc: bucketContext.fromPersistedDocument(rest),
278
+ size
279
+ };
280
+ });
133
281
  if (batch.length == 0) {
134
- // We've reached the end
282
+ // We've reached the end.
135
283
  break;
136
284
  }
137
- // Set upperBound for the next batch
138
- upperBound = batch[batch.length - 1]._id;
139
- for (let doc of batch) {
140
- if (doc._id.o > this.maxOpId) {
285
+ // Reuse the exact collection _id value from Mongo for the next bound.
286
+ upperBound = rawBatch[rawBatch.length - 1]._id;
287
+ for (const { doc, size } of batch) {
288
+ if (doc.o > this.maxOpId) {
141
289
  continue;
142
290
  }
143
291
  currentState.checksum = addChecksums(currentState.checksum, Number(doc.checksum));
144
292
  currentState.opCount += 1;
145
293
  let isPersistentPut = doc.op == 'PUT';
146
- currentState.opBytes += Number(doc.size);
294
+ currentState.opBytes += Number(size);
147
295
  if (doc.op == 'REMOVE' || doc.op == 'PUT') {
148
296
  const key = `${doc.table}/${doc.row_id}/${cacheKey(doc.source_table, doc.source_key)}`;
149
297
  const targetOp = currentState.seen.get(key);
150
298
  if (targetOp) {
151
- // Will convert to MOVE, so don't count as PUT
299
+ // Will convert to MOVE, so don't count as PUT.
152
300
  isPersistentPut = false;
153
301
  this.updates.push({
154
302
  updateOne: {
155
- filter: {
156
- _id: doc._id
157
- },
303
+ filter: { _id: bucketContext.docId(doc.o) },
158
304
  update: {
159
305
  $set: {
160
306
  op: 'MOVE',
@@ -170,22 +316,17 @@ export class MongoCompactor {
170
316
  }
171
317
  }
172
318
  });
173
- currentState.opBytes += 200 - Number(doc.size); // TODO: better estimate for this
319
+ // TODO: better estimate for this.
320
+ currentState.opBytes += 200 - Number(size);
174
321
  }
175
- else {
176
- if (currentState.trackingSize >= idLimitBytes) {
177
- // Reached memory limit.
178
- // Keep the highest seen values in this case.
179
- }
180
- else {
181
- // flatstr reduces the memory usage by flattening the string
182
- currentState.seen.set(utils.flatstr(key), doc._id.o);
183
- // length + 16 for the string
184
- // 24 for the bigint
185
- // 50 for map overhead
186
- // 50 for additional overhead
187
- currentState.trackingSize += key.length + 140;
188
- }
322
+ else if (currentState.trackingSize < idLimitBytes) {
323
+ // flatstr reduces the memory usage by flattening the string.
324
+ currentState.seen.set(utils.flatstr(key), doc.o);
325
+ // length + 16 for the string
326
+ // 24 for the bigint
327
+ // 50 for map overhead
328
+ // 50 for additional overhead
329
+ currentState.trackingSize += key.length + 140;
189
330
  }
190
331
  }
191
332
  if (isPersistentPut) {
@@ -194,44 +335,36 @@ export class MongoCompactor {
194
335
  }
195
336
  else if (doc.op != 'CLEAR') {
196
337
  if (currentState.lastNotPut == null) {
197
- currentState.lastNotPut = doc._id.o;
338
+ currentState.lastNotPut = doc.o;
198
339
  }
199
340
  currentState.opsSincePut += 1;
200
341
  }
201
342
  if (this.updates.length + this.bucketStateUpdates.length >= this.moveBatchLimit) {
202
- await this.flush();
343
+ await this.flush(bucketContext);
203
344
  }
204
345
  }
205
- logger.info(`Processed batch of length ${batch.length} current bucket: ${bucket}`);
346
+ this.logger.info(`Processed batch of length ${batch.length} current bucket: ${bucket}`);
206
347
  }
207
- // Free memory before clearing bucket
348
+ // Free memory before clearing the bucket.
208
349
  currentState.seen.clear();
209
350
  if (currentState.lastNotPut != null && currentState.opsSincePut >= 1) {
210
- logger.info(`Inserting CLEAR at ${this.group_id}:${bucket}:${currentState.lastNotPut} to remove ${currentState.opsSincePut} operations`);
211
- // Need flush() before clear()
212
- await this.flush();
213
- await this.clearBucket(currentState);
351
+ this.logger.info(`Inserting CLEAR at ${this.group_id}:${bucket}:${currentState.lastNotPut} to remove ${currentState.opsSincePut} operations`);
352
+ // Need flush() before clear().
353
+ await this.flush(bucketContext);
354
+ await this.clearBucket(currentState, bucketContext);
214
355
  }
215
- // Do this _after_ clearBucket so that we have accurate counts.
356
+ // Do this after clearBucket so we have accurate counts.
216
357
  this.updateBucketChecksums(currentState);
217
- // Need another flush after updateBucketChecksums()
218
- await this.flush();
358
+ // Need another flush after updateBucketChecksums().
359
+ await this.flush(bucketContext);
219
360
  }
220
- /**
221
- * Call when done with a bucket.
222
- */
223
361
  updateBucketChecksums(state) {
224
362
  if (state.opCount < 0) {
225
363
  throw new ServiceAssertionError(`Invalid opCount: ${state.opCount} checksum ${state.checksum} opsSincePut: ${state.opsSincePut} maxOpId: ${this.maxOpId}`);
226
364
  }
227
365
  this.bucketStateUpdates.push({
228
366
  updateOne: {
229
- filter: {
230
- _id: {
231
- g: this.group_id,
232
- b: state.bucket
233
- }
234
- },
367
+ filter: this.bucketStateFilter(state.bucket, state.definitionId),
235
368
  update: {
236
369
  $set: {
237
370
  compacted_state: {
@@ -241,9 +374,8 @@ export class MongoCompactor {
241
374
  bytes: state.opBytes
242
375
  },
243
376
  estimate_since_compact: {
244
- // Note: There could have been a whole bunch of new operations added to the bucket _while_ compacting,
245
- // which we don't currently cater for.
246
- // We could potentially query for that, but that could add overhead.
377
+ // There could have been a whole bunch of new operations added to the bucket while compacting,
378
+ // which we don't currently cater for. We could potentially query for that, but that adds overhead.
247
379
  count: 0,
248
380
  bytes: 0
249
381
  }
@@ -255,60 +387,49 @@ export class MongoCompactor {
255
387
  }
256
388
  });
257
389
  }
258
- async flush() {
390
+ async flush(col) {
259
391
  if (this.updates.length > 0) {
260
- logger.info(`Compacting ${this.updates.length} ops`);
261
- await this.db.bucket_data.bulkWrite(this.updates, {
262
- // Order is not important.
263
- // Since checksums are not affected, these operations can happen in any order,
264
- // and it's fine if the operations are partially applied.
265
- // Each individual operation is atomic.
392
+ this.logger.info(`Compacting ${this.updates.length} ops`);
393
+ await col.collection.bulkWrite(this.updates, {
394
+ // Order is not important. Since checksums are not affected, these operations can happen in any order,
395
+ // and it's fine if the operations are partially applied. Each individual operation is atomic.
266
396
  ordered: false
267
397
  });
268
398
  this.updates = [];
269
399
  }
400
+ await this.flushBucketStateUpdates();
401
+ }
402
+ async flushBucketStateUpdates() {
270
403
  if (this.bucketStateUpdates.length > 0) {
271
- logger.info(`Updating ${this.bucketStateUpdates.length} bucket states`);
272
- await this.db.bucket_state.bulkWrite(this.bucketStateUpdates, {
273
- ordered: false
274
- });
404
+ this.logger.info(`Updating ${this.bucketStateUpdates.length} bucket states`);
405
+ await this.writeBucketStateUpdates();
275
406
  this.bucketStateUpdates = [];
276
407
  }
277
408
  }
278
409
  /**
279
410
  * Perform a CLEAR compact for a bucket.
280
411
  *
281
- *
282
- * @param bucket bucket name
283
- * @param op op_id of the last non-PUT operation, which will be converted to CLEAR.
412
+ * @param currentState tracks the last non-PUT op, which will be converted to CLEAR.
284
413
  */
285
- async clearBucket(currentState) {
286
- const bucket = currentState.bucket;
414
+ async clearBucket(currentState, col) {
287
415
  const clearOp = currentState.lastNotPut;
288
416
  const opFilter = {
289
417
  _id: {
290
- $gte: {
291
- g: this.group_id,
292
- b: bucket,
293
- o: new mongo.MinKey()
294
- },
295
- $lte: {
296
- g: this.group_id,
297
- b: bucket,
298
- o: clearOp
299
- }
418
+ $gte: col.minId,
419
+ $lte: col.docId(clearOp)
300
420
  }
301
421
  };
302
422
  const session = this.db.client.startSession();
303
423
  try {
304
424
  let done = false;
305
- while (!done && !this.signal?.aborted) {
425
+ while (!done) {
426
+ this.signal?.throwIfAborted();
306
427
  let opCountDiff = 0;
307
428
  // Do the CLEAR operation in batches, with each batch a separate transaction.
308
429
  // The state after each batch is fully consistent.
309
430
  // We need a transaction per batch to make sure checksums stay consistent.
310
431
  await session.withTransaction(async () => {
311
- const query = this.db.bucket_data.find(opFilter, {
432
+ const query = col.collection.find(opFilter, {
312
433
  session,
313
434
  sort: { _id: 1 },
314
435
  projection: {
@@ -320,56 +441,52 @@ export class MongoCompactor {
320
441
  limit: this.clearBatchLimit
321
442
  });
322
443
  let checksum = 0;
323
- let lastOpId = null;
444
+ let lastOp = null;
324
445
  let targetOp = null;
325
446
  let gotAnOp = false;
326
447
  let numberOfOpsToClear = 0;
327
- for await (let op of query.stream()) {
448
+ for await (const rawOp of query.stream()) {
449
+ const op = col.fromPartialPersistedDocument(rawOp);
328
450
  if (op.op == 'MOVE' || op.op == 'REMOVE' || op.op == 'CLEAR') {
329
451
  checksum = utils.addChecksums(checksum, Number(op.checksum));
330
- lastOpId = op._id;
452
+ lastOp = op;
331
453
  numberOfOpsToClear += 1;
332
454
  if (op.op != 'CLEAR') {
333
455
  gotAnOp = true;
334
456
  }
335
- if (op.target_op != null) {
336
- if (targetOp == null || op.target_op > targetOp) {
337
- targetOp = op.target_op;
338
- }
457
+ if (op.target_op != null && (targetOp == null || op.target_op > targetOp)) {
458
+ targetOp = op.target_op;
339
459
  }
340
460
  }
341
461
  else {
342
- throw new ReplicationAssertionError(`Unexpected ${op.op} operation at ${op._id.g}:${op._id.b}:${op._id.o}`);
462
+ throw new ReplicationAssertionError(`Unexpected ${op.op} operation at ${this.formatBucketDataKey(op)}`);
343
463
  }
344
464
  }
345
465
  if (!gotAnOp) {
346
466
  done = true;
347
467
  return;
348
468
  }
349
- logger.info(`Flushing CLEAR for ${numberOfOpsToClear} ops at ${lastOpId?.o}`);
350
- await this.db.bucket_data.deleteMany({
469
+ this.logger.info(`Flushing CLEAR for ${numberOfOpsToClear} ops at ${lastOp?.o}`);
470
+ await col.collection.deleteMany({
351
471
  _id: {
352
- $gte: {
353
- g: this.group_id,
354
- b: bucket,
355
- o: new mongo.MinKey()
356
- },
357
- $lte: lastOpId
472
+ $gte: col.minId,
473
+ $lte: col.docId(lastOp.o)
358
474
  }
359
475
  }, { session });
360
- await this.db.bucket_data.insertOne({
361
- _id: lastOpId,
476
+ const op = col.toPersistedDocument({
477
+ o: lastOp.o,
362
478
  op: 'CLEAR',
363
479
  checksum: BigInt(checksum),
364
480
  data: null,
365
481
  target_op: targetOp
366
- }, { session });
482
+ });
483
+ await col.collection.insertOne(op, { session });
367
484
  opCountDiff = -numberOfOpsToClear + 1;
368
485
  }, {
369
486
  writeConcern: { w: 'majority' },
370
487
  readConcern: { level: 'snapshot' }
371
488
  });
372
- // Update _outside_ the transaction, since the transaction can be retried multiple times.
489
+ // Update outside the transaction, since the transaction can be retried multiple times.
373
490
  currentState.opCount += opCountDiff;
374
491
  }
375
492
  }
@@ -377,175 +494,17 @@ export class MongoCompactor {
377
494
  await session.endSession();
378
495
  }
379
496
  }
380
- /**
381
- * Subset of compact, only populating checksums where relevant.
382
- */
383
- async populateChecksums(options) {
384
- let count = 0;
385
- while (!this.signal?.aborted) {
386
- const buckets = await this.dirtyBucketBatchForChecksums(options);
387
- if (buckets.length == 0 || this.signal?.aborted) {
388
- // All done
389
- break;
390
- }
391
- const start = Date.now();
392
- // Filter batch by estimated bucket size, to reduce possibility of timeouts
393
- let checkBuckets = [];
394
- let totalCountEstimate = 0;
395
- for (let bucket of buckets) {
396
- checkBuckets.push(bucket);
397
- totalCountEstimate += bucket.estimatedCount;
398
- if (totalCountEstimate > 50_000) {
399
- break;
400
- }
401
- }
402
- logger.info(`Calculating checksums for batch of ${buckets.length} buckets, estimated count of ${totalCountEstimate}`);
403
- await this.updateChecksumsBatch(checkBuckets.map((b) => b.bucket));
404
- logger.info(`Updated checksums for batch of ${checkBuckets.length} buckets in ${Date.now() - start}ms`);
405
- count += checkBuckets.length;
406
- }
407
- return { buckets: count };
408
- }
409
- /**
410
- * Return batches of dirty buckets.
411
- *
412
- * Can be used to iterate through all buckets.
413
- *
414
- * minBucketChanges: minimum number of changes for a bucket to be included in the results.
415
- * minChangeRatio: minimum ratio of changes to total ops for a bucket to be included in the results, number between 0 and 1.
416
- */
417
- async *dirtyBucketBatches(options) {
418
- // Previously, we used an index on {_id.g: 1, estimate_since_compact.count: 1} to only buckets with changes.
419
- // This works well if there are only a small number of buckets with changes.
420
- // However, if buckets are continuosly modified while we are compacting, we get the same buckets over and over again.
421
- // This has caused the compact process to re-read the same collection around 5x times in total, which is very inefficient.
422
- // To solve this, we now just iterate through all buckets, and filter out the ones with low changes.
423
- if (options.minBucketChanges <= 0) {
424
- throw new ReplicationAssertionError('minBucketChanges must be >= 1');
425
- }
426
- let lastId = { g: this.group_id, b: new mongo.MinKey() };
427
- const maxId = { g: this.group_id, b: new mongo.MaxKey() };
428
- while (true) {
429
- // To avoid timeouts from too many buckets not meeting the minBucketChanges criteria, we use an aggregation pipeline
430
- // to scan a fixed batch of buckets at a time, but only return buckets that meet the criteria, rather than limiting
431
- // on the output number.
432
- const [result] = await this.db.bucket_state
433
- .aggregate([
434
- {
435
- $match: {
436
- _id: { $gt: lastId, $lt: maxId }
437
- }
438
- },
439
- {
440
- $sort: { _id: 1 }
441
- },
442
- {
443
- // Scan a fixed number of docs each query so sparse matches don't block progress.
444
- $limit: DIRTY_BUCKET_SCAN_BATCH_SIZE
445
- },
446
- {
447
- $facet: {
448
- // This is the results for the batch
449
- buckets: [
450
- {
451
- $match: {
452
- 'estimate_since_compact.count': { $gte: options.minBucketChanges }
453
- }
454
- },
455
- {
456
- $project: {
457
- _id: 1,
458
- estimate_since_compact: 1,
459
- compacted_state: 1
460
- }
461
- }
462
- ],
463
- // This is used for the next query.
464
- cursor: [{ $sort: { _id: -1 } }, { $limit: 1 }, { $project: { _id: 1 } }]
465
- }
466
- }
467
- ], { maxTimeMS: MONGO_OPERATION_TIMEOUT_MS })
468
- .toArray();
469
- const cursor = result?.cursor?.[0];
470
- if (cursor == null) {
471
- break;
472
- }
473
- lastId = cursor._id;
474
- const mapped = (result?.buckets ?? []).map((b) => {
475
- const updatedCount = b.estimate_since_compact?.count ?? 0;
476
- const totalCount = (b.compacted_state?.count ?? 0) + updatedCount;
477
- const updatedBytes = b.estimate_since_compact?.bytes ?? 0;
478
- const totalBytes = (b.compacted_state?.bytes ?? 0) + updatedBytes;
479
- const dirtyChangeNumber = totalCount > 0 ? updatedCount / totalCount : 0;
480
- const dirtyChangeBytes = totalBytes > 0 ? updatedBytes / totalBytes : 0;
481
- return {
482
- bucket: b._id.b,
483
- estimatedCount: totalCount,
484
- dirtyRatio: Math.max(dirtyChangeNumber, dirtyChangeBytes)
485
- };
486
- });
487
- const filtered = mapped.filter((b) => b.estimatedCount >= options.minBucketChanges && b.dirtyRatio >= options.minChangeRatio);
488
- yield filtered;
489
- }
490
- }
491
- /**
492
- * Returns a batch of dirty buckets - buckets with most changes first.
493
- *
494
- * This cannot be used to iterate on its own - the client is expected to process these buckets and
495
- * set estimate_since_compact.count: 0 when done, before fetching the next batch.
496
- *
497
- * Unlike dirtyBucketBatches, used for compacting, this is specifically designed to be resuamble after a restart,
498
- * since it is used as the last step for initial replication.
499
- *
500
- * We currently don't get new data while doing populateChecksums, so we don't need to worry about buckets changing while processing.
501
- */
502
- async dirtyBucketBatchForChecksums(options) {
503
- if (options.minBucketChanges <= 0) {
504
- throw new ReplicationAssertionError('minBucketChanges must be >= 1');
505
- }
506
- // We make use of an index on {_id.g: 1, 'estimate_since_compact.count': -1}
507
- const dirtyBuckets = await this.db.bucket_state
508
- .find({
509
- '_id.g': this.group_id,
510
- 'estimate_since_compact.count': { $gte: options.minBucketChanges }
511
- }, {
512
- projection: {
513
- _id: 1,
514
- estimate_since_compact: 1,
515
- compacted_state: 1
516
- },
517
- sort: {
518
- 'estimate_since_compact.count': -1
519
- },
520
- limit: 200,
521
- maxTimeMS: MONGO_OPERATION_TIMEOUT_MS
522
- })
523
- .toArray();
524
- return dirtyBuckets.map((bucket) => ({
525
- bucket: bucket._id.b,
526
- estimatedCount: bucket.estimate_since_compact.count + (bucket.compacted_state?.count ?? 0)
527
- }));
528
- }
529
497
  async updateChecksumsBatch(buckets) {
530
- const checksums = await this.storage.checksums.computePartialChecksumsDirect(buckets.map((bucket) => {
531
- return {
532
- bucket,
533
- end: this.maxOpId
534
- };
535
- }));
536
- for (let bucketChecksum of checksums.values()) {
498
+ const checksums = await this.computeChecksumsForBuckets(buckets);
499
+ const definitionIdByBucket = new Map(buckets.map((bucket) => [bucket.bucket, bucket.definitionId]));
500
+ for (const bucketChecksum of checksums.values()) {
537
501
  if (isPartialChecksum(bucketChecksum)) {
538
- // Should never happen since we don't specify `start`
502
+ // Should never happen since we don't specify `start`.
539
503
  throw new ServiceAssertionError(`Full checksum expected, got ${JSON.stringify(bucketChecksum)}`);
540
504
  }
541
505
  this.bucketStateUpdates.push({
542
506
  updateOne: {
543
- filter: {
544
- _id: {
545
- g: this.group_id,
546
- b: bucketChecksum.bucket
547
- }
548
- },
507
+ filter: this.bucketStateFilter(bucketChecksum.bucket, definitionIdByBucket.get(bucketChecksum.bucket) ?? null),
549
508
  update: {
550
509
  $set: {
551
510
  compacted_state: {
@@ -560,13 +519,16 @@ export class MongoCompactor {
560
519
  }
561
520
  }
562
521
  },
563
- // We don't create new ones here - it gets tricky to get the last_op right with the unique index on:
564
- // bucket_updates: {'id.g': 1, 'last_op': 1}
522
+ // We don't create new ones here - it gets tricky to get the last_op right with the unique index on
523
+ // bucket_updates.
565
524
  upsert: false
566
525
  }
567
526
  });
568
527
  }
569
- await this.flush();
528
+ await this.flushBucketStateUpdates();
529
+ }
530
+ formatBucketDataKey(doc) {
531
+ return `${doc.bucketKey.replicationStreamId}:${doc.bucketKey.bucket}:${doc.o}`;
570
532
  }
571
533
  }
572
534
  //# sourceMappingURL=MongoCompactor.js.map