@powersync/service-module-mongodb-storage 0.0.0-dev-20260602073133 → 0.0.0-dev-20260909133214

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 (275) hide show
  1. package/CHANGELOG.md +177 -5
  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 +71 -18
  6. package/dist/storage/MongoBucketStorage.js +516 -202
  7. package/dist/storage/MongoBucketStorage.js.map +1 -1
  8. package/dist/storage/implementation/MongoBucketBatch.d.ts +71 -29
  9. package/dist/storage/implementation/MongoBucketBatch.js +140 -66
  10. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  11. package/dist/storage/implementation/MongoChecksums.d.ts +41 -13
  12. package/dist/storage/implementation/MongoChecksums.js +36 -121
  13. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  14. package/dist/storage/implementation/MongoCompactor.d.ts +28 -90
  15. package/dist/storage/implementation/MongoCompactor.js +87 -481
  16. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  17. package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
  18. package/dist/storage/implementation/MongoHydrationState.js +49 -0
  19. package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
  20. package/dist/storage/implementation/MongoParameterCompactor.d.ts +82 -8
  21. package/dist/storage/implementation/MongoParameterCompactor.js +312 -76
  22. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  23. package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +21 -0
  24. package/dist/storage/implementation/MongoParsedSyncConfigSet.js +93 -0
  25. package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
  26. package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
  27. package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
  28. package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
  29. package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
  30. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +125 -0
  31. package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
  32. package/dist/storage/implementation/MongoStorageProvider.js +41 -4
  33. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  34. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +81 -37
  35. package/dist/storage/implementation/MongoSyncBucketStorage.js +159 -86
  36. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  37. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
  38. package/dist/storage/implementation/MongoSyncRulesLock.js +23 -11
  39. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  40. package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +11 -8
  41. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +159 -33
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  43. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
  44. package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
  45. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
  46. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +28 -0
  47. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
  48. package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
  49. package/dist/storage/implementation/common/PersistedBatch.d.ts +34 -2
  50. package/dist/storage/implementation/common/PersistedBatch.js +63 -10
  51. package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
  52. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
  53. package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
  54. package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
  55. package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
  56. package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
  57. package/dist/storage/implementation/db.d.ts +4 -1
  58. package/dist/storage/implementation/db.js +31 -16
  59. package/dist/storage/implementation/db.js.map +1 -1
  60. package/dist/storage/implementation/models.d.ts +55 -13
  61. package/dist/storage/implementation/models.js.map +1 -1
  62. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +15 -1
  63. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +135 -107
  64. package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
  65. package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +4 -4
  66. package/dist/storage/implementation/v1/MongoChecksumsV1.js +119 -7
  67. package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
  68. package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +47 -7
  69. package/dist/storage/implementation/v1/MongoCompactorV1.js +478 -8
  70. package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
  71. package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
  72. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
  73. package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
  74. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +33 -27
  75. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +146 -73
  76. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
  77. package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
  78. package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
  79. package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
  80. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
  81. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
  82. package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
  83. package/dist/storage/implementation/v1/models.d.ts +10 -1
  84. package/dist/storage/implementation/v1/models.js +1 -0
  85. package/dist/storage/implementation/v1/models.js.map +1 -1
  86. package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
  87. package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
  88. package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
  89. package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
  90. package/dist/storage/implementation/v3/CompactionLease.js +131 -0
  91. package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
  92. package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +16 -5
  93. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +393 -262
  94. package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
  95. package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +30 -9
  96. package/dist/storage/implementation/v3/MongoChecksumsV3.js +161 -20
  97. package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
  98. package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +108 -18
  99. package/dist/storage/implementation/v3/MongoCompactorV3.js +1115 -47
  100. package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
  101. package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +12 -3
  102. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +13 -8
  103. package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -1
  104. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +54 -0
  105. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +319 -0
  106. package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
  107. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +61 -36
  108. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +353 -161
  109. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
  110. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
  111. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
  112. package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
  113. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +8 -3
  114. package/dist/storage/implementation/v3/PersistedBatchV3.js +131 -29
  115. package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
  116. package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
  117. package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
  118. package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
  119. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +26 -14
  120. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +73 -29
  121. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
  122. package/dist/storage/implementation/v3/bucket-format.d.ts +13 -0
  123. package/dist/storage/implementation/v3/bucket-format.js +79 -0
  124. package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
  125. package/dist/storage/implementation/v3/chunking.d.ts +10 -0
  126. package/dist/storage/implementation/v3/chunking.js +30 -0
  127. package/dist/storage/implementation/v3/chunking.js.map +1 -0
  128. package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
  129. package/dist/storage/implementation/v3/compact-utils.js +216 -0
  130. package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
  131. package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
  132. package/dist/storage/implementation/v3/compaction-constants.js +6 -0
  133. package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
  134. package/dist/storage/implementation/v3/models.d.ts +187 -23
  135. package/dist/storage/implementation/v3/models.js +11 -28
  136. package/dist/storage/implementation/v3/models.js.map +1 -1
  137. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +20 -0
  138. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +48 -0
  139. package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
  140. package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +33 -0
  141. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
  142. package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
  143. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +68 -0
  144. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +154 -0
  145. package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
  146. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
  147. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
  148. package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
  149. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +67 -0
  150. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +439 -0
  151. package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
  152. package/dist/storage/implementation/v3/source-table-utils.d.ts +63 -0
  153. package/dist/storage/implementation/v3/source-table-utils.js +206 -0
  154. package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
  155. package/dist/storage/storage-index.d.ts +5 -4
  156. package/dist/storage/storage-index.js +5 -4
  157. package/dist/storage/storage-index.js.map +1 -1
  158. package/dist/types/types.d.ts +71 -3
  159. package/dist/types/types.js +58 -2
  160. package/dist/types/types.js.map +1 -1
  161. package/dist/utils/test-utils.d.ts +1 -2
  162. package/dist/utils/test-utils.js +7 -1
  163. package/dist/utils/test-utils.js.map +1 -1
  164. package/dist/utils/util.d.ts +9 -2
  165. package/dist/utils/util.js +131 -5
  166. package/dist/utils/util.js.map +1 -1
  167. package/package.json +13 -19
  168. package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
  169. package/src/storage/MongoBucketStorage.ts +728 -242
  170. package/src/storage/implementation/MongoBucketBatch.ts +196 -91
  171. package/src/storage/implementation/MongoChecksums.ts +93 -153
  172. package/src/storage/implementation/MongoCompactor.ts +106 -631
  173. package/src/storage/implementation/MongoHydrationState.ts +61 -0
  174. package/src/storage/implementation/MongoParameterCompactor.ts +392 -87
  175. package/src/storage/implementation/MongoParsedSyncConfigSet.ts +122 -0
  176. package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
  177. package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +156 -0
  178. package/src/storage/implementation/MongoStorageProvider.ts +48 -4
  179. package/src/storage/implementation/MongoSyncBucketStorage.ts +226 -118
  180. package/src/storage/implementation/MongoSyncRulesLock.ts +28 -12
  181. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +194 -52
  182. package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
  183. package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +31 -0
  184. package/src/storage/implementation/common/PersistedBatch.ts +83 -15
  185. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
  186. package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
  187. package/src/storage/implementation/db.ts +40 -22
  188. package/src/storage/implementation/models.ts +58 -14
  189. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +174 -125
  190. package/src/storage/implementation/v1/MongoChecksumsV1.ts +154 -12
  191. package/src/storage/implementation/v1/MongoCompactorV1.ts +594 -25
  192. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
  193. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +226 -129
  194. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
  195. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
  196. package/src/storage/implementation/v1/models.ts +12 -1
  197. package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
  198. package/src/storage/implementation/v3/CompactionLease.ts +152 -0
  199. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +502 -324
  200. package/src/storage/implementation/v3/MongoChecksumsV3.ts +250 -32
  201. package/src/storage/implementation/v3/MongoCompactorV3.ts +1439 -70
  202. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +19 -9
  203. package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +473 -0
  204. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +453 -211
  205. package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
  206. package/src/storage/implementation/v3/PersistedBatchV3.ts +169 -45
  207. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
  208. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +99 -38
  209. package/src/storage/implementation/v3/bucket-format.ts +99 -0
  210. package/src/storage/implementation/v3/chunking.ts +40 -0
  211. package/src/storage/implementation/v3/compact-utils.ts +320 -0
  212. package/src/storage/implementation/v3/compaction-constants.ts +5 -0
  213. package/src/storage/implementation/v3/models.ts +228 -58
  214. package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +62 -0
  215. package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +44 -0
  216. package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +182 -0
  217. package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
  218. package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +573 -0
  219. package/src/storage/implementation/v3/source-table-utils.ts +398 -0
  220. package/src/storage/storage-index.ts +14 -4
  221. package/src/types/types.ts +76 -2
  222. package/src/utils/test-utils.ts +8 -3
  223. package/src/utils/util.ts +188 -7
  224. package/test/src/__snapshots__/storage.test.ts.snap +4 -54
  225. package/test/src/__snapshots__/storage_sync.test.ts.snap +652 -645
  226. package/test/src/cleanup-stopped-sync-configs.test.ts +698 -0
  227. package/test/src/compact-utils.test.ts +226 -0
  228. package/test/src/helpers/MemoryObjectStorage.ts +61 -0
  229. package/test/src/helpers/s3TestFactory.ts +69 -0
  230. package/test/src/object_storage_lifecycle.test.ts +59 -0
  231. package/test/src/object_storage_usage.test.ts +255 -0
  232. package/test/src/parameter_compacting_v1.test.ts +120 -0
  233. package/test/src/parameter_compacting_v3.test.ts +192 -0
  234. package/test/src/parameter_compaction_fence.test.ts +276 -0
  235. package/test/src/setup.ts +6 -1
  236. package/test/src/storage.test.ts +478 -13
  237. package/test/src/storage_compacting.test.ts +2516 -107
  238. package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
  239. package/test/src/storage_s3_checksums.test.ts +190 -0
  240. package/test/src/storage_s3_compaction_lifecycle.test.ts +543 -0
  241. package/test/src/storage_s3_reading.test.ts +768 -0
  242. package/test/src/storage_s3_writing.test.ts +136 -0
  243. package/test/src/storage_sync.test.ts +1507 -76
  244. package/test/src/storeCurrentData.test.ts +227 -0
  245. package/test/src/util.ts +3 -2
  246. package/tsconfig.json +1 -4
  247. package/tsconfig.tsbuildinfo +1 -1
  248. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -15
  249. package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
  250. package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
  251. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
  252. package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -67
  253. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
  254. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
  255. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
  256. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
  257. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
  258. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
  259. package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
  260. package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
  261. package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
  262. package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
  263. package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -4
  264. package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
  265. package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
  266. package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
  267. package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -48
  268. package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
  269. package/src/storage/implementation/BucketDefinitionMapping.ts +0 -75
  270. package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
  271. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
  272. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
  273. package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
  274. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -11
  275. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -68
