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