@powersync/service-module-mongodb-storage 0.10.3 → 0.11.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.
Files changed (28) hide show
  1. package/CHANGELOG.md +48 -0
  2. package/dist/storage/implementation/MongoBucketBatch.d.ts +20 -1
  3. package/dist/storage/implementation/MongoBucketBatch.js +59 -3
  4. package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
  5. package/dist/storage/implementation/MongoParameterCompactor.d.ts +17 -0
  6. package/dist/storage/implementation/MongoParameterCompactor.js +92 -0
  7. package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -0
  8. package/dist/storage/implementation/MongoStorageProvider.js +2 -0
  9. package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
  10. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +12 -4
  11. package/dist/storage/implementation/MongoSyncBucketStorage.js +153 -109
  12. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  13. package/dist/storage/implementation/db.js +5 -2
  14. package/dist/storage/implementation/db.js.map +1 -1
  15. package/dist/storage/implementation/models.d.ts +6 -0
  16. package/dist/storage/implementation/util.d.ts +1 -4
  17. package/dist/storage/implementation/util.js +14 -7
  18. package/dist/storage/implementation/util.js.map +1 -1
  19. package/package.json +7 -7
  20. package/src/storage/implementation/MongoBucketBatch.ts +74 -2
  21. package/src/storage/implementation/MongoParameterCompactor.ts +105 -0
  22. package/src/storage/implementation/MongoStorageProvider.ts +2 -1
  23. package/src/storage/implementation/MongoSyncBucketStorage.ts +169 -152
  24. package/src/storage/implementation/db.ts +8 -2
  25. package/src/storage/implementation/models.ts +6 -0
  26. package/src/storage/implementation/util.ts +14 -8
  27. package/test/src/storage_compacting.test.ts +2 -0
  28. package/tsconfig.tsbuildinfo +1 -1
@@ -1 +1 @@
1
- {"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/storage/implementation/util.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"util.js","sourceRoot":"","sources":["../../../src/storage/implementation/util.ts"],"names":[],"mappings":"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"}
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.10.3",
5
+ "version": "0.11.0",
6
6
  "main": "dist/index.js",
7
7
  "license": "FSL-1.1-Apache-2.0",
8
8
  "type": "module",
@@ -27,15 +27,15 @@
27
27
  "lru-cache": "^10.2.2",
28
28
  "ts-codec": "^1.3.0",
29
29
  "uuid": "^11.1.0",
30
- "@powersync/lib-service-mongodb": "0.6.1",
31
- "@powersync/lib-services-framework": "0.7.0",
32
- "@powersync/service-core": "1.13.3",
30
+ "@powersync/lib-service-mongodb": "0.6.3",
31
+ "@powersync/lib-services-framework": "0.7.2",
32
+ "@powersync/service-core": "1.14.0",
33
33
  "@powersync/service-jsonbig": "0.17.10",
34
- "@powersync/service-sync-rules": "0.27.0",
35
- "@powersync/service-types": "0.12.0"
34
+ "@powersync/service-sync-rules": "0.28.0",
35
+ "@powersync/service-types": "0.12.1"
36
36
  },
