@powersync/service-module-mongodb-storage 0.15.4 → 0.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (202) hide show
  1. package/CHANGELOG.md +69 -0
  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 +2 -2
  5. package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
  6. package/dist/storage/MongoBucketStorage.d.ts +8 -6
  7. package/dist/storage/MongoBucketStorage.js +153 -66
  8. package/dist/storage/MongoBucketStorage.js.map +1 -1
  9. package/dist/storage/implementation/BucketDefinitionMapping.d.ts +15 -0
  10. package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
  11. package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
  12. package/dist/storage/implementation/CheckpointState.d.ts +20 -0
  13. package/dist/storage/implementation/CheckpointState.js +31 -0
  14. package/dist/storage/implementation/CheckpointState.js.map +1 -0
  15. package/dist/storage/implementation/MongoBucketBatch.d.ts +48 -35
  16. package/dist/storage/implementation/MongoBucketBatch.js +118 -379
  17. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  18. package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
  19. package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
  20. package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
  21. package/dist/storage/implementation/MongoChecksums.d.ts +29 -17
  22. package/dist/storage/implementation/MongoChecksums.js +13 -72
  23. package/dist/storage/implementation/MongoChecksums.js.map +1 -1
  24. package/dist/storage/implementation/MongoCompactor.d.ts +98 -58
  25. package/dist/storage/implementation/MongoCompactor.js +229 -296
  26. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  27. package/dist/storage/implementation/MongoParameterCompactor.d.ts +11 -6
  28. package/dist/storage/implementation/MongoParameterCompactor.js +11 -8
  29. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
  30. package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
  31. package/dist/storage/implementation/MongoPersistedSyncRules.js +67 -0
  32. package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
  33. package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +22 -5
  34. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +56 -13
  35. package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
  36. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +61 -32
  37. package/dist/storage/implementation/MongoSyncBucketStorage.js +85 -523
  38. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  39. package/dist/storage/implementation/MongoSyncRulesLock.d.ts +10 -4
  40. package/dist/storage/implementation/MongoSyncRulesLock.js +19 -13
  41. package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
  42. package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
  43. package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
  44. package/dist/storage/implementation/OperationBatch.js +1 -1
  45. package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
  46. package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
  47. package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
  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 +35 -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 +41 -36
  70. package/dist/storage/implementation/db.js +77 -99
  71. package/dist/storage/implementation/db.js.map +1 -1
  72. package/dist/storage/implementation/models.d.ts +79 -66
  73. package/dist/storage/implementation/models.js +20 -1
  74. package/dist/storage/implementation/models.js.map +1 -1
  75. package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +27 -0
  76. package/dist/storage/implementation/v1/MongoBucketBatchV1.js +407 -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 +50 -0
  88. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +354 -0
  89. package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
  90. package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +25 -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 +45 -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 +30 -0
  106. package/dist/storage/implementation/v3/MongoBucketBatchV3.js +463 -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 +4 -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 +63 -0
  121. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +508 -0
  122. package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
  123. package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +28 -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 +22 -0
  133. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +74 -0
  134. package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
  135. package/dist/storage/implementation/v3/models.d.ts +101 -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 +6 -3
  139. package/dist/storage/storage-index.js +6 -3
  140. package/dist/storage/storage-index.js.map +1 -1
  141. package/dist/utils/util.d.ts +10 -3
  142. package/dist/utils/util.js +24 -3
  143. package/dist/utils/util.js.map +1 -1
  144. package/package.json +9 -9
  145. package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
  146. package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
  147. package/src/storage/MongoBucketStorage.ts +254 -99
  148. package/src/storage/implementation/BucketDefinitionMapping.ts +75 -0
  149. package/src/storage/implementation/CheckpointState.ts +59 -0
  150. package/src/storage/implementation/MongoBucketBatch.ts +182 -490
  151. package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
  152. package/src/storage/implementation/MongoChecksums.ts +53 -75
  153. package/src/storage/implementation/MongoCompactor.ts +374 -404
  154. package/src/storage/implementation/MongoParameterCompactor.ts +37 -24
  155. package/src/storage/implementation/MongoPersistedSyncRules.ts +82 -0
  156. package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +78 -16
  157. package/src/storage/implementation/MongoSyncBucketStorage.ts +179 -628
  158. package/src/storage/implementation/MongoSyncRulesLock.ts +20 -16
  159. package/src/storage/implementation/MongoWriteCheckpointAPI.ts +3 -1
  160. package/src/storage/implementation/OperationBatch.ts +1 -1
  161. package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
  162. package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
  163. package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
  164. package/src/storage/implementation/common/PersistedBatch.ts +364 -0
  165. package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
  166. package/src/storage/implementation/common/SourceRecordStore.ts +48 -0
  167. package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
  168. package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
  169. package/src/storage/implementation/db.ts +110 -131
  170. package/src/storage/implementation/models.ts +102 -79
  171. package/src/storage/implementation/v1/MongoBucketBatchV1.ts +509 -0
  172. package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
  173. package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
  174. package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
  175. package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +543 -0
  176. package/src/storage/implementation/v1/PersistedBatchV1.ts +229 -0
  177. package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
  178. package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
  179. package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
  180. package/src/storage/implementation/v1/models.ts +99 -0
  181. package/src/storage/implementation/v3/MongoBucketBatchV3.ts +607 -0
  182. package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
  183. package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
  184. package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
  185. package/src/storage/implementation/v3/MongoParameterLookupV3.ts +11 -0
  186. package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +678 -0
  187. package/src/storage/implementation/v3/PersistedBatchV3.ts +317 -0
  188. package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
  189. package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
  190. package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +117 -0
  191. package/src/storage/implementation/v3/models.ts +164 -0
  192. package/src/storage/storage-index.ts +6 -3
  193. package/src/utils/util.ts +34 -5
  194. package/test/src/storage_compacting.test.ts +57 -29
  195. package/test/src/storage_sync.test.ts +767 -5
  196. package/test/src/storeCurrentData.test.ts +211 -0
  197. package/test/tsconfig.json +0 -1
  198. package/tsconfig.tsbuildinfo +1 -1
  199. package/dist/storage/implementation/PersistedBatch.d.ts +0 -71
  200. package/dist/storage/implementation/PersistedBatch.js +0 -354
  201. package/dist/storage/implementation/PersistedBatch.js.map +0 -1
  202. package/src/storage/implementation/PersistedBatch.ts +0 -432
