@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,17 +1,13 @@
1
1
  import { mongo } from '@powersync/lib-service-mongodb';
2
2
  import * as bson from 'bson';
3
- import { BaseObserver, container, logger as defaultLogger, ErrorCode, errors, ReplicationAssertionError, ServiceError } from '@powersync/lib-services-framework';
4
- import { deserializeBson, isCompleteRow, SaveOperationTag, storage, utils } from '@powersync/service-core';
3
+ import { BaseObserver, container, ErrorCode, errors, ReplicationAssertionError, ServiceError } from '@powersync/lib-services-framework';
4
+ import { deserializeBson, isCompleteRow, PerformanceTracer, SaveOperationTag, storage, SyncRuleState, utils } from '@powersync/service-core';
5
5
  import * as timers from 'node:timers/promises';
6
- import { idPrefixFilter } from '../../utils/util.js';
6
+ import { mongoTableId } from '../../utils/util.js';
7
+ import { MAX_ROW_SIZE } from './MongoBucketBatchShared.js';
7
8
  import { MongoIdSequence } from './MongoIdSequence.js';
8
9
  import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
9
- import { cacheKey, OperationBatch, RecordOperation } from './OperationBatch.js';
10
- import { PersistedBatch } from './PersistedBatch.js';
11
- /**
12
- * 15MB
13
- */
14
- export const MAX_ROW_SIZE = 15 * 1024 * 1024;
10
+ import { OperationBatch, RecordOperation } from './OperationBatch.js';
15
11
  // Currently, we can only have a single flush() at a time, since it locks the op_id sequence.
16
12
  // While the MongoDB transaction retry mechanism handles this okay, using an in-process Mutex
17
13
  // makes it more fair and has less overhead.
