@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,11 +1,10 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { SqlEventDescriptor, SqliteRow, SqliteValue
|
|
2
|
+
import { HydratedSyncRules, SqlEventDescriptor, SqliteRow, SqliteValue } from '@powersync/service-sync-rules';
|
|
3
3
|
import * as bson from 'bson';
|
|
4
4
|
|
|
5
5
|
import {
|
|
6
6
|
BaseObserver,
|
|
7
7
|
container,
|
|
8
|
-
logger as defaultLogger,
|
|
9
8
|
ErrorCode,
|
|
10
9
|
errors,
|
|
11
10
|
Logger,
|
|
@@ -14,26 +13,27 @@ import {
|
|
|
14
13
|
} from '@powersync/lib-services-framework';
|
|
15
14
|
import {
|
|
16
15
|
BucketStorageMarkRecordUnavailable,
|
|
16
|
+
CheckpointResult,
|
|
17
17
|
deserializeBson,
|
|
18
18
|
InternalOpId,
|
|
19
19
|
isCompleteRow,
|
|
20
|
+
PerformanceTracer,
|
|
20
21
|
SaveOperationTag,
|
|
21
22
|
storage,
|
|
23
|
+
SyncRuleState,
|
|
22
24
|
utils
|
|
23
25
|
} from '@powersync/service-core';
|
|
24
26
|
import * as timers from 'node:timers/promises';
|
|
25
|
-
import {
|
|
26
|
-
import {
|
|
27
|
-
import {
|
|
27
|
+
import { mongoTableId } from '../../utils/util.js';
|
|
28
|
+
import { BucketDefinitionMapping } from './BucketDefinitionMapping.js';
|
|
29
|
+
import { PersistedBatch } from './common/PersistedBatch.js';
|
|
30
|
+
import { LoadedSourceRecord, SourceRecordStore } from './common/SourceRecordStore.js';
|
|
31
|
+
import type { VersionedPowerSyncMongo } from './db.js';
|
|
32
|
+
import { SyncRuleDocument } from './models.js';
|
|
33
|
+
import { MAX_ROW_SIZE } from './MongoBucketBatchShared.js';
|
|
28
34
|
import { MongoIdSequence } from './MongoIdSequence.js';
|
|
29
35
|
import { batchCreateCustomWriteCheckpoints } from './MongoWriteCheckpointAPI.js';
|
|
30
|
-
import {
|
|
31
|
-
import { PersistedBatch } from './PersistedBatch.js';
|
|
32
|
-
|
|
33
|
-
/**
|
|
34
|
-
* 15MB
|
|
35
|
-
*/
|
|
36
|
-
export const MAX_ROW_SIZE = 15 * 1024 * 1024;
|
|
36
|
+
import { OperationBatch, RecordOperation } from './OperationBatch.js';
|
|
37
37
|
|
|
38
38
|
// Currently, we can only have a single flush() at a time, since it locks the op_id sequence.
|
|
39
39
|
// While the MongoDB transaction retry mechanism handles this okay, using an in-process Mutex
|
|
@@ -43,15 +43,15 @@ export const MAX_ROW_SIZE = 15 * 1024 * 1024;
|
|
|
43
43
|
const replicationMutex = new utils.Mutex();
|
|
44
44
|
|
|
45
45
|
export interface MongoBucketBatchOptions {
|
|
46
|
-
db:
|
|
46
|
+
db: VersionedPowerSyncMongo;
|
|
47
47
|
syncRules: HydratedSyncRules;
|
|
48
48
|
groupId: number;
|
|
49
49
|
slotName: string;
|
|
50
50
|
lastCheckpointLsn: string | null;
|
|
51
51
|
keepaliveOp: InternalOpId | null;
|
|
52
|
-
noCheckpointBeforeLsn: string;
|
|
53
52
|
resumeFromLsn: string | null;
|
|
54
53
|
storeCurrentData: boolean;
|
|
54
|
+
mapping: BucketDefinitionMapping;
|
|
55
55
|
/**
|
|
56
56
|
* Set to true for initial replication.
|
|
57
57
|
*/
|
|
@@ -59,31 +59,35 @@ export interface MongoBucketBatchOptions {
|
|
|
59
59
|
|
|
60
60
|
markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
|
|
61
61
|
|
|
62
|
-
logger
|
|
62
|
+
logger: Logger;
|
|
63
|
+
tracer?: PerformanceTracer<'storage' | 'evaluate'>;
|
|
63
64
|
}
|
|
64
65
|
|
|
65
|
-
export class MongoBucketBatch
|
|
66
|
+
export abstract class MongoBucketBatch
|
|
66
67
|
extends BaseObserver<storage.BucketBatchStorageListener>
|
|
67
68
|
implements storage.BucketStorageBatch
|
|
68
69
|
{
|
|
69
|
-
|
|
70
|
+
protected logger: Logger;
|
|
70
71
|
|
|
71
72
|
private readonly client: mongo.MongoClient;
|
|
72
|
-
public readonly db:
|
|
73
|
+
public readonly db: VersionedPowerSyncMongo;
|
|
73
74
|
public readonly session: mongo.ClientSession;
|
|
74
75
|
private readonly sync_rules: HydratedSyncRules;
|
|
75
76
|
|
|
76
|
-
|
|
77
|
+
protected readonly group_id: number;
|
|
77
78
|
|
|
78
79
|
private readonly slot_name: string;
|
|
79
80
|
private readonly storeCurrentData: boolean;
|
|
80
81
|
private readonly skipExistingRows: boolean;
|
|
82
|
+
protected readonly mapping: BucketDefinitionMapping;
|
|
81
83
|
|
|
82
84
|
private batch: OperationBatch | null = null;
|
|
83
85
|
private write_checkpoint_batch: storage.CustomWriteCheckpointOptions[] = [];
|
|
84
86
|
private markRecordUnavailable: BucketStorageMarkRecordUnavailable | undefined;
|
|
85
87
|
private clearedError = false;
|
|
86
88
|
|
|
89
|
+
private tracer: PerformanceTracer<'storage' | 'evaluate'>;
|
|
90
|
+
|
|
87
91
|
/**
|
|
88
92
|
* Last LSN received associated with a checkpoint.
|
|
89
93
|
*
|
|
@@ -93,8 +97,6 @@ export class MongoBucketBatch
|
|
|
93
97
|
*/
|
|
94
98
|
private last_checkpoint_lsn: string | null = null;
|
|
95
99
|
|
|
96
|
-
private no_checkpoint_before_lsn: string;
|
|
97
|
-
|
|
98
100
|
private persisted_op: InternalOpId | null = null;
|
|
99
101
|
|
|
100
102
|
/**
|
|
@@ -118,22 +120,23 @@ export class MongoBucketBatch
|
|
|
118
120
|
|
|
119
121
|
constructor(options: MongoBucketBatchOptions) {
|
|
120
122
|
super();
|
|
121
|
-
this.logger = options.logger
|
|
123
|
+
this.logger = options.logger;
|
|
122
124
|
this.client = options.db.client;
|
|
123
125
|
this.db = options.db;
|
|
124
126
|
this.group_id = options.groupId;
|
|
125
127
|
this.last_checkpoint_lsn = options.lastCheckpointLsn;
|
|
126
|
-
this.no_checkpoint_before_lsn = options.noCheckpointBeforeLsn;
|
|
127
128
|
this.resumeFromLsn = options.resumeFromLsn;
|
|
128
129
|
this.session = this.client.startSession();
|
|
129
130
|
this.slot_name = options.slotName;
|
|
130
131
|
this.sync_rules = options.syncRules;
|
|
131
132
|
this.storeCurrentData = options.storeCurrentData;
|
|
133
|
+
this.mapping = options.mapping;
|
|
132
134
|
this.skipExistingRows = options.skipExistingRows;
|
|
133
135
|
this.markRecordUnavailable = options.markRecordUnavailable;
|
|
134
136
|
this.batch = new OperationBatch();
|
|
135
137
|
|
|
136
138
|
this.persisted_op = options.keepaliveOp ?? null;
|
|
139
|
+
this.tracer = options.tracer ?? new PerformanceTracer('MongoDB storage');
|
|
137
140
|
}
|
|
138
141
|
|
|
139
142
|
addCustomWriteCheckpoint(checkpoint: storage.BatchedCustomWriteCheckpointOptions): void {
|
|
@@ -147,9 +150,11 @@ export class MongoBucketBatch
|
|
|
147
150
|
return this.last_checkpoint_lsn;
|
|
148
151
|
}
|
|
149
152
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
+
protected abstract createPersistedBatch(writtenSize: number): PersistedBatch;
|
|
154
|
+
|
|
155
|
+
protected abstract get sourceRecordStore(): SourceRecordStore;
|
|
156
|
+
|
|
157
|
+
protected abstract cleanupDroppedSourceTables(sourceTables: storage.SourceTable[]): Promise<void>;
|
|
153
158
|
|
|
154
159
|
async flush(options?: storage.BatchBucketFlushOptions): Promise<storage.FlushedResult | null> {
|
|
155
160
|
let result: storage.FlushedResult | null = null;
|
|
@@ -169,6 +174,8 @@ export class MongoBucketBatch
|
|
|
169
174
|
let last_op: InternalOpId | null = null;
|
|
170
175
|
let resumeBatch: OperationBatch | null = null;
|
|
171
176
|
|
|
177
|
+
using _ = this.tracer.span('storage', 'flush');
|
|
178
|
+
|
|
172
179
|
await this.withReplicationTransaction(`Flushing ${batch?.length ?? 0} ops`, async (session, opSeq) => {
|
|
173
180
|
if (batch != null) {
|
|
174
181
|
resumeBatch = await this.replicateBatch(session, batch, opSeq, options);
|
|
@@ -202,6 +209,7 @@ export class MongoBucketBatch
|
|
|
202
209
|
options?: storage.BucketBatchCommitOptions
|
|
203
210
|
): Promise<OperationBatch | null> {
|
|
204
211
|
let sizes: Map<string, number> | undefined = undefined;
|
|
212
|
+
using _ = this.tracer.span('storage', 'replicate_batch');
|
|
205
213
|
if (this.storeCurrentData && !this.skipExistingRows) {
|
|
206
214
|
// We skip this step if we don't store current_data, since the sizes will
|
|
207
215
|
// always be small in that case.
|
|
@@ -216,32 +224,12 @@ export class MongoBucketBatch
|
|
|
216
224
|
// (automatically limited to 48MB(?) per batch by MongoDB). The issue is that it changes
|
|
217
225
|
// the order of processing, which then becomes really tricky to manage.
|
|
218
226
|
// This now takes 2+ queries, but doesn't have any issues with order of operations.
|
|
219
|
-
const sizeLookups
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
sizes = new Map<string, number>();
|
|
227
|
+
const sizeLookups = batch.batch.map((r) => ({
|
|
228
|
+
sourceTableId: mongoTableId(r.record.sourceTable.id),
|
|
229
|
+
replicaId: r.beforeId
|
|
230
|
+
}));
|
|
224
231
|
|
|
225
|
-
|
|
226
|
-
[
|
|
227
|
-
{
|
|
228
|
-
$match: {
|
|
229
|
-
_id: { $in: sizeLookups }
|
|
230
|
-
}
|
|
231
|
-
},
|
|
232
|
-
{
|
|
233
|
-
$project: {
|
|
234
|
-
_id: 1,
|
|
235
|
-
size: { $bsonSize: '$$ROOT' }
|
|
236
|
-
}
|
|
237
|
-
}
|
|
238
|
-
],
|
|
239
|
-
{ session }
|
|
240
|
-
);
|
|
241
|
-
for await (let doc of sizeCursor.stream()) {
|
|
242
|
-
const key = cacheKey(doc._id.t, doc._id.k);
|
|
243
|
-
sizes.set(key, doc.size);
|
|
244
|
-
}
|
|
232
|
+
sizes = await this.sourceRecordStore.loadSizes(session, sizeLookups);
|
|
245
233
|
}
|
|
246
234
|
|
|
247
235
|
// If set, we need to start a new transaction with this batch.
|
|
@@ -259,64 +247,65 @@ export class MongoBucketBatch
|
|
|
259
247
|
}
|
|
260
248
|
continue;
|
|
261
249
|
}
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
current_data_lookup.set(cacheKey(doc._id.t, doc._id.k), doc);
|
|
276
|
-
}
|
|
277
|
-
|
|
278
|
-
let persistedBatch: PersistedBatch | null = new PersistedBatch(this.group_id, transactionSize, {
|
|
279
|
-
logger: this.logger
|
|
280
|
-
});
|
|
281
|
-
|
|
250
|
+
using lookupSpan = this.tracer.span('storage', 'lookup');
|
|
251
|
+
const lookups = b.map((r) => ({
|
|
252
|
+
sourceTableId: mongoTableId(r.record.sourceTable.id),
|
|
253
|
+
replicaId: r.beforeId
|
|
254
|
+
}));
|
|
255
|
+
let sourceRecordLookup = await this.sourceRecordStore.loadDocuments(session, lookups, this.skipExistingRows);
|
|
256
|
+
lookupSpan.end();
|
|
257
|
+
|
|
258
|
+
let persistedBatch: PersistedBatch | null = this.createPersistedBatch(transactionSize);
|
|
259
|
+
|
|
260
|
+
// The current code structure makes it tricky to cleanly split this span from the one
|
|
261
|
+
// where fluhsing. So we manually end and re-create this span whenever we flush.
|
|
262
|
+
let evalSpan = this.tracer.span('evaluate');
|
|
282
263
|
for (let op of b) {
|
|
283
264
|
if (resumeBatch) {
|
|
284
265
|
resumeBatch.push(op);
|
|
285
266
|
continue;
|
|
286
267
|
}
|
|
287
|
-
const
|
|
288
|
-
if (
|
|
268
|
+
const sourceRecord = sourceRecordLookup.get(op.internalBeforeKey) ?? null;
|
|
269
|
+
if (sourceRecord != null) {
|
|
289
270
|
// If it will be used again later, it will be set again using nextData below
|
|
290
|
-
|
|
271
|
+
sourceRecordLookup.delete(op.internalBeforeKey);
|
|
291
272
|
}
|
|
292
|
-
const nextData = this.saveOperation(persistedBatch!, op,
|
|
273
|
+
const nextData = this.saveOperation(persistedBatch!, op, sourceRecord, op_seq);
|
|
293
274
|
if (nextData != null) {
|
|
294
275
|
// Update our current_data and size cache
|
|
295
|
-
|
|
296
|
-
sizes?.set(op.internalAfterKey!, nextData.data
|
|
276
|
+
sourceRecordLookup.set(op.internalAfterKey!, nextData);
|
|
277
|
+
sizes?.set(op.internalAfterKey!, nextData.data?.length() ?? 0);
|
|
297
278
|
}
|
|
298
279
|
|
|
299
280
|
if (persistedBatch!.shouldFlushTransaction()) {
|
|
281
|
+
evalSpan.end();
|
|
300
282
|
// Transaction is getting big.
|
|
301
283
|
// Flush, and resume in a new transaction.
|
|
302
|
-
|
|
284
|
+
using persistSpan = this.tracer.span('storage', 'persist_flush');
|
|
285
|
+
const { flushedAny } = await persistedBatch!.flush(this.session, options);
|
|
286
|
+
|
|
303
287
|
didFlush ||= flushedAny;
|
|
304
288
|
persistedBatch = null;
|
|
305
289
|
// Computing our current progress is a little tricky here, since
|
|
306
290
|
// we're stopping in the middle of a batch.
|
|
307
291
|
// We create a new batch, and push any remaining operations to it.
|
|
308
292
|
resumeBatch = new OperationBatch();
|
|
293
|
+
persistSpan.end();
|
|
294
|
+
evalSpan = this.tracer.span('evaluate');
|
|
309
295
|
}
|
|
310
296
|
}
|
|
297
|
+
evalSpan.end();
|
|
311
298
|
|
|
312
299
|
if (persistedBatch) {
|
|
313
300
|
transactionSize = persistedBatch.currentSize;
|
|
314
|
-
|
|
301
|
+
using _ = this.tracer.span('storage', 'persist_flush');
|
|
302
|
+
const { flushedAny } = await persistedBatch.flush(this.session, options);
|
|
315
303
|
didFlush ||= flushedAny;
|
|
316
304
|
}
|
|
317
305
|
}
|
|
318
306
|
|
|
319
307
|
if (didFlush) {
|
|
308
|
+
using _ = this.tracer.span('storage', 'clear_error');
|
|
320
309
|
await this.clearError();
|
|
321
310
|
}
|
|
322
311
|
|
|
@@ -326,7 +315,7 @@ export class MongoBucketBatch
|
|
|
326
315
|
private saveOperation(
|
|
327
316
|
batch: PersistedBatch,
|
|
328
317
|
operation: RecordOperation,
|
|
329
|
-
|
|
318
|
+
sourceRecord: LoadedSourceRecord | null,
|
|
330
319
|
opSeq: MongoIdSequence
|
|
331
320
|
) {
|
|
332
321
|
const record = operation.record;
|
|
@@ -335,16 +324,16 @@ export class MongoBucketBatch
|
|
|
335
324
|
let after = record.after;
|
|
336
325
|
const sourceTable = record.sourceTable;
|
|
337
326
|
|
|
338
|
-
let existing_buckets:
|
|
339
|
-
let new_buckets:
|
|
340
|
-
let existing_lookups:
|
|
341
|
-
let new_lookups:
|
|
327
|
+
let existing_buckets: LoadedSourceRecord['buckets'] = [];
|
|
328
|
+
let new_buckets: LoadedSourceRecord['buckets'] = [];
|
|
329
|
+
let existing_lookups: LoadedSourceRecord['lookups'] = [];
|
|
330
|
+
let new_lookups: LoadedSourceRecord['lookups'] = [];
|
|
342
331
|
|
|
343
|
-
const
|
|
332
|
+
const sourceTableId = mongoTableId(record.sourceTable.id);
|
|
344
333
|
|
|
345
334
|
if (this.skipExistingRows) {
|
|
346
335
|
if (record.tag == SaveOperationTag.INSERT) {
|
|
347
|
-
if (
|
|
336
|
+
if (sourceRecord != null) {
|
|
348
337
|
// Initial replication, and we already have the record.
|
|
349
338
|
// This may be a different version of the record, but streaming replication
|
|
350
339
|
// will take care of that.
|
|
@@ -357,7 +346,7 @@ export class MongoBucketBatch
|
|
|
357
346
|
}
|
|
358
347
|
|
|
359
348
|
if (record.tag == SaveOperationTag.UPDATE) {
|
|
360
|
-
const result =
|
|
349
|
+
const result = sourceRecord;
|
|
361
350
|
if (result == null) {
|
|
362
351
|
// Not an error if we re-apply a transaction
|
|
363
352
|
existing_buckets = [];
|
|
@@ -378,13 +367,13 @@ export class MongoBucketBatch
|
|
|
378
367
|
} else {
|
|
379
368
|
existing_buckets = result.buckets;
|
|
380
369
|
existing_lookups = result.lookups;
|
|
381
|
-
if (this.storeCurrentData) {
|
|
382
|
-
const data = deserializeBson(
|
|
370
|
+
if (this.storeCurrentData && result.data != null) {
|
|
371
|
+
const data = deserializeBson(result.data.buffer) as SqliteRow;
|
|
383
372
|
after = storage.mergeToast<SqliteValue>(after!, data);
|
|
384
373
|
}
|
|
385
374
|
}
|
|
386
375
|
} else if (record.tag == SaveOperationTag.DELETE) {
|
|
387
|
-
const result =
|
|
376
|
+
const result = sourceRecord;
|
|
388
377
|
if (result == null) {
|
|
389
378
|
// Not an error if we re-apply a transaction
|
|
390
379
|
existing_buckets = [];
|
|
@@ -401,9 +390,9 @@ export class MongoBucketBatch
|
|
|
401
390
|
}
|
|
402
391
|
}
|
|
403
392
|
|
|
404
|
-
let afterData: bson.Binary |
|
|
393
|
+
let afterData: bson.Binary | null = null;
|
|
405
394
|
if (afterId != null && !this.storeCurrentData) {
|
|
406
|
-
afterData =
|
|
395
|
+
afterData = null;
|
|
407
396
|
} else if (afterId != null) {
|
|
408
397
|
try {
|
|
409
398
|
// This will fail immediately if the record is > 16MB.
|
|
@@ -501,13 +490,7 @@ export class MongoBucketBatch
|
|
|
501
490
|
table: sourceTable,
|
|
502
491
|
before_buckets: existing_buckets
|
|
503
492
|
});
|
|
504
|
-
new_buckets =
|
|
505
|
-
return {
|
|
506
|
-
bucket: e.bucket,
|
|
507
|
-
table: e.table,
|
|
508
|
-
id: e.id
|
|
509
|
-
};
|
|
510
|
-
});
|
|
493
|
+
new_buckets = this.sourceRecordStore.mapEvaluatedBuckets(evaluated);
|
|
511
494
|
}
|
|
512
495
|
|
|
513
496
|
if (sourceTable.syncParameters) {
|
|
@@ -540,40 +523,46 @@ export class MongoBucketBatch
|
|
|
540
523
|
evaluated: paramEvaluated,
|
|
541
524
|
existing_lookups
|
|
542
525
|
});
|
|
543
|
-
new_lookups =
|
|
544
|
-
return storage.serializeLookup(p.lookup);
|
|
545
|
-
});
|
|
526
|
+
new_lookups = this.sourceRecordStore.mapParameterLookups(paramEvaluated);
|
|
546
527
|
}
|
|
547
528
|
}
|
|
548
529
|
|
|
549
|
-
let result:
|
|
530
|
+
let result: LoadedSourceRecord | null = null;
|
|
550
531
|
|
|
551
532
|
// 5. TOAST: Update current data and bucket list.
|
|
552
533
|
if (afterId) {
|
|
553
534
|
// Insert or update
|
|
554
|
-
|
|
555
|
-
|
|
535
|
+
batch.upsertCurrentData({
|
|
536
|
+
sourceTableId,
|
|
537
|
+
replicaId: afterId,
|
|
556
538
|
data: afterData,
|
|
557
539
|
buckets: new_buckets,
|
|
558
540
|
lookups: new_lookups
|
|
559
541
|
});
|
|
560
542
|
result = {
|
|
561
|
-
|
|
562
|
-
|
|
543
|
+
sourceTableId,
|
|
544
|
+
replicaId: afterId,
|
|
545
|
+
data: afterData,
|
|
563
546
|
buckets: new_buckets,
|
|
564
|
-
lookups: new_lookups
|
|
547
|
+
lookups: new_lookups,
|
|
548
|
+
cacheKey: operation.internalAfterKey!
|
|
565
549
|
};
|
|
566
550
|
}
|
|
567
551
|
|
|
568
552
|
if (afterId == null || !storage.replicaIdEquals(beforeId, afterId)) {
|
|
569
553
|
// Either a delete (afterId == null), or replaced the old replication id
|
|
570
|
-
|
|
554
|
+
// Note that this is a soft delete.
|
|
555
|
+
// We don't specifically need a new or unique op_id here, but it must be greater than the
|
|
556
|
+
// last checkpoint, so we use next().
|
|
557
|
+
batch.softDeleteCurrentData(sourceTableId, beforeId, opSeq.next());
|
|
571
558
|
}
|
|
572
559
|
return result;
|
|
573
560
|
}
|
|
574
561
|
|
|
575
562
|
private async withTransaction(cb: () => Promise<void>) {
|
|
563
|
+
using lockSpan = this.tracer.span('storage', 'internal_lock');
|
|
576
564
|
await replicationMutex.exclusiveLock(async () => {
|
|
565
|
+
lockSpan.end();
|
|
577
566
|
await this.session.withTransaction(
|
|
578
567
|
async () => {
|
|
579
568
|
try {
|
|
@@ -584,7 +573,9 @@ export class MongoBucketBatch
|
|
|
584
573
|
} else {
|
|
585
574
|
this.logger.warn('Transaction error', e as Error);
|
|
586
575
|
}
|
|
587
|
-
|
|
576
|
+
const delay = Math.random() * 50;
|
|
577
|
+
using _ = this.tracer.span('storage', 'retry_delay');
|
|
578
|
+
await timers.setTimeout(delay);
|
|
588
579
|
throw e;
|
|
589
580
|
}
|
|
590
581
|
},
|
|
@@ -664,75 +655,28 @@ export class MongoBucketBatch
|
|
|
664
655
|
}
|
|
665
656
|
|
|
666
657
|
async [Symbol.asyncDispose]() {
|
|
658
|
+
if (this.batch != null || this.write_checkpoint_batch.length > 0) {
|
|
659
|
+
// We don't error here, since:
|
|
660
|
+
// 1. In error states, this is expected (we can't distinguish between disposing after success or error).
|
|
661
|
+
// 2. SuppressedError is messy to deal with.
|
|
662
|
+
this.logger.warn('Disposing writer with unflushed changes');
|
|
663
|
+
}
|
|
667
664
|
await this.session.endSession();
|
|
668
665
|
super.clearListeners();
|
|
669
666
|
}
|
|
670
667
|
|
|
668
|
+
async dispose() {
|
|
669
|
+
await this[Symbol.asyncDispose]();
|
|
670
|
+
}
|
|
671
|
+
|
|
671
672
|
private lastWaitingLogThottled = 0;
|
|
672
673
|
|
|
673
|
-
async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<
|
|
674
|
+
async commit(lsn: string, options?: storage.BucketBatchCommitOptions): Promise<CheckpointResult> {
|
|
674
675
|
const { createEmptyCheckpoints } = { ...storage.DEFAULT_BUCKET_BATCH_COMMIT_OPTIONS, ...options };
|
|
675
676
|
|
|
676
677
|
await this.flush(options);
|
|
677
678
|
|
|
678
|
-
if (this.last_checkpoint_lsn != null && lsn < this.last_checkpoint_lsn) {
|
|
679
|
-
// When re-applying transactions, don't create a new checkpoint until
|
|
680
|
-
// we are past the last transaction.
|
|
681
|
-
this.logger.info(`Re-applied transaction ${lsn} - skipping checkpoint`);
|
|
682
|
-
// Cannot create a checkpoint yet - return false
|
|
683
|
-
return false;
|
|
684
|
-
}
|
|
685
|
-
if (lsn < this.no_checkpoint_before_lsn) {
|
|
686
|
-
if (Date.now() - this.lastWaitingLogThottled > 5_000) {
|
|
687
|
-
this.logger.info(
|
|
688
|
-
`Waiting until ${this.no_checkpoint_before_lsn} before creating checkpoint, currently at ${lsn}. Persisted op: ${this.persisted_op}`
|
|
689
|
-
);
|
|
690
|
-
this.lastWaitingLogThottled = Date.now();
|
|
691
|
-
}
|
|
692
|
-
|
|
693
|
-
// Edge case: During initial replication, we have a no_checkpoint_before_lsn set,
|
|
694
|
-
// and don't actually commit the snapshot.
|
|
695
|
-
// The first commit can happen from an implicit keepalive message.
|
|
696
|
-
// That needs the persisted_op to get an accurate checkpoint, so
|
|
697
|
-
// we persist that in keepalive_op.
|
|
698
|
-
|
|
699
|
-
await this.db.sync_rules.updateOne(
|
|
700
|
-
{
|
|
701
|
-
_id: this.group_id
|
|
702
|
-
},
|
|
703
|
-
{
|
|
704
|
-
$set: {
|
|
705
|
-
keepalive_op: this.persisted_op == null ? null : String(this.persisted_op)
|
|
706
|
-
}
|
|
707
|
-
},
|
|
708
|
-
{ session: this.session }
|
|
709
|
-
);
|
|
710
|
-
await this.db.notifyCheckpoint();
|
|
711
|
-
|
|
712
|
-
// Cannot create a checkpoint yet - return false
|
|
713
|
-
return false;
|
|
714
|
-
}
|
|
715
|
-
|
|
716
|
-
if (!createEmptyCheckpoints && this.persisted_op == null) {
|
|
717
|
-
// Nothing to commit - also return true
|
|
718
|
-
await this.autoActivate(lsn);
|
|
719
|
-
return true;
|
|
720
|
-
}
|
|
721
|
-
|
|
722
679
|
const now = new Date();
|
|
723
|
-
const update: Partial<SyncRuleDocument> = {
|
|
724
|
-
last_checkpoint_lsn: lsn,
|
|
725
|
-
last_checkpoint_ts: now,
|
|
726
|
-
last_keepalive_ts: now,
|
|
727
|
-
snapshot_done: true,
|
|
728
|
-
last_fatal_error: null,
|
|
729
|
-
last_fatal_error_ts: null,
|
|
730
|
-
keepalive_op: null
|
|
731
|
-
};
|
|
732
|
-
|
|
733
|
-
if (this.persisted_op != null) {
|
|
734
|
-
update.last_checkpoint = this.persisted_op;
|
|
735
|
-
}
|
|
736
680
|
|
|
737
681
|
// Mark relevant write checkpoints as "processed".
|
|
738
682
|
// This makes it easier to identify write checkpoints that are "valid" in order.
|
|
@@ -751,21 +695,155 @@ export class MongoBucketBatch
|
|
|
751
695
|
}
|
|
752
696
|
);
|
|
753
697
|
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
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']
|
|
761
|
+
},
|
|
762
|
+
last_keepalive_ts: { $literal: now },
|
|
763
|
+
last_fatal_error: { $literal: null },
|
|
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
|
+
],
|
|
758
777
|
{
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
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
|
+
}
|
|
763
789
|
);
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
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 };
|
|
769
847
|
}
|
|
770
848
|
|
|
771
849
|
/**
|
|
@@ -785,7 +863,7 @@ export class MongoBucketBatch
|
|
|
785
863
|
let activated = false;
|
|
786
864
|
await session.withTransaction(async () => {
|
|
787
865
|
const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
|
|
788
|
-
if (doc && doc.state ==
|
|
866
|
+
if (doc && doc.state == SyncRuleState.PROCESSING && doc.snapshot_done && doc.last_checkpoint != null) {
|
|
789
867
|
await this.db.sync_rules.updateOne(
|
|
790
868
|
{
|
|
791
869
|
_id: this.group_id
|
|
@@ -811,68 +889,19 @@ export class MongoBucketBatch
|
|
|
811
889
|
{ session }
|
|
812
890
|
);
|
|
813
891
|
activated = true;
|
|
892
|
+
} else if (doc?.state != SyncRuleState.PROCESSING) {
|
|
893
|
+
this.needsActivation = false;
|
|
814
894
|
}
|
|
815
895
|
});
|
|
816
896
|
if (activated) {
|
|
817
|
-
this.logger.info(`Activated new
|
|
897
|
+
this.logger.info(`Activated new replication stream at ${lsn}`);
|
|
818
898
|
await this.db.notifyCheckpoint();
|
|
899
|
+
this.needsActivation = false;
|
|
819
900
|
}
|
|
820
|
-
this.needsActivation = false;
|
|
821
901
|
}
|
|
822
902
|
|
|
823
|
-
async keepalive(lsn: string): Promise<
|
|
824
|
-
|
|
825
|
-
// No-op
|
|
826
|
-
return false;
|
|
827
|
-
}
|
|
828
|
-
|
|
829
|
-
if (lsn < this.no_checkpoint_before_lsn) {
|
|
830
|
-
return false;
|
|
831
|
-
}
|
|
832
|
-
|
|
833
|
-
if (this.persisted_op != null) {
|
|
834
|
-
// The commit may have been skipped due to "no_checkpoint_before_lsn".
|
|
835
|
-
// Apply it now if relevant
|
|
836
|
-
this.logger.info(`Commit due to keepalive at ${lsn} / ${this.persisted_op}`);
|
|
837
|
-
return await this.commit(lsn);
|
|
838
|
-
}
|
|
839
|
-
|
|
840
|
-
await this.db.write_checkpoints.updateMany(
|
|
841
|
-
{
|
|
842
|
-
processed_at_lsn: null,
|
|
843
|
-
'lsns.1': { $lte: lsn }
|
|
844
|
-
},
|
|
845
|
-
{
|
|
846
|
-
$set: {
|
|
847
|
-
processed_at_lsn: lsn
|
|
848
|
-
}
|
|
849
|
-
},
|
|
850
|
-
{
|
|
851
|
-
session: this.session
|
|
852
|
-
}
|
|
853
|
-
);
|
|
854
|
-
|
|
855
|
-
await this.db.sync_rules.updateOne(
|
|
856
|
-
{
|
|
857
|
-
_id: this.group_id
|
|
858
|
-
},
|
|
859
|
-
{
|
|
860
|
-
$set: {
|
|
861
|
-
last_checkpoint_lsn: lsn,
|
|
862
|
-
snapshot_done: true,
|
|
863
|
-
last_fatal_error: null,
|
|
864
|
-
last_fatal_error_ts: null,
|
|
865
|
-
last_keepalive_ts: new Date()
|
|
866
|
-
},
|
|
867
|
-
$unset: { snapshot_lsn: 1 }
|
|
868
|
-
},
|
|
869
|
-
{ session: this.session }
|
|
870
|
-
);
|
|
871
|
-
await this.autoActivate(lsn);
|
|
872
|
-
await this.db.notifyCheckpoint();
|
|
873
|
-
this.last_checkpoint_lsn = lsn;
|
|
874
|
-
|
|
875
|
-
return true;
|
|
903
|
+
async keepalive(lsn: string): Promise<CheckpointResult> {
|
|
904
|
+
return await this.commit(lsn, { createEmptyCheckpoints: true });
|
|
876
905
|
}
|
|
877
906
|
|
|
878
907
|
async setResumeLsn(lsn: string): Promise<void> {
|
|
@@ -938,9 +967,11 @@ export class MongoBucketBatch
|
|
|
938
967
|
|
|
939
968
|
await this.withTransaction(async () => {
|
|
940
969
|
for (let table of sourceTables) {
|
|
941
|
-
await this.db.
|
|
970
|
+
await this.db.commonSourceTables(this.group_id).deleteOne({ _id: mongoTableId(table.id) });
|
|
942
971
|
}
|
|
943
972
|
});
|
|
973
|
+
|
|
974
|
+
await this.cleanupDroppedSourceTables(sourceTables);
|
|
944
975
|
return result;
|
|
945
976
|
}
|
|
946
977
|
|
|
@@ -972,21 +1003,10 @@ export class MongoBucketBatch
|
|
|
972
1003
|
let lastBatchCount = BATCH_LIMIT;
|
|
973
1004
|
while (lastBatchCount == BATCH_LIMIT) {
|
|
974
1005
|
await this.withReplicationTransaction(`Truncate ${sourceTable.qualifiedName}`, async (session, opSeq) => {
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
|
|
979
|
-
const cursor = this.db.current_data.find(current_data_filter, {
|
|
980
|
-
projection: {
|
|
981
|
-
_id: 1,
|
|
982
|
-
buckets: 1,
|
|
983
|
-
lookups: 1
|
|
984
|
-
},
|
|
985
|
-
limit: BATCH_LIMIT,
|
|
986
|
-
session: session
|
|
987
|
-
});
|
|
988
|
-
const batch = await cursor.toArray();
|
|
989
|
-
const persistedBatch = new PersistedBatch(this.group_id, 0, { logger: this.logger });
|
|
1006
|
+
using evalSpan = this.tracer.span('evaluate');
|
|
1007
|
+
const sourceTableId = mongoTableId(sourceTable.id);
|
|
1008
|
+
const batch = await this.sourceRecordStore.loadTruncateBatch(session, sourceTableId, BATCH_LIMIT);
|
|
1009
|
+
const persistedBatch = this.createPersistedBatch(0);
|
|
990
1010
|
|
|
991
1011
|
for (let value of batch) {
|
|
992
1012
|
persistedBatch.saveBucketData({
|
|
@@ -994,19 +1014,23 @@ export class MongoBucketBatch
|
|
|
994
1014
|
before_buckets: value.buckets,
|
|
995
1015
|
evaluated: [],
|
|
996
1016
|
table: sourceTable,
|
|
997
|
-
sourceKey: value.
|
|
1017
|
+
sourceKey: value.replicaId
|
|
998
1018
|
});
|
|
999
1019
|
persistedBatch.saveParameterData({
|
|
1000
1020
|
op_seq: opSeq,
|
|
1001
1021
|
existing_lookups: value.lookups,
|
|
1002
1022
|
evaluated: [],
|
|
1003
1023
|
sourceTable: sourceTable,
|
|
1004
|
-
sourceKey: value.
|
|
1024
|
+
sourceKey: value.replicaId
|
|
1005
1025
|
});
|
|
1006
1026
|
|
|
1007
|
-
|
|
1027
|
+
// Since this is not from streaming replication, we can do a hard delete
|
|
1028
|
+
persistedBatch.hardDeleteCurrentData(sourceTableId, value.replicaId);
|
|
1008
1029
|
}
|
|
1009
|
-
|
|
1030
|
+
evalSpan.end();
|
|
1031
|
+
|
|
1032
|
+
using _ = this.tracer.span('storage', 'persist_flush');
|
|
1033
|
+
await persistedBatch.flush(session);
|
|
1010
1034
|
lastBatchCount = batch.length;
|
|
1011
1035
|
|
|
1012
1036
|
last_op = opSeq.last();
|
|
@@ -1029,8 +1053,8 @@ export class MongoBucketBatch
|
|
|
1029
1053
|
copy.snapshotStatus = snapshotStatus;
|
|
1030
1054
|
|
|
1031
1055
|
await this.withTransaction(async () => {
|
|
1032
|
-
await this.db.
|
|
1033
|
-
{ _id: table.id },
|
|
1056
|
+
await this.db.commonSourceTables(this.group_id).updateOne(
|
|
1057
|
+
{ _id: mongoTableId(table.id) },
|
|
1034
1058
|
{
|
|
1035
1059
|
$set: {
|
|
1036
1060
|
snapshot_status: {
|
|
@@ -1047,12 +1071,44 @@ export class MongoBucketBatch
|
|
|
1047
1071
|
return copy;
|
|
1048
1072
|
}
|
|
1049
1073
|
|
|
1050
|
-
async
|
|
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) {
|
|
1051
1107
|
const session = this.session;
|
|
1052
|
-
const ids = tables.map((table) => table.id);
|
|
1108
|
+
const ids = tables.map((table) => mongoTableId(table.id));
|
|
1053
1109
|
|
|
1054
1110
|
await this.withTransaction(async () => {
|
|
1055
|
-
await this.db.
|
|
1111
|
+
await this.db.commonSourceTables(this.group_id).updateMany(
|
|
1056
1112
|
{ _id: { $in: ids } },
|
|
1057
1113
|
{
|
|
1058
1114
|
$set: {
|
|
@@ -1065,17 +1121,17 @@ export class MongoBucketBatch
|
|
|
1065
1121
|
{ session }
|
|
1066
1122
|
);
|
|
1067
1123
|
|
|
1068
|
-
if (no_checkpoint_before_lsn
|
|
1069
|
-
this.no_checkpoint_before_lsn = no_checkpoint_before_lsn;
|
|
1070
|
-
|
|
1124
|
+
if (no_checkpoint_before_lsn != null) {
|
|
1071
1125
|
await this.db.sync_rules.updateOne(
|
|
1072
1126
|
{
|
|
1073
1127
|
_id: this.group_id
|
|
1074
1128
|
},
|
|
1075
1129
|
{
|
|
1076
1130
|
$set: {
|
|
1077
|
-
no_checkpoint_before: no_checkpoint_before_lsn,
|
|
1078
1131
|
last_keepalive_ts: new Date()
|
|
1132
|
+
},
|
|
1133
|
+
$max: {
|
|
1134
|
+
no_checkpoint_before: no_checkpoint_before_lsn
|
|
1079
1135
|
}
|
|
1080
1136
|
},
|
|
1081
1137
|
{ session: this.session }
|
|
@@ -1118,7 +1174,3 @@ export class MongoBucketBatch
|
|
|
1118
1174
|
);
|
|
1119
1175
|
}
|
|
1120
1176
|
}
|
|
1121
|
-
|
|
1122
|
-
export function currentBucketKey(b: CurrentBucket) {
|
|
1123
|
-
return `${b.bucket}/${b.table}/${b.id}`;
|
|
1124
|
-
}
|