@powersync/service-module-mongodb-storage 0.16.0 → 0.18.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 +53 -0
- package/dist/storage/MongoBucketStorage.d.ts +35 -15
- package/dist/storage/MongoBucketStorage.js +309 -86
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- 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 +69 -39
- package/dist/storage/implementation/MongoBucketBatch.js +104 -313
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +11 -9
- package/dist/storage/implementation/MongoChecksums.js +3 -111
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +3 -1
- package/dist/storage/implementation/MongoCompactor.js +18 -9
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
- package/dist/storage/implementation/MongoHydrationState.js +49 -0
- package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +6 -5
- package/dist/storage/implementation/MongoParameterCompactor.js +24 -1
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +121 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/implementation/MongoStorageProvider.js +5 -2
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +50 -21
- package/dist/storage/implementation/MongoSyncBucketStorage.js +62 -199
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +9 -5
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +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 +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +6 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -3
- package/dist/storage/implementation/common/PersistedBatch.js +5 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +10 -2
- package/dist/storage/implementation/db.js +9 -14
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +25 -50
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +23 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +403 -2
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +114 -3
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +22 -15
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +100 -27
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +1 -2
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.d.ts +21 -1
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +21 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +459 -3
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +27 -7
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +202 -18
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +20 -3
- package/dist/storage/implementation/v3/MongoCompactorV3.js +511 -22
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +62 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +350 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +50 -16
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +292 -75
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -3
- package/dist/storage/implementation/v3/PersistedBatchV3.js +43 -20
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +52 -16
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +16 -14
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +32 -27
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +4 -0
- package/dist/storage/implementation/v3/bucket-format.js +58 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
- package/dist/storage/implementation/v3/chunking.d.ts +10 -0
- package/dist/storage/implementation/v3/chunking.js +28 -0
- package/dist/storage/implementation/v3/chunking.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +111 -10
- package/dist/storage/implementation/v3/models.js +11 -28
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
- package/dist/storage/implementation/v3/source-table-utils.js +214 -0
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
- package/dist/storage/storage-index.d.ts +4 -4
- package/dist/storage/storage-index.js +4 -4
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +2 -2
- package/dist/utils/test-utils.d.ts +1 -2
- package/dist/utils/test-utils.js +5 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +1 -1
- package/dist/utils/util.js +3 -2
- package/dist/utils/util.js.map +1 -1
- package/package.json +9 -19
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +464 -95
- package/src/storage/implementation/CheckpointState.ts +59 -0
- package/src/storage/implementation/MongoBucketBatch.ts +159 -405
- package/src/storage/implementation/MongoChecksums.ts +7 -138
- package/src/storage/implementation/MongoCompactor.ts +20 -10
- package/src/storage/implementation/MongoHydrationState.ts +61 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +24 -5
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +152 -0
- package/src/storage/implementation/MongoStorageProvider.ts +5 -2
- package/src/storage/implementation/MongoSyncBucketStorage.ts +89 -250
- package/src/storage/implementation/MongoSyncRulesLock.ts +14 -5
- package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
- package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
- package/src/storage/implementation/common/PersistedBatch.ts +8 -4
- package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +16 -21
- package/src/storage/implementation/models.ts +28 -61
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +503 -5
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
- package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +150 -49
- package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +25 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +611 -4
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
- package/src/storage/implementation/v3/MongoCompactorV3.ts +647 -30
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +397 -111
- package/src/storage/implementation/v3/PersistedBatchV3.ts +63 -38
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +59 -19
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +48 -38
- package/src/storage/implementation/v3/bucket-format.ts +70 -0
- package/src/storage/implementation/v3/chunking.ts +38 -0
- package/src/storage/implementation/v3/models.ts +150 -44
- package/src/storage/implementation/v3/source-table-utils.ts +392 -0
- package/src/storage/storage-index.ts +12 -4
- package/src/utils/test-utils.ts +6 -3
- package/src/utils/util.ts +3 -2
- package/test/src/__snapshots__/storage.test.ts.snap +0 -75
- package/test/src/__snapshots__/storage_sync.test.ts.snap +48 -48
- package/test/src/cleanup-stopped-sync-configs.test.ts +648 -0
- package/test/src/storage.test.ts +11 -11
- package/test/src/storage_compacting.test.ts +1956 -6
- package/test/src/storage_sync.test.ts +1429 -42
- package/test/src/storeCurrentData.test.ts +221 -0
- package/test/src/util.ts +2 -1
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -17
- package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
- package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -64
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -15
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -42
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -5
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
- package/src/storage/implementation/BucketDefinitionMapping.ts +0 -72
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -76
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -56
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -12
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import {
|
|
2
|
+
import { HydratedSyncConfig, SqlEventDescriptor, SqliteRow, SqliteValue } from '@powersync/service-sync-rules';
|
|
3
3
|
import * as bson from 'bson';
|
|
4
4
|
|
|
5
5
|
import {
|
|
@@ -12,26 +12,24 @@ import {
|
|
|
12
12
|
ServiceError
|
|
13
13
|
} from '@powersync/lib-services-framework';
|
|
14
14
|
import {
|
|
15
|
+
BucketDefinitionMapping,
|
|
15
16
|
BucketStorageMarkRecordUnavailable,
|
|
16
|
-
CheckpointResult,
|
|
17
17
|
deserializeBson,
|
|
18
18
|
InternalOpId,
|
|
19
19
|
isCompleteRow,
|
|
20
20
|
PerformanceTracer,
|
|
21
21
|
SaveOperationTag,
|
|
22
22
|
storage,
|
|
23
|
-
SyncRuleState,
|
|
24
23
|
utils
|
|
25
24
|
} from '@powersync/service-core';
|
|
26
25
|
import * as timers from 'node:timers/promises';
|
|
27
26
|
import { mongoTableId } from '../../utils/util.js';
|
|
28
|
-
import { BucketDefinitionMapping } from './BucketDefinitionMapping.js';
|
|
29
27
|
import { PersistedBatch } from './common/PersistedBatch.js';
|
|
30
28
|
import { LoadedSourceRecord, SourceRecordStore } from './common/SourceRecordStore.js';
|
|
31
29
|
import type { VersionedPowerSyncMongo } from './db.js';
|
|
32
|
-
import { SyncRuleDocument } from './models.js';
|
|
33
30
|
import { MAX_ROW_SIZE } from './MongoBucketBatchShared.js';
|
|
34
31
|
import { MongoIdSequence } from './MongoIdSequence.js';
|
|
32
|
+
import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
|
|
35
33
|
import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
|
|
36
34
|
import { OperationBatch, RecordOperation } from './OperationBatch.js';
|
|
37
35
|
|
|
@@ -44,20 +42,32 @@ const replicationMutex = new utils.Mutex();
|
|
|
44
42
|
|
|
45
43
|
export interface MongoBucketBatchOptions {
|
|
46
44
|
db: VersionedPowerSyncMongo;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
45
|
+
/**
|
|
46
|
+
* The parsed sync config set for this batch.
|
|
47
|
+
*
|
|
48
|
+
* The batch derives both the hydrated sync rules and the bucket definition mapping from
|
|
49
|
+
* this single set, so they always come from the same parse. Do not add separate
|
|
50
|
+
* syncRules/mapping options - pairing values from different parses is exactly the bug
|
|
51
|
+
* this shape prevents.
|
|
52
|
+
*/
|
|
53
|
+
parsedSyncConfig: MongoParsedSyncConfigSet;
|
|
54
|
+
replicationStreamId: number;
|
|
55
|
+
replicationStreamName: string;
|
|
56
|
+
syncConfigIds?: bson.ObjectId[];
|
|
57
|
+
/**
|
|
58
|
+
* Seeds the in-memory persisted-op tracking for v1 storage. Not used by v3 storage, which
|
|
59
|
+
* tracks the persisted-op head durably on the replication stream document instead.
|
|
60
|
+
*/
|
|
61
|
+
keepaliveOp?: InternalOpId | null;
|
|
52
62
|
resumeFromLsn: string | null;
|
|
53
63
|
storeCurrentData: boolean;
|
|
54
|
-
mapping: BucketDefinitionMapping;
|
|
55
64
|
/**
|
|
56
65
|
* Set to true for initial replication.
|
|
57
66
|
*/
|
|
58
67
|
skipExistingRows: boolean;
|
|
59
68
|
|
|
60
69
|
markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
|
|
70
|
+
hooks: storage.StorageHooks | undefined;
|
|
61
71
|
|
|
62
72
|
logger: Logger;
|
|
63
73
|
tracer?: PerformanceTracer<'storage' | 'evaluate'>;
|
|
@@ -67,47 +77,47 @@ export abstract class MongoBucketBatch
|
|
|
67
77
|
extends BaseObserver<storage.BucketBatchStorageListener>
|
|
68
78
|
implements storage.BucketStorageBatch
|
|
69
79
|
{
|
|
80
|
+
protected readonly options: MongoBucketBatchOptions;
|
|
70
81
|
protected logger: Logger;
|
|
71
82
|
|
|
72
83
|
private readonly client: mongo.MongoClient;
|
|
73
|
-
|
|
84
|
+
readonly db: VersionedPowerSyncMongo;
|
|
74
85
|
public readonly session: mongo.ClientSession;
|
|
75
|
-
|
|
86
|
+
protected readonly sync_rules: HydratedSyncConfig;
|
|
76
87
|
|
|
77
|
-
protected readonly
|
|
88
|
+
protected readonly replicationStreamId: number;
|
|
78
89
|
|
|
79
|
-
private readonly
|
|
90
|
+
private readonly replicationStreamName: string;
|
|
91
|
+
/**
|
|
92
|
+
* Source-level setting for whether raw row data should be stored in current_data.
|
|
93
|
+
*
|
|
94
|
+
* Some sources always send complete rows (MongoDB, MySQL with binlog_row_image=full),
|
|
95
|
+
* in which case this is false for the whole batch. For sources where it depends on the
|
|
96
|
+
* table (Postgres REPLICA IDENTITY), this is true and the decision is refined per-table
|
|
97
|
+
* via SourceTable.storeCurrentData. The effective per-record value is the conjunction of
|
|
98
|
+
* the two.
|
|
99
|
+
*/
|
|
80
100
|
private readonly storeCurrentData: boolean;
|
|
81
|
-
|
|
101
|
+
public readonly skipExistingRows: boolean;
|
|
82
102
|
protected readonly mapping: BucketDefinitionMapping;
|
|
83
103
|
|
|
84
104
|
private batch: OperationBatch | null = null;
|
|
85
105
|
private write_checkpoint_batch: storage.CustomWriteCheckpointOptions[] = [];
|
|
86
106
|
private markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
|
|
107
|
+
private hooks: storage.StorageHooks | undefined;
|
|
87
108
|
private clearedError = false;
|
|
88
109
|
|
|
89
110
|
private tracer: PerformanceTracer<'storage' | 'evaluate'>;
|
|
90
111
|
|
|
91
|
-
/**
|
|
92
|
-
* Last LSN received associated with a checkpoint.
|
|
93
|
-
*
|
|
94
|
-
* This could be either:
|
|
95
|
-
* 1. A commit LSN.
|
|
96
|
-
* 2. A keepalive message LSN.
|
|
97
|
-
*/
|
|
98
|
-
private last_checkpoint_lsn: string | null = null;
|
|
99
|
-
|
|
100
|
-
private persisted_op: InternalOpId | null = null;
|
|
101
|
-
|
|
102
112
|
/**
|
|
103
113
|
* Last written op, if any. This may not reflect a consistent checkpoint.
|
|
104
114
|
*/
|
|
105
115
|
public last_flushed_op: InternalOpId | null = null;
|
|
106
116
|
|
|
107
117
|
/**
|
|
108
|
-
*
|
|
118
|
+
* LSN to resume replication from.
|
|
109
119
|
*
|
|
110
|
-
*
|
|
120
|
+
* This is typically the last commit LSN, but there are cases where it differs:
|
|
111
121
|
* 1. After an initial snapshot, we don't have a consistent commit yet, but need to resume from the snapshot LSN.
|
|
112
122
|
* 2. If "no_checkpoint_before_lsn" is set far in advance, it may take a while to reach that point. We
|
|
113
123
|
* may want to resume at incremental points before that.
|
|
@@ -116,39 +126,35 @@ export abstract class MongoBucketBatch
|
|
|
116
126
|
*/
|
|
117
127
|
public resumeFromLsn: string | null = null;
|
|
118
128
|
|
|
119
|
-
private needsActivation = true;
|
|
120
|
-
|
|
121
129
|
constructor(options: MongoBucketBatchOptions) {
|
|
122
130
|
super();
|
|
123
131
|
this.logger = options.logger;
|
|
132
|
+
this.options = options;
|
|
124
133
|
this.client = options.db.client;
|
|
125
134
|
this.db = options.db;
|
|
126
|
-
this.
|
|
127
|
-
this.last_checkpoint_lsn = options.lastCheckpointLsn;
|
|
135
|
+
this.replicationStreamId = options.replicationStreamId;
|
|
128
136
|
this.resumeFromLsn = options.resumeFromLsn;
|
|
129
137
|
this.session = this.client.startSession();
|
|
130
|
-
this.
|
|
131
|
-
this.sync_rules = options.
|
|
138
|
+
this.replicationStreamName = options.replicationStreamName;
|
|
139
|
+
this.sync_rules = options.parsedSyncConfig.hydratedSyncConfig;
|
|
132
140
|
this.storeCurrentData = options.storeCurrentData;
|
|
133
|
-
this.mapping = options.mapping;
|
|
141
|
+
this.mapping = options.parsedSyncConfig.mapping;
|
|
134
142
|
this.skipExistingRows = options.skipExistingRows;
|
|
135
143
|
this.markRecordUnavailable = options.markRecordUnavailable;
|
|
144
|
+
this.hooks = options.hooks;
|
|
136
145
|
this.batch = new OperationBatch();
|
|
137
146
|
|
|
138
|
-
this.persisted_op = options.keepaliveOp ?? null;
|
|
139
147
|
this.tracer = options.tracer ?? new PerformanceTracer('MongoDB storage');
|
|
140
148
|
}
|
|
141
149
|
|
|
142
150
|
addCustomWriteCheckpoint(checkpoint: storage.BatchedCustomWriteCheckpointOptions): void {
|
|
143
151
|
this.write_checkpoint_batch.push({
|
|
144
152
|
...checkpoint,
|
|
145
|
-
sync_rules_id: this.
|
|
153
|
+
sync_rules_id: this.replicationStreamId
|
|
146
154
|
});
|
|
147
155
|
}
|
|
148
156
|
|
|
149
|
-
|
|
150
|
-
return this.last_checkpoint_lsn;
|
|
151
|
-
}
|
|
157
|
+
abstract resolveTables(options: storage.ResolveTablesOptions): Promise<storage.ResolveTablesResult>;
|
|
152
158
|
|
|
153
159
|
protected abstract createPersistedBatch(writtenSize: number): PersistedBatch;
|
|
154
160
|
|
|
@@ -156,6 +162,25 @@ export abstract class MongoBucketBatch
|
|
|
156
162
|
|
|
157
163
|
protected abstract cleanupDroppedSourceTables(sourceTables: storage.SourceTable[]): Promise<void>;
|
|
158
164
|
|
|
165
|
+
abstract commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<storage.CheckpointResult>;
|
|
166
|
+
|
|
167
|
+
abstract keepalive(lsn: string): Promise<storage.CheckpointResult>;
|
|
168
|
+
|
|
169
|
+
abstract setResumeLsn(lsn: string): Promise<void>;
|
|
170
|
+
|
|
171
|
+
abstract getSourceTableStatus(table: storage.SourceTable): Promise<storage.SourceTable | null>;
|
|
172
|
+
|
|
173
|
+
abstract markAllSnapshotDone(no_checkpoint_before_lsn: string): Promise<void>;
|
|
174
|
+
|
|
175
|
+
abstract markSnapshotDone(no_checkpoint_before_lsn: string, options?: { throwOnConflict?: boolean }): Promise<void>;
|
|
176
|
+
|
|
177
|
+
abstract markTableSnapshotRequired(table: storage.SourceTable): Promise<void>;
|
|
178
|
+
|
|
179
|
+
abstract markTableSnapshotDone(
|
|
180
|
+
tables: storage.SourceTable[],
|
|
181
|
+
no_checkpoint_before_lsn?: string
|
|
182
|
+
): Promise<storage.SourceTable[]>;
|
|
183
|
+
|
|
159
184
|
async flush(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null> {
|
|
160
185
|
let result: storage.FlushedResult | null = null;
|
|
161
186
|
// One flush may be split over multiple transactions.
|
|
@@ -176,6 +201,8 @@ export abstract class MongoBucketBatch
|
|
|
176
201
|
|
|
177
202
|
using _ = this.tracer.span('storage', 'flush');
|
|
178
203
|
|
|
204
|
+
await this.hooks?.beforeBatchFlush?.(this);
|
|
205
|
+
|
|
179
206
|
await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
|
|
180
207
|
if (batch != null) {
|
|
181
208
|
resumeBatch = await this.replicateBatch(session, batch, opSeq, options);
|
|
@@ -197,8 +224,9 @@ export abstract class MongoBucketBatch
|
|
|
197
224
|
throw new ReplicationAssertionError('Unexpected last_op == null');
|
|
198
225
|
}
|
|
199
226
|
|
|
200
|
-
this.
|
|
227
|
+
this.recordPersistedOp(last_op);
|
|
201
228
|
this.last_flushed_op = last_op;
|
|
229
|
+
await this.hooks?.afterBatchFlush?.(this);
|
|
202
230
|
return { flushed_op: last_op };
|
|
203
231
|
}
|
|
204
232
|
|
|
@@ -210,8 +238,12 @@ export abstract class MongoBucketBatch
|
|
|
210
238
|
): Promise<OperationBatch | null> {
|
|
211
239
|
let sizes: Map<string, number> | undefined = undefined;
|
|
212
240
|
using _ = this.tracer.span('storage', 'replicate_batch');
|
|
213
|
-
if
|
|
214
|
-
|
|
241
|
+
// Only look up current_data sizes if the batch stores current_data and at least one
|
|
242
|
+
// table in it does too (per-table can disable it, e.g. Postgres REPLICA IDENTITY FULL).
|
|
243
|
+
const anyTableStoresCurrentData =
|
|
244
|
+
this.storeCurrentData && batch.batch.some((r) => r.record.sourceTable.storeCurrentData);
|
|
245
|
+
if (anyTableStoresCurrentData && !this.skipExistingRows) {
|
|
246
|
+
// We skip this step if no tables store current_data, since the sizes will
|
|
215
247
|
// always be small in that case.
|
|
216
248
|
|
|
217
249
|
// With skipExistingRows, we don't load the full documents into memory,
|
|
@@ -224,10 +256,14 @@ export abstract class MongoBucketBatch
|
|
|
224
256
|
// (automatically limited to 48MB(?) per batch by MongoDB). The issue is that it changes
|
|
225
257
|
// the order of processing, which then becomes really tricky to manage.
|
|
226
258
|
// This now takes 2+ queries, but doesn't have any issues with order of operations.
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
259
|
+
// Within this branch this.storeCurrentData is true, so the per-table flag is the
|
|
260
|
+
// effective value - only look up sizes for tables that actually store current_data.
|
|
261
|
+
const sizeLookups = batch.batch
|
|
262
|
+
.filter((r) => r.record.sourceTable.storeCurrentData)
|
|
263
|
+
.map((r) => ({
|
|
264
|
+
sourceTableId: mongoTableId(r.record.sourceTable.id),
|
|
265
|
+
replicaId: r.beforeId
|
|
266
|
+
}));
|
|
231
267
|
|
|
232
268
|
sizes = await this.sourceRecordStore.loadSizes(session, sizeLookups);
|
|
233
269
|
}
|
|
@@ -323,6 +359,9 @@ export abstract class MongoBucketBatch
|
|
|
323
359
|
const afterId = operation.afterId;
|
|
324
360
|
let after = record.after;
|
|
325
361
|
const sourceTable = record.sourceTable;
|
|
362
|
+
// Effective per-record flag: store current_data only if both the batch (source-level,
|
|
363
|
+
// e.g. Postgres) and the table (e.g. non-FULL replica identity) require it.
|
|
364
|
+
const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
|
|
326
365
|
|
|
327
366
|
let existing_buckets: LoadedSourceRecord['buckets'] = [];
|
|
328
367
|
let new_buckets: LoadedSourceRecord['buckets'] = [];
|
|
@@ -351,7 +390,7 @@ export abstract class MongoBucketBatch
|
|
|
351
390
|
// Not an error if we re-apply a transaction
|
|
352
391
|
existing_buckets = [];
|
|
353
392
|
existing_lookups = [];
|
|
354
|
-
if (!isCompleteRow(
|
|
393
|
+
if (!isCompleteRow(storeCurrentData, after!)) {
|
|
355
394
|
if (this.markRecordUnavailable != null) {
|
|
356
395
|
// This will trigger a "resnapshot" of the record.
|
|
357
396
|
// This is not relevant if storeCurrentData is false, since we'll get the full row
|
|
@@ -367,7 +406,7 @@ export abstract class MongoBucketBatch
|
|
|
367
406
|
} else {
|
|
368
407
|
existing_buckets = result.buckets;
|
|
369
408
|
existing_lookups = result.lookups;
|
|
370
|
-
if (
|
|
409
|
+
if (storeCurrentData && result.data != null) {
|
|
371
410
|
const data = deserializeBson(result.data.buffer) as SqliteRow;
|
|
372
411
|
after = storage.mergeToast<SqliteValue>(after!, data);
|
|
373
412
|
}
|
|
@@ -378,7 +417,9 @@ export abstract class MongoBucketBatch
|
|
|
378
417
|
// Not an error if we re-apply a transaction
|
|
379
418
|
existing_buckets = [];
|
|
380
419
|
existing_lookups = [];
|
|
381
|
-
// Log to help with debugging if there was a consistency issue
|
|
420
|
+
// Log to help with debugging if there was a consistency issue.
|
|
421
|
+
// Gate on the batch-level flag: FULL tables (per-record flag false) still get a
|
|
422
|
+
// current_data entry, so a missing record on DELETE is meaningful for them too.
|
|
382
423
|
if (this.storeCurrentData && this.markRecordUnavailable == null) {
|
|
383
424
|
this.logger.warn(
|
|
384
425
|
`Cannot find previous record for delete on ${record.sourceTable.qualifiedName}: ${beforeId} / ${record.before?.id}`
|
|
@@ -391,7 +432,7 @@ export abstract class MongoBucketBatch
|
|
|
391
432
|
}
|
|
392
433
|
|
|
393
434
|
let afterData: bson.Binary | null = null;
|
|
394
|
-
if (afterId != null && !
|
|
435
|
+
if (afterId != null && !storeCurrentData) {
|
|
395
436
|
afterData = null;
|
|
396
437
|
} else if (afterId != null) {
|
|
397
438
|
try {
|
|
@@ -415,7 +456,7 @@ export abstract class MongoBucketBatch
|
|
|
415
456
|
{
|
|
416
457
|
level: errors.ErrorSeverity.WARNING,
|
|
417
458
|
metadata: {
|
|
418
|
-
replication_slot: this.
|
|
459
|
+
replication_slot: this.replicationStreamName,
|
|
419
460
|
table: record.sourceTable.qualifiedName
|
|
420
461
|
}
|
|
421
462
|
}
|
|
@@ -458,13 +499,15 @@ export abstract class MongoBucketBatch
|
|
|
458
499
|
// However, it will be valid by the end of the transaction.
|
|
459
500
|
//
|
|
460
501
|
// In this case, we don't save the op, but we do save the current data.
|
|
461
|
-
if (afterId && after && utils.isCompleteRow(
|
|
502
|
+
if (afterId && after && utils.isCompleteRow(storeCurrentData, after)) {
|
|
462
503
|
// Insert or update
|
|
463
504
|
if (sourceTable.syncData) {
|
|
464
|
-
const { results
|
|
505
|
+
const { results, errors: syncErrors } = this.sync_rules.evaluateRowWithErrors({
|
|
465
506
|
record: after,
|
|
466
|
-
sourceTable
|
|
507
|
+
sourceTable: sourceTable.ref,
|
|
508
|
+
bucketDataSources: sourceTable.bucketDataSources
|
|
467
509
|
});
|
|
510
|
+
const evaluated = results;
|
|
468
511
|
|
|
469
512
|
for (let error of syncErrors) {
|
|
470
513
|
container.reporter.captureMessage(
|
|
@@ -472,7 +515,7 @@ export abstract class MongoBucketBatch
|
|
|
472
515
|
{
|
|
473
516
|
level: errors.ErrorSeverity.WARNING,
|
|
474
517
|
metadata: {
|
|
475
|
-
replication_slot: this.
|
|
518
|
+
replication_slot: this.replicationStreamName,
|
|
476
519
|
table: record.sourceTable.qualifiedName
|
|
477
520
|
}
|
|
478
521
|
}
|
|
@@ -496,8 +539,9 @@ export abstract class MongoBucketBatch
|
|
|
496
539
|
if (sourceTable.syncParameters) {
|
|
497
540
|
// Parameters
|
|
498
541
|
const { results: paramEvaluated, errors: paramErrors } = this.sync_rules.evaluateParameterRowWithErrors(
|
|
499
|
-
sourceTable,
|
|
500
|
-
after
|
|
542
|
+
sourceTable.ref,
|
|
543
|
+
after,
|
|
544
|
+
{ parameterLookupSources: sourceTable.parameterLookupSources }
|
|
501
545
|
);
|
|
502
546
|
|
|
503
547
|
for (let error of paramErrors) {
|
|
@@ -506,7 +550,7 @@ export abstract class MongoBucketBatch
|
|
|
506
550
|
{
|
|
507
551
|
level: errors.ErrorSeverity.WARNING,
|
|
508
552
|
metadata: {
|
|
509
|
-
replication_slot: this.
|
|
553
|
+
replication_slot: this.replicationStreamName,
|
|
510
554
|
table: record.sourceTable.qualifiedName
|
|
511
555
|
}
|
|
512
556
|
}
|
|
@@ -559,7 +603,7 @@ export abstract class MongoBucketBatch
|
|
|
559
603
|
return result;
|
|
560
604
|
}
|
|
561
605
|
|
|
562
|
-
|
|
606
|
+
protected async withTransaction(cb: () => Promise<void>) {
|
|
563
607
|
using lockSpan = this.tracer.span('storage', 'internal_lock');
|
|
564
608
|
await replicationMutex.exclusiveLock(async () => {
|
|
565
609
|
lockSpan.end();
|
|
@@ -641,7 +685,7 @@ export abstract class MongoBucketBatch
|
|
|
641
685
|
|
|
642
686
|
await this.db.sync_rules.updateOne(
|
|
643
687
|
{
|
|
644
|
-
_id: this.
|
|
688
|
+
_id: this.replicationStreamId
|
|
645
689
|
},
|
|
646
690
|
{
|
|
647
691
|
$set: {
|
|
@@ -650,12 +694,41 @@ export abstract class MongoBucketBatch
|
|
|
650
694
|
},
|
|
651
695
|
{ session }
|
|
652
696
|
);
|
|
697
|
+
|
|
698
|
+
// Allow subclasses to persist additional flush-time state in the same transaction
|
|
699
|
+
// (e.g. v3 advances the stream-level last_persisted_op).
|
|
700
|
+
await this.onReplicationTransactionFlush(session, opSeq.last());
|
|
653
701
|
// We don't notify checkpoint here - we don't make any checkpoint updates directly
|
|
654
702
|
});
|
|
655
703
|
}
|
|
656
704
|
|
|
705
|
+
/**
|
|
706
|
+
* Hook called inside the replication flush transaction, after ops have been persisted.
|
|
707
|
+
*
|
|
708
|
+
* The base implementation does nothing; v3 storage overrides this to `$max` the stream-level
|
|
709
|
+
* `last_persisted_op` durably within the same transaction.
|
|
710
|
+
*/
|
|
711
|
+
protected async onReplicationTransactionFlush(_session: mongo.ClientSession, _lastOp: InternalOpId): Promise<void> {
|
|
712
|
+
// No-op by default (v1 behaviour unchanged).
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
/**
|
|
716
|
+
* Called after a replication transaction has successfully committed, with the last persisted op id.
|
|
717
|
+
*
|
|
718
|
+
* v1 storage tracks this in memory to fold into the next checkpoint. v3 storage does not need it:
|
|
719
|
+
* the stream-level `last_persisted_op` is already advanced durably by
|
|
720
|
+
* {@link onReplicationTransactionFlush} within the same transaction, and checkpoints read it
|
|
721
|
+
* from the document.
|
|
722
|
+
*
|
|
723
|
+
* Keep calls to this adjacent to {@link withReplicationTransaction} usage - both must observe
|
|
724
|
+
* every path that persists ops.
|
|
725
|
+
*/
|
|
726
|
+
protected recordPersistedOp(_lastOp: InternalOpId): void {
|
|
727
|
+
// No-op by default.
|
|
728
|
+
}
|
|
729
|
+
|
|
657
730
|
async [Symbol.asyncDispose]() {
|
|
658
|
-
if (this.batch
|
|
731
|
+
if (this.batch?.hasData() || this.write_checkpoint_batch.length > 0) {
|
|
659
732
|
// We don't error here, since:
|
|
660
733
|
// 1. In error states, this is expected (we can't distinguish between disposing after success or error).
|
|
661
734
|
// 2. SuppressedError is messy to deal with.
|
|
@@ -669,272 +742,27 @@ export abstract class MongoBucketBatch
|
|
|
669
742
|
await this[Symbol.asyncDispose]();
|
|
670
743
|
}
|
|
671
744
|
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
{
|
|
689
|
-
$set: {
|
|
690
|
-
processed_at_lsn: lsn
|
|
691
|
-
}
|
|
692
|
-
},
|
|
693
|
-
{
|
|
694
|
-
session: this.session
|
|
695
|
-
}
|
|
696
|
-
);
|
|
697
|
-
|
|
698
|
-
const can_checkpoint = {
|
|
699
|
-
$and: [
|
|
700
|
-
{ $eq: ['$snapshot_done', true] },
|
|
701
|
-
{
|
|
702
|
-
$or: [{ $eq: ['$last_checkpoint_lsn', null] }, { $lte: ['$last_checkpoint_lsn', { $literal: lsn }] }]
|
|
703
|
-
},
|
|
704
|
-
{
|
|
705
|
-
$or: [{ $eq: ['$no_checkpoint_before', null] }, { $lte: ['$no_checkpoint_before', { $literal: lsn }] }]
|
|
706
|
-
}
|
|
707
|
-
]
|
|
708
|
-
};
|
|
709
|
-
|
|
710
|
-
const new_keepalive_op = {
|
|
711
|
-
$cond: [
|
|
712
|
-
can_checkpoint,
|
|
713
|
-
{ $literal: null },
|
|
714
|
-
{
|
|
715
|
-
$toString: {
|
|
716
|
-
$max: [{ $toLong: '$keepalive_op' }, { $literal: this.persisted_op }, 0n]
|
|
717
|
-
}
|
|
718
|
-
}
|
|
719
|
-
]
|
|
720
|
-
};
|
|
721
|
-
|
|
722
|
-
const new_last_checkpoint = {
|
|
723
|
-
$cond: [
|
|
724
|
-
can_checkpoint,
|
|
725
|
-
{
|
|
726
|
-
$max: ['$last_checkpoint', { $literal: this.persisted_op }, { $toLong: '$keepalive_op' }, 0n]
|
|
727
|
-
},
|
|
728
|
-
'$last_checkpoint'
|
|
729
|
-
]
|
|
730
|
-
};
|
|
731
|
-
|
|
732
|
-
// For this query, we need to handle multiple cases, depending on the state:
|
|
733
|
-
// 1. Normal commit - advance last_checkpoint to this.persisted_op.
|
|
734
|
-
// 2. Commit delayed by no_checkpoint_before due to snapshot. In this case we only advance keepalive_op.
|
|
735
|
-
// 3. Commit with no new data - here may may set last_checkpoint = keepalive_op, if a delayed commit is relevant.
|
|
736
|
-
// We want to do as much as possible in a single atomic database operation, which makes this somewhat complex.
|
|
737
|
-
let preUpdateDocument = await this.db.sync_rules.findOneAndUpdate(
|
|
738
|
-
{ _id: this.group_id },
|
|
739
|
-
[
|
|
740
|
-
{
|
|
741
|
-
$set: {
|
|
742
|
-
_can_checkpoint: can_checkpoint,
|
|
743
|
-
_not_empty: createEmptyCheckpoints
|
|
744
|
-
? true
|
|
745
|
-
: {
|
|
746
|
-
$or: [
|
|
747
|
-
{ $literal: createEmptyCheckpoints },
|
|
748
|
-
{ $ne: ['$keepalive_op', new_keepalive_op] },
|
|
749
|
-
{ $ne: ['$last_checkpoint', new_last_checkpoint] }
|
|
750
|
-
]
|
|
751
|
-
}
|
|
752
|
-
}
|
|
753
|
-
},
|
|
754
|
-
{
|
|
755
|
-
$set: {
|
|
756
|
-
last_checkpoint_lsn: {
|
|
757
|
-
$cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: lsn }, '$last_checkpoint_lsn']
|
|
758
|
-
},
|
|
759
|
-
last_checkpoint_ts: {
|
|
760
|
-
$cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: now }, '$last_checkpoint_ts']
|
|
745
|
+
async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
|
|
746
|
+
const { after, before, sourceTable, tag } = record;
|
|
747
|
+
const storeCurrentData = this.storeCurrentData && sourceTable.storeCurrentData;
|
|
748
|
+
// syncEvent is the per-table designation from resolveTables. With v3 storage, multiple
|
|
749
|
+
// SourceTables can exist for the same ref, with a row change saved once per table -
|
|
750
|
+
// only the designated event carrier may fire events, so each event fires once per row.
|
|
751
|
+
if (sourceTable.syncEvent) {
|
|
752
|
+
for (const event of this.getTableEvents(sourceTable)) {
|
|
753
|
+
this.iterateListeners((cb) =>
|
|
754
|
+
cb.replicationEvent?.({
|
|
755
|
+
batch: this,
|
|
756
|
+
table: sourceTable,
|
|
757
|
+
data: {
|
|
758
|
+
op: tag,
|
|
759
|
+
after: after && utils.isCompleteRow(storeCurrentData, after) ? after : undefined,
|
|
760
|
+
before: before && utils.isCompleteRow(storeCurrentData, before) ? before : undefined
|
|
761
761
|
},
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
last_fatal_error_ts: { $literal: null },
|
|
765
|
-
keepalive_op: new_keepalive_op,
|
|
766
|
-
last_checkpoint: new_last_checkpoint,
|
|
767
|
-
// Unset snapshot_lsn on checkpoint
|
|
768
|
-
snapshot_lsn: {
|
|
769
|
-
$cond: [{ $and: ['$_can_checkpoint', '$_not_empty'] }, { $literal: null }, '$snapshot_lsn']
|
|
770
|
-
}
|
|
771
|
-
}
|
|
772
|
-
},
|
|
773
|
-
{
|
|
774
|
-
$unset: ['_can_checkpoint', '_not_empty']
|
|
775
|
-
}
|
|
776
|
-
],
|
|
777
|
-
{
|
|
778
|
-
session: this.session,
|
|
779
|
-
// 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.
|
|
780
|
-
returnDocument: 'before',
|
|
781
|
-
projection: {
|
|
782
|
-
snapshot_done: 1,
|
|
783
|
-
last_checkpoint_lsn: 1,
|
|
784
|
-
no_checkpoint_before: 1,
|
|
785
|
-
keepalive_op: 1,
|
|
786
|
-
last_checkpoint: 1
|
|
787
|
-
}
|
|
788
|
-
}
|
|
789
|
-
);
|
|
790
|
-
|
|
791
|
-
if (preUpdateDocument == null) {
|
|
792
|
-
throw new ReplicationAssertionError(
|
|
793
|
-
'Failed to update checkpoint - no matching sync_rules document for _id: ' + this.group_id
|
|
794
|
-
);
|
|
795
|
-
}
|
|
796
|
-
|
|
797
|
-
// This re-implements the same logic as in the pipeline, to determine what was actually updated.
|
|
798
|
-
// Unfortunately we cannot return these from the pipeline directly, so we need to re-implement the logic.
|
|
799
|
-
const canCheckpoint =
|
|
800
|
-
preUpdateDocument.snapshot_done === true &&
|
|
801
|
-
(preUpdateDocument.last_checkpoint_lsn == null || preUpdateDocument.last_checkpoint_lsn <= lsn) &&
|
|
802
|
-
(preUpdateDocument.no_checkpoint_before == null || preUpdateDocument.no_checkpoint_before <= lsn);
|
|
803
|
-
|
|
804
|
-
const keepaliveOp = preUpdateDocument.keepalive_op == null ? null : BigInt(preUpdateDocument.keepalive_op);
|
|
805
|
-
const maxKeepalive = [keepaliveOp ?? 0n, this.persisted_op ?? 0n, 0n].reduce((a, b) => (a > b ? a : b));
|
|
806
|
-
const newKeepaliveOp = canCheckpoint ? null : maxKeepalive.toString();
|
|
807
|
-
const newLastCheckpoint = canCheckpoint
|
|
808
|
-
? [preUpdateDocument.last_checkpoint ?? 0n, this.persisted_op ?? 0n, keepaliveOp ?? 0n, 0n].reduce((a, b) =>
|
|
809
|
-
a > b ? a : b
|
|
810
|
-
)
|
|
811
|
-
: preUpdateDocument.last_checkpoint;
|
|
812
|
-
const notEmpty =
|
|
813
|
-
createEmptyCheckpoints ||
|
|
814
|
-
preUpdateDocument.keepalive_op !== newKeepaliveOp ||
|
|
815
|
-
preUpdateDocument.last_checkpoint !== newLastCheckpoint;
|
|
816
|
-
const checkpointCreated = canCheckpoint && notEmpty;
|
|
817
|
-
|
|
818
|
-
const checkpointBlocked = !canCheckpoint;
|
|
819
|
-
|
|
820
|
-
if (checkpointBlocked) {
|
|
821
|
-
// Failed on snapshot_done or no_checkpoint_before.
|
|
822
|
-
if (Date.now() - this.lastWaitingLogThottled > 5_000) {
|
|
823
|
-
this.logger.info(
|
|
824
|
-
`Waiting before creating checkpoint, currently at ${lsn} / ${preUpdateDocument.keepalive_op}. Current state: ${JSON.stringify(
|
|
825
|
-
{
|
|
826
|
-
snapshot_done: preUpdateDocument.snapshot_done,
|
|
827
|
-
last_checkpoint_lsn: preUpdateDocument.last_checkpoint_lsn,
|
|
828
|
-
no_checkpoint_before: preUpdateDocument.no_checkpoint_before
|
|
829
|
-
}
|
|
830
|
-
)}`
|
|
831
|
-
);
|
|
832
|
-
this.lastWaitingLogThottled = Date.now();
|
|
833
|
-
}
|
|
834
|
-
} else {
|
|
835
|
-
if (checkpointCreated) {
|
|
836
|
-
this.logger.debug(`Created checkpoint at ${lsn} / ${newLastCheckpoint}`);
|
|
837
|
-
}
|
|
838
|
-
await this.autoActivate(lsn);
|
|
839
|
-
await this.db.notifyCheckpoint();
|
|
840
|
-
this.persisted_op = null;
|
|
841
|
-
this.last_checkpoint_lsn = lsn;
|
|
842
|
-
if (newLastCheckpoint != null) {
|
|
843
|
-
await this.sourceRecordStore.postCommitCleanup(newLastCheckpoint, this.logger);
|
|
844
|
-
}
|
|
845
|
-
}
|
|
846
|
-
return { checkpointBlocked, checkpointCreated };
|
|
847
|
-
}
|
|
848
|
-
|
|
849
|
-
/**
|
|
850
|
-
* Switch from processing -> active if relevant.
|
|
851
|
-
*
|
|
852
|
-
* Called on new commits.
|
|
853
|
-
*/
|
|
854
|
-
private async autoActivate(lsn: string) {
|
|
855
|
-
if (!this.needsActivation) {
|
|
856
|
-
return;
|
|
857
|
-
}
|
|
858
|
-
|
|
859
|
-
// Activate the batch, so it can start processing.
|
|
860
|
-
// This is done automatically when the first save() is called.
|
|
861
|
-
|
|
862
|
-
const session = this.session;
|
|
863
|
-
let activated = false;
|
|
864
|
-
await session.withTransaction(async () => {
|
|
865
|
-
const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
|
|
866
|
-
if (doc && doc.state == SyncRuleState.PROCESSING && doc.snapshot_done && doc.last_checkpoint != null) {
|
|
867
|
-
await this.db.sync_rules.updateOne(
|
|
868
|
-
{
|
|
869
|
-
_id: this.group_id
|
|
870
|
-
},
|
|
871
|
-
{
|
|
872
|
-
$set: {
|
|
873
|
-
state: storage.SyncRuleState.ACTIVE
|
|
874
|
-
}
|
|
875
|
-
},
|
|
876
|
-
{ session }
|
|
877
|
-
);
|
|
878
|
-
|
|
879
|
-
await this.db.sync_rules.updateMany(
|
|
880
|
-
{
|
|
881
|
-
_id: { $ne: this.group_id },
|
|
882
|
-
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
883
|
-
},
|
|
884
|
-
{
|
|
885
|
-
$set: {
|
|
886
|
-
state: storage.SyncRuleState.STOP
|
|
887
|
-
}
|
|
888
|
-
},
|
|
889
|
-
{ session }
|
|
762
|
+
event
|
|
763
|
+
})
|
|
890
764
|
);
|
|
891
|
-
activated = true;
|
|
892
|
-
} else if (doc?.state != SyncRuleState.PROCESSING) {
|
|
893
|
-
this.needsActivation = false;
|
|
894
765
|
}
|
|
895
|
-
});
|
|
896
|
-
if (activated) {
|
|
897
|
-
this.logger.info(`Activated new replication stream at ${lsn}`);
|
|
898
|
-
await this.db.notifyCheckpoint();
|
|
899
|
-
this.needsActivation = false;
|
|
900
|
-
}
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
async keepalive(lsn: string): Promise<CheckpointResult> {
|
|
904
|
-
return await this.commit(lsn, { createEmptyCheckpoints: true });
|
|
905
|
-
}
|
|
906
|
-
|
|
907
|
-
async setResumeLsn(lsn: string): Promise<void> {
|
|
908
|
-
const update: Partial<SyncRuleDocument> = {
|
|
909
|
-
snapshot_lsn: lsn
|
|
910
|
-
};
|
|
911
|
-
|
|
912
|
-
await this.db.sync_rules.updateOne(
|
|
913
|
-
{
|
|
914
|
-
_id: this.group_id
|
|
915
|
-
},
|
|
916
|
-
{
|
|
917
|
-
$set: update
|
|
918
|
-
},
|
|
919
|
-
{ session: this.session }
|
|
920
|
-
);
|
|
921
|
-
}
|
|
922
|
-
|
|
923
|
-
async save(record: storage.SaveOptions): Promise<storage.FlushedResult | null> {
|
|
924
|
-
const { after, before, sourceTable, tag } = record;
|
|
925
|
-
for (const event of this.getTableEvents(sourceTable)) {
|
|
926
|
-
this.iterateListeners((cb) =>
|
|
927
|
-
cb.replicationEvent?.({
|
|
928
|
-
batch: this,
|
|
929
|
-
table: sourceTable,
|
|
930
|
-
data: {
|
|
931
|
-
op: tag,
|
|
932
|
-
after: after && utils.isCompleteRow(this.storeCurrentData, after) ? after : undefined,
|
|
933
|
-
before: before && utils.isCompleteRow(this.storeCurrentData, before) ? before : undefined
|
|
934
|
-
},
|
|
935
|
-
event
|
|
936
|
-
})
|
|
937
|
-
);
|
|
938
766
|
}
|
|
939
767
|
|
|
940
768
|
/**
|
|
@@ -967,7 +795,7 @@ export abstract class MongoBucketBatch
|
|
|
967
795
|
|
|
968
796
|
await this.withTransaction(async () => {
|
|
969
797
|
for (let table of sourceTables) {
|
|
970
|
-
await this.db.commonSourceTables(this.
|
|
798
|
+
await this.db.commonSourceTables(this.replicationStreamId).deleteOne({ _id: mongoTableId(table.id) });
|
|
971
799
|
}
|
|
972
800
|
});
|
|
973
801
|
|
|
@@ -984,7 +812,7 @@ export abstract class MongoBucketBatch
|
|
|
984
812
|
}
|
|
985
813
|
|
|
986
814
|
if (last_op) {
|
|
987
|
-
this.
|
|
815
|
+
this.recordPersistedOp(last_op);
|
|
988
816
|
return {
|
|
989
817
|
flushed_op: last_op
|
|
990
818
|
};
|
|
@@ -1053,7 +881,7 @@ export abstract class MongoBucketBatch
|
|
|
1053
881
|
copy.snapshotStatus = snapshotStatus;
|
|
1054
882
|
|
|
1055
883
|
await this.withTransaction(async () => {
|
|
1056
|
-
await this.db.commonSourceTables(this.
|
|
884
|
+
await this.db.commonSourceTables(this.replicationStreamId).updateOne(
|
|
1057
885
|
{ _id: mongoTableId(table.id) },
|
|
1058
886
|
{
|
|
1059
887
|
$set: {
|
|
@@ -1071,80 +899,6 @@ export abstract class MongoBucketBatch
|
|
|
1071
899
|
return copy;
|
|
1072
900
|
}
|
|
1073
901
|
|
|
1074
|
-
async markAllSnapshotDone(no_checkpoint_before_lsn: string) {
|
|
1075
|
-
await this.db.sync_rules.updateOne(
|
|
1076
|
-
{
|
|
1077
|
-
_id: this.group_id
|
|
1078
|
-
},
|
|
1079
|
-
{
|
|
1080
|
-
$set: {
|
|
1081
|
-
snapshot_done: true,
|
|
1082
|
-
last_keepalive_ts: new Date()
|
|
1083
|
-
},
|
|
1084
|
-
$max: {
|
|
1085
|
-
no_checkpoint_before: no_checkpoint_before_lsn
|
|
1086
|
-
}
|
|
1087
|
-
},
|
|
1088
|
-
{ session: this.session }
|
|
1089
|
-
);
|
|
1090
|
-
}
|
|
1091
|
-
|
|
1092
|
-
async markTableSnapshotRequired(table: storage.SourceTable): Promise<void> {
|
|
1093
|
-
await this.db.sync_rules.updateOne(
|
|
1094
|
-
{
|
|
1095
|
-
_id: this.group_id
|
|
1096
|
-
},
|
|
1097
|
-
{
|
|
1098
|
-
$set: {
|
|
1099
|
-
snapshot_done: false
|
|
1100
|
-
}
|
|
1101
|
-
},
|
|
1102
|
-
{ session: this.session }
|
|
1103
|
-
);
|
|
1104
|
-
}
|
|
1105
|
-
|
|
1106
|
-
async markTableSnapshotDone(tables: storage.SourceTable[], no_checkpoint_before_lsn?: string) {
|
|
1107
|
-
const session = this.session;
|
|
1108
|
-
const ids = tables.map((table) => mongoTableId(table.id));
|
|
1109
|
-
|
|
1110
|
-
await this.withTransaction(async () => {
|
|
1111
|
-
await this.db.commonSourceTables(this.group_id).updateMany(
|
|
1112
|
-
{ _id: { $in: ids } },
|
|
1113
|
-
{
|
|
1114
|
-
$set: {
|
|
1115
|
-
snapshot_done: true
|
|
1116
|
-
},
|
|
1117
|
-
$unset: {
|
|
1118
|
-
snapshot_status: 1
|
|
1119
|
-
}
|
|
1120
|
-
},
|
|
1121
|
-
{ session }
|
|
1122
|
-
);
|
|
1123
|
-
|
|
1124
|
-
if (no_checkpoint_before_lsn != null) {
|
|
1125
|
-
await this.db.sync_rules.updateOne(
|
|
1126
|
-
{
|
|
1127
|
-
_id: this.group_id
|
|
1128
|
-
},
|
|
1129
|
-
{
|
|
1130
|
-
$set: {
|
|
1131
|
-
last_keepalive_ts: new Date()
|
|
1132
|
-
},
|
|
1133
|
-
$max: {
|
|
1134
|
-
no_checkpoint_before: no_checkpoint_before_lsn
|
|
1135
|
-
}
|
|
1136
|
-
},
|
|
1137
|
-
{ session: this.session }
|
|
1138
|
-
);
|
|
1139
|
-
}
|
|
1140
|
-
});
|
|
1141
|
-
return tables.map((table) => {
|
|
1142
|
-
const copy = table.clone();
|
|
1143
|
-
copy.snapshotComplete = true;
|
|
1144
|
-
return copy;
|
|
1145
|
-
});
|
|
1146
|
-
}
|
|
1147
|
-
|
|
1148
902
|
protected async clearError(): Promise<void> {
|
|
1149
903
|
// No need to clear an error more than once per batch, since an error would always result in restarting the batch.
|
|
1150
904
|
if (this.clearedError) {
|
|
@@ -1153,7 +907,7 @@ export abstract class MongoBucketBatch
|
|
|
1153
907
|
|
|
1154
908
|
await this.db.sync_rules.updateOne(
|
|
1155
909
|
{
|
|
1156
|
-
_id: this.
|
|
910
|
+
_id: this.replicationStreamId
|
|
1157
911
|
},
|
|
1158
912
|
{
|
|
1159
913
|
$set: {
|
|
@@ -1170,7 +924,7 @@ export abstract class MongoBucketBatch
|
|
|
1170
924
|
*/
|
|
1171
925
|
protected getTableEvents(table: storage.SourceTable): SqlEventDescriptor[] {
|
|
1172
926
|
return this.sync_rules.eventDescriptors.filter((evt) =>
|
|
1173
|
-
[...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table))
|
|
927
|
+
[...evt.getSourceTables()].some((sourceTable) => sourceTable.matches(table.ref))
|
|
1174
928
|
);
|
|
1175
929
|
}
|
|
1176
930
|
}
|