@@ -28,10 +24,12 @@ export class MongoBucketBatch extends BaseObserver {
28
24
  slot_name;
29
25
  storeCurrentData;
30
26
  skipExistingRows;
27
+ mapping;
31
28
  batch = null;
32
29
  write_checkpoint_batch = [];
33
30
  markRecordUnavailable;
34
31
  clearedError = false;
32
+ tracer;
35
33
  /**
36
34
  * Last LSN received associated with a checkpoint.
37
35
  *
@@ -40,7 +38,6 @@ export class MongoBucketBatch extends BaseObserver {
40
38
  * 2. A keepalive message LSN.
41
39
  */
42
40
  last_checkpoint_lsn = null;
43
- no_checkpoint_before_lsn;
44
41
  persisted_op = null;
45
42
  /**
46
43
  * Last written op, if any. This may not reflect a consistent checkpoint.
@@ -60,21 +57,22 @@ export class MongoBucketBatch extends BaseObserver {
60
57
  needsActivation = true;
61
58
  constructor(options) {
62
59
  super();
63
- this.logger = options.logger ?? defaultLogger;
60
+ this.logger = options.logger;
64
61
  this.client = options.db.client;
65
62
  this.db = options.db;
66
63
  this.group_id = options.groupId;
67
64
  this.last_checkpoint_lsn = options.lastCheckpointLsn;
68
- this.no_checkpoint_before_lsn = options.noCheckpointBeforeLsn;
69
65
  this.resumeFromLsn = options.resumeFromLsn;
70
66
  this.session = this.client.startSession();
71
67
  this.slot_name = options.slotName;
72
68
  this.sync_rules = options.syncRules;
73
69
  this.storeCurrentData = options.storeCurrentData;
70
+ this.mapping = options.mapping;
74
71
  this.skipExistingRows = options.skipExistingRows;
75
72
  this.markRecordUnavailable = options.markRecordUnavailable;
76
73
  this.batch = new OperationBatch();
77
74
  this.persisted_op = options.keepaliveOp ?? null;
75
+ this.tracer = options.tracer ?? new PerformanceTracer('MongoDB storage');
78
76
  }
79
77
  addCustomWriteCheckpoint(checkpoint) {
80
78
  this.write_checkpoint_batch.push({
@@ -85,9 +83,6 @@ export class MongoBucketBatch extends BaseObserver {
85
83
  get lastCheckpointLsn() {
86
84
  return this.last_checkpoint_lsn;
87
85
  }
88
- get noCheckpointBeforeLsn() {
89
- return this.no_checkpoint_before_lsn;
90
- }
91
86
  async flush(options) {
92
87
  let result = null;
93
88
  // One flush may be split over multiple transactions.
@@ -104,6 +99,7 @@ export class MongoBucketBatch extends BaseObserver {
104
99
  const batch = this.batch;
105
100
  let last_op = null;
106
101
  let resumeBatch = null;
102
+ using _ = this.tracer.span('storage', 'flush');
107
103
  await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
108
104
  if (batch != null) {
109
105
  resumeBatch = await this.replicateBatch(session, batch, opSeq, options);
@@ -126,6 +122,7 @@ export class MongoBucketBatch extends BaseObserver {
126
122
  }
127
123
  async replicateBatch(session, batch, op_seq, options) {
128
124
  let sizes = undefined;
125
+ using _ = this.tracer.span('storage', 'replicate_batch');
129
126
  if (this.storeCurrentData && !this.skipExistingRows) {
130
127
  // We skip this step if we don't store current_data, since the sizes will
131
128
  // always be small in that case.
@@ -138,27 +135,11 @@ export class MongoBucketBatch extends BaseObserver {
138
135
  // (automatically limited to 48MB(?) per batch by MongoDB). The issue is that it changes
139
136
  // the order of processing, which then becomes really tricky to manage.
140
137
  // This now takes 2+ queries, but doesn't have any issues with order of operations.
141
- const sizeLookups = batch.batch.map((r) => {
142
- return { g: this.group_id, t: r.record.sourceTable.id, k: r.beforeId };
143
- });
144
- sizes = new Map();
145
- const sizeCursor = this.db.current_data.aggregate([
146
- {
147
- $match: {
148
- _id: { $in: sizeLookups }
149
- }
150
- },
151
- {
152
- $project: {
153
- _id: 1,
154
- size: { $bsonSize: '$$ROOT' }
155
- }
156
- }
157
- ], { session });
158
- for await (let doc of sizeCursor.stream()) {
159
- const key = cacheKey(doc._id.t, doc._id.k);
160
- sizes.set(key, doc.size);
161
- }
138
+ const sizeLookups = batch.batch.map((r) => ({
139
+ sourceTableId: mongoTableId(r.record.sourceTable.id),
140
+ replicaId: r.beforeId
141
+ }));
142
+ sizes = await this.sourceRecordStore.loadSizes(session, sizeLookups);
162
143
  }
163
144
  // If set, we need to start a new transaction with this batch.
164
145
  let resumeBatch = null;
@@ -173,61 +154,64 @@ export class MongoBucketBatch extends BaseObserver {
173
154
  }
174
155
  continue;
175
156
  }
176
- const lookups = b.map((r) => {
177
- return { g: this.group_id, t: r.record.sourceTable.id, k: r.beforeId };
178
- });
179
- let current_data_lookup = new Map();
180
- // With skipExistingRows, we only need to know whether or not the row exists.
181
- const projection = this.skipExistingRows ? { _id: 1 } : undefined;
182
- const cursor = this.db.current_data.find({
183
- _id: { $in: lookups }
184
- }, { session, projection });
185
- for await (let doc of cursor.stream()) {
186
- current_data_lookup.set(cacheKey(doc._id.t, doc._id.k), doc);
187
- }
188
- let persistedBatch = new PersistedBatch(this.group_id, transactionSize, {
189
- logger: this.logger
190
- });
157
+ using lookupSpan = this.tracer.span('storage', 'lookup');
158
+ const lookups = b.map((r) => ({
159
+ sourceTableId: mongoTableId(r.record.sourceTable.id),
160
+ replicaId: r.beforeId
161
+ }));
162
+ let sourceRecordLookup = await this.sourceRecordStore.loadDocuments(session, lookups, this.skipExistingRows);
163
+ lookupSpan.end();
164
+ let persistedBatch = this.createPersistedBatch(transactionSize);
165
+ // The current code structure makes it tricky to cleanly split this span from the one
166
+ // where fluhsing. So we manually end and re-create this span whenever we flush.
167
+ let evalSpan = this.tracer.span('evaluate');
191
168
  for (let op of b) {
192
169
  if (resumeBatch) {
193
170
  resumeBatch.push(op);
194
171
  continue;
195
172
  }
196
- const currentData = current_data_lookup.get(op.internalBeforeKey) ?? null;
197
- if (currentData != null) {
173
+ const sourceRecord = sourceRecordLookup.get(op.internalBeforeKey) ?? null;
174
+ if (sourceRecord != null) {
198
175
  // If it will be used again later, it will be set again using nextData below
199
- current_data_lookup.delete(op.internalBeforeKey);
176
+ sourceRecordLookup.delete(op.internalBeforeKey);
200
177
  }
201
- const nextData = this.saveOperation(persistedBatch, op, currentData, op_seq);
178
+ const nextData = this.saveOperation(persistedBatch, op, sourceRecord, op_seq);
202
179
  if (nextData != null) {
203
180
  // Update our current_data and size cache
204
- current_data_lookup.set(op.internalAfterKey, nextData);
205
- sizes?.set(op.internalAfterKey, nextData.data.length());
181
+ sourceRecordLookup.set(op.internalAfterKey, nextData);
182
+ sizes?.set(op.internalAfterKey, nextData.data?.length() ?? 0);
206
183
  }
207
184
  if (persistedBatch.shouldFlushTransaction()) {
185
+ evalSpan.end();
208
186
  // Transaction is getting big.
209
187
  // Flush, and resume in a new transaction.
210
- const { flushedAny } = await persistedBatch.flush(this.db, this.session, options);
188
+ using persistSpan = this.tracer.span('storage', 'persist_flush');
189
+ const { flushedAny } = await persistedBatch.flush(this.session, options);
211
190
  didFlush ||= flushedAny;
212
191
  persistedBatch = null;
213
192
  // Computing our current progress is a little tricky here, since
214
193
  // we're stopping in the middle of a batch.
215
194
  // We create a new batch, and push any remaining operations to it.
216
195
  resumeBatch = new OperationBatch();
196
+ persistSpan.end();
197
+ evalSpan = this.tracer.span('evaluate');
217
198
  }
218
199
  }
200
+ evalSpan.end();
219
201
  if (persistedBatch) {
220
202
  transactionSize = persistedBatch.currentSize;
221
- const { flushedAny } = await persistedBatch.flush(this.db, this.session, options);
203
+ using _ = this.tracer.span('storage', 'persist_flush');
204
+ const { flushedAny } = await persistedBatch.flush(this.session, options);
222
205
  didFlush ||= flushedAny;
223
206
  }
224
207
  }
225
208
  if (didFlush) {
209
+ using _ = this.tracer.span('storage', 'clear_error');
226
210
  await this.clearError();
227
211
  }
228
212
  return resumeBatch?.hasData() ? resumeBatch : null;
229
213
  }
230
- saveOperation(batch, operation, current_data, opSeq) {
214
+ saveOperation(batch, operation, sourceRecord, opSeq) {
231
215
  const record = operation.record;
232
216
  const beforeId = operation.beforeId;
233
217
  const afterId = operation.afterId;
@@ -237,10 +221,10 @@ export class MongoBucketBatch extends BaseObserver {
237
221
  let new_buckets = [];
238
222
  let existing_lookups = [];
239
223
  let new_lookups = [];
240
- const before_key = { g: this.group_id, t: record.sourceTable.id, k: beforeId };
224
+ const sourceTableId = mongoTableId(record.sourceTable.id);
241
225
  if (this.skipExistingRows) {
242
226
  if (record.tag == SaveOperationTag.INSERT) {
243
- if (current_data != null) {
227
+ if (sourceRecord != null) {
244
228
  // Initial replication, and we already have the record.
245
229
  // This may be a different version of the record, but streaming replication
246
230
  // will take care of that.
@@ -253,7 +237,7 @@ export class MongoBucketBatch extends BaseObserver {
253
237
  }
254
238
  }
255
239
  if (record.tag == SaveOperationTag.UPDATE) {
256
- const result = current_data;
240
+ const result = sourceRecord;
257
241
  if (result == null) {
258
242
  // Not an error if we re-apply a transaction
259
243
  existing_buckets = [];
@@ -274,14 +258,14 @@ export class MongoBucketBatch extends BaseObserver {
274
258
  else {
275
259
  existing_buckets = result.buckets;
276
260
  existing_lookups = result.lookups;
277
- if (this.storeCurrentData) {
261
+ if (this.storeCurrentData && result.data != null) {
278
262
  const data = deserializeBson(result.data.buffer);
279
263
  after = storage.mergeToast(after, data);
280
264
  }
281
265
  }
282
266
  }
283
267
  else if (record.tag == SaveOperationTag.DELETE) {
284
- const result = current_data;
268
+ const result = sourceRecord;
285
269
  if (result == null) {
286
270
  // Not an error if we re-apply a transaction
287
271
  existing_buckets = [];
@@ -296,9 +280,9 @@ export class MongoBucketBatch extends BaseObserver {
296
280
  existing_lookups = result.lookups;
297
281
  }
298
282
  }
299
- let afterData;
283
+ let afterData = null;
300
284
  if (afterId != null && !this.storeCurrentData) {
301
- afterData = new bson.Binary(bson.serialize({}));
285
+ afterData = null;
302
286
  }
303
287
  else if (afterId != null) {
304
288
  try {
@@ -382,13 +366,7 @@ export class MongoBucketBatch extends BaseObserver {
382
366
  table: sourceTable,
383
367
  before_buckets: existing_buckets
384
368
  });
385
- new_buckets = evaluated.map((e) => {
386
- return {
387
- bucket: e.bucket,
388
- table: e.table,
389
- id: e.id
390
- };
391
- });
369
+ new_buckets = this.sourceRecordStore.mapEvaluatedBuckets(evaluated);
392
370
  }
393
371
  if (sourceTable.syncParameters) {
394
372
  // Parameters
@@ -410,36 +388,42 @@ export class MongoBucketBatch extends BaseObserver {
410
388
  evaluated: paramEvaluated,
411
389
  existing_lookups
412
390
  });
413
- new_lookups = paramEvaluated.map((p) => {
414
- return storage.serializeLookup(p.lookup);
415
- });
391
+ new_lookups = this.sourceRecordStore.mapParameterLookups(paramEvaluated);
416
392
  }
417
393
  }
418
394
  let result = null;
419
395
  // 5. TOAST: Update current data and bucket list.
420
396
  if (afterId) {
421
397
  // Insert or update
422
- const after_key = { g: this.group_id, t: sourceTable.id, k: afterId };
423
- batch.upsertCurrentData(after_key, {
398
+ batch.upsertCurrentData({
399
+ sourceTableId,
400
+ replicaId: afterId,
424
401
  data: afterData,
425
402
  buckets: new_buckets,
426
403
  lookups: new_lookups
427
404
  });
428
405
  result = {
429
- _id: after_key,
406
+ sourceTableId,
407
+ replicaId: afterId,
430
408
  data: afterData,
431
409
  buckets: new_buckets,
432
- lookups: new_lookups
410
+ lookups: new_lookups,
411
+ cacheKey: operation.internalAfterKey
433
412
  };
434
413
  }
435
414
  if (afterId == null || !storage.replicaIdEquals(beforeId, afterId)) {
436
415
  // Either a delete (afterId == null), or replaced the old replication id
437
- batch.deleteCurrentData(before_key);
416
+ // Note that this is a soft delete.
417
+ // We don't specifically need a new or unique op_id here, but it must be greater than the
418
+ // last checkpoint, so we use next().
419
+ batch.softDeleteCurrentData(sourceTableId, beforeId, opSeq.next());
438
420
  }
439
421
  return result;
440
422
  }
441
423
  async withTransaction(cb) {
424
+ using lockSpan = this.tracer.span('storage', 'internal_lock');
442
425
  await replicationMutex.exclusiveLock(async () => {
426
+ lockSpan.end();
443
427
  await this.session.withTransaction(async () => {
444
428
  try {
445
429
  await cb();
@@ -451,7 +435,9 @@ export class MongoBucketBatch extends BaseObserver {
451
435
  else {
452
436
  this.logger.warn('Transaction error', e);
453
437
  }
454
- await timers.setTimeout(Math.random() * 50);
438
+ const delay = Math.random() * 50;
439
+ using _ = this.tracer.span('storage', 'retry_delay');
440
+ await timers.setTimeout(delay);
455
441
  throw e;
456
442
  }
457
443
  }, { maxCommitTimeMS: 10000 });
@@ -505,59 +491,23 @@ export class MongoBucketBatch extends BaseObserver {
505
491
  });
506
492
  }
507
493
  async [Symbol.asyncDispose]() {
494
+ if (this.batch != null || this.write_checkpoint_batch.length > 0) {
495
+ // We don't error here, since:
496
+ // 1. In error states, this is expected (we can't distinguish between disposing after success or error).
497
+ // 2. SuppressedError is messy to deal with.
498
+ this.logger.warn('Disposing writer with unflushed changes');
499
+ }
508
500
  await this.session.endSession();
509
501
  super.clearListeners();
510
502
  }
503
+ async dispose() {
504
+ await this[Symbol.asyncDispose]();
505
+ }
511
506
  lastWaitingLogThottled = 0;
512
507
  async commit(lsn, options) {
513
508
  const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
514
509
  await this.flush(options);
515
- if (this.last_checkpoint_lsn != null && lsn < this.last_checkpoint_lsn) {
516
- // When re-applying transactions, don't create a new checkpoint until
517
- // we are past the last transaction.
518
- this.logger.info(`Re-applied transaction ${lsn} - skipping checkpoint`);
519
- // Cannot create a checkpoint yet - return false
520
- return false;
521
- }
522
- if (lsn < this.no_checkpoint_before_lsn) {
523
- if (Date.now() - this.lastWaitingLogThottled > 5_000) {
524
- this.logger.info(`Waiting until ${this.no_checkpoint_before_lsn} before creating checkpoint, currently at ${lsn}. Persisted op: ${this.persisted_op}`);
525
- this.lastWaitingLogThottled = Date.now();
526
- }
527
- // Edge case: During initial replication, we have a no_checkpoint_before_lsn set,
528
- // and don't actually commit the snapshot.
529
- // The first commit can happen from an implicit keepalive message.
530
- // That needs the persisted_op to get an accurate checkpoint, so
531
- // we persist that in keepalive_op.
532
- await this.db.sync_rules.updateOne({
533
- _id: this.group_id
534
- }, {
535
- $set: {
536
- keepalive_op: this.persisted_op == null ? null : String(this.persisted_op)
537
- }
538
- }, { session: this.session });
539
- await this.db.notifyCheckpoint();
540
- // Cannot create a checkpoint yet - return false
541
- return false;
542
- }
543
- if (!createEmptyCheckpoints && this.persisted_op == null) {
544
- // Nothing to commit - also return true
545
- await this.autoActivate(lsn);
546
- return true;
547
- }
548
510
  const now = new Date();
549
- const update = {
550
- last_checkpoint_lsn: lsn,
551
- last_checkpoint_ts: now,
552
- last_keepalive_ts: now,
553
- snapshot_done: true,
554
- last_fatal_error: null,
555
- last_fatal_error_ts: null,
556
- keepalive_op: null
557
- };
558
- if (this.persisted_op != null) {
559
- update.last_checkpoint = this.persisted_op;
560
- }
561
511
  // Mark relevant write checkpoints as "processed".
562
512
  // This makes it easier to identify write checkpoints that are "valid" in order.
563
513
  await this.db.write_checkpoints.updateMany({
@@ -570,17 +520,134 @@ export class MongoBucketBatch extends BaseObserver {
570
520
  }, {
571
521
  session: this.session
572
522
  });
573
- await this.db.sync_rules.updateOne({
574
- _id: this.group_id
575
- }, {
576
- $set: update,
577
- $unset: { snapshot_lsn: 1 }
578
- }, { session: this.session });
579
- await this.autoActivate(lsn);
580
- await this.db.notifyCheckpoint();
581
- this.persisted_op = null;
582
- this.last_checkpoint_lsn = lsn;
583
- return true;
523
+ const can_checkpoint = {
524
+ $and: [
525
+ { $eq: ['$snapshot_done', true] },
526
+ {
527
+ $or: [{ $eq: ['$last_checkpoint_lsn', null] }, { $lte: ['$last_checkpoint_lsn', { $literal: lsn }] }]
528
+ },
529
+ {
530
+ $or: [{ $eq: ['$no_checkpoint_before', null] }, { $lte: ['$no_checkpoint_before', { $literal: lsn }] }]
531
+ }
532
+ ]
533
+ };
534
+ const new_keepalive_op = {
535
+ $cond: [
536
+ can_checkpoint,
537
+ { $literal: null },
538
+ {
539
+ $toString: {
540
+ $max: [{ $toLong: '$keepalive_op' }, { $literal: this.persisted_op }, 0n]
541
+ }
542
+ }
543
+ ]
544
+ };
545
+ const new_last_checkpoint = {
546
+ $cond: [
547
+ can_checkpoint,
548
+ {
549
+ $max: ['$last_checkpoint', { $literal: this.persisted_op }, { $toLong: '$keepalive_op' }, 0n]
550
+ },
551
+ '$last_checkpoint'
552
+ ]
553
+ };
554
+ // For this query, we need to handle multiple cases, depending on the state:
555
+ // 1. Normal commit - advance last_checkpoint to this.persisted_op.
556
+ // 2. Commit delayed by no_checkpoint_before due to snapshot. In this case we only advance keepalive_op.
557
+ // 3. Commit with no new data - here may may set last_checkpoint = keepalive_op, if a delayed commit is relevant.
558
+ // We want to do as much as possible in a single atomic database operation, which makes this somewhat complex.
559
+ let preUpdateDocument = await this.db.sync_rules.findOneAndUpdate({ _id: this.group_id }, [
560
+ {
561
+ $set: {
562
+ _can_checkpoint: can_checkpoint,
563
+ _not_empty: createEmptyCheckpoints
564
+ ? true
565
+ : {
566
+ $or: [
567
+ { $literal: createEmptyCheckpoints },
568
+ { $ne: ['$keepalive_op', new_keepalive_op] },
569
+ { $ne: ['$last_checkpoint', new_last_checkpoint] }
570
+ ]
571
+ }
572
+ }
573
+ },
574
+ {
575
+ $set: {
576
+ last_checkpoint_lsn: {
577
+ $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: lsn }, '$last_checkpoint_lsn']
578
+ },
579
+ last_checkpoint_ts: {
580
+ $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: now }, '$last_checkpoint_ts']
581
+ },
582
+ last_keepalive_ts: { $literal: now },
583
+ last_fatal_error: { $literal: null },
584
+ last_fatal_error_ts: { $literal: null },
585
+ keepalive_op: new_keepalive_op,
586
+ last_checkpoint: new_last_checkpoint,
587
+ // Unset snapshot_lsn on checkpoint
588
+ snapshot_lsn: {
589
+ $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: null }, '$snapshot_lsn']
590
+ }
591
+ }
592
+ },
593
+ {
594
+ $unset: ['_can_checkpoint', '_not_empty']
595
+ }
596
+ ], {
597
+ session: this.session,
598
+ // We return the before document, so that we can check the previous state to determine if a checkpoint was actually created or if we were blocked by snapshot/no_checkpoint_before.
599
+ returnDocument: 'before',
600
+ projection: {
601
+ snapshot_done: 1,
602
+ last_checkpoint_lsn: 1,
603
+ no_checkpoint_before: 1,
604
+ keepalive_op: 1,
605
+ last_checkpoint: 1
606
+ }
607
+ });
608
+ if (preUpdateDocument == null) {
609
+ throw new ReplicationAssertionError('Failed to update checkpoint - no matching sync_rules document for _id: ' + this.group_id);
610
+ }
611
+ // This re-implements the same logic as in the pipeline, to determine what was actually updated.
612
+ // Unfortunately we cannot return these from the pipeline directly, so we need to re-implement the logic.
613
+ const canCheckpoint = preUpdateDocument.snapshot_done === true &&
614
+ (preUpdateDocument.last_checkpoint_lsn == null || preUpdateDocument.last_checkpoint_lsn <= lsn) &&
615
+ (preUpdateDocument.no_checkpoint_before == null || preUpdateDocument.no_checkpoint_before <= lsn);
616
+ const keepaliveOp = preUpdateDocument.keepalive_op == null ? null : BigInt(preUpdateDocument.keepalive_op);
617
+ const maxKeepalive = [keepaliveOp ?? 0n, this.persisted_op ?? 0n, 0n].reduce((a, b) => (a > b ? a : b));
618
+ const newKeepaliveOp = canCheckpoint ? null : maxKeepalive.toString();
619
+ const newLastCheckpoint = canCheckpoint
620
+ ? [preUpdateDocument.last_checkpoint ?? 0n, this.persisted_op ?? 0n, keepaliveOp ?? 0n, 0n].reduce((a, b) => a > b ? a : b)
621
+ : preUpdateDocument.last_checkpoint;
622
+ const notEmpty = createEmptyCheckpoints ||
623
+ preUpdateDocument.keepalive_op !== newKeepaliveOp ||
624
+ preUpdateDocument.last_checkpoint !== newLastCheckpoint;
625
+ const checkpointCreated = canCheckpoint && notEmpty;
626
+ const checkpointBlocked = !canCheckpoint;
627
+ if (checkpointBlocked) {
628
+ // Failed on snapshot_done or no_checkpoint_before.
629
+ if (Date.now() - this.lastWaitingLogThottled > 5_000) {
630
+ this.logger.info(`Waiting before creating checkpoint, currently at ${lsn} / ${preUpdateDocument.keepalive_op}. Current state: ${JSON.stringify({
631
+ snapshot_done: preUpdateDocument.snapshot_done,
632
+ last_checkpoint_lsn: preUpdateDocument.last_checkpoint_lsn,
633
+ no_checkpoint_before: preUpdateDocument.no_checkpoint_before
634
+ })}`);
635
+ this.lastWaitingLogThottled = Date.now();
636
+ }
637
+ }
638
+ else {
639
+ if (checkpointCreated) {
640
+ this.logger.debug(`Created checkpoint at ${lsn} / ${newLastCheckpoint}`);
641
+ }
642
+ await this.autoActivate(lsn);
643
+ await this.db.notifyCheckpoint();
644
+ this.persisted_op = null;
645
+ this.last_checkpoint_lsn = lsn;
646
+ if (newLastCheckpoint != null) {
647
+ await this.sourceRecordStore.postCommitCleanup(newLastCheckpoint, this.logger);
648
+ }
649
+ }
650
+ return { checkpointBlocked, checkpointCreated };
584
651
  }
585
652
  /**
586
653
  * Switch from processing -> active if relevant.
@@ -597,7 +664,7 @@ export class MongoBucketBatch extends BaseObserver {
597
664
  let activated = false;
598
665
  await session.withTransaction(async () => {
599
666
  const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
600
- if (doc && doc.state == 'PROCESSING') {
667
+ if (doc && doc.state == SyncRuleState.PROCESSING && doc.snapshot_done && doc.last_checkpoint != null) {
601
668
  await this.db.sync_rules.updateOne({
602
669
  _id: this.group_id
603
670
  }, {
@@ -615,53 +682,18 @@ export class MongoBucketBatch extends BaseObserver {
615
682
  }, { session });
616
683
  activated = true;
617
684
  }
685
+ else if (doc?.state != SyncRuleState.PROCESSING) {
686
+ this.needsActivation = false;
687
+ }
618
688
  });
619
689
  if (activated) {
620
- this.logger.info(`Activated new sync rules at ${lsn}`);
690
+ this.logger.info(`Activated new replication stream at ${lsn}`);
621
691
  await this.db.notifyCheckpoint();
692
+ this.needsActivation = false;
622
693
  }
623
- this.needsActivation = false;
624
694
  }
625
695
  async keepalive(lsn) {
626
- if (this.last_checkpoint_lsn != null && lsn < this.last_checkpoint_lsn) {
627
- // No-op
628
- return false;
629
- }
630
- if (lsn < this.no_checkpoint_before_lsn) {
631
- return false;
632
- }
633
- if (this.persisted_op != null) {
634
- // The commit may have been skipped due to "no_checkpoint_before_lsn".
635
- // Apply it now if relevant
636
- this.logger.info(`Commit due to keepalive at ${lsn} / ${this.persisted_op}`);
637
- return await this.commit(lsn);
638
- }
639
- await this.db.write_checkpoints.updateMany({
640
- processed_at_lsn: null,
641
- 'lsns.1': { $lte: lsn }
642
- }, {
643
- $set: {
644
- processed_at_lsn: lsn
645
- }
646
- }, {
647
- session: this.session
648
- });
649
- await this.db.sync_rules.updateOne({
650
- _id: this.group_id
651
- }, {
652
- $set: {
653
- last_checkpoint_lsn: lsn,
654
- snapshot_done: true,
655
- last_fatal_error: null,
656
- last_fatal_error_ts: null,
657
- last_keepalive_ts: new Date()
658
- },
659
- $unset: { snapshot_lsn: 1 }
660
- }, { session: this.session });
661
- await this.autoActivate(lsn);
662
- await this.db.notifyCheckpoint();
663
- this.last_checkpoint_lsn = lsn;
664
- return true;
696
+ return await this.commit(lsn, { createEmptyCheckpoints: true });
665
697
  }
666
698
  async setResumeLsn(lsn) {
667
699
  const update = {
@@ -712,9 +744,10 @@ export class MongoBucketBatch extends BaseObserver {
712
744
  const result = await this.flush();
713
745
  await this.withTransaction(async () => {
714
746
  for (let table of sourceTables) {
715
- await this.db.source_tables.deleteOne({ _id: table.id });
747
+ await this.db.commonSourceTables(this.group_id).deleteOne({ _id: mongoTableId(table.id) });
716
748
  }
717
749
  });
750
+ await this.cleanupDroppedSourceTables(sourceTables);
718
751
  return result;
719
752
  }
720
753
  async truncate(sourceTables) {
@@ -741,38 +774,31 @@ export class MongoBucketBatch extends BaseObserver {
741
774
  let lastBatchCount = BATCH_LIMIT;
742
775
  while (lastBatchCount == BATCH_LIMIT) {
743
776
  await this.withReplicationTransaction(`Truncate ${sourceTable.qualifiedName}`, async (session, opSeq) => {
744
- const current_data_filter = {
745
- _id: idPrefixFilter({ g: this.group_id, t: sourceTable.id }, ['k'])
746
- };
747
- const cursor = this.db.current_data.find(current_data_filter, {
748
- projection: {
749
- _id: 1,
750
- buckets: 1,
751
- lookups: 1
752
- },
753
- limit: BATCH_LIMIT,
754
- session: session
755
- });
756
- const batch = await cursor.toArray();
757
- const persistedBatch = new PersistedBatch(this.group_id, 0, { logger: this.logger });
777
+ using evalSpan = this.tracer.span('evaluate');
778
+ const sourceTableId = mongoTableId(sourceTable.id);
779
+ const batch = await this.sourceRecordStore.loadTruncateBatch(session, sourceTableId, BATCH_LIMIT);
780
+ const persistedBatch = this.createPersistedBatch(0);
758
781
  for (let value of batch) {
759
782
  persistedBatch.saveBucketData({
760
783
  op_seq: opSeq,
761
784
  before_buckets: value.buckets,
762
785
  evaluated: [],
763
786
  table: sourceTable,
764
- sourceKey: value._id.k
787
+ sourceKey: value.replicaId
765
788
  });
766
789
  persistedBatch.saveParameterData({
767
790
  op_seq: opSeq,
768
791
  existing_lookups: value.lookups,
769
792
  evaluated: [],
770
793
  sourceTable: sourceTable,
771
- sourceKey: value._id.k
794
+ sourceKey: value.replicaId
772
795
  });
773
- persistedBatch.deleteCurrentData(value._id);
796
+ // Since this is not from streaming replication, we can do a hard delete
797
+ persistedBatch.hardDeleteCurrentData(sourceTableId, value.replicaId);
774
798
  }
775
- await persistedBatch.flush(this.db, session);
799
+ evalSpan.end();
800
+ using _ = this.tracer.span('storage', 'persist_flush');
801
+ await persistedBatch.flush(session);
776
802
  lastBatchCount = batch.length;
777
803
  last_op = opSeq.last();
778
804
  });
@@ -788,7 +814,7 @@ export class MongoBucketBatch extends BaseObserver {
788
814
  };
789
815
  copy.snapshotStatus = snapshotStatus;
790
816
  await this.withTransaction(async () => {
791
- await this.db.source_tables.updateOne({ _id: table.id }, {
817
+ await this.db.commonSourceTables(this.group_id).updateOne({ _id: mongoTableId(table.id) }, {
792
818
  $set: {
793
819
  snapshot_status: {
794
820
  last_key: snapshotStatus.lastKey == null ? null : new bson.Binary(snapshotStatus.lastKey),
@@ -800,11 +826,33 @@ export class MongoBucketBatch extends BaseObserver {
800
826
  });
801
827
  return copy;
802
828
  }
803
- async markSnapshotDone(tables, no_checkpoint_before_lsn) {
829
+ async markAllSnapshotDone(no_checkpoint_before_lsn) {
830
+ await this.db.sync_rules.updateOne({
831
+ _id: this.group_id
832
+ }, {
833
+ $set: {
834
+ snapshot_done: true,
835
+ last_keepalive_ts: new Date()
836
+ },
837
+ $max: {
838
+ no_checkpoint_before: no_checkpoint_before_lsn
839
+ }
840
+ }, { session: this.session });
841
+ }
842
+ async markTableSnapshotRequired(table) {
843
+ await this.db.sync_rules.updateOne({
844
+ _id: this.group_id
845
+ }, {
846
+ $set: {
847
+ snapshot_done: false
848
+ }
849
+ }, { session: this.session });
850
+ }
851
+ async markTableSnapshotDone(tables, no_checkpoint_before_lsn) {
804
852
  const session = this.session;
805
- const ids = tables.map((table) => table.id);
853
+ const ids = tables.map((table) => mongoTableId(table.id));
806
854
  await this.withTransaction(async () => {
807
- await this.db.source_tables.updateMany({ _id: { $in: ids } }, {
855
+ await this.db.commonSourceTables(this.group_id).updateMany({ _id: { $in: ids } }, {
808
856
  $set: {
809
857
  snapshot_done: true
810
858
  },
@@ -812,14 +860,15 @@ export class MongoBucketBatch extends BaseObserver {
812
860
  snapshot_status: 1
813
861
  }
814
862
  }, { session });
815
- if (no_checkpoint_before_lsn > this.no_checkpoint_before_lsn) {
816
- this.no_checkpoint_before_lsn = no_checkpoint_before_lsn;
863
+ if (no_checkpoint_before_lsn != null) {
817
864
  await this.db.sync_rules.updateOne({
818
865
  _id: this.group_id
819
866
  }, {
820
867
  $set: {
821
- no_checkpoint_before: no_checkpoint_before_lsn,
822
868
  last_keepalive_ts: new Date()
869
+ },
870
+ $max: {
871
+ no_checkpoint_before: no_checkpoint_before_lsn
823
872
  }
824
873
  }, { session: this.session });
825
874
  }
@@ -852,7 +901,4 @@ export class MongoBucketBatch extends BaseObserver {
852
901
  return this.sync_rules.eventDescriptors.filter((evt) => [...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table)));
853
902
  }
854
903
  }
855
- export function currentBucketKey(b) {
856
- return `${b.bucket}/${b.table}/${b.id}`;
857
- }
858
904
  //# sourceMappingURL=MongoBucketBatch.js.map