@@ -1,38 +1,77 @@
1
1
  import * as lib_mongo from '@powersync/lib-service-mongodb';
2
2
  import { ServiceAssertionError } from '@powersync/lib-services-framework';
3
- import { internalToExternalOpId, maxLsn, ParameterSetLimitExceededError, storage } from '@powersync/service-core';
3
+ import { internalToExternalOpId, ParameterSetLimitExceededError, storage } from '@powersync/service-core';
4
4
  import { JSONBig } from '@powersync/service-jsonbig';
5
5
  import * as bson from 'bson';
6
6
  import { mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../../utils/util.js';
7
- import { MongoPersistedSyncRulesContentV3 } from '../MongoPersistedSyncRulesContent.js';
8
7
  import { MongoSyncBucketStorage } from '../MongoSyncBucketStorage.js';
9
- import { loadBucketDataDocumentV3 } from './models.js';
8
+ import { loadBucketDataDocument, maxOpId } from './bucket-format.js';
9
+ import { deserializeParameterLookup, serializeParameterLookup } from './models.js';
10
10
  import { MongoBucketBatchV3 } from './MongoBucketBatchV3.js';
11
11
  import { MongoChecksumsV3 } from './MongoChecksumsV3.js';
12
12
  import { MongoCompactorV3 } from './MongoCompactorV3.js';
13
13
  import { MongoParameterCompactorV3 } from './MongoParameterCompactorV3.js';
14
- import { deserializeParameterLookupV3, serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
14
+ import { MongoStoppedSyncConfigCleanup } from './MongoStoppedSyncConfigCleanup.js';
15
+ import { MongoWriteCheckpointAPIV3 } from './MongoWriteCheckpointAPIV3.js';
16
+ import { hydrateBucketDataDocuments } from './object-storage/BucketDataObjectStorage.js';
17
+ import { ObjectStorageLifecycle } from './object-storage/ObjectStorageLifecycle.js';
18
+ import { ObjectStorageUsage } from './object-storage/ObjectStorageUsage.js';
19
+ const BUCKET_DATA_FETCH_BATCH_LIMIT_BYTES = 16 * 1024 * 1024;
20
+ /**
21
+ * Keep the documents hydrated for one sync response within a bounded payload or slightly higher.
22
+ *
23
+ * This deserializes on-demand, so no deserialization is performed for discarded data.
24
+ */
25
+ function cutBucketDataBatch(rawDocuments) {
26
+ let cumulativeBytes = 0;
27
+ let documents = [];
28
+ for (const raw of rawDocuments) {
29
+ const doc = bson.deserialize(raw, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS);
30
+ documents.push(doc);
31
+ cumulativeBytes += doc.size;
32
+ if (cumulativeBytes > BUCKET_DATA_FETCH_BATCH_LIMIT_BYTES) {
33
+ return {
34
+ documents,
35
+ wasCut: documents.length < rawDocuments.length
36
+ };
37
+ }
38
+ }
39
+ return { documents, wasCut: false };
40
+ }
41
+ function extractRowsFromDocument(doc, context, bucketStart, endOpId) {
42
+ const rows = [];
43
+ for (const row of loadBucketDataDocument(context, doc)) {
44
+ // In theory a binary search could be faster than a linear scan to find the start.
45
+ // In practice, most cases should not filter out anything here.
46
+ if (row.o <= bucketStart) {
47
+ continue;
48
+ }
49
+ if (row.o > endOpId) {
50
+ break;
51
+ }
52
+ rows.push(row);
53
+ }
54
+ return rows;
55
+ }
15
56
  export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
16
- syncRulesV3;
17
- constructor(factory, group_id, sync_rules, slot_name, writeCheckpointMode, options) {
18
- super(factory, group_id, sync_rules, slot_name, writeCheckpointMode, options);
19
- if (!(sync_rules instanceof MongoPersistedSyncRulesContentV3)) {
57
+ constructor(factory, replicationStreamId, replicationStream, replicationStreamName, writeCheckpointMode, options) {
58
+ super(factory, replicationStreamId, replicationStream, replicationStreamName, writeCheckpointMode, options);
59
+ if (replicationStream.syncConfigIds.length == 0) {
20
60
  throw new ServiceAssertionError('Missing sync config id for storage v3');
21
61
  }
22
- this.syncRulesV3 = sync_rules;
23
62
  }
24
- get syncConfigId() {
25
- return this.syncRulesV3.syncConfigId;
63
+ get syncConfigIds() {
64
+ return this.replicationStream.syncConfigIds;
26
65
  }
27
66
  get syncRulesCollection() {
28
67
  return this.db.sync_rules;
29
68
  }
30
69
  syncConfigMatch(extra = {}) {
31
70
  return {
32
- _id: this.group_id,
71
+ _id: this.replicationStreamId,
33
72
  sync_configs: {
34
73
  $elemMatch: {
35
- _id: this.syncConfigId,
74
+ _id: { $in: this.syncConfigIds },
36
75
  ...extra
37
76
  }
38
77
  }
@@ -41,39 +80,25 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
41
80
  syncConfigProjection(extra = {}) {
42
81
  return {
43
82
  ...extra,
44
- sync_configs: {
45
- $elemMatch: {
46
- _id: this.syncConfigId
47
- }
48
- }
83
+ sync_configs: 1
49
84
  };
50
85
  }
51
- syncConfigArrayFilters() {
52
- return [{ 'config._id': this.syncConfigId }];
53
- }
54
- /**
55
- * For now, we only support a single sync config per replication stream.
56
- *
57
- * In the future we'll add support for multiple.
58
- */
59
- selectedSyncConfig(doc) {
60
- return doc?.sync_configs?.[0] ?? null;
86
+ selectedSyncConfigs(doc) {
87
+ return doc?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
61
88
  }
62
89
  async initializeVersionStorage() {
63
- const mapping = this.mapping;
64
- for (let source of mapping.allBucketDefinitionIds()) {
65
- const collection = this.db.bucketDataV3(this.group_id, source).collectionName;
66
- await this.db.db
67
- .createCollection(collection, { clusteredIndex: { name: '_id', unique: true, key: { _id: 1 } } })
68
- .catch((error) => {
90
+ const storageIds = this.storageIds;
91
+ for (const source of storageIds.bucketDefinitionIds) {
92
+ const collection = this.db.bucketData(this.replicationStreamId, source).collectionName;
93
+ await this.db.db.createCollection(collection, {}).catch((error) => {
69
94
  if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceExists') {
70
95
  return;
71
96
  }
72
97
  throw error;
73
98
  });
74
99
  }
75
- for (let indexId of mapping.allParameterIndexIds()) {
76
- await this.db.parameterIndexV3(this.group_id, indexId).createIndex({
100
+ for (const indexId of storageIds.parameterIndexIds) {
101
+ await this.db.parameterIndex(this.replicationStreamId, indexId).createIndex({
77
102
  lookup: 1,
78
103
  key: 1,
79
104
  _id: -1
@@ -83,53 +108,85 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
83
108
  }
84
109
  }
85
110
  createMongoChecksums(options) {
86
- return new MongoChecksumsV3(this.db, this.group_id, {
111
+ return new MongoChecksumsV3(this.db, this.replicationStreamId, {
87
112
  ...options.checksumOptions,
113
+ checksumCacheTtlMs: options.checksumCacheTtlMs,
88
114
  storageConfig: options?.storageConfig,
89
- mapping: this.sync_rules.mapping
115
+ syncConfigMapping: () => this.singleSyncConfigMapping()
116
+ });
117
+ }
118
+ createWriteCheckpointAPI(options) {
119
+ return new MongoWriteCheckpointAPIV3({
120
+ ...options,
121
+ db: this.db,
122
+ syncConfigMapping: () => this.singleSyncConfigMapping()
90
123
  });
91
124
  }
92
125
  createMongoCompactor(options) {
93
126
  return new MongoCompactorV3(this, this.db, options);
94
127
  }
95
- createMongoParameterCompactor(checkpoint, options) {
96
- return new MongoParameterCompactorV3(this.db, this.group_id, checkpoint, options);
128
+ async compactInitialReplication(options) {
129
+ this.logger.info(`Compacting chunks after initial replication...`);
130
+ const start = Date.now();
131
+ const maxOpId = options.maxOpId ?? (await this.fetchPersistedOpHead()) ?? undefined;
132
+ const compactedBuckets = await this.createMongoCompactor({
133
+ ...options,
134
+ maxOpId,
135
+ compactChunksOnly: true,
136
+ logger: this.logger
137
+ }).compact();
138
+ this.logger.info(`Compacted chunks after initial replication in ${(Date.now() - start) / 1000}s`);
139
+ return { buckets: compactedBuckets };
97
140
  }
98
- createWriterImpl(batchOptions) {
99
- return new MongoBucketBatchV3(batchOptions);
141
+ createMongoParameterCompactor(checkpoint, options) {
142
+ return new MongoParameterCompactorV3(this.db, this.replicationStreamId, checkpoint, options);
143
+ }
144
+ async fetchPersistedOpHead() {
145
+ const doc = await this.syncRulesCollection.findOne({ _id: this.replicationStreamId }, { projection: { last_persisted_op: 1 } });
146
+ return doc?.last_persisted_op == null ? null : BigInt(doc.last_persisted_op);
147
+ }
148
+ async createWriterImpl(options) {
149
+ const batchOptions = this.writerBatchOptions(options);
150
+ const doc = await this.syncRulesCollection.findOne({ _id: this.replicationStreamId }, { projection: { resume_lsn: 1 } });
151
+ return new MongoBucketBatchV3({
152
+ ...batchOptions,
153
+ // The stream-level replication position - per-config checkpoint LSNs are consistency
154
+ // markers and do not affect where replication resumes.
155
+ resumeFromLsn: doc?.resume_lsn ?? null,
156
+ syncConfigIds: this.syncConfigIds
157
+ });
100
158
  }
101
159
  async fetchCheckpointState(session) {
102
160
  const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch({
103
161
  state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
104
162
  }), {
105
163
  session,
106
- projection: this.syncConfigProjection()
164
+ // The invalidation fence must be read in the same snapshot as the checkpoint.
165
+ projection: this.syncConfigProjection({ 'parameter_compaction.checkpoint_changes_invalid_before': 1 })
107
166
  });
108
- const syncConfig = this.selectedSyncConfig(doc);
109
- if (!syncConfig?.snapshot_done) {
167
+ // Checkpoints are served from the single active config. A PROCESSING config in the same
168
+ // stream (incremental reprocessing) does not affect checkpoints until it is activated.
169
+ const syncConfigs = this.selectedSyncConfigs(doc).filter((config) => [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED].includes(config.state));
170
+ if (syncConfigs.length > 1) {
171
+ // Activation atomically replaces the previous active config, so this cannot happen unless
172
+ // the stored state is corrupt.
173
+ throw new ServiceAssertionError(`Expected a single active sync config, got ${syncConfigs.map((config) => config._id.toHexString()).join(', ')}`);
174
+ }
175
+ const syncConfig = syncConfigs[0];
176
+ if (syncConfig == null || !syncConfig.snapshot_done) {
110
177
  return null;
111
178
  }
112
179
  return {
113
180
  checkpoint: syncConfig.last_checkpoint ?? 0n,
114
- lsn: syncConfig.last_checkpoint_lsn ?? null
115
- };
116
- }
117
- async getWriterSyncState() {
118
- const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
119
- projection: this.syncConfigProjection({ snapshot_lsn: 1 })
120
- });
121
- const syncConfig = this.selectedSyncConfig(doc);
122
- const checkpointLsn = syncConfig?.last_checkpoint_lsn ?? null;
123
- return {
124
- lastCheckpointLsn: checkpointLsn,
125
- resumeFromLsn: maxLsn(checkpointLsn, doc?.snapshot_lsn),
126
- keepaliveOp: syncConfig?.keepalive_op ?? null,
127
- syncConfigId: this.syncConfigId
181
+ lsn: syncConfig.last_checkpoint_lsn ?? null,
182
+ // Stream-level state: shared by all sync configs. Defaults to 0n for streams that have
183
+ // never been compacted.
184
+ parameterChangesInvalidBefore: doc?.parameter_compaction?.checkpoint_changes_invalid_before ?? 0n
128
185
  };
129
186
  }
130
187
  async terminateSyncRuleState() {
131
188
  await this.db.sync_rules.updateOne({
132
- _id: this.group_id
189
+ _id: this.replicationStreamId
133
190
  }, {
134
191
  $set: {
135
192
  state: storage.SyncRuleState.TERMINATED,
@@ -140,43 +197,70 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
140
197
  }
141
198
  async getStatusImpl() {
142
199
  const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
143
- projection: this.syncConfigProjection({ state: 1, snapshot_lsn: 1, keepalive_op: 1 })
200
+ projection: this.syncConfigProjection({ resume_lsn: 1 })
144
201
  });
145
- const syncConfig = this.selectedSyncConfig(doc);
146
- if (doc == null || syncConfig == null) {
202
+ const syncConfigs = this.selectedSyncConfigs(doc);
203
+ if (doc == null || syncConfigs.length == 0) {
147
204
  throw new ServiceAssertionError('Cannot find replication stream status');
148
205
  }
149
206
  return {
150
- snapshot_done: syncConfig.snapshot_done ?? false,
151
- snapshot_lsn: doc.snapshot_lsn ?? null,
152
- active: doc.state == storage.SyncRuleState.ACTIVE && syncConfig.state == storage.SyncRuleState.ACTIVE,
153
- checkpoint_lsn: syncConfig.last_checkpoint_lsn ?? null,
154
- keepalive_op: syncConfig.keepalive_op ?? null
207
+ snapshotDone: syncConfigs.every((config) => config.snapshot_done ?? false) &&
208
+ syncConfigs.every((config) => config.last_checkpoint_lsn != null),
209
+ resumeLsn: doc.resume_lsn ?? null
155
210
  };
156
211
  }
157
212
  async clearSyncRuleState() {
158
- await this.syncRulesCollection.updateOne(this.syncConfigMatch(), {
213
+ // Clearing resets the entire replication stream (bucket data and the op sequence), so reset
214
+ // the checkpoint state for _all_ embedded sync configs, not only the ones selected for this
215
+ // storage instance. This maintains the invariant that no config has a last_checkpoint past
216
+ // the stream-level last_persisted_op.
217
+ await this.syncRulesCollection.updateOne({ _id: this.replicationStreamId }, {
159
218
  $set: {
160
219
  persisted_lsn: null,
161
- 'sync_configs.$[config].snapshot_done': false,
162
- 'sync_configs.$[config].last_checkpoint_lsn': null,
163
- 'sync_configs.$[config].last_checkpoint': null,
164
- 'sync_configs.$[config].no_checkpoint_before': null,
165
- 'sync_configs.$[config].keepalive_op': null
220
+ 'sync_configs.$[].snapshot_done': false,
221
+ 'sync_configs.$[].last_checkpoint_lsn': null,
222
+ 'sync_configs.$[].last_checkpoint': null,
223
+ 'sync_configs.$[].no_checkpoint_before': null
166
224
  },
167
225
  $unset: {
168
- snapshot_lsn: 1
226
+ resume_lsn: 1,
227
+ last_persisted_op: 1,
228
+ parameter_compaction: 1
169
229
  }
170
230
  }, {
171
- maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS,
172
- arrayFilters: this.syncConfigArrayFilters()
231
+ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
173
232
  });
174
233
  }
234
+ /**
235
+ * The persisted mapping of the single sync config that read operations are served from.
236
+ *
237
+ * Reads always operate on a single sync config: read-path storage instances are
238
+ * constructed for the active sync config only (see MongoBucketStorage.getActiveSyncConfig),
239
+ * and checkpoints are served from the single active config (see fetchCheckpointState).
240
+ * Within a single sync config, unique names are the persistence key of its rule mapping,
241
+ * so its name-keyed {@link SingleSyncConfigBucketDefinitionMapping} resolves sources from
242
+ * any parse of that config unambiguously - no parsed-set identity is required.
243
+ *
244
+ * Throws on storage instances with multiple sync configs (replication-side instances),
245
+ * which must not serve reads.
246
+ */
247
+ singleSyncConfigMapping() {
248
+ const content = this.replicationStream.syncConfigContent;
249
+ if (content.length != 1) {
250
+ throw new ServiceAssertionError(`Read operations require a storage instance with a single sync config, got ${content.length}`);
251
+ }
252
+ return content[0].mapping;
253
+ }
175
254
  get versionContext() {
255
+ const self = this;
176
256
  return {
177
257
  db: this.db,
178
- group_id: this.group_id,
179
- mapping: this.mapping
258
+ objectStorage: this.objectStorage,
259
+ replicationStreamId: this.replicationStreamId,
260
+ readPreference: this.readPreference,
261
+ get mapping() {
262
+ return self.singleSyncConfigMapping();
263
+ }
180
264
  };
181
265
  }
182
266
  getParameterSetsImpl(checkpoint, lookups, limit) {
@@ -185,27 +269,42 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
185
269
  getBucketDataBatchImpl(checkpoint, dataBuckets, options) {
186
270
  return getBucketDataBatchV3(this.versionContext, checkpoint, dataBuckets, options);
187
271
  }
188
- async clearBucketData(_signal) {
189
- for (const collection of await this.db.listBucketDataCollectionsV3(this.group_id)) {
272
+ async clearBucketData(signal) {
273
+ for (const collection of await this.db.listBucketDataCollections(this.replicationStreamId)) {
190
274
  await collection.drop();
191
275
  }
276
+ const usage = new ObjectStorageUsage(this.db, this.replicationStreamId);
277
+ await this.db.client.withSession((session) => session.withTransaction(() => usage.removeStream(session)));
278
+ if (this.objectStorage) {
279
+ const lifecycle = new ObjectStorageLifecycle(this.db, this.replicationStreamId, this.objectStorage);
280
+ await lifecycle.deletePrefix(lifecycle.streamPrefix(), { signal });
281
+ }
282
+ await this.db
283
+ .pendingObjectStorageDeletes(this.replicationStreamId)
284
+ .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
285
+ .catch((error) => {
286
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
287
+ return;
288
+ }
289
+ throw error;
290
+ });
192
291
  }
193
292
  async clearParameterIndexes(_signal) {
194
- for (const collection of await this.db.listParameterIndexCollectionsV3(this.group_id)) {
293
+ for (const collection of await this.db.listParameterIndexCollections(this.replicationStreamId)) {
195
294
  await collection.collection.drop();
196
295
  }
197
296
  }
198
297
  async clearSourceRecords(_signal) {
199
- for (const collection of await this.db.listSourceRecordCollectionsV3(this.group_id)) {
298
+ for (const collection of await this.db.listSourceRecordCollections(this.replicationStreamId)) {
200
299
  await collection.drop();
201
300
  }
202
301
  }
203
302
  async clearBucketState(_signal) {
204
303
  await this.db
205
- .bucketStateV3(this.group_id)
304
+ .bucketState(this.replicationStreamId)
206
305
  .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
207
306
  .catch((error) => {
208
- if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
307
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
209
308
  return;
210
309
  }
211
310
  throw error;
@@ -213,15 +312,38 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
213
312
  }
214
313
  async clearSourceTables(_signal) {
215
314
  await this.db
216
- .sourceTablesV3(this.group_id)
315
+ .sourceTables(this.replicationStreamId)
217
316
  .drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
218
317
  .catch((error) => {
219
- if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
318
+ if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
220
319
  return;
221
320
  }
222
321
  throw error;
223
322
  });
224
323
  }
324
+ async clearCustomCheckpointRequests(signal) {
325
+ for (const collection of await this.db.listCustomCheckpointRequestCollections(this.replicationStreamId)) {
326
+ signal?.throwIfAborted();
327
+ await collection.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }).catch((error) => {
328
+ if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
329
+ return;
330
+ }
331
+ throw error;
332
+ });
333
+ }
334
+ }
335
+ async cleanupStoppedSyncConfigs(options) {
336
+ return new MongoStoppedSyncConfigCleanup({
337
+ db: this.db,
338
+ replicationStreamId: this.replicationStreamId,
339
+ signal: options.signal,
340
+ logger: options.logger ?? this.logger,
341
+ defaultSchema: options.defaultSchema,
342
+ sourceConnectionTag: options.sourceConnectionTag,
343
+ objectStorage: this.objectStorage,
344
+ clearBatchThrottleRate: this.clearBatchThrottleRate
345
+ }).run();
346
+ }
225
347
  getDataBucketChangesImpl(options) {
226
348
  return getDataBucketChangesV3(this.versionContext, options);
227
349
  }
@@ -234,8 +356,8 @@ export async function getParameterSetsV3(ctx, checkpoint, lookups, limit) {
234
356
  setSessionSnapshotTime(session, checkpoint.snapshotTime);
235
357
  const buildLookupPipeline = (lookup, index) => {
236
358
  const indexId = lookup.indexId;
237
- const collection = ctx.db.parameterIndexV3(ctx.group_id, indexId);
238
- const lookupFilter = serializeParameterLookupV3(lookup);
359
+ const collection = ctx.db.parameterIndex(ctx.replicationStreamId, indexId);
360
+ const lookupFilter = serializeParameterLookup(lookup);
239
361
  return {
240
362
  collection,
241
363
  pipeline: [
@@ -313,13 +435,24 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
313
435
  if (dataBuckets.length == 0) {
314
436
  return;
315
437
  }
316
- if (checkpoint == null) {
438
+ if (checkpoint.checkpoint == null) {
317
439
  throw new Error('checkpoint is null');
318
440
  }
441
+ const readPreference = options?.requestHint == 'bulk' ? ctx.readPreference : undefined;
442
+ const readConcern = ctx.readPreference == null ? undefined : 'majority';
443
+ const session = readPreference == null || checkpoint.snapshotTime == null
444
+ ? undefined
445
+ : ctx.db.client.startSession({ causalConsistency: true });
446
+ await using _ = { [Symbol.asyncDispose]: async () => session?.endSession() };
447
+ if (session != null) {
448
+ session.advanceOperationTime(checkpoint.snapshotTime);
449
+ session.advanceClusterTime(checkpoint.clusterTime);
450
+ }
319
451
  const batchLimit = options?.limit ?? storage.DEFAULT_DOCUMENT_BATCH_LIMIT;
320
452
  const chunkSizeLimitBytes = options?.chunkLimitBytes ?? storage.DEFAULT_DOCUMENT_CHUNK_LIMIT_BYTES;
321
- const end = checkpoint;
322
- let remainingLimit = batchLimit;
453
+ const end = checkpoint.checkpoint;
454
+ // Group requests by definition, so that we can query each definition's bucket data in a single query.
455
+ // We only return the results of a single query per batch.
323
456
  const requestsByDefinition = new Map();
324
457
  for (const request of dataBuckets) {
325
458
  const definitionId = ctx.mapping.bucketSourceId(request.source);
@@ -328,49 +461,67 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
328
461
  requestsByDefinition.set(definitionId, requests);
329
462
  }
330
463
  const definitionGroups = Array.from(requestsByDefinition.entries());
331
- for (let groupIndex = 0; groupIndex < definitionGroups.length && remainingLimit > 0; groupIndex++) {
332
- const [definitionId, requests] = definitionGroups[groupIndex];
464
+ for (const [groupIndex, [definitionId, requests]] of definitionGroups.entries()) {
333
465
  const hasLaterDefinitionGroups = groupIndex < definitionGroups.length - 1;
334
466
  const bucketMap = new Map(requests.map((request) => [request.bucket, request.start]));
335
467
  const filters = Array.from(bucketMap.entries()).map(([bucket, start]) => ({
336
468
  _id: {
337
- $gt: {
338
- b: bucket,
339
- o: start
340
- },
341
- $lte: {
342
- b: bucket,
343
- o: end
344
- }
345
- }
469
+ $gt: { b: bucket, o: start },
470
+ $lte: { b: bucket, o: new bson.MaxKey() }
471
+ },
472
+ min_op: { $lte: end }
473
+ // MongoDB Filter<T> doesn't accept compound _id ranges or dotted field paths in its type.
346
474
  }));
347
- const cursor = ctx.db.bucketDataV3(ctx.group_id, definitionId).find({
348
- $or: filters
349
- }, {
350
- session: undefined,
475
+ const collection = ctx.db.bucketData(ctx.replicationStreamId, definitionId);
476
+ // MongoDB Filter<T> doesn't accept the $or operator in its type.
477
+ const filter = { $or: filters };
478
+ const context = { replicationStreamId: ctx.replicationStreamId, definitionId };
479
+ const cursorOptions = { limit: batchLimit, batchSize: batchLimit + 1 };
480
+ // raw: true returns Buffers, but the driver typing doesn't reflect that
481
+ // without an explicit cast to FindCursor<Buffer>.
482
+ const cursor = collection.find(filter, {
483
+ session,
484
+ readPreference,
485
+ readConcern,
351
486
  sort: { _id: 1 },
352
- limit: remainingLimit,
353
- batchSize: remainingLimit + 1,
354
487
  raw: true,
355
- maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
488
+ maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS,
489
+ ...cursorOptions
356
490
  });
357
- let { data, hasMore: batchHasMore } = await readSingleBatch(cursor).catch((e) => {
491
+ let { data: rawData, hasMore } = await readSingleBatch(cursor).catch((e) => {
358
492
  throw lib_mongo.mapQueryError(e, 'while reading bucket data');
359
493
  });
360
- if (data.length == remainingLimit) {
361
- batchHasMore = true;
494
+ if (rawData.length >= cursorOptions.limit) {
495
+ hasMore = true;
362
496
  }
363
- if (data.length == 0) {
364
- continue;
497
+ // Deserialize the raw documents and cut the batch to a bounded size. Any data not
498
+ // making the cut will be read in the next round, using a fresh query.
499
+ const cutBatch = cutBucketDataBatch(rawData);
500
+ const docs = cutBatch.documents;
501
+ if (cutBatch.wasCut) {
502
+ hasMore = true;
365
503
  }
366
- remainingLimit -= data.length;
367
- let chunkSizeBytes = 0;
504
+ // Hydrate any operations from object storage.
505
+ // In the future we can do this in a more pipelined fashion, but for now we hydrate
506
+ // the entire batch at once.
507
+ await hydrateBucketDataDocuments(docs, ctx.objectStorage, { signal: options?.signal, tracer: options?.tracer });
508
+ let currentChunkSizeBytes = 0;
368
509
  let currentChunk = null;
369
510
  let targetOp = null;
370
- for (let rawData of data) {
371
- const row = loadBucketDataDocumentV3({ replicationStreamId: ctx.group_id, definitionId }, bson.deserialize(rawData, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS));
372
- const bucket = row.bucketKey.bucket;
373
- if (currentChunk == null || currentChunk.bucket != bucket || chunkSizeBytes >= chunkSizeLimitBytes) {
511
+ let seenBuckets = new Set();
512
+ const batchHasMore = hasMore;
513
+ for (const doc of docs) {
514
+ const bucket = doc._id.b;
515
+ seenBuckets.add(bucket);
516
+ const bucketStart = bucketMap.get(bucket);
517
+ if (bucketStart == null) {
518
+ throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
519
+ }
520
+ // Reached a new bucket or size limit: yield the current chunk and start a new one.
521
+ if (currentChunk == null ||
522
+ currentChunk.bucket != bucket ||
523
+ currentChunkSizeBytes >= chunkSizeLimitBytes ||
524
+ currentChunk.data.length >= batchLimit) {
374
525
  let start = undefined;
375
526
  if (currentChunk != null) {
376
527
  if (currentChunk.bucket == bucket) {
@@ -379,16 +530,12 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
379
530
  }
380
531
  const yieldChunk = currentChunk;
381
532
  currentChunk = null;
382
- chunkSizeBytes = 0;
383
- yield { chunkData: yieldChunk, targetOp: targetOp };
533
+ currentChunkSizeBytes = 0;
534
+ yield { chunkData: yieldChunk, targetOp };
384
535
  targetOp = null;
385
536
  }
386
537
  if (start == null) {
387
- const startOpId = bucketMap.get(bucket);
388
- if (startOpId == null) {
389
- throw new Error(`data for unexpected bucket: ${bucket}`);
390
- }
391
- start = internalToExternalOpId(startOpId);
538
+ start = internalToExternalOpId(bucketStart);
392
539
  }
393
540
  currentChunk = {
394
541
  bucket,
@@ -398,28 +545,52 @@ export async function* getBucketDataBatchV3(ctx, checkpoint, dataBuckets, option
398
545
  next_after: start
399
546
  };
400
547
  }
401
- const entry = mapOpEntry(row);
402
- if (row.target_op != null && (targetOp == null || row.target_op > targetOp)) {
403
- targetOp = row.target_op;
404
- }
405
- currentChunk.data.push(entry);
406
- currentChunk.next_after = entry.op_id;
407
- chunkSizeBytes += rawData.byteLength;
548
+ const rows = extractRowsFromDocument(doc, context, bucketStart, end);
549
+ currentChunk.data.push(...rows.map(mapOpEntry));
550
+ currentChunk.next_after = currentChunk.data.at(-1)?.op_id ?? internalToExternalOpId(end);
551
+ targetOp = maxOpId(targetOp, doc.target_op);
552
+ currentChunkSizeBytes += doc.size;
408
553
  }
409
554
  if (currentChunk != null) {
410
555
  const yieldChunk = currentChunk;
411
- yieldChunk.has_more = batchHasMore || (remainingLimit <= 0 && hasLaterDefinitionGroups);
412
- yield { chunkData: yieldChunk, targetOp: targetOp };
556
+ // The last chunk may contain more data that was cut in this batch.
557
+ yieldChunk.has_more = batchHasMore;
558
+ yield { chunkData: yieldChunk, targetOp };
559
+ }
560
+ if (!batchHasMore) {
561
+ for (const bucket of bucketMap.keys()) {
562
+ if (!seenBuckets.has(bucket)) {
563
+ // We processed everything for this definition group, but this bucket had no data in the batch.
564
+ // Yield an empty chunk to indicate that it is complete.
565
+ // This prevents re-querying the same bucket in the next batch.
566
+ yield {
567
+ chunkData: {
568
+ bucket,
569
+ after: internalToExternalOpId(bucketMap.get(bucket)),
570
+ has_more: false,
571
+ data: [],
572
+ next_after: internalToExternalOpId(end)
573
+ },
574
+ targetOp
575
+ };
576
+ }
577
+ }
578
+ }
579
+ if (currentChunk != null) {
580
+ // We yielded data in this group (aside from empty buckets).
581
+ // Return to the caller to allow them to process it before continuing to the next group.
582
+ yield { hasMore: batchHasMore || hasLaterDefinitionGroups };
583
+ break;
413
584
  }
414
- if (batchHasMore || remainingLimit <= 0) {
415
- return;
585
+ else {
586
+ // No data in this definition group - continue in the next group.
416
587
  }
417
588
  }
418
589
  }
419
590
  export async function getDataBucketChangesV3(ctx, options) {
420
591
  const limit = 1000;
421
592
  const bucketStateUpdates = await ctx.db
422
- .bucketStateV3(ctx.group_id)
593
+ .bucketState(ctx.replicationStreamId)
423
594
  .aggregate([
424
595
  {
425
596
  $match: {
@@ -449,12 +620,20 @@ export async function getDataBucketChangesV3(ctx, options) {
449
620
  updatedDataBuckets: invalidateDataBuckets ? new Set() : new Set(buckets)
450
621
  };
451
622
  }
623
+ /**
624
+ * Query the parameter entries changed between the two checkpoints, to determine which parameter
625
+ * lookups need to be re-evaluated.
626
+ *
627
+ * This runs at the next checkpoint's snapshot, so it still sees entries that parameter compaction
628
+ * deleted after that snapshot. Compaction that deleted entries before the snapshot is covered by
629
+ * the invalidation fence, checked before we get here.
630
+ */
452
631
  export async function getParameterBucketChangesV3(ctx, options) {
453
632
  const limit = 1000;
454
633
  const indexIds = ctx.mapping.allParameterIndexIds();
455
634
  const collections = indexIds.map((indexId) => ({
456
635
  indexId,
457
- collection: ctx.db.parameterIndexV3(ctx.group_id, indexId)
636
+ collection: ctx.db.parameterIndex(ctx.replicationStreamId, indexId)
458
637
  }));
459
638
  if (collections.length == 0) {
460
639
  return {
@@ -478,31 +657,44 @@ export async function getParameterBucketChangesV3(ctx, options) {
478
657
  }
479
658
  ];
480
659
  const [firstCollection, ...remainingCollections] = collections;
481
- const parameterUpdates = await firstCollection.collection
482
- .aggregate([
483
- ...pipelineForCollection(firstCollection.indexId),
484
- ...remainingCollections.map((collection) => {
485
- return {
486
- $unionWith: {
487
- coll: collection.collection.collectionName,
488
- pipeline: pipelineForCollection(collection.indexId)
489
- }
490
- };
491
- }),
492
- {
493
- $limit: limit + 1
494
- }
495
- ], {
496
- batchSize: limit + 2,
497
- maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
498
- })
499
- .toArray();
660
+ const parameterUpdates = await ctx.db.client.withSession({ snapshot: true }, async (session) => {
661
+ setSessionSnapshotTime(session, options.nextCheckpoint.snapshotTime);
662
+ return await firstCollection.collection
663
+ .aggregate([
664
+ ...pipelineForCollection(firstCollection.indexId),
665
+ ...remainingCollections.map((collection) => {
666
+ return {
667
+ $unionWith: {
668
+ coll: collection.collection.collectionName,
669
+ pipeline: pipelineForCollection(collection.indexId)
670
+ }
671
+ };
672
+ }),
673
+ {
674
+ $limit: limit + 1
675
+ }
676
+ ], {
677
+ session,
678
+ readConcern: 'snapshot',
679
+ batchSize: limit + 2,
680
+ maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS
681
+ })
682
+ .toArray()
683
+ .catch((e) => {
684
+ // Includes the case where the checkpoint snapshot has expired. Degrading to
685
+ // invalidateParameterBuckets would be safe in itself - it reads nothing - but it gains
686
+ // nothing: the caller responds to that by re-evaluating the parameter queries at this
687
+ // same snapshot, which fails too. The checkpoint has to be refetched instead, which is
688
+ // what the existing sync retry behavior does.
689
+ throw lib_mongo.mapQueryError(e, 'while querying parameter changes');
690
+ });
691
+ });
500
692
  const invalidateParameterUpdates = parameterUpdates.length > limit;
501
693
  return {
502
694
  invalidateParameterBuckets: invalidateParameterUpdates,
503
695
  updatedParameterLookups: invalidateParameterUpdates
504
696
  ? new Set()
505
- : new Set(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookupV3(p.lookup, p.indexId))))
697
+ : new Set(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup, p.indexId))))
506
698
  };
507
699
  }
508
700
  //# sourceMappingURL=MongoSyncBucketStorageV3.js.map