@powersync/service-module-mongodb-storage 0.13.2 → 0.15.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 +51 -0
- package/dist/migrations/db/migrations/1770213298299-storage-version.d.ts +3 -0
- package/dist/migrations/db/migrations/1770213298299-storage-version.js +29 -0
- package/dist/migrations/db/migrations/1770213298299-storage-version.js.map +1 -0
- package/dist/storage/MongoBucketStorage.d.ts +7 -15
- package/dist/storage/MongoBucketStorage.js +28 -53
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +12 -11
- package/dist/storage/implementation/MongoBucketBatch.js +199 -127
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +8 -5
- package/dist/storage/implementation/MongoChecksums.js +8 -4
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +2 -2
- package/dist/storage/implementation/MongoCompactor.js +52 -26
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +2 -2
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +2 -12
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +20 -25
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +7 -4
- package/dist/storage/implementation/MongoSyncBucketStorage.js +11 -8
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +3 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +4 -4
- 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/PersistedBatch.d.ts +11 -4
- package/dist/storage/implementation/PersistedBatch.js +42 -11
- package/dist/storage/implementation/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +35 -1
- package/dist/storage/implementation/db.js +99 -0
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +25 -1
- package/dist/storage/implementation/models.js +10 -1
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/storage-index.d.ts +0 -1
- package/dist/storage/storage-index.js +0 -1
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/utils/test-utils.d.ts +7 -5
- package/dist/utils/test-utils.js +17 -14
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +2 -1
- package/dist/utils/util.js +15 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +7 -7
- package/src/migrations/db/migrations/1770213298299-storage-version.ts +44 -0
- package/src/storage/MongoBucketStorage.ts +44 -61
- package/src/storage/implementation/MongoBucketBatch.ts +253 -177
- package/src/storage/implementation/MongoChecksums.ts +19 -9
- package/src/storage/implementation/MongoCompactor.ts +62 -31
- package/src/storage/implementation/MongoParameterCompactor.ts +3 -3
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +20 -34
- package/src/storage/implementation/MongoSyncBucketStorage.ts +32 -17
- package/src/storage/implementation/MongoSyncRulesLock.ts +3 -3
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +4 -4
- package/src/storage/implementation/OperationBatch.ts +3 -2
- package/src/storage/implementation/PersistedBatch.ts +42 -11
- package/src/storage/implementation/db.ts +129 -1
- package/src/storage/implementation/models.ts +39 -1
- package/src/storage/storage-index.ts +0 -1
- package/src/utils/test-utils.ts +18 -16
- package/src/utils/util.ts +17 -2
- package/test/src/__snapshots__/storage.test.ts.snap +198 -22
- package/test/src/__snapshots__/storage_compacting.test.ts.snap +17 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +2211 -21
- package/test/src/storage.test.ts +9 -7
- package/test/src/storage_compacting.test.ts +33 -24
- package/test/src/storage_sync.test.ts +31 -15
- package/test/src/util.ts +4 -1
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -10
- package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -17
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -20
|
@@ -2,6 +2,7 @@ import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
|
2
2
|
import {
|
|
3
3
|
addPartialChecksums,
|
|
4
4
|
bson,
|
|
5
|
+
BucketChecksumRequest,
|
|
5
6
|
BucketChecksum,
|
|
6
7
|
ChecksumCache,
|
|
7
8
|
ChecksumMap,
|
|
@@ -12,7 +13,8 @@ import {
|
|
|
12
13
|
PartialChecksumMap,
|
|
13
14
|
PartialOrFullChecksum
|
|
14
15
|
} from '@powersync/service-core';
|
|
15
|
-
import {
|
|
16
|
+
import { VersionedPowerSyncMongo } from './db.js';
|
|
17
|
+
import { StorageConfig } from './models.js';
|
|
16
18
|
|
|
17
19
|
/**
|
|
18
20
|
* Checksum calculation options, primarily for tests.
|
|
@@ -27,6 +29,8 @@ export interface MongoChecksumOptions {
|
|
|
27
29
|
* Limit on the number of documents to calculate a checksum on at a time.
|
|
28
30
|
*/
|
|
29
31
|
operationBatchLimit?: number;
|
|
32
|
+
|
|
33
|
+
storageConfig: StorageConfig;
|
|
30
34
|
}
|
|
31
35
|
|
|
32
36
|
const DEFAULT_BUCKET_BATCH_LIMIT = 200;
|
|
@@ -43,12 +47,15 @@ const DEFAULT_OPERATION_BATCH_LIMIT = 50_000;
|
|
|
43
47
|
*/
|
|
44
48
|
export class MongoChecksums {
|
|
45
49
|
private _cache: ChecksumCache | undefined;
|
|
50
|
+
private readonly storageConfig: StorageConfig;
|
|
46
51
|
|
|
47
52
|
constructor(
|
|
48
|
-
private db:
|
|
53
|
+
private db: VersionedPowerSyncMongo,
|
|
49
54
|
private group_id: number,
|
|
50
|
-
private options
|
|
51
|
-
) {
|
|
55
|
+
private options: MongoChecksumOptions
|
|
56
|
+
) {
|
|
57
|
+
this.storageConfig = options.storageConfig;
|
|
58
|
+
}
|
|
52
59
|
|
|
53
60
|
/**
|
|
54
61
|
* Lazy-instantiated cache.
|
|
@@ -68,7 +75,7 @@ export class MongoChecksums {
|
|
|
68
75
|
* Calculate checksums, utilizing the cache for partial checkums, and querying the remainder from
|
|
69
76
|
* the database (bucket_state + bucket_data).
|
|
70
77
|
*/
|
|
71
|
-
async getChecksums(checkpoint: InternalOpId, buckets:
|
|
78
|
+
async getChecksums(checkpoint: InternalOpId, buckets: BucketChecksumRequest[]): Promise<ChecksumMap> {
|
|
72
79
|
return this.cache.getChecksumMap(checkpoint, buckets);
|
|
73
80
|
}
|
|
74
81
|
|
|
@@ -222,6 +229,11 @@ export class MongoChecksums {
|
|
|
222
229
|
});
|
|
223
230
|
}
|
|
224
231
|
|
|
232
|
+
// Historically, checksum may be stored as 'int' or 'double'.
|
|
233
|
+
// More recently, this should be a 'long'.
|
|
234
|
+
// $toLong ensures that we always sum it as a long, avoiding inaccuracies in the calculations.
|
|
235
|
+
const checksumLong = this.storageConfig.longChecksums ? '$checksum' : { $toLong: '$checksum' };
|
|
236
|
+
|
|
225
237
|
// Aggregate over a max of `batchLimit` operations at a time.
|
|
226
238
|
// Let's say we have 3 buckets (A, B, C), each with 10 operations, and our batch limit is 12.
|
|
227
239
|
// Then we'll do three batches:
|
|
@@ -245,10 +257,7 @@ export class MongoChecksums {
|
|
|
245
257
|
{
|
|
246
258
|
$group: {
|
|
247
259
|
_id: '$_id.b',
|
|
248
|
-
|
|
249
|
-
// More recently, this should be a 'long'.
|
|
250
|
-
// $toLong ensures that we always sum it as a long, avoiding inaccuracies in the calculations.
|
|
251
|
-
checksum_total: { $sum: { $toLong: '$checksum' } },
|
|
260
|
+
checksum_total: { $sum: checksumLong },
|
|
252
261
|
count: { $sum: 1 },
|
|
253
262
|
has_clear_op: {
|
|
254
263
|
$max: {
|
|
@@ -290,6 +299,7 @@ export class MongoChecksums {
|
|
|
290
299
|
const req = requests.get(bucket);
|
|
291
300
|
requests.set(bucket, {
|
|
292
301
|
bucket,
|
|
302
|
+
source: req!.source,
|
|
293
303
|
start: doc.last_op,
|
|
294
304
|
end: req!.end
|
|
295
305
|
});
|
|
@@ -9,7 +9,7 @@ import {
|
|
|
9
9
|
utils
|
|
10
10
|
} from '@powersync/service-core';
|
|
11
11
|
|
|
12
|
-
import {
|
|
12
|
+
import { VersionedPowerSyncMongo } from './db.js';
|
|
13
13
|
import { BucketDataDocument, BucketDataKey, BucketStateDocument } from './models.js';
|
|
14
14
|
import { MongoSyncBucketStorage } from './MongoSyncBucketStorage.js';
|
|
15
15
|
import { cacheKey } from './OperationBatch.js';
|
|
@@ -63,6 +63,7 @@ const DEFAULT_MOVE_BATCH_LIMIT = 2000;
|
|
|
63
63
|
const DEFAULT_MOVE_BATCH_QUERY_LIMIT = 10_000;
|
|
64
64
|
const DEFAULT_MIN_BUCKET_CHANGES = 10;
|
|
65
65
|
const DEFAULT_MIN_CHANGE_RATIO = 0.1;
|
|
66
|
+
const DIRTY_BUCKET_SCAN_BATCH_SIZE = 2_000;
|
|
66
67
|
|
|
67
68
|
/** This default is primarily for tests. */
|
|
68
69
|
const DEFAULT_MEMORY_LIMIT_MB = 64;
|
|
@@ -84,19 +85,19 @@ export class MongoCompactor {
|
|
|
84
85
|
|
|
85
86
|
constructor(
|
|
86
87
|
private storage: MongoSyncBucketStorage,
|
|
87
|
-
private db:
|
|
88
|
-
options
|
|
88
|
+
private db: VersionedPowerSyncMongo,
|
|
89
|
+
options: MongoCompactOptions
|
|
89
90
|
) {
|
|
90
91
|
this.group_id = storage.group_id;
|
|
91
|
-
this.idLimitBytes = (options
|
|
92
|
-
this.moveBatchLimit = options
|
|
93
|
-
this.moveBatchQueryLimit = options
|
|
94
|
-
this.clearBatchLimit = options
|
|
95
|
-
this.minBucketChanges = options
|
|
96
|
-
this.minChangeRatio = options
|
|
97
|
-
this.maxOpId = options
|
|
98
|
-
this.buckets = options
|
|
99
|
-
this.signal = options
|
|
92
|
+
this.idLimitBytes = (options.memoryLimitMB ?? DEFAULT_MEMORY_LIMIT_MB) * 1024 * 1024;
|
|
93
|
+
this.moveBatchLimit = options.moveBatchLimit ?? DEFAULT_MOVE_BATCH_LIMIT;
|
|
94
|
+
this.moveBatchQueryLimit = options.moveBatchQueryLimit ?? DEFAULT_MOVE_BATCH_QUERY_LIMIT;
|
|
95
|
+
this.clearBatchLimit = options.clearBatchLimit ?? DEFAULT_CLEAR_BATCH_LIMIT;
|
|
96
|
+
this.minBucketChanges = options.minBucketChanges ?? DEFAULT_MIN_BUCKET_CHANGES;
|
|
97
|
+
this.minChangeRatio = options.minChangeRatio ?? DEFAULT_MIN_CHANGE_RATIO;
|
|
98
|
+
this.maxOpId = options.maxOpId ?? 0n;
|
|
99
|
+
this.buckets = options.compactBuckets;
|
|
100
|
+
this.signal = options.signal;
|
|
100
101
|
}
|
|
101
102
|
|
|
102
103
|
/**
|
|
@@ -538,31 +539,60 @@ export class MongoCompactor {
|
|
|
538
539
|
let lastId = { g: this.group_id, b: new mongo.MinKey() as any };
|
|
539
540
|
const maxId = { g: this.group_id, b: new mongo.MaxKey() as any };
|
|
540
541
|
while (true) {
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
542
|
+
// To avoid timeouts from too many buckets not meeting the minBucketChanges criteria, we use an aggregation pipeline
|
|
543
|
+
// to scan a fixed batch of buckets at a time, but only return buckets that meet the criteria, rather than limiting
|
|
544
|
+
// on the output number.
|
|
545
|
+
const [result] = await this.db.bucket_state
|
|
546
|
+
.aggregate<{
|
|
547
|
+
buckets: Pick<BucketStateDocument, '_id' | 'estimate_since_compact' | 'compacted_state'>[];
|
|
548
|
+
cursor: Pick<BucketStateDocument, '_id'>[];
|
|
549
|
+
}>(
|
|
550
|
+
[
|
|
551
|
+
{
|
|
552
|
+
$match: {
|
|
553
|
+
_id: { $gt: lastId, $lt: maxId }
|
|
554
|
+
}
|
|
552
555
|
},
|
|
553
|
-
|
|
554
|
-
_id: 1
|
|
556
|
+
{
|
|
557
|
+
$sort: { _id: 1 }
|
|
555
558
|
},
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
+
{
|
|
560
|
+
// Scan a fixed number of docs each query so sparse matches don't block progress.
|
|
561
|
+
$limit: DIRTY_BUCKET_SCAN_BATCH_SIZE
|
|
562
|
+
},
|
|
563
|
+
{
|
|
564
|
+
$facet: {
|
|
565
|
+
// This is the results for the batch
|
|
566
|
+
buckets: [
|
|
567
|
+
{
|
|
568
|
+
$match: {
|
|
569
|
+
'estimate_since_compact.count': { $gte: options.minBucketChanges }
|
|
570
|
+
}
|
|
571
|
+
},
|
|
572
|
+
{
|
|
573
|
+
$project: {
|
|
574
|
+
_id: 1,
|
|
575
|
+
estimate_since_compact: 1,
|
|
576
|
+
compacted_state: 1
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
],
|
|
580
|
+
// This is used for the next query.
|
|
581
|
+
cursor: [{ $sort: { _id: -1 } }, { $limit: 1 }, { $project: { _id: 1 } }]
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
],
|
|
585
|
+
{ maxTimeMS: MONGO_OPERATION_TIMEOUT_MS }
|
|
559
586
|
)
|
|
560
587
|
.toArray();
|
|
561
|
-
|
|
588
|
+
|
|
589
|
+
const cursor = result?.cursor?.[0];
|
|
590
|
+
if (cursor == null) {
|
|
562
591
|
break;
|
|
563
592
|
}
|
|
564
|
-
lastId =
|
|
565
|
-
|
|
593
|
+
lastId = cursor._id;
|
|
594
|
+
|
|
595
|
+
const mapped = (result?.buckets ?? []).map((b) => {
|
|
566
596
|
const updatedCount = b.estimate_since_compact?.count ?? 0;
|
|
567
597
|
const totalCount = (b.compacted_state?.count ?? 0) + updatedCount;
|
|
568
598
|
const updatedBytes = b.estimate_since_compact?.bytes ?? 0;
|
|
@@ -632,6 +662,7 @@ export class MongoCompactor {
|
|
|
632
662
|
buckets.map((bucket) => {
|
|
633
663
|
return {
|
|
634
664
|
bucket,
|
|
665
|
+
source: {} as any,
|
|
635
666
|
end: this.maxOpId
|
|
636
667
|
};
|
|
637
668
|
})
|
|
@@ -1,8 +1,8 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
1
2
|
import { logger } from '@powersync/lib-services-framework';
|
|
2
3
|
import { bson, CompactOptions, InternalOpId } from '@powersync/service-core';
|
|
3
4
|
import { LRUCache } from 'lru-cache';
|
|
4
|
-
import {
|
|
5
|
-
import { mongo } from '@powersync/lib-service-mongodb';
|
|
5
|
+
import { VersionedPowerSyncMongo } from './db.js';
|
|
6
6
|
import { BucketParameterDocument } from './models.js';
|
|
7
7
|
|
|
8
8
|
/**
|
|
@@ -14,7 +14,7 @@ import { BucketParameterDocument } from './models.js';
|
|
|
14
14
|
*/
|
|
15
15
|
export class MongoParameterCompactor {
|
|
16
16
|
constructor(
|
|
17
|
-
private db:
|
|
17
|
+
private db: VersionedPowerSyncMongo,
|
|
18
18
|
private group_id: number,
|
|
19
19
|
private checkpoint: InternalOpId,
|
|
20
20
|
private options: CompactOptions
|
|
@@ -1,52 +1,38 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { storage } from '@powersync/service-core';
|
|
3
|
-
import { SqlSyncRules } from '@powersync/service-sync-rules';
|
|
4
|
-
import { MongoPersistedSyncRules } from './MongoPersistedSyncRules.js';
|
|
5
3
|
import { MongoSyncRulesLock } from './MongoSyncRulesLock.js';
|
|
6
|
-
import { PowerSyncMongo } from './db.js';
|
|
7
|
-
import { SyncRuleDocument } from './models.js';
|
|
8
|
-
|
|
9
|
-
export class MongoPersistedSyncRulesContent implements storage.PersistedSyncRulesContent {
|
|
10
|
-
public readonly slot_name: string;
|
|
11
|
-
|
|
12
|
-
public readonly id: number;
|
|
13
|
-
public readonly sync_rules_content: string;
|
|
14
|
-
public readonly last_checkpoint_lsn: string | null;
|
|
15
|
-
public readonly last_fatal_error: string | null;
|
|
16
|
-
public readonly last_fatal_error_ts: Date | null;
|
|
17
|
-
public readonly last_keepalive_ts: Date | null;
|
|
18
|
-
public readonly last_checkpoint_ts: Date | null;
|
|
19
|
-
public readonly active: boolean;
|
|
4
|
+
import { PowerSyncMongo, VersionedPowerSyncMongo } from './db.js';
|
|
5
|
+
import { getMongoStorageConfig, SyncRuleDocument } from './models.js';
|
|
20
6
|
|
|
7
|
+
export class MongoPersistedSyncRulesContent extends storage.PersistedSyncRulesContent {
|
|
21
8
|
public current_lock: MongoSyncRulesLock | null = null;
|
|
22
9
|
|
|
23
10
|
constructor(
|
|
24
11
|
private db: PowerSyncMongo,
|
|
25
12
|
doc: mongo.WithId<SyncRuleDocument>
|
|
26
13
|
) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
14
|
+
super({
|
|
15
|
+
id: doc._id,
|
|
16
|
+
sync_rules_content: doc.content,
|
|
17
|
+
compiled_plan: doc.serialized_plan ?? null,
|
|
18
|
+
last_checkpoint_lsn: doc.last_checkpoint_lsn,
|
|
19
|
+
// Handle legacy values
|
|
20
|
+
slot_name: doc.slot_name ?? `powersync_${doc._id}`,
|
|
21
|
+
last_fatal_error: doc.last_fatal_error,
|
|
22
|
+
last_fatal_error_ts: doc.last_fatal_error_ts,
|
|
23
|
+
last_checkpoint_ts: doc.last_checkpoint_ts,
|
|
24
|
+
last_keepalive_ts: doc.last_keepalive_ts,
|
|
25
|
+
active: doc.state == 'ACTIVE',
|
|
26
|
+
storageVersion: doc.storage_version ?? storage.LEGACY_STORAGE_VERSION
|
|
27
|
+
});
|
|
37
28
|
}
|
|
38
29
|
|
|
39
|
-
|
|
40
|
-
return
|
|
41
|
-
this.id,
|
|
42
|
-
SqlSyncRules.fromYaml(this.sync_rules_content, options),
|
|
43
|
-
this.last_checkpoint_lsn,
|
|
44
|
-
this.slot_name
|
|
45
|
-
);
|
|
30
|
+
getStorageConfig() {
|
|
31
|
+
return getMongoStorageConfig(this.storageVersion);
|
|
46
32
|
}
|
|
47
33
|
|
|
48
34
|
async lock() {
|
|
49
|
-
const lock = await MongoSyncRulesLock.createLock(this.db, this);
|
|
35
|
+
const lock = await MongoSyncRulesLock.createLock(this.db.versioned(this.getStorageConfig()), this);
|
|
50
36
|
this.current_lock = lock;
|
|
51
37
|
return lock;
|
|
52
38
|
}
|
|
@@ -31,16 +31,25 @@ import { LRUCache } from 'lru-cache';
|
|
|
31
31
|
import * as timers from 'timers/promises';
|
|
32
32
|
import { idPrefixFilter, mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../utils/util.js';
|
|
33
33
|
import { MongoBucketStorage } from '../MongoBucketStorage.js';
|
|
34
|
-
import {
|
|
35
|
-
import {
|
|
34
|
+
import { VersionedPowerSyncMongo } from './db.js';
|
|
35
|
+
import {
|
|
36
|
+
BucketDataDocument,
|
|
37
|
+
BucketDataKey,
|
|
38
|
+
BucketStateDocument,
|
|
39
|
+
SourceKey,
|
|
40
|
+
SourceTableDocument,
|
|
41
|
+
StorageConfig
|
|
42
|
+
} from './models.js';
|
|
36
43
|
import { MongoBucketBatch } from './MongoBucketBatch.js';
|
|
37
44
|
import { MongoChecksumOptions, MongoChecksums } from './MongoChecksums.js';
|
|
38
45
|
import { MongoCompactor } from './MongoCompactor.js';
|
|
39
46
|
import { MongoParameterCompactor } from './MongoParameterCompactor.js';
|
|
47
|
+
import { MongoPersistedSyncRulesContent } from './MongoPersistedSyncRulesContent.js';
|
|
40
48
|
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
41
49
|
|
|
42
50
|
export interface MongoSyncBucketStorageOptions {
|
|
43
|
-
checksumOptions?: MongoChecksumOptions
|
|
51
|
+
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
|
|
52
|
+
storageConfig: StorageConfig;
|
|
44
53
|
}
|
|
45
54
|
|
|
46
55
|
/**
|
|
@@ -58,7 +67,7 @@ export class MongoSyncBucketStorage
|
|
|
58
67
|
extends BaseObserver<storage.SyncRulesBucketStorageListener>
|
|
59
68
|
implements storage.SyncRulesBucketStorage
|
|
60
69
|
{
|
|
61
|
-
private readonly db:
|
|
70
|
+
private readonly db: VersionedPowerSyncMongo;
|
|
62
71
|
readonly checksums: MongoChecksums;
|
|
63
72
|
|
|
64
73
|
private parsedSyncRulesCache: { parsed: HydratedSyncRules; options: storage.ParseSyncRulesOptions } | undefined;
|
|
@@ -67,14 +76,17 @@ export class MongoSyncBucketStorage
|
|
|
67
76
|
constructor(
|
|
68
77
|
public readonly factory: MongoBucketStorage,
|
|
69
78
|
public readonly group_id: number,
|
|
70
|
-
private readonly sync_rules:
|
|
79
|
+
private readonly sync_rules: MongoPersistedSyncRulesContent,
|
|
71
80
|
public readonly slot_name: string,
|
|
72
|
-
writeCheckpointMode
|
|
73
|
-
options
|
|
81
|
+
writeCheckpointMode: storage.WriteCheckpointMode | undefined,
|
|
82
|
+
options: MongoSyncBucketStorageOptions
|
|
74
83
|
) {
|
|
75
84
|
super();
|
|
76
|
-
this.db = factory.db;
|
|
77
|
-
this.checksums = new MongoChecksums(this.db, this.group_id,
|
|
85
|
+
this.db = factory.db.versioned(sync_rules.getStorageConfig());
|
|
86
|
+
this.checksums = new MongoChecksums(this.db, this.group_id, {
|
|
87
|
+
...options.checksumOptions,
|
|
88
|
+
storageConfig: options?.storageConfig
|
|
89
|
+
});
|
|
78
90
|
this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
|
|
79
91
|
db: this.db,
|
|
80
92
|
mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
|
|
@@ -175,7 +187,6 @@ export class MongoSyncBucketStorage
|
|
|
175
187
|
slotName: this.slot_name,
|
|
176
188
|
lastCheckpointLsn: checkpoint_lsn,
|
|
177
189
|
resumeFromLsn: maxLsn(checkpoint_lsn, doc?.snapshot_lsn),
|
|
178
|
-
noCheckpointBeforeLsn: doc?.no_checkpoint_before ?? options.zeroLSN,
|
|
179
190
|
keepaliveOp: doc?.keepalive_op ? BigInt(doc.keepalive_op) : null,
|
|
180
191
|
storeCurrentData: options.storeCurrentData,
|
|
181
192
|
skipExistingRows: options.skipExistingRows ?? false,
|
|
@@ -361,19 +372,20 @@ export class MongoSyncBucketStorage
|
|
|
361
372
|
|
|
362
373
|
async *getBucketDataBatch(
|
|
363
374
|
checkpoint: utils.InternalOpId,
|
|
364
|
-
dataBuckets:
|
|
375
|
+
dataBuckets: storage.BucketDataRequest[],
|
|
365
376
|
options?: storage.BucketDataBatchOptions
|
|
366
377
|
): AsyncIterable<storage.SyncBucketDataChunk> {
|
|
367
|
-
if (dataBuckets.
|
|
378
|
+
if (dataBuckets.length == 0) {
|
|
368
379
|
return;
|
|
369
380
|
}
|
|
370
381
|
let filters: mongo.Filter<BucketDataDocument>[] = [];
|
|
382
|
+
const bucketMap = new Map(dataBuckets.map((request) => [request.bucket, request.start]));
|
|
371
383
|
|
|
372
384
|
if (checkpoint == null) {
|
|
373
385
|
throw new ServiceAssertionError('checkpoint is null');
|
|
374
386
|
}
|
|
375
387
|
const end = checkpoint;
|
|
376
|
-
for (let
|
|
388
|
+
for (let { bucket: name, start } of dataBuckets) {
|
|
377
389
|
filters.push({
|
|
378
390
|
_id: {
|
|
379
391
|
$gt: {
|
|
@@ -466,7 +478,7 @@ export class MongoSyncBucketStorage
|
|
|
466
478
|
}
|
|
467
479
|
|
|
468
480
|
if (start == null) {
|
|
469
|
-
const startOpId =
|
|
481
|
+
const startOpId = bucketMap.get(bucket);
|
|
470
482
|
if (startOpId == null) {
|
|
471
483
|
throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
|
|
472
484
|
}
|
|
@@ -508,7 +520,10 @@ export class MongoSyncBucketStorage
|
|
|
508
520
|
}
|
|
509
521
|
}
|
|
510
522
|
|
|
511
|
-
async getChecksums(
|
|
523
|
+
async getChecksums(
|
|
524
|
+
checkpoint: utils.InternalOpId,
|
|
525
|
+
buckets: storage.BucketChecksumRequest[]
|
|
526
|
+
): Promise<utils.ChecksumMap> {
|
|
512
527
|
return this.checksums.getChecksums(checkpoint, buckets);
|
|
513
528
|
}
|
|
514
529
|
|
|
@@ -565,7 +580,7 @@ export class MongoSyncBucketStorage
|
|
|
565
580
|
async clear(options?: storage.ClearStorageOptions): Promise<void> {
|
|
566
581
|
while (true) {
|
|
567
582
|
if (options?.signal?.aborted) {
|
|
568
|
-
throw new ReplicationAbortedError('Aborted clearing data');
|
|
583
|
+
throw new ReplicationAbortedError('Aborted clearing data', options.signal.reason);
|
|
569
584
|
}
|
|
570
585
|
try {
|
|
571
586
|
await this.clearIteration();
|
|
@@ -620,7 +635,7 @@ export class MongoSyncBucketStorage
|
|
|
620
635
|
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
621
636
|
);
|
|
622
637
|
|
|
623
|
-
await this.db.
|
|
638
|
+
await this.db.common_current_data.deleteMany(
|
|
624
639
|
{
|
|
625
640
|
_id: idPrefixFilter<SourceKey>({ g: this.group_id }, ['t', 'k'])
|
|
626
641
|
},
|
|
@@ -2,7 +2,7 @@ import crypto from 'crypto';
|
|
|
2
2
|
|
|
3
3
|
import { ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
|
|
4
4
|
import { storage } from '@powersync/service-core';
|
|
5
|
-
import { PowerSyncMongo } from './db.js';
|
|
5
|
+
import { PowerSyncMongo, VersionedPowerSyncMongo } from './db.js';
|
|
6
6
|
|
|
7
7
|
/**
|
|
8
8
|
* Manages a lock on a sync rules document, so that only one process
|
|
@@ -12,7 +12,7 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
|
|
|
12
12
|
private readonly refreshInterval: NodeJS.Timeout;
|
|
13
13
|
|
|
14
14
|
static async createLock(
|
|
15
|
-
db:
|
|
15
|
+
db: VersionedPowerSyncMongo,
|
|
16
16
|
sync_rules: storage.PersistedSyncRulesContent
|
|
17
17
|
): Promise<MongoSyncRulesLock> {
|
|
18
18
|
const lockId = crypto.randomBytes(8).toString('hex');
|
|
@@ -52,7 +52,7 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
|
|
|
52
52
|
}
|
|
53
53
|
|
|
54
54
|
constructor(
|
|
55
|
-
private db:
|
|
55
|
+
private db: VersionedPowerSyncMongo,
|
|
56
56
|
public sync_rules_id: number,
|
|
57
57
|
private lock_id: string
|
|
58
58
|
) {
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
2
|
import * as framework from '@powersync/lib-services-framework';
|
|
3
3
|
import { GetCheckpointChangesOptions, InternalOpId, storage } from '@powersync/service-core';
|
|
4
|
-
import { PowerSyncMongo } from './db.js';
|
|
4
|
+
import { PowerSyncMongo, VersionedPowerSyncMongo } from './db.js';
|
|
5
5
|
|
|
6
6
|
export type MongoCheckpointAPIOptions = {
|
|
7
|
-
db:
|
|
7
|
+
db: VersionedPowerSyncMongo;
|
|
8
8
|
mode: storage.WriteCheckpointMode;
|
|
9
9
|
sync_rules_id: number;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
12
|
export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
13
|
-
readonly db:
|
|
13
|
+
readonly db: VersionedPowerSyncMongo;
|
|
14
14
|
private _mode: storage.WriteCheckpointMode;
|
|
15
15
|
|
|
16
16
|
constructor(options: MongoCheckpointAPIOptions) {
|
|
@@ -166,7 +166,7 @@ export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
|
166
166
|
}
|
|
167
167
|
|
|
168
168
|
export async function batchCreateCustomWriteCheckpoints(
|
|
169
|
-
db:
|
|
169
|
+
db: VersionedPowerSyncMongo,
|
|
170
170
|
session: mongo.ClientSession,
|
|
171
171
|
checkpoints: storage.CustomWriteCheckpointOptions[],
|
|
172
172
|
opId: InternalOpId
|
|
@@ -2,6 +2,7 @@ import { ToastableSqliteRow } from '@powersync/service-sync-rules';
|
|
|
2
2
|
import * as bson from 'bson';
|
|
3
3
|
|
|
4
4
|
import { storage } from '@powersync/service-core';
|
|
5
|
+
import { mongoTableId } from '../storage-index.js';
|
|
5
6
|
|
|
6
7
|
/**
|
|
7
8
|
* Maximum number of operations in a batch.
|
|
@@ -86,8 +87,8 @@ export class RecordOperation {
|
|
|
86
87
|
const beforeId = record.beforeReplicaId ?? record.afterReplicaId;
|
|
87
88
|
this.afterId = afterId;
|
|
88
89
|
this.beforeId = beforeId;
|
|
89
|
-
this.internalBeforeKey = cacheKey(record.sourceTable.id, beforeId);
|
|
90
|
-
this.internalAfterKey = afterId ? cacheKey(record.sourceTable.id, afterId) : null;
|
|
90
|
+
this.internalBeforeKey = cacheKey(mongoTableId(record.sourceTable.id), beforeId);
|
|
91
|
+
this.internalAfterKey = afterId ? cacheKey(mongoTableId(record.sourceTable.id), afterId) : null;
|
|
91
92
|
|
|
92
93
|
this.estimatedSize = estimateRowSize(record.before) + estimateRowSize(record.after);
|
|
93
94
|
}
|
|
@@ -5,9 +5,9 @@ import * as bson from 'bson';
|
|
|
5
5
|
|
|
6
6
|
import { Logger, logger as defaultLogger } from '@powersync/lib-services-framework';
|
|
7
7
|
import { InternalOpId, storage, utils } from '@powersync/service-core';
|
|
8
|
-
import { currentBucketKey, MAX_ROW_SIZE } from './MongoBucketBatch.js';
|
|
8
|
+
import { currentBucketKey, EMPTY_DATA, MAX_ROW_SIZE } from './MongoBucketBatch.js';
|
|
9
9
|
import { MongoIdSequence } from './MongoIdSequence.js';
|
|
10
|
-
import { PowerSyncMongo } from './db.js';
|
|
10
|
+
import { PowerSyncMongo, VersionedPowerSyncMongo } from './db.js';
|
|
11
11
|
import {
|
|
12
12
|
BucketDataDocument,
|
|
13
13
|
BucketParameterDocument,
|
|
@@ -16,7 +16,7 @@ import {
|
|
|
16
16
|
CurrentDataDocument,
|
|
17
17
|
SourceKey
|
|
18
18
|
} from './models.js';
|
|
19
|
-
import { replicaIdToSubkey } from '../../utils/util.js';
|
|
19
|
+
import { mongoTableId, replicaIdToSubkey } from '../../utils/util.js';
|
|
20
20
|
|
|
21
21
|
/**
|
|
22
22
|
* Maximum size of operations we write in a single transaction.
|
|
@@ -63,6 +63,7 @@ export class PersistedBatch {
|
|
|
63
63
|
currentSize = 0;
|
|
64
64
|
|
|
65
65
|
constructor(
|
|
66
|
+
private db: VersionedPowerSyncMongo,
|
|
66
67
|
private group_id: number,
|
|
67
68
|
writtenSize: number,
|
|
68
69
|
options?: { logger?: Logger }
|
|
@@ -132,7 +133,7 @@ export class PersistedBatch {
|
|
|
132
133
|
o: op_id
|
|
133
134
|
},
|
|
134
135
|
op: 'PUT',
|
|
135
|
-
source_table: options.table.id,
|
|
136
|
+
source_table: mongoTableId(options.table.id),
|
|
136
137
|
source_key: options.sourceKey,
|
|
137
138
|
table: k.table,
|
|
138
139
|
row_id: k.id,
|
|
@@ -159,7 +160,7 @@ export class PersistedBatch {
|
|
|
159
160
|
o: op_id
|
|
160
161
|
},
|
|
161
162
|
op: 'REMOVE',
|
|
162
|
-
source_table: options.table.id,
|
|
163
|
+
source_table: mongoTableId(options.table.id),
|
|
163
164
|
source_key: options.sourceKey,
|
|
164
165
|
table: bd.table,
|
|
165
166
|
row_id: bd.id,
|
|
@@ -208,7 +209,7 @@ export class PersistedBatch {
|
|
|
208
209
|
_id: op_id,
|
|
209
210
|
key: {
|
|
210
211
|
g: this.group_id,
|
|
211
|
-
t: sourceTable.id,
|
|
212
|
+
t: mongoTableId(sourceTable.id),
|
|
212
213
|
k: sourceKey
|
|
213
214
|
},
|
|
214
215
|
lookup: binLookup,
|
|
@@ -230,7 +231,7 @@ export class PersistedBatch {
|
|
|
230
231
|
_id: op_id,
|
|
231
232
|
key: {
|
|
232
233
|
g: this.group_id,
|
|
233
|
-
t: sourceTable.id,
|
|
234
|
+
t: mongoTableId(sourceTable.id),
|
|
234
235
|
k: sourceKey
|
|
235
236
|
},
|
|
236
237
|
lookup: lookup,
|
|
@@ -243,7 +244,7 @@ export class PersistedBatch {
|
|
|
243
244
|
}
|
|
244
245
|
}
|
|
245
246
|
|
|
246
|
-
|
|
247
|
+
hardDeleteCurrentData(id: SourceKey) {
|
|
247
248
|
const op: mongo.AnyBulkWriteOperation<CurrentDataDocument> = {
|
|
248
249
|
deleteOne: {
|
|
249
250
|
filter: { _id: id }
|
|
@@ -253,12 +254,41 @@ export class PersistedBatch {
|
|
|
253
254
|
this.currentSize += 50;
|
|
254
255
|
}
|
|
255
256
|
|
|
257
|
+
/**
|
|
258
|
+
* Mark a current_data document as soft deleted, to delete on the next commit.
|
|
259
|
+
*
|
|
260
|
+
* If softDeleteCurrentData is not enabled, this falls back to a hard delete.
|
|
261
|
+
*/
|
|
262
|
+
softDeleteCurrentData(id: SourceKey, checkpointGreaterThan: bigint) {
|
|
263
|
+
if (!this.db.storageConfig.softDeleteCurrentData) {
|
|
264
|
+
this.hardDeleteCurrentData(id);
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
const op: mongo.AnyBulkWriteOperation<CurrentDataDocument> = {
|
|
268
|
+
updateOne: {
|
|
269
|
+
filter: { _id: id },
|
|
270
|
+
update: {
|
|
271
|
+
$set: {
|
|
272
|
+
data: EMPTY_DATA,
|
|
273
|
+
buckets: [],
|
|
274
|
+
lookups: [],
|
|
275
|
+
pending_delete: checkpointGreaterThan
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
upsert: true
|
|
279
|
+
}
|
|
280
|
+
};
|
|
281
|
+
this.currentData.push(op);
|
|
282
|
+
this.currentSize += 50;
|
|
283
|
+
}
|
|
284
|
+
|
|
256
285
|
upsertCurrentData(id: SourceKey, values: Partial<CurrentDataDocument>) {
|
|
257
286
|
const op: mongo.AnyBulkWriteOperation<CurrentDataDocument> = {
|
|
258
287
|
updateOne: {
|
|
259
288
|
filter: { _id: id },
|
|
260
289
|
update: {
|
|
261
|
-
$set: values
|
|
290
|
+
$set: values,
|
|
291
|
+
$unset: { pending_delete: 1 }
|
|
262
292
|
},
|
|
263
293
|
upsert: true
|
|
264
294
|
}
|
|
@@ -276,7 +306,8 @@ export class PersistedBatch {
|
|
|
276
306
|
);
|
|
277
307
|
}
|
|
278
308
|
|
|
279
|
-
async flush(
|
|
309
|
+
async flush(session: mongo.ClientSession, options?: storage.BucketBatchCommitOptions) {
|
|
310
|
+
const db = this.db;
|
|
280
311
|
const startAt = performance.now();
|
|
281
312
|
let flushedSomething = false;
|
|
282
313
|
if (this.bucketData.length > 0) {
|
|
@@ -297,7 +328,7 @@ export class PersistedBatch {
|
|
|
297
328
|
}
|
|
298
329
|
if (this.currentData.length > 0) {
|
|
299
330
|
flushedSomething = true;
|
|
300
|
-
await db.
|
|
331
|
+
await db.common_current_data.bulkWrite(this.currentData, {
|
|
301
332
|
session,
|
|
302
333
|
// may update and delete data within the same batch - order matters
|
|
303
334
|
ordered: true
|