@powersync/service-module-mongodb-storage 0.7.2 → 0.8.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 +16 -0
- package/dist/migrations/db/migrations/1741697235857-bucket-state-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1741697235857-bucket-state-index.js +28 -0
- package/dist/migrations/db/migrations/1741697235857-bucket-state-index.js.map +1 -0
- package/dist/storage/implementation/MongoCompactor.js +3 -1
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +7 -4
- package/dist/storage/implementation/MongoSyncBucketStorage.js +128 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +15 -2
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +193 -17
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/PersistedBatch.d.ts +8 -0
- package/dist/storage/implementation/PersistedBatch.js +44 -0
- package/dist/storage/implementation/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +2 -1
- package/dist/storage/implementation/db.js +4 -0
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +17 -0
- package/package.json +4 -4
- package/src/migrations/db/migrations/1741697235857-bucket-state-index.ts +40 -0
- package/src/storage/implementation/MongoCompactor.ts +3 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +164 -35
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +262 -25
- package/src/storage/implementation/PersistedBatch.ts +52 -0
- package/src/storage/implementation/db.ts +5 -0
- package/src/storage/implementation/models.ts +18 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +171 -0
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -11,6 +11,7 @@ import { PowerSyncMongo } from './db.js';
|
|
|
11
11
|
import {
|
|
12
12
|
BucketDataDocument,
|
|
13
13
|
BucketParameterDocument,
|
|
14
|
+
BucketStateDocument,
|
|
14
15
|
CurrentBucket,
|
|
15
16
|
CurrentDataDocument,
|
|
16
17
|
SourceKey
|
|
@@ -48,6 +49,7 @@ export class PersistedBatch {
|
|
|
48
49
|
bucketData: mongo.AnyBulkWriteOperation<BucketDataDocument>[] = [];
|
|
49
50
|
bucketParameters: mongo.AnyBulkWriteOperation<BucketParameterDocument>[] = [];
|
|
50
51
|
currentData: mongo.AnyBulkWriteOperation<CurrentDataDocument>[] = [];
|
|
52
|
+
bucketStates: Map<string, BucketStateUpdate> = new Map();
|
|
51
53
|
|
|
52
54
|
/**
|
|
53
55
|
* For debug logging only.
|
|
@@ -66,6 +68,19 @@ export class PersistedBatch {
|
|
|
66
68
|
this.currentSize = writtenSize;
|
|
67
69
|
}
|
|
68
70
|
|
|
71
|
+
private incrementBucket(bucket: string, op_id: InternalOpId) {
|
|
72
|
+
let existingState = this.bucketStates.get(bucket);
|
|
73
|
+
if (existingState) {
|
|
74
|
+
existingState.lastOp = op_id;
|
|
75
|
+
existingState.incrementCount += 1;
|
|
76
|
+
} else {
|
|
77
|
+
this.bucketStates.set(bucket, {
|
|
78
|
+
lastOp: op_id,
|
|
79
|
+
incrementCount: 1
|
|
80
|
+
});
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
69
84
|
saveBucketData(options: {
|
|
70
85
|
op_seq: MongoIdSequence;
|
|
71
86
|
sourceKey: storage.ReplicaId;
|
|
@@ -120,6 +135,7 @@ export class PersistedBatch {
|
|
|
120
135
|
}
|
|
121
136
|
}
|
|
122
137
|
});
|
|
138
|
+
this.incrementBucket(k.bucket, op_id);
|
|
123
139
|
}
|
|
124
140
|
|
|
125
141
|
for (let bd of remaining_buckets.values()) {
|
|
@@ -147,6 +163,7 @@ export class PersistedBatch {
|
|
|
147
163
|
}
|
|
148
164
|
});
|
|
149
165
|
this.currentSize += 200;
|
|
166
|
+
this.incrementBucket(bd.bucket, op_id);
|
|
150
167
|
}
|
|
151
168
|
}
|
|
152
169
|
|
|
@@ -277,6 +294,14 @@ export class PersistedBatch {
|
|
|
277
294
|
});
|
|
278
295
|
}
|
|
279
296
|
|
|
297
|
+
if (this.bucketStates.size > 0) {
|
|
298
|
+
await db.bucket_state.bulkWrite(this.getBucketStateUpdates(), {
|
|
299
|
+
session,
|
|
300
|
+
// Per-bucket operation - order doesn't matter
|
|
301
|
+
ordered: false
|
|
302
|
+
});
|
|
303
|
+
}
|
|
304
|
+
|
|
280
305
|
const duration = performance.now() - startAt;
|
|
281
306
|
logger.info(
|
|
282
307
|
`powersync_${this.group_id} Flushed ${this.bucketData.length} + ${this.bucketParameters.length} + ${
|
|
@@ -287,7 +312,34 @@ export class PersistedBatch {
|
|
|
287
312
|
this.bucketData = [];
|
|
288
313
|
this.bucketParameters = [];
|
|
289
314
|
this.currentData = [];
|
|
315
|
+
this.bucketStates.clear();
|
|
290
316
|
this.currentSize = 0;
|
|
291
317
|
this.debugLastOpId = null;
|
|
292
318
|
}
|
|
319
|
+
|
|
320
|
+
private getBucketStateUpdates(): mongo.AnyBulkWriteOperation<BucketStateDocument>[] {
|
|
321
|
+
return Array.from(this.bucketStates.entries()).map(([bucket, state]) => {
|
|
322
|
+
return {
|
|
323
|
+
updateOne: {
|
|
324
|
+
filter: {
|
|
325
|
+
_id: {
|
|
326
|
+
g: this.group_id,
|
|
327
|
+
b: bucket
|
|
328
|
+
}
|
|
329
|
+
},
|
|
330
|
+
update: {
|
|
331
|
+
$set: {
|
|
332
|
+
last_op: state.lastOp
|
|
333
|
+
}
|
|
334
|
+
},
|
|
335
|
+
upsert: true
|
|
336
|
+
}
|
|
337
|
+
} satisfies mongo.AnyBulkWriteOperation<BucketStateDocument>;
|
|
338
|
+
});
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
interface BucketStateUpdate {
|
|
343
|
+
lastOp: InternalOpId;
|
|
344
|
+
incrementCount: number;
|
|
293
345
|
}
|
|
@@ -6,6 +6,7 @@ import { MongoStorageConfig } from '../../types/types.js';
|
|
|
6
6
|
import {
|
|
7
7
|
BucketDataDocument,
|
|
8
8
|
BucketParameterDocument,
|
|
9
|
+
BucketStateDocument,
|
|
9
10
|
CurrentDataDocument,
|
|
10
11
|
CustomWriteCheckpointDocument,
|
|
11
12
|
IdSequenceDocument,
|
|
@@ -33,6 +34,7 @@ export class PowerSyncMongo {
|
|
|
33
34
|
readonly write_checkpoints: mongo.Collection<WriteCheckpointDocument>;
|
|
34
35
|
readonly instance: mongo.Collection<InstanceDocument>;
|
|
35
36
|
readonly locks: mongo.Collection<lib_mongo.locks.Lock>;
|
|
37
|
+
readonly bucket_state: mongo.Collection<BucketStateDocument>;
|
|
36
38
|
|
|
37
39
|
readonly client: mongo.MongoClient;
|
|
38
40
|
readonly db: mongo.Db;
|
|
@@ -55,6 +57,7 @@ export class PowerSyncMongo {
|
|
|
55
57
|
this.write_checkpoints = db.collection('write_checkpoints');
|
|
56
58
|
this.instance = db.collection('instance');
|
|
57
59
|
this.locks = this.db.collection('locks');
|
|
60
|
+
this.bucket_state = this.db.collection('bucket_state');
|
|
58
61
|
}
|
|
59
62
|
|
|
60
63
|
/**
|
|
@@ -70,6 +73,8 @@ export class PowerSyncMongo {
|
|
|
70
73
|
await this.write_checkpoints.deleteMany({});
|
|
71
74
|
await this.instance.deleteOne({});
|
|
72
75
|
await this.locks.deleteMany({});
|
|
76
|
+
await this.bucket_state.deleteMany({});
|
|
77
|
+
await this.custom_write_checkpoints.deleteMany({});
|
|
73
78
|
}
|
|
74
79
|
|
|
75
80
|
/**
|
|
@@ -75,6 +75,24 @@ export interface SourceTableDocument {
|
|
|
75
75
|
snapshot_done: boolean | undefined;
|
|
76
76
|
}
|
|
77
77
|
|
|
78
|
+
/**
|
|
79
|
+
* Record the state of each bucket.
|
|
80
|
+
*
|
|
81
|
+
* Right now, this is just used to track when buckets are updated, for efficient incremental sync.
|
|
82
|
+
* In the future, this could be used to track operation counts, both for diagnostic purposes, and for
|
|
83
|
+
* determining when a compact and/or defragment could be beneficial.
|
|
84
|
+
*
|
|
85
|
+
* Note: There is currently no migration to populate this collection from existing data - it is only
|
|
86
|
+
* populated by new updates.
|
|
87
|
+
*/
|
|
88
|
+
export interface BucketStateDocument {
|
|
89
|
+
_id: {
|
|
90
|
+
g: number;
|
|
91
|
+
b: string;
|
|
92
|
+
};
|
|
93
|
+
last_op: bigint;
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
export interface IdSequenceDocument {
|
|
79
97
|
_id: string;
|
|
80
98
|
op_id: bigint;
|
|
@@ -357,6 +357,74 @@ exports[`sync - mongodb > sync legacy non-raw data 1`] = `
|
|
|
357
357
|
]
|
|
358
358
|
`;
|
|
359
359
|
|
|
360
|
+
exports[`sync - mongodb > sync updates to data query only 1`] = `
|
|
361
|
+
[
|
|
362
|
+
{
|
|
363
|
+
"checkpoint": {
|
|
364
|
+
"buckets": [
|
|
365
|
+
{
|
|
366
|
+
"bucket": "by_user["user1"]",
|
|
367
|
+
"checksum": 0,
|
|
368
|
+
"count": 0,
|
|
369
|
+
"priority": 3,
|
|
370
|
+
},
|
|
371
|
+
],
|
|
372
|
+
"last_op_id": "1",
|
|
373
|
+
"write_checkpoint": undefined,
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
"checkpoint_complete": {
|
|
378
|
+
"last_op_id": "1",
|
|
379
|
+
},
|
|
380
|
+
},
|
|
381
|
+
]
|
|
382
|
+
`;
|
|
383
|
+
|
|
384
|
+
exports[`sync - mongodb > sync updates to data query only 2`] = `
|
|
385
|
+
[
|
|
386
|
+
{
|
|
387
|
+
"checkpoint_diff": {
|
|
388
|
+
"last_op_id": "2",
|
|
389
|
+
"removed_buckets": [],
|
|
390
|
+
"updated_buckets": [
|
|
391
|
+
{
|
|
392
|
+
"bucket": "by_user["user1"]",
|
|
393
|
+
"checksum": 1418351250,
|
|
394
|
+
"count": 1,
|
|
395
|
+
"priority": 3,
|
|
396
|
+
},
|
|
397
|
+
],
|
|
398
|
+
"write_checkpoint": undefined,
|
|
399
|
+
},
|
|
400
|
+
},
|
|
401
|
+
{
|
|
402
|
+
"data": {
|
|
403
|
+
"after": "0",
|
|
404
|
+
"bucket": "by_user["user1"]",
|
|
405
|
+
"data": [
|
|
406
|
+
{
|
|
407
|
+
"checksum": 1418351250n,
|
|
408
|
+
"data": "{"id":"list1","user_id":"user1","name":"User 1"}",
|
|
409
|
+
"object_id": "list1",
|
|
410
|
+
"object_type": "lists",
|
|
411
|
+
"op": "PUT",
|
|
412
|
+
"op_id": "2",
|
|
413
|
+
"subkey": "0ffb7b58-d14d-5efa-be6c-c8eda74ab7a8",
|
|
414
|
+
},
|
|
415
|
+
],
|
|
416
|
+
"has_more": false,
|
|
417
|
+
"next_after": "2",
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
{
|
|
421
|
+
"checkpoint_complete": {
|
|
422
|
+
"last_op_id": "2",
|
|
423
|
+
},
|
|
424
|
+
},
|
|
425
|
+
]
|
|
426
|
+
`;
|
|
427
|
+
|
|
360
428
|
exports[`sync - mongodb > sync updates to global data 1`] = `
|
|
361
429
|
[
|
|
362
430
|
{
|
|
@@ -468,3 +536,106 @@ exports[`sync - mongodb > sync updates to global data 3`] = `
|
|
|
468
536
|
},
|
|
469
537
|
]
|
|
470
538
|
`;
|
|
539
|
+
|
|
540
|
+
exports[`sync - mongodb > sync updates to parameter query + data 1`] = `
|
|
541
|
+
[
|
|
542
|
+
{
|
|
543
|
+
"checkpoint": {
|
|
544
|
+
"buckets": [],
|
|
545
|
+
"last_op_id": "0",
|
|
546
|
+
"write_checkpoint": undefined,
|
|
547
|
+
},
|
|
548
|
+
},
|
|
549
|
+
{
|
|
550
|
+
"checkpoint_complete": {
|
|
551
|
+
"last_op_id": "0",
|
|
552
|
+
},
|
|
553
|
+
},
|
|
554
|
+
]
|
|
555
|
+
`;
|
|
556
|
+
|
|
557
|
+
exports[`sync - mongodb > sync updates to parameter query + data 2`] = `
|
|
558
|
+
[
|
|
559
|
+
{
|
|
560
|
+
"checkpoint_diff": {
|
|
561
|
+
"last_op_id": "2",
|
|
562
|
+
"removed_buckets": [],
|
|
563
|
+
"updated_buckets": [
|
|
564
|
+
{
|
|
565
|
+
"bucket": "by_user["user1"]",
|
|
566
|
+
"checksum": 1418351250,
|
|
567
|
+
"count": 1,
|
|
568
|
+
"priority": 3,
|
|
569
|
+
},
|
|
570
|
+
],
|
|
571
|
+
"write_checkpoint": undefined,
|
|
572
|
+
},
|
|
573
|
+
},
|
|
574
|
+
{
|
|
575
|
+
"data": {
|
|
576
|
+
"after": "0",
|
|
577
|
+
"bucket": "by_user["user1"]",
|
|
578
|
+
"data": [
|
|
579
|
+
{
|
|
580
|
+
"checksum": 1418351250n,
|
|
581
|
+
"data": "{"id":"list1","user_id":"user1","name":"User 1"}",
|
|
582
|
+
"object_id": "list1",
|
|
583
|
+
"object_type": "lists",
|
|
584
|
+
"op": "PUT",
|
|
585
|
+
"op_id": "1",
|
|
586
|
+
"subkey": "0ffb7b58-d14d-5efa-be6c-c8eda74ab7a8",
|
|
587
|
+
},
|
|
588
|
+
],
|
|
589
|
+
"has_more": false,
|
|
590
|
+
"next_after": "1",
|
|
591
|
+
},
|
|
592
|
+
},
|
|
593
|
+
{
|
|
594
|
+
"checkpoint_complete": {
|
|
595
|
+
"last_op_id": "2",
|
|
596
|
+
},
|
|
597
|
+
},
|
|
598
|
+
]
|
|
599
|
+
`;
|
|
600
|
+
|
|
601
|
+
exports[`sync - mongodb > sync updates to parameter query only 1`] = `
|
|
602
|
+
[
|
|
603
|
+
{
|
|
604
|
+
"checkpoint": {
|
|
605
|
+
"buckets": [],
|
|
606
|
+
"last_op_id": "0",
|
|
607
|
+
"write_checkpoint": undefined,
|
|
608
|
+
},
|
|
609
|
+
},
|
|
610
|
+
{
|
|
611
|
+
"checkpoint_complete": {
|
|
612
|
+
"last_op_id": "0",
|
|
613
|
+
},
|
|
614
|
+
},
|
|
615
|
+
]
|
|
616
|
+
`;
|
|
617
|
+
|
|
618
|
+
exports[`sync - mongodb > sync updates to parameter query only 2`] = `
|
|
619
|
+
[
|
|
620
|
+
{
|
|
621
|
+
"checkpoint_diff": {
|
|
622
|
+
"last_op_id": "1",
|
|
623
|
+
"removed_buckets": [],
|
|
624
|
+
"updated_buckets": [
|
|
625
|
+
{
|
|
626
|
+
"bucket": "by_user["user1"]",
|
|
627
|
+
"checksum": 0,
|
|
628
|
+
"count": 0,
|
|
629
|
+
"priority": 3,
|
|
630
|
+
},
|
|
631
|
+
],
|
|
632
|
+
"write_checkpoint": undefined,
|
|
633
|
+
},
|
|
634
|
+
},
|
|
635
|
+
{
|
|
636
|
+
"checkpoint_complete": {
|
|
637
|
+
"last_op_id": "1",
|
|
638
|
+
},
|
|
639
|
+
},
|
|
640
|
+
]
|
|
641
|
+
`;
|