@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.
- package/CHANGELOG.md +69 -0
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js +1 -1
- package/dist/migrations/db/migrations/1688556755264-initial-sync-rules.js.map +1 -1
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js +2 -2
- package/dist/migrations/db/migrations/1702295701188-sync-rule-state.js.map +1 -1
- package/dist/storage/MongoBucketStorage.d.ts +8 -6
- package/dist/storage/MongoBucketStorage.js +153 -66
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +15 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js +58 -0
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +1 -0
- package/dist/storage/implementation/CheckpointState.d.ts +20 -0
- package/dist/storage/implementation/CheckpointState.js +31 -0
- package/dist/storage/implementation/CheckpointState.js.map +1 -0
- package/dist/storage/implementation/MongoBucketBatch.d.ts +48 -35
- package/dist/storage/implementation/MongoBucketBatch.js +118 -379
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatchShared.d.ts +5 -0
- package/dist/storage/implementation/MongoBucketBatchShared.js +8 -0
- package/dist/storage/implementation/MongoBucketBatchShared.js.map +1 -0
- package/dist/storage/implementation/MongoChecksums.d.ts +29 -17
- package/dist/storage/implementation/MongoChecksums.js +13 -72
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +98 -58
- package/dist/storage/implementation/MongoCompactor.js +229 -296
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +11 -6
- package/dist/storage/implementation/MongoParameterCompactor.js +11 -8
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +14 -0
- package/dist/storage/implementation/MongoPersistedSyncRules.js +67 -0
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +22 -5
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +56 -13
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +61 -32
- package/dist/storage/implementation/MongoSyncBucketStorage.js +85 -523
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +10 -4
- package/dist/storage/implementation/MongoSyncRulesLock.js +19 -13
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/OperationBatch.js +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
- package/dist/storage/implementation/common/BucketDataDoc.d.ts +35 -0
- package/dist/storage/implementation/common/BucketDataDoc.js +2 -0
- package/dist/storage/implementation/common/BucketDataDoc.js.map +1 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +13 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +108 -0
- package/dist/storage/implementation/common/PersistedBatch.js +237 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -0
- package/dist/storage/implementation/common/SingleBucketStore.d.ts +54 -0
- package/dist/storage/implementation/common/SingleBucketStore.js +3 -0
- package/dist/storage/implementation/common/SingleBucketStore.js.map +1 -0
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +35 -0
- package/dist/storage/implementation/common/SourceRecordStore.js +2 -0
- package/dist/storage/implementation/common/SourceRecordStore.js.map +1 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +27 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +57 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +7 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +9 -0
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -0
- package/dist/storage/implementation/db.d.ts +41 -36
- package/dist/storage/implementation/db.js +77 -99
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +79 -66
- package/dist/storage/implementation/models.js +20 -1
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +27 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +407 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.d.ts +12 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +56 -0
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +23 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.js +52 -0
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +9 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -0
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +50 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +354 -0
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +25 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.js +183 -0
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +18 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js +57 -0
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.d.ts +19 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.js +105 -0
- package/dist/storage/implementation/v1/SourceRecordStoreV1.js.map +1 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +12 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +20 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -0
- package/dist/storage/implementation/v1/models.d.ts +45 -0
- package/dist/storage/implementation/v1/models.js +37 -0
- package/dist/storage/implementation/v1/models.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +30 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +463 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +15 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +84 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.js +68 -0
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +9 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +4 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +9 -0
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +63 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +508 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +28 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +259 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +18 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +48 -0
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +22 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +164 -0
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +22 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +74 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +101 -0
- package/dist/storage/implementation/v3/models.js +34 -0
- package/dist/storage/implementation/v3/models.js.map +1 -0
- package/dist/storage/storage-index.d.ts +6 -3
- package/dist/storage/storage-index.js +6 -3
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/utils/util.d.ts +10 -3
- package/dist/utils/util.js +24 -3
- package/dist/utils/util.js.map +1 -1
- package/package.json +9 -9
- package/src/migrations/db/migrations/1688556755264-initial-sync-rules.ts +1 -1
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +7 -7
- package/src/storage/MongoBucketStorage.ts +254 -99
- package/src/storage/implementation/BucketDefinitionMapping.ts +75 -0
- package/src/storage/implementation/CheckpointState.ts +59 -0
- package/src/storage/implementation/MongoBucketBatch.ts +182 -490
- package/src/storage/implementation/MongoBucketBatchShared.ts +11 -0
- package/src/storage/implementation/MongoChecksums.ts +53 -75
- package/src/storage/implementation/MongoCompactor.ts +374 -404
- package/src/storage/implementation/MongoParameterCompactor.ts +37 -24
- package/src/storage/implementation/MongoPersistedSyncRules.ts +82 -0
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +78 -16
- package/src/storage/implementation/MongoSyncBucketStorage.ts +179 -628
- package/src/storage/implementation/MongoSyncRulesLock.ts +20 -16
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +3 -1
- package/src/storage/implementation/OperationBatch.ts +1 -1
- package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
- package/src/storage/implementation/common/BucketDataDoc.ts +37 -0
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +15 -0
- package/src/storage/implementation/common/PersistedBatch.ts +364 -0
- package/src/storage/implementation/common/SingleBucketStore.ts +63 -0
- package/src/storage/implementation/common/SourceRecordStore.ts +48 -0
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +80 -0
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +25 -0
- package/src/storage/implementation/db.ts +110 -131
- package/src/storage/implementation/models.ts +102 -79
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +509 -0
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +75 -0
- package/src/storage/implementation/v1/MongoCompactorV1.ts +93 -0
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +26 -0
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +543 -0
- package/src/storage/implementation/v1/PersistedBatchV1.ts +229 -0
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +74 -0
- package/src/storage/implementation/v1/SourceRecordStoreV1.ts +156 -0
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +28 -0
- package/src/storage/implementation/v1/models.ts +99 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +607 -0
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +120 -0
- package/src/storage/implementation/v3/MongoCompactorV3.ts +107 -0
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +24 -0
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +11 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +678 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +317 -0
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +68 -0
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +226 -0
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +117 -0
- package/src/storage/implementation/v3/models.ts +164 -0
- package/src/storage/storage-index.ts +6 -3
- package/src/utils/util.ts +34 -5
- package/test/src/storage_compacting.test.ts +57 -29
- package/test/src/storage_sync.test.ts +767 -5
- package/test/src/storeCurrentData.test.ts +211 -0
- package/test/tsconfig.json +0 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/PersistedBatch.d.ts +0 -71
- package/dist/storage/implementation/PersistedBatch.js +0 -354
- package/dist/storage/implementation/PersistedBatch.js.map +0 -1
- 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,
|
|
4
|
-
import { deserializeBson, isCompleteRow, SaveOperationTag, storage,
|
|
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 {
|
|
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 {
|
|
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
|
|
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
|
-
|
|
126
|
-
|
|
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
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
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
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
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
|
|
193
|
-
if (
|
|
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
|
-
|
|
197
|
+
sourceRecordLookup.delete(op.internalBeforeKey);
|
|
196
198
|
}
|
|
197
|
-
const nextData = this.saveOperation(persistedBatch, op,
|
|
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
|
-
|
|
201
|
-
sizes?.set(op.internalAfterKey, nextData.data
|
|
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,
|
|
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
|
|
248
|
+
const sourceTableId = mongoTableId(record.sourceTable.id);
|
|
237
249
|
if (this.skipExistingRows) {
|
|
238
250
|
if (record.tag == SaveOperationTag.INSERT) {
|
|
239
|
-
if (
|
|
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 =
|
|
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(
|
|
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 (
|
|
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 =
|
|
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 && !
|
|
297
|
-
afterData =
|
|
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(
|
|
370
|
+
if (afterId && after && utils.isCompleteRow(storeCurrentData, after)) {
|
|
357
371
|
// Insert or update
|
|
358
372
|
if (sourceTable.syncData) {
|
|
359
|
-
const { results
|
|
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 =
|
|
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 =
|
|
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
|
-
|
|
419
|
-
|
|
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
|
-
|
|
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(
|
|
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
|
-
|
|
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(
|
|
739
|
-
before: before && utils.isCompleteRow(
|
|
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.
|
|
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
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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.
|
|
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
|