@powersync/service-module-mongodb-storage 0.13.2 → 0.14.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 +30 -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 +13 -51
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +5 -2
- package/dist/storage/implementation/MongoChecksums.js +7 -4
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.js +42 -17
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +2 -12
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +24 -24
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +4 -2
- package/dist/storage/implementation/MongoSyncBucketStorage.js +4 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +13 -1
- package/dist/storage/implementation/models.js +9 -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 +3 -4
- package/dist/utils/test-utils.js +2 -2
- package/dist/utils/test-utils.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 +21 -59
- package/src/storage/implementation/MongoChecksums.ts +14 -6
- package/src/storage/implementation/MongoCompactor.ts +49 -19
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +26 -32
- package/src/storage/implementation/MongoSyncBucketStorage.ts +16 -5
- package/src/storage/implementation/models.ts +25 -1
- package/src/storage/storage-index.ts +0 -1
- package/src/utils/test-utils.ts +3 -4
- package/test/src/__snapshots__/storage_sync.test.ts.snap +1116 -21
- package/test/src/storage_compacting.test.ts +28 -22
- package/test/src/storage_sync.test.ts +27 -14
- package/test/src/util.ts +3 -0
- 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
- package/test/src/__snapshots__/storage.test.ts.snap +0 -25
|
@@ -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;
|
|
@@ -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;
|
|
@@ -1,48 +1,42 @@
|
|
|
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
4
|
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;
|
|
5
|
+
import { getMongoStorageConfig, SyncRuleDocument } from './models.js';
|
|
6
|
+
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
20
7
|
|
|
8
|
+
export class MongoPersistedSyncRulesContent extends storage.PersistedSyncRulesContent {
|
|
21
9
|
public current_lock: MongoSyncRulesLock | null = null;
|
|
22
10
|
|
|
23
11
|
constructor(
|
|
24
12
|
private db: PowerSyncMongo,
|
|
25
13
|
doc: mongo.WithId<SyncRuleDocument>
|
|
26
14
|
) {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
15
|
+
super({
|
|
16
|
+
id: doc._id,
|
|
17
|
+
sync_rules_content: doc.content,
|
|
18
|
+
compiled_plan: doc.serialized_plan ?? null,
|
|
19
|
+
last_checkpoint_lsn: doc.last_checkpoint_lsn,
|
|
20
|
+
// Handle legacy values
|
|
21
|
+
slot_name: doc.slot_name ?? `powersync_${doc._id}`,
|
|
22
|
+
last_fatal_error: doc.last_fatal_error,
|
|
23
|
+
last_fatal_error_ts: doc.last_fatal_error_ts,
|
|
24
|
+
last_checkpoint_ts: doc.last_checkpoint_ts,
|
|
25
|
+
last_keepalive_ts: doc.last_keepalive_ts,
|
|
26
|
+
active: doc.state == 'ACTIVE',
|
|
27
|
+
storageVersion: doc.storage_version ?? storage.LEGACY_STORAGE_VERSION
|
|
28
|
+
});
|
|
37
29
|
}
|
|
38
30
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
31
|
+
getStorageConfig() {
|
|
32
|
+
const storageConfig = getMongoStorageConfig(this.storageVersion);
|
|
33
|
+
if (storageConfig == null) {
|
|
34
|
+
throw new ServiceError(
|
|
35
|
+
ErrorCode.PSYNC_S1005,
|
|
36
|
+
`Unsupported storage version ${this.storageVersion} for sync rules ${this.id}`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
return storageConfig;
|
|
46
40
|
}
|
|
47
41
|
|
|
48
42
|
async lock() {
|
|
@@ -32,7 +32,14 @@ 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
34
|
import { PowerSyncMongo } from './db.js';
|
|
35
|
-
import {
|
|
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';
|
|
@@ -40,7 +47,8 @@ import { MongoParameterCompactor } from './MongoParameterCompactor.js';
|
|
|
40
47
|
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
41
48
|
|
|
42
49
|
export interface MongoSyncBucketStorageOptions {
|
|
43
|
-
checksumOptions?: MongoChecksumOptions
|
|
50
|
+
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
|
|
51
|
+
storageConfig: StorageConfig;
|
|
44
52
|
}
|
|
45
53
|
|
|
46
54
|
/**
|
|
@@ -69,12 +77,15 @@ export class MongoSyncBucketStorage
|
|
|
69
77
|
public readonly group_id: number,
|
|
70
78
|
private readonly sync_rules: storage.PersistedSyncRulesContent,
|
|
71
79
|
public readonly slot_name: string,
|
|
72
|
-
writeCheckpointMode
|
|
73
|
-
options
|
|
80
|
+
writeCheckpointMode: storage.WriteCheckpointMode | undefined,
|
|
81
|
+
options: MongoSyncBucketStorageOptions
|
|
74
82
|
) {
|
|
75
83
|
super();
|
|
76
84
|
this.db = factory.db;
|
|
77
|
-
this.checksums = new MongoChecksums(this.db, this.group_id,
|
|
85
|
+
this.checksums = new MongoChecksums(this.db, this.group_id, {
|
|
86
|
+
...options.checksumOptions,
|
|
87
|
+
storageConfig: options?.storageConfig
|
|
88
|
+
});
|
|
78
89
|
this.writeCheckpointAPI = new MongoWriteCheckpointAPI({
|
|
79
90
|
db: this.db,
|
|
80
91
|
mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { InternalOpId, storage } from '@powersync/service-core';
|
|
1
|
+
import { InternalOpId, SerializedSyncPlan, storage } from '@powersync/service-core';
|
|
2
2
|
import { SqliteJsonValue } from '@powersync/service-sync-rules';
|
|
3
3
|
import * as bson from 'bson';
|
|
4
4
|
import { event_types } from '@powersync/service-types';
|
|
@@ -199,11 +199,35 @@ export interface SyncRuleDocument {
|
|
|
199
199
|
last_fatal_error_ts: Date | null;
|
|
200
200
|
|
|
201
201
|
content: string;
|
|
202
|
+
serialized_plan?: SerializedSyncPlan | null;
|
|
202
203
|
|
|
203
204
|
lock?: {
|
|
204
205
|
id: string;
|
|
205
206
|
expires_at: Date;
|
|
206
207
|
} | null;
|
|
208
|
+
|
|
209
|
+
storage_version?: number;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
export interface StorageConfig extends storage.StorageVersionConfig {
|
|
213
|
+
/**
|
|
214
|
+
* When true, bucket_data.checksum is guaranteed to be persisted as a Long.
|
|
215
|
+
*
|
|
216
|
+
* When false, it could also have been persisted as an Int32 or Double, in which case it must be converted to
|
|
217
|
+
* a Long before summing.
|
|
218
|
+
*/
|
|
219
|
+
longChecksums: boolean;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const LONG_CHECKSUMS_STORAGE_VERSION = 2;
|
|
223
|
+
|
|
224
|
+
export function getMongoStorageConfig(storageVersion: number): StorageConfig | undefined {
|
|
225
|
+
const baseConfig = storage.STORAGE_VERSION_CONFIG[storageVersion];
|
|
226
|
+
if (baseConfig == null) {
|
|
227
|
+
return undefined;
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
return { ...baseConfig, longChecksums: storageVersion >= LONG_CHECKSUMS_STORAGE_VERSION };
|
|
207
231
|
}
|
|
208
232
|
|
|
209
233
|
export interface CheckpointEventDocument {
|
|
@@ -2,7 +2,6 @@ export * from './implementation/db.js';
|
|
|
2
2
|
export * from './implementation/models.js';
|
|
3
3
|
export * from './implementation/MongoBucketBatch.js';
|
|
4
4
|
export * from './implementation/MongoIdSequence.js';
|
|
5
|
-
export * from './implementation/MongoPersistedSyncRules.js';
|
|
6
5
|
export * from './implementation/MongoPersistedSyncRulesContent.js';
|
|
7
6
|
export * from './implementation/MongoStorageProvider.js';
|
|
8
7
|
export * from './implementation/MongoSyncBucketStorage.js';
|
package/src/utils/test-utils.ts
CHANGED
|
@@ -1,14 +1,13 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { PowerSyncMongo } from '../storage/implementation/db.js';
|
|
3
2
|
import { TestStorageOptions } from '@powersync/service-core';
|
|
3
|
+
import { MongoBucketStorage, MongoBucketStorageOptions } from '../storage/MongoBucketStorage.js';
|
|
4
4
|
import { MongoReportStorage } from '../storage/MongoReportStorage.js';
|
|
5
|
-
import {
|
|
6
|
-
import { MongoSyncBucketStorageOptions } from '../storage/implementation/MongoSyncBucketStorage.js';
|
|
5
|
+
import { PowerSyncMongo } from '../storage/implementation/db.js';
|
|
7
6
|
|
|
8
7
|
export type MongoTestStorageOptions = {
|
|
9
8
|
url: string;
|
|
10
9
|
isCI: boolean;
|
|
11
|
-
internalOptions?:
|
|
10
|
+
internalOptions?: MongoBucketStorageOptions;
|
|
12
11
|
};
|
|
13
12
|
|
|
14
13
|
export function mongoTestStorageFactoryGenerator(factoryOptions: MongoTestStorageOptions) {
|