@powersync/service-module-mongodb-storage 0.0.0-dev-20250310190630 → 0.0.0-dev-20250312090341
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 +6 -8
- 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/MongoBucketBatch.d.ts +4 -0
- package/dist/storage/implementation/MongoBucketBatch.js +1 -1
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.js +14 -1
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +6 -0
- package/dist/storage/implementation/MongoSyncBucketStorage.js +100 -8
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/PersistedBatch.d.ts +8 -0
- package/dist/storage/implementation/PersistedBatch.js +57 -2
- package/dist/storage/implementation/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +2 -1
- package/dist/storage/implementation/db.js +3 -0
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +8 -0
- package/package.json +5 -5
- package/src/migrations/db/migrations/1741697235857-bucket-state-index.ts +40 -0
- package/src/storage/implementation/MongoBucketBatch.ts +1 -1
- package/src/storage/implementation/MongoCompactor.ts +19 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +128 -10
- package/src/storage/implementation/PersistedBatch.ts +66 -2
- package/src/storage/implementation/db.ts +4 -0
- package/src/storage/implementation/models.ts +9 -0
- package/test/src/setup.ts +3 -2
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { JSONBig } from '@powersync/service-jsonbig';
|
|
2
2
|
import { logger } from '@powersync/lib-services-framework';
|
|
3
3
|
import { storage, utils } from '@powersync/service-core';
|
|
4
|
-
import { currentBucketKey } from './MongoBucketBatch.js';
|
|
4
|
+
import { currentBucketKey, MAX_ROW_SIZE } from './MongoBucketBatch.js';
|
|
5
5
|
import { replicaIdToSubkey } from './util.js';
|
|
6
6
|
/**
|
|
7
7
|
* Maximum size of operations we write in a single transaction.
|
|
@@ -33,6 +33,7 @@ export class PersistedBatch {
|
|
|
33
33
|
bucketData = [];
|
|
34
34
|
bucketParameters = [];
|
|
35
35
|
currentData = [];
|
|
36
|
+
bucketStates = new Map();
|
|
36
37
|
/**
|
|
37
38
|
* For debug logging only.
|
|
38
39
|
*/
|
|
@@ -45,6 +46,19 @@ export class PersistedBatch {
|
|
|
45
46
|
this.group_id = group_id;
|
|
46
47
|
this.currentSize = writtenSize;
|
|
47
48
|
}
|
|
49
|
+
incrementBucket(bucket, op_id) {
|
|
50
|
+
let existingState = this.bucketStates.get(bucket);
|
|
51
|
+
if (existingState) {
|
|
52
|
+
existingState.lastOp = op_id;
|
|
53
|
+
existingState.incrementCount += 1;
|
|
54
|
+
}
|
|
55
|
+
else {
|
|
56
|
+
this.bucketStates.set(bucket, {
|
|
57
|
+
lastOp: op_id,
|
|
58
|
+
incrementCount: 1
|
|
59
|
+
});
|
|
60
|
+
}
|
|
61
|
+
}
|
|
48
62
|
saveBucketData(options) {
|
|
49
63
|
const remaining_buckets = new Map();
|
|
50
64
|
for (let b of options.before_buckets) {
|
|
@@ -54,10 +68,18 @@ export class PersistedBatch {
|
|
|
54
68
|
const dchecksum = utils.hashDelete(replicaIdToSubkey(options.table.id, options.sourceKey));
|
|
55
69
|
for (const k of options.evaluated) {
|
|
56
70
|
const key = currentBucketKey(k);
|
|
57
|
-
remaining_buckets.delete(key);
|
|
58
71
|
// INSERT
|
|
59
72
|
const recordData = JSONBig.stringify(k.data);
|
|
60
73
|
const checksum = utils.hashData(k.table, k.id, recordData);
|
|
74
|
+
if (recordData.length > MAX_ROW_SIZE) {
|
|
75
|
+
// In many cases, the raw data size would have been too large already. But there are cases where
|
|
76
|
+
// the BSON size is small enough, but the JSON size is too large.
|
|
77
|
+
// In these cases, we can't store the data, so we skip it, or generate a REMOVE operation if the row
|
|
78
|
+
// was synced previously.
|
|
79
|
+
logger.error(`powersync_${this.group_id} Row ${key} too large: ${recordData.length} bytes. Removing.`);
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
remaining_buckets.delete(key);
|
|
61
83
|
this.currentSize += recordData.length + 200;
|
|
62
84
|
const op_id = options.op_seq.next();
|
|
63
85
|
this.debugLastOpId = op_id;
|
|
@@ -79,6 +101,7 @@ export class PersistedBatch {
|
|
|
79
101
|
}
|
|
80
102
|
}
|
|
81
103
|
});
|
|
104
|
+
this.incrementBucket(k.bucket, op_id);
|
|
82
105
|
}
|
|
83
106
|
for (let bd of remaining_buckets.values()) {
|
|
84
107
|
// REMOVE
|
|
@@ -103,6 +126,7 @@ export class PersistedBatch {
|
|
|
103
126
|
}
|
|
104
127
|
});
|
|
105
128
|
this.currentSize += 200;
|
|
129
|
+
this.incrementBucket(bd.bucket, op_id);
|
|
106
130
|
}
|
|
107
131
|
}
|
|
108
132
|
saveParameterData(data) {
|
|
@@ -213,13 +237,44 @@ export class PersistedBatch {
|
|
|
213
237
|
ordered: true
|
|
214
238
|
});
|
|
215
239
|
}
|
|
240
|
+
if (this.bucketStates.size > 0) {
|
|
241
|
+
await db.bucket_state.bulkWrite(this.getBucketStateUpdates(), {
|
|
242
|
+
session,
|
|
243
|
+
// Per-bucket operation - order doesn't matter
|
|
244
|
+
ordered: false
|
|
245
|
+
});
|
|
246
|
+
}
|
|
216
247
|
const duration = performance.now() - startAt;
|
|
217
248
|
logger.info(`powersync_${this.group_id} Flushed ${this.bucketData.length} + ${this.bucketParameters.length} + ${this.currentData.length} updates, ${Math.round(this.currentSize / 1024)}kb in ${duration.toFixed(0)}ms. Last op_id: ${this.debugLastOpId}`);
|
|
218
249
|
this.bucketData = [];
|
|
219
250
|
this.bucketParameters = [];
|
|
220
251
|
this.currentData = [];
|
|
252
|
+
this.bucketStates.clear();
|
|
221
253
|
this.currentSize = 0;
|
|
222
254
|
this.debugLastOpId = null;
|
|
223
255
|
}
|
|
256
|
+
getBucketStateUpdates() {
|
|
257
|
+
return Array.from(this.bucketStates.entries()).map(([bucket, state]) => {
|
|
258
|
+
return {
|
|
259
|
+
updateOne: {
|
|
260
|
+
filter: {
|
|
261
|
+
_id: {
|
|
262
|
+
g: this.group_id,
|
|
263
|
+
b: bucket
|
|
264
|
+
}
|
|
265
|
+
},
|
|
266
|
+
update: {
|
|
267
|
+
$set: {
|
|
268
|
+
last_op: state.lastOp
|
|
269
|
+
},
|
|
270
|
+
$inc: {
|
|
271
|
+
op_count: state.incrementCount
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
upsert: true
|
|
275
|
+
}
|
|
276
|
+
};
|
|
277
|
+
});
|
|
278
|
+
}
|
|
224
279
|
}
|
|
225
280
|
//# sourceMappingURL=PersistedBatch.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PersistedBatch.js","sourceRoot":"","sources":["../../../src/storage/implementation/PersistedBatch.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,OAAO,EAAE,MAAM,4BAA4B,CAAC;AAIrD,OAAO,EAAE,MAAM,EAAE,MAAM,mCAAmC,CAAC;AAC3D,OAAO,EAAgB,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AACvE,OAAO,EAAE,gBAAgB,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"PersistedBatch.js","sourceRoot":"","sources":["../../../src/storage/implementation/PersistedBatch.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
3
|
import { MongoStorageConfig } from '../../types/types.js';
|
|
4
|
-
import { BucketDataDocument, BucketParameterDocument, CurrentDataDocument, CustomWriteCheckpointDocument, IdSequenceDocument, InstanceDocument, SourceTableDocument, SyncRuleDocument, WriteCheckpointDocument } from './models.js';
|
|
4
|
+
import { BucketDataDocument, BucketParameterDocument, BucketStateDocument, CurrentDataDocument, CustomWriteCheckpointDocument, IdSequenceDocument, InstanceDocument, SourceTableDocument, SyncRuleDocument, WriteCheckpointDocument } from './models.js';
|
|
5
5
|
export interface PowerSyncMongoOptions {
|
|
6
6
|
/**
|
|
7
7
|
* Optional - uses the database from the MongoClient connection URI if not specified.
|
|
@@ -19,6 +19,7 @@ export declare class PowerSyncMongo {
|
|
|
19
19
|
readonly write_checkpoints: mongo.Collection<WriteCheckpointDocument>;
|
|
20
20
|
readonly instance: mongo.Collection<InstanceDocument>;
|
|
21
21
|
readonly locks: mongo.Collection<lib_mongo.locks.Lock>;
|
|
22
|
+
readonly bucket_state: mongo.Collection<BucketStateDocument>;
|
|
22
23
|
readonly client: mongo.MongoClient;
|
|
23
24
|
readonly db: mongo.Db;
|
|
24
25
|
constructor(client: mongo.MongoClient, options?: PowerSyncMongoOptions);
|
|
@@ -11,6 +11,7 @@ export class PowerSyncMongo {
|
|
|
11
11
|
write_checkpoints;
|
|
12
12
|
instance;
|
|
13
13
|
locks;
|
|
14
|
+
bucket_state;
|
|
14
15
|
client;
|
|
15
16
|
db;
|
|
16
17
|
constructor(client, options) {
|
|
@@ -29,6 +30,7 @@ export class PowerSyncMongo {
|
|
|
29
30
|
this.write_checkpoints = db.collection('write_checkpoints');
|
|
30
31
|
this.instance = db.collection('instance');
|
|
31
32
|
this.locks = this.db.collection('locks');
|
|
33
|
+
this.bucket_state = this.db.collection('bucket_state');
|
|
32
34
|
}
|
|
33
35
|
/**
|
|
34
36
|
* Clear all collections.
|
|
@@ -43,6 +45,7 @@ export class PowerSyncMongo {
|
|
|
43
45
|
await this.write_checkpoints.deleteMany({});
|
|
44
46
|
await this.instance.deleteOne({});
|
|
45
47
|
await this.locks.deleteMany({});
|
|
48
|
+
await this.bucket_state.deleteMany({});
|
|
46
49
|
}
|
|
47
50
|
/**
|
|
48
51
|
* Drop the entire database.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/storage/implementation/db.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,SAAS,MAAM,gCAAgC,CAAC;AAE5D,OAAO,EAAE,OAAO,EAAE,MAAM,yBAAyB,CAAC;
|
|
1
|
+
{"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/storage/implementation/db.ts"],"names":[],"mappings":"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"}
|
|
@@ -69,6 +69,14 @@ export interface SourceTableDocument {
|
|
|
69
69
|
}[] | undefined;
|
|
70
70
|
snapshot_done: boolean | undefined;
|
|
71
71
|
}
|
|
72
|
+
export interface BucketStateDocument {
|
|
73
|
+
_id: {
|
|
74
|
+
g: number;
|
|
75
|
+
b: string;
|
|
76
|
+
};
|
|
77
|
+
last_op: bigint;
|
|
78
|
+
op_count: number;
|
|
79
|
+
}
|
|
72
80
|
export interface IdSequenceDocument {
|
|
73
81
|
_id: string;
|
|
74
82
|
op_id: bigint;
|
package/package.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "@powersync/service-module-mongodb-storage",
|
|
3
3
|
"repository": "https://github.com/powersync-ja/powersync-service",
|
|
4
4
|
"types": "dist/index.d.ts",
|
|
5
|
-
"version": "0.0.0-dev-
|
|
5
|
+
"version": "0.0.0-dev-20250312090341",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"license": "FSL-1.1-Apache-2.0",
|
|
8
8
|
"type": "module",
|
|
@@ -28,15 +28,15 @@
|
|
|
28
28
|
"lru-cache": "^10.2.2",
|
|
29
29
|
"uuid": "^9.0.1",
|
|
30
30
|
"@powersync/lib-services-framework": "0.5.3",
|
|
31
|
-
"@powersync/service-core": "0.0.0-dev-
|
|
31
|
+
"@powersync/service-core": "0.0.0-dev-20250312090341",
|
|
32
32
|
"@powersync/service-jsonbig": "0.17.10",
|
|
33
33
|
"@powersync/service-sync-rules": "0.24.1",
|
|
34
|
-
"@powersync/service-types": "0.
|
|
35
|
-
"@powersync/lib-service-mongodb": "0.
|
|
34
|
+
"@powersync/service-types": "0.9.0",
|
|
35
|
+
"@powersync/lib-service-mongodb": "0.0.0-dev-20250312090341"
|
|
36
36
|
},
|
|
37
37
|
"devDependencies": {
|
|
38
38
|
"@types/uuid": "^9.0.4",
|
|
39
|
-
"@powersync/service-core-tests": "0.0.0-dev-
|
|
39
|
+
"@powersync/service-core-tests": "0.0.0-dev-20250312090341"
|
|
40
40
|
},
|
|
41
41
|
"scripts": {
|
|
42
42
|
"build": "tsc -b",
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { migrations } from '@powersync/service-core';
|
|
2
|
+
import * as storage from '../../../storage/storage-index.js';
|
|
3
|
+
import { MongoStorageConfig } from '../../../types/types.js';
|
|
4
|
+
|
|
5
|
+
const INDEX_NAME = 'bucket_updates';
|
|
6
|
+
|
|
7
|
+
export const up: migrations.PowerSyncMigrationFunction = async (context) => {
|
|
8
|
+
const {
|
|
9
|
+
service_context: { configuration }
|
|
10
|
+
} = context;
|
|
11
|
+
const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig);
|
|
12
|
+
|
|
13
|
+
try {
|
|
14
|
+
await db.bucket_state.createIndex(
|
|
15
|
+
{
|
|
16
|
+
'_id.g': 1,
|
|
17
|
+
last_op: 1
|
|
18
|
+
},
|
|
19
|
+
{ name: INDEX_NAME, unique: true }
|
|
20
|
+
);
|
|
21
|
+
} finally {
|
|
22
|
+
await db.client.close();
|
|
23
|
+
}
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
export const down: migrations.PowerSyncMigrationFunction = async (context) => {
|
|
27
|
+
const {
|
|
28
|
+
service_context: { configuration }
|
|
29
|
+
} = context;
|
|
30
|
+
|
|
31
|
+
const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig);
|
|
32
|
+
|
|
33
|
+
try {
|
|
34
|
+
if (await db.bucket_state.indexExists(INDEX_NAME)) {
|
|
35
|
+
await db.bucket_state.dropIndex(INDEX_NAME);
|
|
36
|
+
}
|
|
37
|
+
} finally {
|
|
38
|
+
await db.client.close();
|
|
39
|
+
}
|
|
40
|
+
};
|
|
@@ -24,7 +24,7 @@ import { idPrefixFilter } from './util.js';
|
|
|
24
24
|
/**
|
|
25
25
|
* 15MB
|
|
26
26
|
*/
|
|
27
|
-
const MAX_ROW_SIZE = 15 * 1024 * 1024;
|
|
27
|
+
export const MAX_ROW_SIZE = 15 * 1024 * 1024;
|
|
28
28
|
|
|
29
29
|
// Currently, we can only have a single flush() at a time, since it locks the op_id sequence.
|
|
30
30
|
// While the MongoDB transaction retry mechanism handles this okay, using an in-process Mutex
|
|
@@ -314,10 +314,12 @@ export class MongoCompactor {
|
|
|
314
314
|
let lastOpId: BucketDataKey | null = null;
|
|
315
315
|
let targetOp: bigint | null = null;
|
|
316
316
|
let gotAnOp = false;
|
|
317
|
+
let numberOfOpsToClear = 0;
|
|
317
318
|
for await (let op of query.stream()) {
|
|
318
319
|
if (op.op == 'MOVE' || op.op == 'REMOVE' || op.op == 'CLEAR') {
|
|
319
320
|
checksum = utils.addChecksums(checksum, op.checksum);
|
|
320
321
|
lastOpId = op._id;
|
|
322
|
+
numberOfOpsToClear += 1;
|
|
321
323
|
if (op.op != 'CLEAR') {
|
|
322
324
|
gotAnOp = true;
|
|
323
325
|
}
|
|
@@ -337,7 +339,7 @@ export class MongoCompactor {
|
|
|
337
339
|
return;
|
|
338
340
|
}
|
|
339
341
|
|
|
340
|
-
logger.info(`Flushing CLEAR at ${lastOpId?.o}`);
|
|
342
|
+
logger.info(`Flushing CLEAR for ${numberOfOpsToClear} ops at ${lastOpId?.o}`);
|
|
341
343
|
await this.db.bucket_data.deleteMany(
|
|
342
344
|
{
|
|
343
345
|
_id: {
|
|
@@ -362,6 +364,22 @@ export class MongoCompactor {
|
|
|
362
364
|
},
|
|
363
365
|
{ session }
|
|
364
366
|
);
|
|
367
|
+
|
|
368
|
+
// Note: This does not update anything if there is no existing state
|
|
369
|
+
await this.db.bucket_state.updateOne(
|
|
370
|
+
{
|
|
371
|
+
_id: {
|
|
372
|
+
g: this.group_id,
|
|
373
|
+
b: bucket
|
|
374
|
+
}
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
$inc: {
|
|
378
|
+
op_count: 1 - numberOfOpsToClear
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
{ session }
|
|
382
|
+
);
|
|
365
383
|
},
|
|
366
384
|
{
|
|
367
385
|
writeConcern: { w: 'majority' },
|
|
@@ -9,27 +9,30 @@ import {
|
|
|
9
9
|
} from '@powersync/lib-services-framework';
|
|
10
10
|
import {
|
|
11
11
|
BroadcastIterable,
|
|
12
|
-
CHECKPOINT_INVALIDATE_ALL,
|
|
13
12
|
CheckpointChanges,
|
|
14
13
|
GetCheckpointChangesOptions,
|
|
15
14
|
InternalOpId,
|
|
16
15
|
internalToExternalOpId,
|
|
17
16
|
ProtocolOpId,
|
|
17
|
+
getLookupBucketDefinitionName,
|
|
18
18
|
ReplicationCheckpoint,
|
|
19
|
-
SourceTable,
|
|
20
19
|
storage,
|
|
21
20
|
utils,
|
|
22
|
-
WatchWriteCheckpointOptions
|
|
21
|
+
WatchWriteCheckpointOptions,
|
|
22
|
+
CHECKPOINT_INVALIDATE_ALL,
|
|
23
|
+
deserializeParameterLookup
|
|
23
24
|
} from '@powersync/service-core';
|
|
24
25
|
import { SqliteJsonRow, SqliteJsonValue, SqlSyncRules } from '@powersync/service-sync-rules';
|
|
25
26
|
import * as bson from 'bson';
|
|
26
27
|
import { wrapWithAbort } from 'ix/asynciterable/operators/withabort.js';
|
|
28
|
+
import { LRUCache } from 'lru-cache';
|
|
27
29
|
import * as timers from 'timers/promises';
|
|
28
30
|
import { MongoBucketStorage } from '../MongoBucketStorage.js';
|
|
29
31
|
import { PowerSyncMongo } from './db.js';
|
|
30
32
|
import {
|
|
31
33
|
BucketDataDocument,
|
|
32
34
|
BucketDataKey,
|
|
35
|
+
BucketStateDocument,
|
|
33
36
|
SourceKey,
|
|
34
37
|
SourceTableDocument,
|
|
35
38
|
SyncRuleCheckpointState,
|
|
@@ -39,6 +42,7 @@ import { MongoBucketBatch } from './MongoBucketBatch.js';
|
|
|
39
42
|
import { MongoCompactor } from './MongoCompactor.js';
|
|
40
43
|
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
41
44
|
import { idPrefixFilter, mapOpEntry, readSingleBatch } from './util.js';
|
|
45
|
+
import { JSONBig } from '@powersync/service-jsonbig';
|
|
42
46
|
|
|
43
47
|
export class MongoSyncBucketStorage
|
|
44
48
|
extends BaseObserver<storage.SyncRulesBucketStorageListener>
|
|
@@ -585,6 +589,13 @@ export class MongoSyncBucketStorage
|
|
|
585
589
|
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
586
590
|
);
|
|
587
591
|
|
|
592
|
+
await this.db.bucket_state.deleteMany(
|
|
593
|
+
{
|
|
594
|
+
_id: idPrefixFilter<BucketStateDocument['_id']>({ g: this.group_id }, ['b'])
|
|
595
|
+
},
|
|
596
|
+
{ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS }
|
|
597
|
+
);
|
|
598
|
+
|
|
588
599
|
await this.db.source_tables.deleteMany(
|
|
589
600
|
{
|
|
590
601
|
group_id: this.group_id
|
|
@@ -795,12 +806,7 @@ export class MongoSyncBucketStorage
|
|
|
795
806
|
|
|
796
807
|
const updates: CheckpointChanges =
|
|
797
808
|
lastCheckpoint == null
|
|
798
|
-
?
|
|
799
|
-
invalidateDataBuckets: true,
|
|
800
|
-
invalidateParameterBuckets: true,
|
|
801
|
-
updatedDataBuckets: [],
|
|
802
|
-
updatedParameterBucketDefinitions: []
|
|
803
|
-
}
|
|
809
|
+
? CHECKPOINT_INVALIDATE_ALL
|
|
804
810
|
: await this.getCheckpointChanges({
|
|
805
811
|
lastCheckpoint: lastCheckpoint,
|
|
806
812
|
nextCheckpoint: checkpoint
|
|
@@ -869,7 +875,119 @@ export class MongoSyncBucketStorage
|
|
|
869
875
|
return pipeline;
|
|
870
876
|
}
|
|
871
877
|
|
|
878
|
+
private async getDataBucketChanges(
|
|
879
|
+
options: GetCheckpointChangesOptions
|
|
880
|
+
): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
|
|
881
|
+
const bucketStateUpdates = await this.db.bucket_state
|
|
882
|
+
.find(
|
|
883
|
+
{
|
|
884
|
+
// We have an index on (_id.g, last_op).
|
|
885
|
+
'_id.g': this.group_id,
|
|
886
|
+
last_op: { $gt: BigInt(options.lastCheckpoint) }
|
|
887
|
+
},
|
|
888
|
+
{
|
|
889
|
+
projection: {
|
|
890
|
+
'_id.b': 1
|
|
891
|
+
},
|
|
892
|
+
limit: 1001,
|
|
893
|
+
batchSize: 1001,
|
|
894
|
+
singleBatch: true
|
|
895
|
+
}
|
|
896
|
+
)
|
|
897
|
+
.toArray();
|
|
898
|
+
|
|
899
|
+
const buckets = bucketStateUpdates.map((doc) => doc._id.b);
|
|
900
|
+
const invalidateDataBuckets = buckets.length > 1000;
|
|
901
|
+
|
|
902
|
+
return {
|
|
903
|
+
invalidateDataBuckets: invalidateDataBuckets,
|
|
904
|
+
updatedDataBuckets: invalidateDataBuckets ? [] : buckets
|
|
905
|
+
};
|
|
906
|
+
}
|
|
907
|
+
|
|
908
|
+
private async getParameterBucketChanges(
|
|
909
|
+
options: GetCheckpointChangesOptions
|
|
910
|
+
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
|
|
911
|
+
// TODO: limit max query running time
|
|
912
|
+
const parameterUpdates = await this.db.bucket_parameters
|
|
913
|
+
.find(
|
|
914
|
+
{
|
|
915
|
+
_id: { $gt: BigInt(options.lastCheckpoint), $lt: BigInt(options.nextCheckpoint) },
|
|
916
|
+
'key.g': this.group_id
|
|
917
|
+
},
|
|
918
|
+
{
|
|
919
|
+
projection: {
|
|
920
|
+
lookup: 1
|
|
921
|
+
},
|
|
922
|
+
limit: 1001,
|
|
923
|
+
batchSize: 1001,
|
|
924
|
+
singleBatch: true
|
|
925
|
+
}
|
|
926
|
+
)
|
|
927
|
+
.toArray();
|
|
928
|
+
const invalidateParameterUpdates = parameterUpdates.length > 1000;
|
|
929
|
+
|
|
930
|
+
return {
|
|
931
|
+
invalidateParameterBuckets: invalidateParameterUpdates,
|
|
932
|
+
updatedParameterLookups: invalidateParameterUpdates
|
|
933
|
+
? new Set<string>()
|
|
934
|
+
: new Set<string>(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup))))
|
|
935
|
+
};
|
|
936
|
+
}
|
|
937
|
+
|
|
938
|
+
// TODO:
|
|
939
|
+
// We can optimize this by implementing it like ChecksumCache: We can use partial cache results to do
|
|
940
|
+
// more efficient lookups in some cases.
|
|
941
|
+
private checkpointChangesCache = new LRUCache<string, CheckpointChanges, { options: GetCheckpointChangesOptions }>({
|
|
942
|
+
max: 50,
|
|
943
|
+
maxSize: 10 * 1024 * 1024,
|
|
944
|
+
sizeCalculation: (value: CheckpointChanges) => {
|
|
945
|
+
const paramSize = [...value.updatedParameterLookups].reduce<number>((a, b) => a + b.length, 0);
|
|
946
|
+
const bucketSize = [...value.updatedDataBuckets].reduce<number>((a, b) => a + b.length, 0);
|
|
947
|
+
return 100 + paramSize + bucketSize;
|
|
948
|
+
},
|
|
949
|
+
fetchMethod: async (_key, _staleValue, options) => {
|
|
950
|
+
return this.getCheckpointChangesInternal(options.context.options);
|
|
951
|
+
}
|
|
952
|
+
});
|
|
953
|
+
|
|
954
|
+
private _hasDynamicBucketsCached: boolean | undefined = undefined;
|
|
955
|
+
|
|
956
|
+
private hasDynamicBucketQueries(): boolean {
|
|
957
|
+
if (this._hasDynamicBucketsCached != null) {
|
|
958
|
+
return this._hasDynamicBucketsCached;
|
|
959
|
+
}
|
|
960
|
+
const syncRules = this.getParsedSyncRules({
|
|
961
|
+
defaultSchema: 'default' // n/a
|
|
962
|
+
});
|
|
963
|
+
const hasDynamicBuckets = syncRules.hasDynamicBucketQueries();
|
|
964
|
+
this._hasDynamicBucketsCached = hasDynamicBuckets;
|
|
965
|
+
return hasDynamicBuckets;
|
|
966
|
+
}
|
|
967
|
+
|
|
872
968
|
async getCheckpointChanges(options: GetCheckpointChangesOptions): Promise<CheckpointChanges> {
|
|
873
|
-
|
|
969
|
+
if (!this.hasDynamicBucketQueries()) {
|
|
970
|
+
// Special case when we have no dynamic parameter queries.
|
|
971
|
+
// In this case, we can avoid doing any queries.
|
|
972
|
+
return {
|
|
973
|
+
invalidateDataBuckets: true,
|
|
974
|
+
updatedDataBuckets: [],
|
|
975
|
+
invalidateParameterBuckets: false,
|
|
976
|
+
updatedParameterLookups: new Set<string>()
|
|
977
|
+
};
|
|
978
|
+
}
|
|
979
|
+
const key = `${options.lastCheckpoint}_${options.nextCheckpoint}`;
|
|
980
|
+
const result = await this.checkpointChangesCache.fetch(key, { context: { options } });
|
|
981
|
+
return result!;
|
|
982
|
+
}
|
|
983
|
+
|
|
984
|
+
private async getCheckpointChangesInternal(options: GetCheckpointChangesOptions): Promise<CheckpointChanges> {
|
|
985
|
+
const dataUpdates = await this.getDataBucketChanges(options);
|
|
986
|
+
const parameterUpdates = await this.getParameterBucketChanges(options);
|
|
987
|
+
|
|
988
|
+
return {
|
|
989
|
+
...dataUpdates,
|
|
990
|
+
...parameterUpdates
|
|
991
|
+
};
|
|
874
992
|
}
|
|
875
993
|
}
|
|
@@ -5,12 +5,13 @@ import * as bson from 'bson';
|
|
|
5
5
|
|
|
6
6
|
import { logger } from '@powersync/lib-services-framework';
|
|
7
7
|
import { InternalOpId, storage, utils } from '@powersync/service-core';
|
|
8
|
-
import { currentBucketKey } from './MongoBucketBatch.js';
|
|
8
|
+
import { currentBucketKey, MAX_ROW_SIZE } from './MongoBucketBatch.js';
|
|
9
9
|
import { MongoIdSequence } from './MongoIdSequence.js';
|
|
10
10
|
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;
|
|
@@ -83,11 +98,20 @@ export class PersistedBatch {
|
|
|
83
98
|
|
|
84
99
|
for (const k of options.evaluated) {
|
|
85
100
|
const key = currentBucketKey(k);
|
|
86
|
-
remaining_buckets.delete(key);
|
|
87
101
|
|
|
88
102
|
// INSERT
|
|
89
103
|
const recordData = JSONBig.stringify(k.data);
|
|
90
104
|
const checksum = utils.hashData(k.table, k.id, recordData);
|
|
105
|
+
if (recordData.length > MAX_ROW_SIZE) {
|
|
106
|
+
// In many cases, the raw data size would have been too large already. But there are cases where
|
|
107
|
+
// the BSON size is small enough, but the JSON size is too large.
|
|
108
|
+
// In these cases, we can't store the data, so we skip it, or generate a REMOVE operation if the row
|
|
109
|
+
// was synced previously.
|
|
110
|
+
logger.error(`powersync_${this.group_id} Row ${key} too large: ${recordData.length} bytes. Removing.`);
|
|
111
|
+
continue;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
remaining_buckets.delete(key);
|
|
91
115
|
this.currentSize += recordData.length + 200;
|
|
92
116
|
|
|
93
117
|
const op_id = options.op_seq.next();
|
|
@@ -111,6 +135,7 @@ export class PersistedBatch {
|
|
|
111
135
|
}
|
|
112
136
|
}
|
|
113
137
|
});
|
|
138
|
+
this.incrementBucket(k.bucket, op_id);
|
|
114
139
|
}
|
|
115
140
|
|
|
116
141
|
for (let bd of remaining_buckets.values()) {
|
|
@@ -138,6 +163,7 @@ export class PersistedBatch {
|
|
|
138
163
|
}
|
|
139
164
|
});
|
|
140
165
|
this.currentSize += 200;
|
|
166
|
+
this.incrementBucket(bd.bucket, op_id);
|
|
141
167
|
}
|
|
142
168
|
}
|
|
143
169
|
|
|
@@ -268,6 +294,14 @@ export class PersistedBatch {
|
|
|
268
294
|
});
|
|
269
295
|
}
|
|
270
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
|
+
|
|
271
305
|
const duration = performance.now() - startAt;
|
|
272
306
|
logger.info(
|
|
273
307
|
`powersync_${this.group_id} Flushed ${this.bucketData.length} + ${this.bucketParameters.length} + ${
|
|
@@ -278,7 +312,37 @@ export class PersistedBatch {
|
|
|
278
312
|
this.bucketData = [];
|
|
279
313
|
this.bucketParameters = [];
|
|
280
314
|
this.currentData = [];
|
|
315
|
+
this.bucketStates.clear();
|
|
281
316
|
this.currentSize = 0;
|
|
282
317
|
this.debugLastOpId = null;
|
|
283
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
|
+
$inc: {
|
|
335
|
+
op_count: state.incrementCount
|
|
336
|
+
}
|
|
337
|
+
},
|
|
338
|
+
upsert: true
|
|
339
|
+
}
|
|
340
|
+
} satisfies mongo.AnyBulkWriteOperation<BucketStateDocument>;
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
interface BucketStateUpdate {
|
|
346
|
+
lastOp: InternalOpId;
|
|
347
|
+
incrementCount: number;
|
|
284
348
|
}
|