37
37
  "devDependencies": {
38
- "@powersync/service-core-tests": "0.10.3"
38
+ "@powersync/service-core-tests": "0.11.0"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b",
@@ -16,6 +16,7 @@ import {
16
16
  BucketStorageMarkRecordUnavailable,
17
17
  deserializeBson,
18
18
  InternalOpId,
19
+ isCompleteRow,
19
20
  SaveOperationTag,
20
21
  storage,
21
22
  utils
@@ -49,6 +50,7 @@ export interface MongoBucketBatchOptions {
49
50
  lastCheckpointLsn: string | null;
50
51
  keepaliveOp: InternalOpId | null;
51
52
  noCheckpointBeforeLsn: string;
53
+ resumeFromLsn: string | null;
52
54
  storeCurrentData: boolean;
53
55
  /**
54
56
  * Set to true for initial replication.
@@ -99,6 +101,20 @@ export class MongoBucketBatch
99
101
  */
100
102
  public last_flushed_op: InternalOpId | null = null;
101
103
 
104
+ /**
105
+ * lastCheckpointLsn is the last consistent commit.
106
+ *
107
+ * While that is generally a "safe" point to resume from, there are cases where we may want to resume from a different point:
108
+ * 1. After an initial snapshot, we don't have a consistent commit yet, but need to resume from the snapshot LSN.
109
+ * 2. If "no_checkpoint_before_lsn" is set far in advance, it may take a while to reach that point. We
110
+ * may want to resume at incremental points before that.
111
+ *
112
+ * This is set when creating the batch, but may not be updated afterwards.
113
+ */
114
+ public resumeFromLsn: string | null = null;
115
+
116
+ private needsActivation = true;
117
+
102
118
  constructor(options: MongoBucketBatchOptions) {
103
119
  super();
104
120
  this.logger = options.logger ?? defaultLogger;
@@ -107,6 +123,7 @@ export class MongoBucketBatch
107
123
  this.group_id = options.groupId;
108
124
  this.last_checkpoint_lsn = options.lastCheckpointLsn;
109
125
  this.no_checkpoint_before_lsn = options.noCheckpointBeforeLsn;
126
+ this.resumeFromLsn = options.resumeFromLsn;
110
127
  this.session = this.client.startSession();
111
128
  this.slot_name = options.slotName;
112
129
  this.sync_rules = options.syncRules;
@@ -332,7 +349,7 @@ export class MongoBucketBatch
332
349
  // Not an error if we re-apply a transaction
333
350
  existing_buckets = [];
334
351
  existing_lookups = [];
335
- if (this.storeCurrentData) {
352
+ if (!isCompleteRow(this.storeCurrentData, after!)) {
336
353
  if (this.markRecordUnavailable != null) {
337
354
  // This will trigger a "resnapshot" of the record.
338
355
  // This is not relevant if storeCurrentData is false, since we'll get the full row
@@ -685,6 +702,7 @@ export class MongoBucketBatch
685
702
 
686
703
  if (!createEmptyCheckpoints && this.persisted_op == null) {
687
704
  // Nothing to commit - also return true
705
+ await this.autoActivate(lsn);
688
706
  return true;
689
707
  }
690
708
 
@@ -729,12 +747,65 @@ export class MongoBucketBatch
729
747
  },
730
748
  { session: this.session }
731
749
  );
750
+ await this.autoActivate(lsn);
732
751
  await this.db.notifyCheckpoint();
733
752
  this.persisted_op = null;
734
753
  this.last_checkpoint_lsn = lsn;
735
754
  return true;
736
755
  }
737
756
 
757
+ /**
758
+ * Switch from processing -> active if relevant.
759
+ *
760
+ * Called on new commits.
761
+ */
762
+ private async autoActivate(lsn: string) {
763
+ if (!this.needsActivation) {
764
+ return;
765
+ }
766
+
767
+ // Activate the batch, so it can start processing.
768
+ // This is done automatically when the first save() is called.
769
+
770
+ const session = this.session;
771
+ let activated = false;
772
+ await session.withTransaction(async () => {
773
+ const doc = await this.db.sync_rules.findOne({ _id: this.group_id }, { session });
774
+ if (doc && doc.state == 'PROCESSING') {
775
+ await this.db.sync_rules.updateOne(
776
+ {
777
+ _id: this.group_id
778
+ },
779
+ {
780
+ $set: {
781
+ state: storage.SyncRuleState.ACTIVE
782
+ }
783
+ },
784
+ { session }
785
+ );
786
+
787
+ await this.db.sync_rules.updateMany(
788
+ {
789
+ _id: { $ne: this.group_id },
790
+ state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
791
+ },
792
+ {
793
+ $set: {
794
+ state: storage.SyncRuleState.STOP
795
+ }
796
+ },
797
+ { session }
798
+ );
799
+ activated = true;
800
+ }
801
+ });
802
+ if (activated) {
803
+ this.logger.info(`Activated new sync rules at ${lsn}`);
804
+ await this.db.notifyCheckpoint();
805
+ }
806
+ this.needsActivation = false;
807
+ }
808
+
738
809
  async keepalive(lsn: string): Promise<boolean> {
739
810
  if (this.last_checkpoint_lsn != null && lsn <= this.last_checkpoint_lsn) {
740
811
  // No-op
@@ -782,13 +853,14 @@ export class MongoBucketBatch
782
853
  },
783
854
  { session: this.session }
784
855
  );
856
+ await this.autoActivate(lsn);
785
857
  await this.db.notifyCheckpoint();
786
858
  this.last_checkpoint_lsn = lsn;
787
859
 
788
860
  return true;
789
861
  }
790
862
 
791
- async setSnapshotLsn(lsn: string): Promise<void> {
863
+ async setResumeLsn(lsn: string): Promise<void> {
792
864
  const update: Partial<SyncRuleDocument> = {
793
865
  snapshot_lsn: lsn
794
866
  };
@@ -0,0 +1,105 @@
1
+ import { logger } from '@powersync/lib-services-framework';
2
+ import { bson, CompactOptions, InternalOpId } from '@powersync/service-core';
3
+ import { LRUCache } from 'lru-cache';
4
+ import { PowerSyncMongo } from './db.js';
5
+ import { mongo } from '@powersync/lib-service-mongodb';
6
+ import { BucketParameterDocument } from './models.js';
7
+
8
+ /**
9
+ * Compacts parameter lookup data (the bucket_parameters collection).
10
+ *
11
+ * This scans through the entire collection to find data to compact.
12
+ *
13
+ * For background, see the `/docs/parameters-lookups.md` file.
14
+ */
15
+ export class MongoParameterCompactor {
16
+ constructor(
17
+ private db: PowerSyncMongo,
18
+ private group_id: number,
19
+ private checkpoint: InternalOpId,
20
+ private options: CompactOptions
21
+ ) {}
22
+
23
+ async compact() {
24
+ logger.info(`Compacting parameters for group ${this.group_id} up to checkpoint ${this.checkpoint}`);
25
+ // This is the currently-active checkpoint.
26
+ // We do not remove any data that may be used by this checkpoint.
27
+ // snapshot queries ensure that if any clients are still using older checkpoints, they would
28
+ // not be affected by this compaction.
29
+ const checkpoint = this.checkpoint;
30
+
31
+ // Index on {'key.g': 1, lookup: 1, _id: 1}
32
+ // In theory, we could let MongoDB do more of the work here, by grouping by (key, lookup)
33
+ // in MongoDB already. However, that risks running into cases where MongoDB needs to process
34
+ // very large amounts of data before returning results, which could lead to timeouts.
35
+ const cursor = this.db.bucket_parameters.find(
36
+ {
37
+ 'key.g': this.group_id
38
+ },
39
+ {
40
+ sort: { lookup: 1, _id: 1 },
41
+ batchSize: 10_000,
42
+ projection: { _id: 1, key: 1, lookup: 1, bucket_parameters: 1 }
43
+ }
44
+ );
45
+
46
+ // The index doesn't cover sorting by key, so we keep our own cache of the last seen key.
47
+ let lastByKey = new LRUCache<string, InternalOpId>({
48
+ max: this.options.compactParameterCacheLimit ?? 10_000
49
+ });
50
+ let removeIds: InternalOpId[] = [];
51
+ let removeDeleted: mongo.AnyBulkWriteOperation<BucketParameterDocument>[] = [];
52
+
53
+ const flush = async (force: boolean) => {
54
+ if (removeIds.length >= 1000 || (force && removeIds.length > 0)) {
55
+ const results = await this.db.bucket_parameters.deleteMany({ _id: { $in: removeIds } });
56
+ logger.info(`Removed ${results.deletedCount} (${removeIds.length}) superseded parameter entries`);
57
+ removeIds = [];
58
+ }
59
+
60
+ if (removeDeleted.length > 10 || (force && removeDeleted.length > 0)) {
61
+ const results = await this.db.bucket_parameters.bulkWrite(removeDeleted);
62
+ logger.info(`Removed ${results.deletedCount} (${removeDeleted.length}) deleted parameter entries`);
63
+ removeDeleted = [];
64
+ }
65
+ };
66
+
67
+ while (await cursor.hasNext()) {
68
+ const batch = cursor.readBufferedDocuments();
69
+ for (let doc of batch) {
70
+ if (doc._id >= checkpoint) {
71
+ continue;
72
+ }
73
+ const uniqueKey = (
74
+ bson.serialize({
75
+ k: doc.key,
76
+ l: doc.lookup
77
+ }) as Buffer
78
+ ).toString('base64');
79
+ const previous = lastByKey.get(uniqueKey);
80
+ if (previous != null && previous < doc._id) {
81
+ // We have a newer entry for the same key, so we can remove the old one.
82
+ removeIds.push(previous);
83
+ }
84
+ lastByKey.set(uniqueKey, doc._id);
85
+
86
+ if (doc.bucket_parameters?.length == 0) {
87
+ // This is a delete operation, so we can remove it completely.
88
+ // For this we cannot remove the operation itself only: There is a possibility that
89
+ // there is still an earlier operation with the same key and lookup, that we don't have
90
+ // in the cache due to cache size limits. So we need to explicitly remove all earlier operations.
91
+ removeDeleted.push({
92
+ deleteMany: {
93
+ filter: { 'key.g': doc.key.g, lookup: doc.lookup, _id: { $lte: doc._id }, key: doc.key }
94
+ }
95
+ });
96
+ }
97
+ }
98
+
99
+ await flush(false);
100
+ }
101
+
102
+ await flush(true);
103
+ logger.info('Parameter compaction completed');
104
+ }
105
+ }
@@ -1,6 +1,6 @@
1
1
  import * as lib_mongo from '@powersync/lib-service-mongodb';
2
2
  import { ErrorCode, logger, ServiceAssertionError, ServiceError } from '@powersync/lib-services-framework';
3
- import { storage } from '@powersync/service-core';
3
+ import { POWERSYNC_VERSION, storage } from '@powersync/service-core';
4
4
  import { MongoStorageConfig } from '../../types/types.js';
5
5
  import { MongoBucketStorage } from '../MongoBucketStorage.js';
6
6
  import { PowerSyncMongo } from './db.js';
@@ -23,6 +23,7 @@ export class MongoStorageProvider implements storage.BucketStorageProvider {
23
23
 
24
24
  const decodedConfig = MongoStorageConfig.decode(storage as any);
25
25
  const client = lib_mongo.db.createMongoClient(decodedConfig, {
26
+ powersyncVersion: POWERSYNC_VERSION,
26
27
  maxPoolSize: resolvedConfig.storage.max_pool_size ?? 8
27
28
  });
28
29