@@ -1,25 +1,21 @@
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, SyncRuleState, 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, utils } from '@powersync/service-core';
5
5
  import * as timers from 'node:timers/promises';
6
- import { idPrefixFilter, mongoTableId } 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.
18
14
  //
19
15
  // In the future, we can investigate allowing multiple replication streams operating independently.
20
16
  const replicationMutex = new utils.Mutex();
21
- export const EMPTY_DATA = new bson.Binary(bson.serialize({}));
22
17
  export class MongoBucketBatch extends BaseObserver {
18
+ options;
23
19
  logger;
24
20
  client;
25
21
  db;
@@ -27,12 +23,24 @@ export class MongoBucketBatch extends BaseObserver {
27
23
  sync_rules;
28
24
  group_id;
29
25
  slot_name;
26
+ /**
27
+ * Source-level setting for whether raw row data should be stored in current_data.
28
+ *
29
+ * Some sources always send complete rows (MongoDB, MySQL with binlog_row_image=full),
30
+ * in which case this is false for the whole batch. For sources where it depends on the
31
+ * table (Postgres REPLICA IDENTITY), this is true and the decision is refined per-table
32
+ * via SourceTable.storeCurrentData. The effective per-record value is the conjunction of
33
+ * the two.
34
+ */
30
35
  storeCurrentData;
31
36
  skipExistingRows;
37
+ mapping;
32
38
  batch = null;
33
39
  write_checkpoint_batch = [];
34
40
  markRecordUnavailable;
41
+ hooks;
35
42
  clearedError = false;
43
+ tracer;
36
44
  /**
37
45
  * Last LSN received associated with a checkpoint.
38
46
  *
@@ -57,10 +65,10 @@ export class MongoBucketBatch extends BaseObserver {
57
65
  * This is set when creating the batch, but may not be updated afterwards.
58
66
  */
59
67
  resumeFromLsn = null;
60
- needsActivation = true;
61
68
  constructor(options) {
62
69
  super();
63
- this.logger = options.logger ?? defaultLogger;
70
+ this.logger = options.logger;
71
+ this.options = options;
64
72
  this.client = options.db.client;
65
73
  this.db = options.db;
66
74
  this.group_id = options.groupId;
@@ -70,10 +78,13 @@ export class MongoBucketBatch extends BaseObserver {
70
78
  this.slot_name = options.slotName;
71
79
  this.sync_rules = options.syncRules;
72
80
  this.storeCurrentData = options.storeCurrentData;
81
+ this.mapping = options.mapping;
73
82
  this.skipExistingRows = options.skipExistingRows;
74
83
  this.markRecordUnavailable = options.markRecordUnavailable;
84
+ this.hooks = options.hooks;
75
85
  this.batch = new OperationBatch();
76
86
  this.persisted_op = options.keepaliveOp ?? null;
87
+ this.tracer = options.tracer ?? new PerformanceTracer('MongoDB storage');
77
88
  }
78
89
  addCustomWriteCheckpoint(checkpoint) {
79
90
  this.write_checkpoint_batch.push({
@@ -100,6 +111,8 @@ export class MongoBucketBatch extends BaseObserver {
100
111
  const batch = this.batch;
101
112
  let last_op = null;
102
113
  let resumeBatch = null;
114
+ using _ = this.tracer.span('storage', 'flush');
115
+ await this.hooks?.beforeBatchFlush?.(this);
103
116
  await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
104
117
  if (batch != null) {
105
118
  resumeBatch = await this.replicateBatch(session, batch, opSeq, options);
@@ -118,12 +131,17 @@ export class MongoBucketBatch extends BaseObserver {
118
131
  }
119
132
  this.persisted_op = last_op;
120
133
  this.last_flushed_op = last_op;
134
+ await this.hooks?.afterBatchFlush?.(this);
121
135
  return { flushed_op: last_op };
122
136
  }
123
137
  async replicateBatch(session, batch, op_seq, options) {
124
138
  let sizes = undefined;
125
- if (this.storeCurrentData && !this.skipExistingRows) {
126
- // We skip this step if we don't store current_data, since the sizes will
139
+ using _ = this.tracer.span('storage', 'replicate_batch');
140
+ // Only look up current_data sizes if the batch stores current_data and at least one
141
+ // table in it does too (per-table can disable it, e.g. Postgres REPLICA IDENTITY FULL).
142
+ const anyTableStoresCurrentData = this.storeCurrentData && batch.batch.some((r) => r.record.sourceTable.storeCurrentData);
143
+ if (anyTableStoresCurrentData && !this.skipExistingRows) {
144
+ // We skip this step if no tables store current_data, since the sizes will
127
145
  // always be small in that case.
128
146
  // With skipExistingRows, we don't load the full documents into memory,
129
147
  // so we can also skip the size lookup step.
@@ -134,27 +152,15 @@ export class MongoBucketBatch extends BaseObserver {
134
152
  // (automatically limited to 48MB(?) per batch by MongoDB). The issue is that it changes
135
153
  // the order of processing, which then becomes really tricky to manage.
136
154
  // This now takes 2+ queries, but doesn't have any issues with order of operations.
137
- const sizeLookups = batch.batch.map((r) => {
138
- return { g: this.group_id, t: mongoTableId(r.record.sourceTable.id), k: r.beforeId };
139
- });
140
- sizes = new Map();
141
- const sizeCursor = this.db.common_current_data.aggregate([
142
- {
143
- $match: {
144
- _id: { $in: sizeLookups }
145
- }
146
- },
147
- {
148
- $project: {
149
- _id: 1,
150
- size: { $bsonSize: '$$ROOT' }
151
- }
152
- }
153
- ], { session });
154
- for await (let doc of sizeCursor.stream()) {
155
- const key = cacheKey(doc._id.t, doc._id.k);
156
- sizes.set(key, doc.size);
157
- }
155
+ // Within this branch this.storeCurrentData is true, so the per-table flag is the
156
+ // effective value - only look up sizes for tables that actually store current_data.
157
+ const sizeLookups = batch.batch
158
+ .filter((r) => r.record.sourceTable.storeCurrentData)
159
+ .map((r) => ({
160
+ sourceTableId: mongoTableId(r.record.sourceTable.id),
161
+ replicaId: r.beforeId
162
+ }));
163
+ sizes = await this.sourceRecordStore.loadSizes(session, sizeLookups);
158
164
  }
159
165
  // If set, we need to start a new transaction with this batch.
160
166
  let resumeBatch = null;
@@ -169,40 +175,38 @@ export class MongoBucketBatch extends BaseObserver {
169
175
  }
170
176
  continue;
171
177
  }
172
- const lookups = b.map((r) => {
173
- return { g: this.group_id, t: mongoTableId(r.record.sourceTable.id), k: r.beforeId };
174
- });
175
- let current_data_lookup = new Map();
176
- // With skipExistingRows, we only need to know whether or not the row exists.
177
- const projection = this.skipExistingRows ? { _id: 1 } : undefined;
178
- const cursor = this.db.common_current_data.find({
179
- _id: { $in: lookups }
180
- }, { session, projection });
181
- for await (let doc of cursor.stream()) {
182
- current_data_lookup.set(cacheKey(doc._id.t, doc._id.k), doc);
183
- }
184
- let persistedBatch = new PersistedBatch(this.db, this.group_id, transactionSize, {
185
- logger: this.logger
186
- });
178
+ using lookupSpan = this.tracer.span('storage', 'lookup');
179
+ const lookups = b.map((r) => ({
180
+ sourceTableId: mongoTableId(r.record.sourceTable.id),
181
+ replicaId: r.beforeId
182
+ }));
183
+ let sourceRecordLookup = await this.sourceRecordStore.loadDocuments(session, lookups, this.skipExistingRows);
184
+ lookupSpan.end();
185
+ let persistedBatch = this.createPersistedBatch(transactionSize);
186
+ // The current code structure makes it tricky to cleanly split this span from the one
187
+ // where fluhsing. So we manually end and re-create this span whenever we flush.
188
+ let evalSpan = this.tracer.span('evaluate');
187
189
  for (let op of b) {
188
190
  if (resumeBatch) {
189
191
  resumeBatch.push(op);
190
192
  continue;
191
193
  }
192
- const currentData = current_data_lookup.get(op.internalBeforeKey) ?? null;
193
- if (currentData != null) {
194
+ const sourceRecord = sourceRecordLookup.get(op.internalBeforeKey) ?? null;
195
+ if (sourceRecord != null) {
194
196
  // If it will be used again later, it will be set again using nextData below
195
- current_data_lookup.delete(op.internalBeforeKey);
197
+ sourceRecordLookup.delete(op.internalBeforeKey);
196
198
  }
197
- const nextData = this.saveOperation(persistedBatch, op, currentData, op_seq);
199
+ const nextData = this.saveOperation(persistedBatch, op, sourceRecord, op_seq);
198
200
  if (nextData != null) {
199
201
  // Update our current_data and size cache
200
- current_data_lookup.set(op.internalAfterKey, nextData);
201
- sizes?.set(op.internalAfterKey, nextData.data.length());
202
+ sourceRecordLookup.set(op.internalAfterKey, nextData);
203
+ sizes?.set(op.internalAfterKey, nextData.data?.length() ?? 0);
202
204
  }
203
205
  if (persistedBatch.shouldFlushTransaction()) {
206
+ evalSpan.end();
204
207
  // Transaction is getting big.
205
208
  // Flush, and resume in a new transaction.
209
+ using persistSpan = this.tracer.span('storage', 'persist_flush');
206
210
  const { flushedAny } = await persistedBatch.flush(this.session, options);
207
211
  didFlush ||= flushedAny;
208
212
  persistedBatch = null;
@@ -210,33 +214,41 @@ export class MongoBucketBatch extends BaseObserver {
210
214
  // we're stopping in the middle of a batch.
211
215
  // We create a new batch, and push any remaining operations to it.
212
216
  resumeBatch = new OperationBatch();
217
+ persistSpan.end();
218
+ evalSpan = this.tracer.span('evaluate');
213
219
  }
214
220
  }
221
+ evalSpan.end();
215
222
  if (persistedBatch) {
216
223
  transactionSize = persistedBatch.currentSize;
224
+ using _ = this.tracer.span('storage', 'persist_flush');
217
225
  const { flushedAny } = await persistedBatch.flush(this.session, options);
218
226
  didFlush ||= flushedAny;
219
227
  }
220
228
  }
221
229
  if (didFlush) {
230
+ using _ = this.tracer.span('storage', 'clear_error');
222
231
  await this.clearError();
223
232
  }
224
233
  return resumeBatch?.hasData() ? resumeBatch : null;
225
234
  }
226
- saveOperation(batch, operation, current_data, opSeq) {
235
+ saveOperation(batch, operation, sourceRecord, opSeq) {
227
236
  const record = operation.record;
228
237
  const beforeId = operation.beforeId;
229
238
  const afterId = operation.afterId;
230
239
  let after = record.after;
231
240
  const sourceTable = record.sourceTable;
241
+ // Effective per-record flag: store current_data only if both the batch (source-level,
242
+ // e.g. Postgres) and the table (e.g. non-FULL replica identity) require it.
243
+ const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
232
244
  let existing_buckets = [];
233
245
  let new_buckets = [];
234
246
  let existing_lookups = [];
235
247
  let new_lookups = [];
236
- const before_key = { g: this.group_id, t: mongoTableId(record.sourceTable.id), k: beforeId };
248
+ const sourceTableId = mongoTableId(record.sourceTable.id);
237
249
  if (this.skipExistingRows) {
238
250
  if (record.tag == SaveOperationTag.INSERT) {
239
- if (current_data != null) {
251
+ if (sourceRecord != null) {
240
252
  // Initial replication, and we already have the record.
241
253
  // This may be a different version of the record, but streaming replication
242
254
  // will take care of that.
@@ -249,12 +261,12 @@ export class MongoBucketBatch extends BaseObserver {
249
261
  }
250
262
  }
251
263
  if (record.tag == SaveOperationTag.UPDATE) {
252
- const result = current_data;
264
+ const result = sourceRecord;
253
265
  if (result == null) {
254
266
  // Not an error if we re-apply a transaction
255
267
  existing_buckets = [];
256
268
  existing_lookups = [];
257
- if (!isCompleteRow(this.storeCurrentData, after)) {
269
+ if (!isCompleteRow(storeCurrentData, after)) {
258
270
  if (this.markRecordUnavailable != null) {
259
271
  // This will trigger a "resnapshot" of the record.
260
272
  // This is not relevant if storeCurrentData is false, since we'll get the full row
@@ -270,19 +282,21 @@ export class MongoBucketBatch extends BaseObserver {
270
282
  else {
271
283
  existing_buckets = result.buckets;
272
284
  existing_lookups = result.lookups;
273
- if (this.storeCurrentData) {
285
+ if (storeCurrentData && result.data != null) {
274
286
  const data = deserializeBson(result.data.buffer);
275
287
  after = storage.mergeToast(after, data);
276
288
  }
277
289
  }
278
290
  }
279
291
  else if (record.tag == SaveOperationTag.DELETE) {
280
- const result = current_data;
292
+ const result = sourceRecord;
281
293
  if (result == null) {
282
294
  // Not an error if we re-apply a transaction
283
295
  existing_buckets = [];
284
296
  existing_lookups = [];
285
- // Log to help with debugging if there was a consistency issue
297
+ // Log to help with debugging if there was a consistency issue.
298
+ // Gate on the batch-level flag: FULL tables (per-record flag false) still get a
299
+ // current_data entry, so a missing record on DELETE is meaningful for them too.
286
300
  if (this.storeCurrentData && this.markRecordUnavailable == null) {
287
301
  this.logger.warn(`Cannot find previous record for delete on ${record.sourceTable.qualifiedName}: ${beforeId} / ${record.before?.id}`);
288
302
  }
@@ -292,9 +306,9 @@ export class MongoBucketBatch extends BaseObserver {
292
306
  existing_lookups = result.lookups;
293
307
  }
294
308
  }
295
- let afterData;
296
- if (afterId != null && !this.storeCurrentData) {
297
- afterData = EMPTY_DATA;
309
+ let afterData = null;
310
+ if (afterId != null && !storeCurrentData) {
311
+ afterData = null;
298
312
  }
299
313
  else if (afterId != null) {
300
314
  try {
@@ -353,13 +367,15 @@ export class MongoBucketBatch extends BaseObserver {
353
367
  // However, it will be valid by the end of the transaction.
354
368
  //
355
369
  // In this case, we don't save the op, but we do save the current data.
356
- if (afterId && after && utils.isCompleteRow(this.storeCurrentData, after)) {
370
+ if (afterId && after && utils.isCompleteRow(storeCurrentData, after)) {
357
371
  // Insert or update
358
372
  if (sourceTable.syncData) {
359
- const { results: evaluated, errors: syncErrors } = this.sync_rules.evaluateRowWithErrors({
373
+ const { results, errors: syncErrors } = this.sync_rules.evaluateRowWithErrors({
360
374
  record: after,
361
- sourceTable
375
+ sourceTable: sourceTable.ref,
376
+ bucketDataSources: sourceTable.bucketDataSources
362
377
  });
378
+ const evaluated = results;
363
379
  for (let error of syncErrors) {
364
380
  container.reporter.captureMessage(`Failed to evaluate data query on ${record.sourceTable.qualifiedName}.${record.after?.id}: ${error.error}`, {
365
381
  level: errors.ErrorSeverity.WARNING,
@@ -378,17 +394,11 @@ export class MongoBucketBatch extends BaseObserver {
378
394
  table: sourceTable,
379
395
  before_buckets: existing_buckets
380
396
  });
381
- new_buckets = evaluated.map((e) => {
382
- return {
383
- bucket: e.bucket,
384
- table: e.table,
385
- id: e.id
386
- };
387
- });
397
+ new_buckets = this.sourceRecordStore.mapEvaluatedBuckets(evaluated);
388
398
  }
389
399
  if (sourceTable.syncParameters) {
390
400
  // Parameters
391
- const { results: paramEvaluated, errors: paramErrors } = this.sync_rules.evaluateParameterRowWithErrors(sourceTable, after);
401
+ const { results: paramEvaluated, errors: paramErrors } = this.sync_rules.evaluateParameterRowWithErrors(sourceTable.ref, after, { parameterLookupSources: sourceTable.parameterLookupSources });
392
402
  for (let error of paramErrors) {
393
403
  container.reporter.captureMessage(`Failed to evaluate parameter query on ${record.sourceTable.qualifiedName}.${record.after?.id}: ${error.error}`, {
394
404
  level: errors.ErrorSeverity.WARNING,
@@ -406,26 +416,27 @@ export class MongoBucketBatch extends BaseObserver {
406
416
  evaluated: paramEvaluated,
407
417
  existing_lookups
408
418
  });
409
- new_lookups = paramEvaluated.map((p) => {
410
- return storage.serializeLookup(p.lookup);
411
- });
419
+ new_lookups = this.sourceRecordStore.mapParameterLookups(paramEvaluated);
412
420
  }
413
421
  }
414
422
  let result = null;
415
423
  // 5. TOAST: Update current data and bucket list.
416
424
  if (afterId) {
417
425
  // Insert or update
418
- const after_key = { g: this.group_id, t: mongoTableId(sourceTable.id), k: afterId };
419
- batch.upsertCurrentData(after_key, {
426
+ batch.upsertCurrentData({
427
+ sourceTableId,
428
+ replicaId: afterId,
420
429
  data: afterData,
421
430
  buckets: new_buckets,
422
431
  lookups: new_lookups
423
432
  });
424
433
  result = {
425
- _id: after_key,
434
+ sourceTableId,
435
+ replicaId: afterId,
426
436
  data: afterData,
427
437
  buckets: new_buckets,
428
- lookups: new_lookups
438
+ lookups: new_lookups,
439
+ cacheKey: operation.internalAfterKey
429
440
  };
430
441
  }
431
442
  if (afterId == null || !storage.replicaIdEquals(beforeId, afterId)) {
@@ -433,12 +444,14 @@ export class MongoBucketBatch extends BaseObserver {
433
444
  // Note that this is a soft delete.
434
445
  // We don't specifically need a new or unique op_id here, but it must be greater than the
435
446
  // last checkpoint, so we use next().
436
- batch.softDeleteCurrentData(before_key, opSeq.next());
447
+ batch.softDeleteCurrentData(sourceTableId, beforeId, opSeq.next());
437
448
  }
438
449
  return result;
439
450
  }
440
451
  async withTransaction(cb) {
452
+ using lockSpan = this.tracer.span('storage', 'internal_lock');
441
453
  await replicationMutex.exclusiveLock(async () => {
454
+ lockSpan.end();
442
455
  await this.session.withTransaction(async () => {
443
456
  try {
444
457
  await cb();
@@ -450,7 +463,9 @@ export class MongoBucketBatch extends BaseObserver {
450
463
  else {
451
464
  this.logger.warn('Transaction error', e);
452
465
  }
453
- await timers.setTimeout(Math.random() * 50);
466
+ const delay = Math.random() * 50;
467
+ using _ = this.tracer.span('storage', 'retry_delay');
468
+ await timers.setTimeout(delay);
454
469
  throw e;
455
470
  }
456
471
  }, { maxCommitTimeMS: 10000 });
@@ -516,227 +531,17 @@ export class MongoBucketBatch extends BaseObserver {
516
531
  async dispose() {
517
532
  await this[Symbol.asyncDispose]();
518
533
  }
519
- lastWaitingLogThottled = 0;
520
- async commit(lsn, options) {
521
- const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
522
- await this.flush(options);
523
- const now = new Date();
524
- // Mark relevant write checkpoints as "processed".
525
- // This makes it easier to identify write checkpoints that are "valid" in order.
526
- await this.db.write_checkpoints.updateMany({
527
- processed_at_lsn: null,
528
- 'lsns.1': { $lte: lsn }
529
- }, {
530
- $set: {
531
- processed_at_lsn: lsn
532
- }
533
- }, {
534
- session: this.session
535
- });
536
- const can_checkpoint = {
537
- $and: [
538
- { $eq: ['$snapshot_done', true] },
539
- {
540
- $or: [{ $eq: ['$last_checkpoint_lsn', null] }, { $lte: ['$last_checkpoint_lsn', { $literal: lsn }] }]
541
- },
542
- {
543
- $or: [{ $eq: ['$no_checkpoint_before', null] }, { $lte: ['$no_checkpoint_before', { $literal: lsn }] }]
544
- }
545
- ]
546
- };
547
- const new_keepalive_op = {
548
- $cond: [
549
- can_checkpoint,
550
- { $literal: null },
551
- {
552
- $toString: {
553
- $max: [{ $toLong: '$keepalive_op' }, { $literal: this.persisted_op }, 0n]
554
- }
555
- }
556
- ]
557
- };
558
- const new_last_checkpoint = {
559
- $cond: [
560
- can_checkpoint,
561
- {
562
- $max: ['$last_checkpoint', { $literal: this.persisted_op }, { $toLong: '$keepalive_op' }, 0n]
563
- },
564
- '$last_checkpoint'
565
- ]
566
- };
567
- // For this query, we need to handle multiple cases, depending on the state:
568
- // 1. Normal commit - advance last_checkpoint to this.persisted_op.
569
- // 2. Commit delayed by no_checkpoint_before due to snapshot. In this case we only advance keepalive_op.
570
- // 3. Commit with no new data - here may may set last_checkpoint = keepalive_op, if a delayed commit is relevant.
571
- // We want to do as much as possible in a single atomic database operation, which makes this somewhat complex.
572
- let preUpdateDocument = await this.db.sync_rules.findOneAndUpdate({ _id: this.group_id }, [
573
- {
574
- $set: {
575
- _can_checkpoint: can_checkpoint,
576
- _not_empty: createEmptyCheckpoints
577
- ? true
578
- : {
579
- $or: [
580
- { $literal: createEmptyCheckpoints },
581
- { $ne: ['$keepalive_op', new_keepalive_op] },
582
- { $ne: ['$last_checkpoint', new_last_checkpoint] }
583
- ]
584
- }
585
- }
586
- },
587
- {
588
- $set: {
589
- last_checkpoint_lsn: {
590
- $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: lsn }, '$last_checkpoint_lsn']
591
- },
592
- last_checkpoint_ts: {
593
- $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: now }, '$last_checkpoint_ts']
594
- },
595
- last_keepalive_ts: { $literal: now },
596
- last_fatal_error: { $literal: null },
597
- last_fatal_error_ts: { $literal: null },
598
- keepalive_op: new_keepalive_op,
599
- last_checkpoint: new_last_checkpoint,
600
- // Unset snapshot_lsn on checkpoint
601
- snapshot_lsn: {
602
- $cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: null }, '$snapshot_lsn']
603
- }
604
- }
605
- },
606
- {
607
- $unset: ['_can_checkpoint', '_not_empty']
608
- }
609
- ], {
610
- session: this.session,
611
- // 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.
612
- returnDocument: 'before',
613
- projection: {
614
- snapshot_done: 1,
615
- last_checkpoint_lsn: 1,
616
- no_checkpoint_before: 1,
617
- keepalive_op: 1,
618
- last_checkpoint: 1
619
- }
620
- });
621
- if (preUpdateDocument == null) {
622
- throw new ReplicationAssertionError('Failed to update checkpoint - no matching sync_rules document for _id: ' + this.group_id);
623
- }
624
- // This re-implements the same logic as in the pipeline, to determine what was actually updated.
625
- // Unfortunately we cannot return these from the pipeline directly, so we need to re-implement the logic.
626
- const canCheckpoint = preUpdateDocument.snapshot_done === true &&
627
- (preUpdateDocument.last_checkpoint_lsn == null || preUpdateDocument.last_checkpoint_lsn <= lsn) &&
628
- (preUpdateDocument.no_checkpoint_before == null || preUpdateDocument.no_checkpoint_before <= lsn);
629
- const keepaliveOp = preUpdateDocument.keepalive_op == null ? null : BigInt(preUpdateDocument.keepalive_op);
630
- const maxKeepalive = [keepaliveOp ?? 0n, this.persisted_op ?? 0n, 0n].reduce((a, b) => (a > b ? a : b));
631
- const newKeepaliveOp = canCheckpoint ? null : maxKeepalive.toString();
632
- const newLastCheckpoint = canCheckpoint
633
- ? [preUpdateDocument.last_checkpoint ?? 0n, this.persisted_op ?? 0n, keepaliveOp ?? 0n, 0n].reduce((a, b) => a > b ? a : b)
634
- : preUpdateDocument.last_checkpoint;
635
- const notEmpty = createEmptyCheckpoints ||
636
- preUpdateDocument.keepalive_op !== newKeepaliveOp ||
637
- preUpdateDocument.last_checkpoint !== newLastCheckpoint;
638
- const checkpointCreated = canCheckpoint && notEmpty;
639
- const checkpointBlocked = !canCheckpoint;
640
- if (checkpointBlocked) {
641
- // Failed on snapshot_done or no_checkpoint_before.
642
- if (Date.now() - this.lastWaitingLogThottled > 5_000) {
643
- this.logger.info(`Waiting before creating checkpoint, currently at ${lsn} / ${preUpdateDocument.keepalive_op}. Current state: ${JSON.stringify({
644
- snapshot_done: preUpdateDocument.snapshot_done,
645
- last_checkpoint_lsn: preUpdateDocument.last_checkpoint_lsn,
646
- no_checkpoint_before: preUpdateDocument.no_checkpoint_before
647
- })}`);
648
- this.lastWaitingLogThottled = Date.now();
649
- }
650
- }
651
- else {
652
- if (checkpointCreated) {
653
- this.logger.debug(`Created checkpoint at ${lsn} / ${newLastCheckpoint}`);
654
- }
655
- await this.autoActivate(lsn);
656
- await this.db.notifyCheckpoint();
657
- this.persisted_op = null;
658
- this.last_checkpoint_lsn = lsn;
659
- if (this.db.storageConfig.softDeleteCurrentData && newLastCheckpoint != null) {
660
- await this.cleanupCurrentData(newLastCheckpoint);
661
- }
662
- }
663
- return { checkpointBlocked, checkpointCreated };
664
- }
665
- async cleanupCurrentData(lastCheckpoint) {
666
- const result = await this.db.v3_current_data.deleteMany({
667
- '_id.g': this.group_id,
668
- pending_delete: { $exists: true, $lte: lastCheckpoint }
669
- });
670
- if (result.deletedCount > 0) {
671
- this.logger.info(`Cleaned up ${result.deletedCount} pending delete current_data records for checkpoint ${lastCheckpoint}`);
672
- }
673
- }
674
- /**
675
- * Switch from processing -> active if relevant.
676
- *
677
- * Called on new commits.
678
- */
679
- async autoActivate(lsn) {
680
- if (!this.needsActivation) {
681
- return;
682
- }
683
- // Activate the batch, so it can start processing.
684
- // This is done automatically when the first save() is called.
685
- const session = this.session;
686
- let activated = false;
687
- await session.withTransaction(async () => {
688
- const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
689
- if (doc && doc.state == SyncRuleState.PROCESSING && doc.snapshot_done && doc.last_checkpoint != null) {
690
- await this.db.sync_rules.updateOne({
691
- _id: this.group_id
692
- }, {
693
- $set: {
694
- state: storage.SyncRuleState.ACTIVE
695
- }
696
- }, { session });
697
- await this.db.sync_rules.updateMany({
698
- _id: { $ne: this.group_id },
699
- state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
700
- }, {
701
- $set: {
702
- state: storage.SyncRuleState.STOP
703
- }
704
- }, { session });
705
- activated = true;
706
- }
707
- else if (doc?.state != SyncRuleState.PROCESSING) {
708
- this.needsActivation = false;
709
- }
710
- });
711
- if (activated) {
712
- this.logger.info(`Activated new sync rules at ${lsn}`);
713
- await this.db.notifyCheckpoint();
714
- this.needsActivation = false;
715
- }
716
- }
717
- async keepalive(lsn) {
718
- return await this.commit(lsn, { createEmptyCheckpoints: true });
719
- }
720
- async setResumeLsn(lsn) {
721
- const update = {
722
- snapshot_lsn: lsn
723
- };
724
- await this.db.sync_rules.updateOne({
725
- _id: this.group_id
726
- }, {
727
- $set: update
728
- }, { session: this.session });
729
- }
730
534
  async save(record) {
731
535
  const { after, before, sourceTable, tag } = record;
536
+ const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
732
537
  for (const event of this.getTableEvents(sourceTable)) {
733
538
  this.iterateListeners((cb) => cb.replicationEvent?.({
734
539
  batch: this,
735
540
  table: sourceTable,
736
541
  data: {
737
542
  op: tag,
738
- after: after && utils.isCompleteRow(this.storeCurrentData, after) ? after : undefined,
739
- before: before && utils.isCompleteRow(this.storeCurrentData, before) ? before : undefined
543
+ after: after && utils.isCompleteRow(storeCurrentData, after) ? after : undefined,
544
+ before: before && utils.isCompleteRow(storeCurrentData, before) ? before : undefined
740
545
  },
741
546
  event
742
547
  }));
@@ -766,9 +571,10 @@ export class MongoBucketBatch extends BaseObserver {
766
571
  const result = await this.flush();
767
572
  await this.withTransaction(async () => {
768
573
  for (let table of sourceTables) {
769
- await this.db.source_tables.deleteOne({ _id: mongoTableId(table.id) });
574
+ await this.db.commonSourceTables(this.group_id).deleteOne({ _id: mongoTableId(table.id) });
770
575
  }
771
576
  });
577
+ await this.cleanupDroppedSourceTables(sourceTables);
772
578
  return result;
773
579
  }
774
580
  async truncate(sourceTables) {
@@ -795,41 +601,30 @@ export class MongoBucketBatch extends BaseObserver {
795
601
  let lastBatchCount = BATCH_LIMIT;
796
602
  while (lastBatchCount == BATCH_LIMIT) {
797
603
  await this.withReplicationTransaction(`Truncate ${sourceTable.qualifiedName}`, async (session, opSeq) => {
798
- const current_data_filter = {
799
- _id: idPrefixFilter({ g: this.group_id, t: mongoTableId(sourceTable.id) }, ['k']),
800
- // Skip soft-deleted data
801
- // Works for both v1 and v3 current_data schemas
802
- pending_delete: { $exists: false }
803
- };
804
- const cursor = this.db.common_current_data.find(current_data_filter, {
805
- projection: {
806
- _id: 1,
807
- buckets: 1,
808
- lookups: 1
809
- },
810
- limit: BATCH_LIMIT,
811
- session: session
812
- });
813
- const batch = await cursor.toArray();
814
- const persistedBatch = new PersistedBatch(this.db, this.group_id, 0, { logger: this.logger });
604
+ using evalSpan = this.tracer.span('evaluate');
605
+ const sourceTableId = mongoTableId(sourceTable.id);
606
+ const batch = await this.sourceRecordStore.loadTruncateBatch(session, sourceTableId, BATCH_LIMIT);
607
+ const persistedBatch = this.createPersistedBatch(0);
815
608
  for (let value of batch) {
816
609
  persistedBatch.saveBucketData({
817
610
  op_seq: opSeq,
818
611
  before_buckets: value.buckets,
819
612
  evaluated: [],
820
613
  table: sourceTable,
821
- sourceKey: value._id.k
614
+ sourceKey: value.replicaId
822
615
  });
823
616
  persistedBatch.saveParameterData({
824
617
  op_seq: opSeq,
825
618
  existing_lookups: value.lookups,
826
619
  evaluated: [],
827
620
  sourceTable: sourceTable,
828
- sourceKey: value._id.k
621
+ sourceKey: value.replicaId
829
622
  });
830
623
  // Since this is not from streaming replication, we can do a hard delete
831
- persistedBatch.hardDeleteCurrentData(value._id);
624
+ persistedBatch.hardDeleteCurrentData(sourceTableId, value.replicaId);
832
625
  }
626
+ evalSpan.end();
627
+ using _ = this.tracer.span('storage', 'persist_flush');
833
628
  await persistedBatch.flush(session);
834
629
  lastBatchCount = batch.length;
835
630
  last_op = opSeq.last();
@@ -846,7 +641,7 @@ export class MongoBucketBatch extends BaseObserver {
846
641
  };
847
642
  copy.snapshotStatus = snapshotStatus;
848
643
  await this.withTransaction(async () => {
849
- await this.db.source_tables.updateOne({ _id: mongoTableId(table.id) }, {
644
+ await this.db.commonSourceTables(this.group_id).updateOne({ _id: mongoTableId(table.id) }, {
850
645
  $set: {
851
646
  snapshot_status: {
852
647
  last_key: snapshotStatus.lastKey == null ? null : new bson.Binary(snapshotStatus.lastKey),
@@ -858,59 +653,6 @@ export class MongoBucketBatch extends BaseObserver {
858
653
  });
859
654
  return copy;
860
655
  }
861
- async markAllSnapshotDone(no_checkpoint_before_lsn) {
862
- await this.db.sync_rules.updateOne({
863
- _id: this.group_id
864
- }, {
865
- $set: {
866
- snapshot_done: true,
867
- last_keepalive_ts: new Date()
868
- },
869
- $max: {
870
- no_checkpoint_before: no_checkpoint_before_lsn
871
- }
872
- }, { session: this.session });
873
- }
874
- async markTableSnapshotRequired(table) {
875
- await this.db.sync_rules.updateOne({
876
- _id: this.group_id
877
- }, {
878
- $set: {
879
- snapshot_done: false
880
- }
881
- }, { session: this.session });
882
- }
883
- async markTableSnapshotDone(tables, no_checkpoint_before_lsn) {
884
- const session = this.session;
885
- const ids = tables.map((table) => mongoTableId(table.id));
886
- await this.withTransaction(async () => {
887
- await this.db.source_tables.updateMany({ _id: { $in: ids } }, {
888
- $set: {
889
- snapshot_done: true
890
- },
891
- $unset: {
892
- snapshot_status: 1
893
- }
894
- }, { session });
895
- if (no_checkpoint_before_lsn != null) {
896
- await this.db.sync_rules.updateOne({
897
- _id: this.group_id
898
- }, {
899
- $set: {
900
- last_keepalive_ts: new Date()
901
- },
902
- $max: {
903
- no_checkpoint_before: no_checkpoint_before_lsn
904
- }
905
- }, { session: this.session });
906
- }
907
- });
908
- return tables.map((table) => {
909
- const copy = table.clone();
910
- copy.snapshotComplete = true;
911
- return copy;
912
- });
913
- }
914
656
  async clearError() {
915
657
  // No need to clear an error more than once per batch, since an error would always result in restarting the batch.
916
658
  if (this.clearedError) {
@@ -930,10 +672,7 @@ export class MongoBucketBatch extends BaseObserver {
930
672
  * Gets relevant {@link SqlEventDescriptor}s for the given {@link SourceTable}
931
673
  */
932
674
  getTableEvents(table) {
933
- return this.sync_rules.eventDescriptors.filter((evt) => [...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table)));
675
+ return this.sync_rules.eventDescriptors.filter((evt) => [...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table.ref)));
934
676
  }
935
677
  }
936
- export function currentBucketKey(b) {
937
- return `${b.bucket}/${b.table}/${b.id}`;
938
- }
939
678
  //# sourceMappingURL=MongoBucketBatch.js.map