@powersync/service-module-mongodb-storage 0.12.0 → 0.12.2

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 (37) hide show
  1. package/CHANGELOG.md +20 -0
  2. package/dist/migrations/db/migrations/1741697235857-bucket-state-index.js +1 -4
  3. package/dist/migrations/db/migrations/1741697235857-bucket-state-index.js.map +1 -1
  4. package/dist/storage/MongoBucketStorage.d.ts +3 -2
  5. package/dist/storage/MongoBucketStorage.js +4 -2
  6. package/dist/storage/MongoBucketStorage.js.map +1 -1
  7. package/dist/storage/implementation/MongoChecksums.d.ts +66 -0
  8. package/dist/storage/implementation/MongoChecksums.js +287 -0
  9. package/dist/storage/implementation/MongoChecksums.js.map +1 -0
  10. package/dist/storage/implementation/MongoCompactor.d.ts +9 -2
  11. package/dist/storage/implementation/MongoCompactor.js +116 -39
  12. package/dist/storage/implementation/MongoCompactor.js.map +1 -1
  13. package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +7 -4
  14. package/dist/storage/implementation/MongoSyncBucketStorage.js +14 -132
  15. package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
  16. package/dist/storage/implementation/MongoTestStorageFactoryGenerator.d.ts +2 -0
  17. package/dist/storage/implementation/MongoTestStorageFactoryGenerator.js +4 -3
  18. package/dist/storage/implementation/MongoTestStorageFactoryGenerator.js.map +1 -1
  19. package/dist/storage/implementation/db.d.ts +4 -0
  20. package/dist/storage/implementation/db.js +10 -0
  21. package/dist/storage/implementation/db.js.map +1 -1
  22. package/dist/storage/implementation/models.d.ts +5 -1
  23. package/dist/storage/implementation/util.js.map +1 -1
  24. package/package.json +4 -4
  25. package/src/migrations/db/migrations/1741697235857-bucket-state-index.ts +1 -7
  26. package/src/storage/MongoBucketStorage.ts +4 -3
  27. package/src/storage/implementation/MongoChecksums.ts +342 -0
  28. package/src/storage/implementation/MongoCompactor.ts +156 -64
  29. package/src/storage/implementation/MongoSyncBucketStorage.ts +21 -152
  30. package/src/storage/implementation/MongoTestStorageFactoryGenerator.ts +7 -4
  31. package/src/storage/implementation/db.ts +14 -0
  32. package/src/storage/implementation/models.ts +5 -1
  33. package/src/storage/implementation/util.ts +1 -1
  34. package/test/src/__snapshots__/storage.test.ts.snap +17 -1
  35. package/test/src/storage.test.ts +38 -1
  36. package/test/src/storage_compacting.test.ts +120 -5
  37. package/tsconfig.tsbuildinfo +1 -1
@@ -7,12 +7,13 @@ export const MongoTestStorageFactoryGenerator = (factoryOptions) => {
7
7
  if (!(await db.db.listCollections({ name: db.bucket_parameters.collectionName }).hasNext())) {
8
8
  await db.db.createCollection('bucket_parameters');
9
9
  }
10
- // Full migrations are not currently run for tests, so we manually create this
11
- await db.createCheckpointEventsCollection();
12
10
  if (!options?.doNotClear) {
13
11
  await db.clear();
14
12
  }
15
- return new MongoBucketStorage(db, { slot_name_prefix: 'test_' });
13
+ // Full migrations are not currently run for tests, so we manually create the important ones
14
+ await db.createCheckpointEventsCollection();
15
+ await db.createBucketStateIndex();
16
+ return new MongoBucketStorage(db, { slot_name_prefix: 'test_' }, factoryOptions.internalOptions);
16
17
  };
17
18
  };
18
19
  //# sourceMappingURL=MongoTestStorageFactoryGenerator.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MongoTestStorageFactoryGenerator.js","sourceRoot":"","sources":["../../../src/storage/implementation/MongoTestStorageFactoryGenerator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AAOjD,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,cAAuC,EAAE,EAAE;IAC1F,OAAO,KAAK,EAAE,OAA4B,EAAE,EAAE;QAC5C,MAAM,EAAE,GAAG,oBAAoB,CAAC,cAAc,CAAC,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;QAEzE,8EAA8E;QAC9E,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC5F,MAAM,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;QACpD,CAAC;QAED,8EAA8E;QAC9E,MAAM,EAAE,CAAC,gCAAgC,EAAE,CAAC;QAE5C,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;YACzB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QAED,OAAO,IAAI,kBAAkB,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,CAAC,CAAC;IACnE,CAAC,CAAC;AACJ,CAAC,CAAC"}
1
+ {"version":3,"file":"MongoTestStorageFactoryGenerator.js","sourceRoot":"","sources":["../../../src/storage/implementation/MongoTestStorageFactoryGenerator.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,kBAAkB,EAAE,MAAM,0BAA0B,CAAC;AAC9D,OAAO,EAAE,oBAAoB,EAAE,MAAM,WAAW,CAAC;AASjD,MAAM,CAAC,MAAM,gCAAgC,GAAG,CAAC,cAAuC,EAAE,EAAE;IAC1F,OAAO,KAAK,EAAE,OAA4B,EAAE,EAAE;QAC5C,MAAM,EAAE,GAAG,oBAAoB,CAAC,cAAc,CAAC,GAAG,EAAE,cAAc,CAAC,IAAI,CAAC,CAAC;QAEzE,8EAA8E;QAC9E,IAAI,CAAC,CAAC,MAAM,EAAE,CAAC,EAAE,CAAC,eAAe,CAAC,EAAE,IAAI,EAAE,EAAE,CAAC,iBAAiB,CAAC,cAAc,EAAE,CAAC,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;YAC5F,MAAM,EAAE,CAAC,EAAE,CAAC,gBAAgB,CAAC,mBAAmB,CAAC,CAAC;QACpD,CAAC;QAED,IAAI,CAAC,OAAO,EAAE,UAAU,EAAE,CAAC;YACzB,MAAM,EAAE,CAAC,KAAK,EAAE,CAAC;QACnB,CAAC;QAED,4FAA4F;QAC5F,MAAM,EAAE,CAAC,gCAAgC,EAAE,CAAC;QAC5C,MAAM,EAAE,CAAC,sBAAsB,EAAE,CAAC;QAElC,OAAO,IAAI,kBAAkB,CAAC,EAAE,EAAE,EAAE,gBAAgB,EAAE,OAAO,EAAE,EAAE,cAAc,CAAC,eAAe,CAAC,CAAC;IACnG,CAAC,CAAC;AACJ,CAAC,CAAC"}
@@ -44,5 +44,9 @@ export declare class PowerSyncMongo {
44
44
  * Only use in migrations and tests.
45
45
  */
46
46
  createCheckpointEventsCollection(): Promise<void>;
47
+ /**
48
+ * Only use in migrations and tests.
49
+ */
50
+ createBucketStateIndex(): Promise<void>;
47
51
  }
48
52
  export declare function createPowerSyncMongo(config: MongoStorageConfig, options?: lib_mongo.MongoConnectionOptions): PowerSyncMongo;
@@ -95,6 +95,16 @@ export class PowerSyncMongo {
95
95
  max: 50 // max number of documents
96
96
  });
97
97
  }
98
+ /**
99
+ * Only use in migrations and tests.
100
+ */
101
+ async createBucketStateIndex() {
102
+ // TODO: Implement a better mechanism to use migrations in tests
103
+ await this.bucket_state.createIndex({
104
+ '_id.g': 1,
105
+ last_op: 1
106
+ }, { name: 'bucket_updates', unique: true });
107
+ }
98
108
  }
99
109
  export function createPowerSyncMongo(config, options) {
100
110
  return new PowerSyncMongo(lib_mongo.createMongoClient(config, {
@@ -1 +1 @@
1
- {"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/storage/implementation/db.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"db.js","sourceRoot":"","sources":["../../../src/storage/implementation/db.ts"],"names":[],"mappings":"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"}
@@ -90,6 +90,10 @@ export interface BucketStateDocument {
90
90
  g: number;
91
91
  b: string;
92
92
  };
93
+ /**
94
+ * Important: There is an unique index on {'_id.g': 1, last_op: 1}.
95
+ * That means the last_op must match an actual op in the bucket, and not the commit checkpoint.
96
+ */
93
97
  last_op: bigint;
94
98
  /**
95
99
  * If set, this can be treated as "cache" of a checksum at a specific point.
@@ -99,7 +103,7 @@ export interface BucketStateDocument {
99
103
  op_id: InternalOpId;
100
104
  count: number;
101
105
  checksum: bigint;
102
- bytes: number;
106
+ bytes: number | null;
103
107
  };
104
108
  estimate_since_compact?: {
105
109
  count: number;
@@ -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":"AAAA,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAC7B,OAAO,KAAK,MAAM,MAAM,QAAQ,CAAC;AACjC,OAAO,KAAK,IAAI,MAAM,MAAM,CAAC;AAE7B,OAAO,EAAE,KAAK,EAAE,MAAM,gCAAgC,CAAC;AACvD,OAAO,EAA0D,OAAO,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAEjH,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAC;AAEzC,OAAO,EAAE,qBAAqB,EAAE,MAAM,mCAAmC,CAAC;AAE1E,MAAM,UAAU,cAAc,CAAI,MAAkB,EAAE,IAAiB;IACrE,IAAI,MAAM,GAAG;QACX,IAAI,EAAE;YACJ,GAAG,MAAM;SACH;QACR,GAAG,EAAE;YACH,GAAG,MAAM;SACH;KACT,CAAC;IAEF,KAAK,IAAI,GAAG,IAAI,IAAI,EAAE,CAAC;QACrB,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QACrC,MAAM,CAAC,GAAG,CAAC,GAAG,CAAC,GAAG,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;IACtC,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,MAAM,UAAU,gBAAgB,CAAC,MAAc,EAAE,aAAqB;IACpE,MAAM,WAAW,GAAG,MAAM,CAAC,WAAW,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;IAC1D,OAAO,GAAG,MAAM,GAAG,aAAa,IAAI,WAAW,EAAE,CAAC;AACpD,CAAC;AAED;;;;;;;;;;GAUG;AACH,MAAM,CAAC,KAAK,UAAU,eAAe,CAAI,MAA+B;IACtE,IAAI,CAAC;QACH,IAAI,IAAS,CAAC;QACd,IAAI,OAAO,GAAG,IAAI,CAAC;QACnB,2CAA2C;QAC3C,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,EAAE,CAAC;QACtC,yCAAyC;QACzC,IAAI,GAAG,MAAM,CAAC,qBAAqB,EAAE,CAAC;QACtC,IAAI,CAAC,MAAM,IAAI,MAAM,CAAC,EAAE,EAAE,MAAM,EAAE,EAAE,CAAC;YACnC,0CAA0C;YAC1C,wEAAwE;YACxE,uEAAuE;YACvE,oCAAoC;YACpC,EAAE;YACF,4EAA4E;YAC5E,2DAA2D;YAC3D,gCAAgC;YAChC,OAAO,GAAG,KAAK,CAAC;QAClB,CAAC;QACD,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,CAAC;IAC3B,CAAC;YAAS,CAAC;QACT,iDAAiD;QACjD,uIAAuI;QACvI,IAAI,CAAC,MAAM,CAAC,MAAM,EAAE,CAAC;YACnB,MAAM,MAAM,CAAC,KAAK,EAAE,CAAC;QACvB,CAAC;IACH,CAAC;AACH,CAAC;AAED,MAAM,UAAU,UAAU,CAAC,GAAuB;IAChD,IAAI,GAAG,CAAC,EAAE,IAAI,KAAK,IAAI,GAAG,CAAC,EAAE,IAAI,QAAQ,EAAE,CAAC;QAC1C,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9C,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,WAAW,EAAE,GAAG,CAAC,KAAK;YACtB,SAAS,EAAE,GAAG,CAAC,MAAM;YACrB,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;YAC9B,MAAM,EAAE,iBAAiB,CAAC,GAAG,CAAC,YAAa,EAAE,GAAG,CAAC,UAAW,CAAC;YAC7D,IAAI,EAAE,GAAG,CAAC,IAAI;SACf,CAAC;IACJ,CAAC;SAAM,CAAC;QACN,cAAc;QAEd,OAAO;YACL,KAAK,EAAE,KAAK,CAAC,sBAAsB,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC;YAC9C,EAAE,EAAE,GAAG,CAAC,EAAE;YACV,QAAQ,EAAE,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC;SAC/B,CAAC;IACJ,CAAC;AACH,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,KAAoB,EAAE,EAAqB;IAC3E,IAAI,OAAO,CAAC,MAAM,CAAC,EAAE,CAAC,EAAE,CAAC;QACvB,mDAAmD;QACnD,OAAO,GAAG,KAAK,CAAC,WAAW,EAAE,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,CAAC;IACtD,CAAC;SAAM,CAAC;QACN,oCAAoC;QACpC,MAAM,IAAI,GAAG,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;QAC3C,OAAO,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,KAAK,CAAC,YAAY,CAAC,CAAC;IAC3C,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,CAAC,MAAM,oBAAoB,GAAG,CAAC,GAAW,EAAE,IAAa,EAAE,EAAE;IACjE,0EAA0E;IAC1E,oEAAoE;IACpE,MAAM,MAAM,GAAG,IAAI,KAAK,CAAC,WAAW,CAAC,GAAG,EAAE;QACxC,gBAAgB,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;QACvC,eAAe,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;QACtC,wBAAwB,EAAE,IAAI,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,KAAK;KAChD,CAAC,CAAC;IACH,OAAO,IAAI,cAAc,CAAC,MAAM,CAAC,CAAC;AACpC,CAAC,CAAC;AAEF,MAAM,UAAU,sBAAsB,CAAC,OAA4B,EAAE,IAAoB;IACvF,gGAAgG;IAChG,IAAI,CAAC,OAAO,CAAC,eAAe,EAAE,CAAC;QAC7B,MAAM,IAAI,qBAAqB,CAAC,oCAAoC,CAAC,CAAC;IACxE,CAAC;IACD,IAAK,OAAe,CAAC,YAAY,IAAI,IAAI,EAAE,CAAC;QACzC,OAAe,CAAC,YAAY,GAAG,IAAI,CAAC;IACvC,CAAC;SAAM,CAAC;QACN,MAAM,IAAI,qBAAqB,CAAC,qCAAqC,CAAC,CAAC;IACzE,CAAC;AACH,CAAC"}
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.12.0",
5
+ "version": "0.12.2",
6
6
  "main": "dist/index.js",
7
7
  "license": "FSL-1.1-ALv2",
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.4",
30
+ "@powersync/lib-service-mongodb": "0.6.5",
31
31
  "@powersync/lib-services-framework": "0.7.3",
32
- "@powersync/service-core": "1.15.0",
32
+ "@powersync/service-core": "1.15.2",
33
33
  "@powersync/service-jsonbig": "0.17.11",
34
34
  "@powersync/service-sync-rules": "0.29.0",
35
35
  "@powersync/service-types": "0.13.0"
36
36
  },
37
37
  "devDependencies": {
38
- "@powersync/service-core-tests": "0.12.0"
38
+ "@powersync/service-core-tests": "0.12.2"
39
39
  },
40
40
  "scripts": {
41
41
  "build": "tsc -b",
@@ -11,13 +11,7 @@ export const up: migrations.PowerSyncMigrationFunction = async (context) => {
11
11
  const db = storage.createPowerSyncMongo(configuration.storage as MongoStorageConfig);
12
12
 
13
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
- );
14
+ await db.createBucketStateIndex();
21
15
  } finally {
22
16
  await db.client.close();
23
17
  }
@@ -11,7 +11,7 @@ import { mongo } from '@powersync/lib-service-mongodb';
11
11
  import { PowerSyncMongo } from './implementation/db.js';
12
12
  import { SyncRuleDocument } from './implementation/models.js';
13
13
  import { MongoPersistedSyncRulesContent } from './implementation/MongoPersistedSyncRulesContent.js';
14
- import { MongoSyncBucketStorage } from './implementation/MongoSyncBucketStorage.js';
14
+ import { MongoSyncBucketStorage, MongoSyncBucketStorageOptions } from './implementation/MongoSyncBucketStorage.js';
15
15
  import { generateSlotName } from './implementation/util.js';
16
16
 
17
17
  export class MongoBucketStorage
@@ -31,7 +31,8 @@ export class MongoBucketStorage
31
31
  db: PowerSyncMongo,
32
32
  options: {
33
33
  slot_name_prefix: string;
34
- }
34
+ },
35
+ private internalOptions?: MongoSyncBucketStorageOptions
35
36
  ) {
36
37
  super();
37
38
  this.client = db.client;
@@ -49,7 +50,7 @@ export class MongoBucketStorage
49
50
  if ((typeof id as any) == 'bigint') {
50
51
  id = Number(id);
51
52
  }
52
- const storage = new MongoSyncBucketStorage(this, id, syncRules, slot_name);
53
+ const storage = new MongoSyncBucketStorage(this, id, syncRules, slot_name, undefined, this.internalOptions);
53
54
  if (!options?.skipLifecycleHooks) {
54
55
  this.iterateListeners((cb) => cb.syncStorageCreated?.(storage));
55
56
  }
@@ -0,0 +1,342 @@
1
+ import * as lib_mongo from '@powersync/lib-service-mongodb';
2
+ import {
3
+ addPartialChecksums,
4
+ bson,
5
+ BucketChecksum,
6
+ ChecksumCache,
7
+ ChecksumMap,
8
+ FetchPartialBucketChecksum,
9
+ InternalOpId,
10
+ isPartialChecksum,
11
+ PartialChecksum,
12
+ PartialChecksumMap,
13
+ PartialOrFullChecksum
14
+ } from '@powersync/service-core';
15
+ import { PowerSyncMongo } from './db.js';
16
+
17
+ /**
18
+ * Checksum calculation options, primarily for tests.
19
+ */
20
+ export interface MongoChecksumOptions {
21
+ /**
22
+ * How many buckets to process in a batch when calculating checksums.
23
+ */
24
+ bucketBatchLimit?: number;
25
+
26
+ /**
27
+ * Limit on the number of documents to calculate a checksum on at a time.
28
+ */
29
+ operationBatchLimit?: number;
30
+ }
31
+
32
+ const DEFAULT_BUCKET_BATCH_LIMIT = 200;
33
+ const DEFAULT_OPERATION_BATCH_LIMIT = 50_000;
34
+
35
+ /**
36
+ * Checksum query implementation.
37
+ *
38
+ * General implementation flow is:
39
+ * 1. getChecksums() -> check cache for (partial) matches. If not found or partial match, query the remainder using computePartialChecksums().
40
+ * 2. computePartialChecksums() -> query bucket_state for partial matches. Query the remainder using computePartialChecksumsDirect().
41
+ * 3. computePartialChecksumsDirect() -> split into batches of 200 buckets at a time -> computePartialChecksumsInternal()
42
+ * 4. computePartialChecksumsInternal() -> aggregate over 50_000 operations in bucket_data at a time
43
+ */
44
+ export class MongoChecksums {
45
+ private cache = new ChecksumCache({
46
+ fetchChecksums: (batch) => {
47
+ return this.computePartialChecksums(batch);
48
+ }
49
+ });
50
+
51
+ constructor(
52
+ private db: PowerSyncMongo,
53
+ private group_id: number,
54
+ private options?: MongoChecksumOptions
55
+ ) {}
56
+
57
+ /**
58
+ * Calculate checksums, utilizing the cache for partial checkums, and querying the remainder from
59
+ * the database (bucket_state + bucket_data).
60
+ */
61
+ async getChecksums(checkpoint: InternalOpId, buckets: string[]): Promise<ChecksumMap> {
62
+ return this.cache.getChecksumMap(checkpoint, buckets);
63
+ }
64
+
65
+ clearCache() {
66
+ this.cache.clear();
67
+ }
68
+
69
+ /**
70
+ * Calculate (partial) checksums from bucket_state (pre-aggregated) and bucket_data (individual operations).
71
+ *
72
+ * Results are not cached here. This method is only called by {@link ChecksumCache.getChecksumMap},
73
+ * which is responsible for caching its result.
74
+ *
75
+ * As long as data is compacted regularly, this should be fast. Large buckets without pre-compacted bucket_state
76
+ * can be slow.
77
+ */
78
+ private async computePartialChecksums(batch: FetchPartialBucketChecksum[]): Promise<PartialChecksumMap> {
79
+ if (batch.length == 0) {
80
+ return new Map();
81
+ }
82
+
83
+ const preFilters: any[] = [];
84
+ for (let request of batch) {
85
+ if (request.start == null) {
86
+ preFilters.push({
87
+ _id: {
88
+ g: this.group_id,
89
+ b: request.bucket
90
+ },
91
+ 'compacted_state.op_id': { $exists: true, $lte: request.end }
92
+ });
93
+ }
94
+ }
95
+
96
+ const preStates = new Map<string, { opId: InternalOpId; checksum: BucketChecksum }>();
97
+
98
+ if (preFilters.length > 0) {
99
+ // For un-cached bucket checksums, attempt to use the compacted state first.
100
+ const states = await this.db.bucket_state
101
+ .find({
102
+ $or: preFilters
103
+ })
104
+ .toArray();
105
+ for (let state of states) {
106
+ const compactedState = state.compacted_state!;
107
+ preStates.set(state._id.b, {
108
+ opId: compactedState.op_id,
109
+ checksum: {
110
+ bucket: state._id.b,
111
+ checksum: Number(compactedState.checksum),
112
+ count: compactedState.count
113
+ }
114
+ });
115
+ }
116
+ }
117
+
118
+ const mappedRequests = batch.map((request) => {
119
+ let start = request.start;
120
+ if (start == null) {
121
+ const preState = preStates.get(request.bucket);
122
+ if (preState != null) {
123
+ start = preState.opId;
124
+ }
125
+ }
126
+ return {
127
+ ...request,
128
+ start
129
+ };
130
+ });
131
+
132
+ const queriedChecksums = await this.computePartialChecksumsDirect(mappedRequests);
133
+
134
+ return new Map<string, PartialOrFullChecksum>(
135
+ batch.map((request) => {
136
+ const bucket = request.bucket;
137
+ // Could be null if this is either (1) a partial request, or (2) no compacted checksum was available
138
+ const preState = preStates.get(bucket);
139
+ // Could be null if we got no data
140
+ const partialChecksum = queriedChecksums.get(bucket);
141
+ const merged = addPartialChecksums(bucket, preState?.checksum ?? null, partialChecksum ?? null);
142
+
143
+ return [bucket, merged];
144
+ })
145
+ );
146
+ }
147
+
148
+ /**
149
+ * Calculate (partial) checksums from the data collection directly, bypassing the cache and bucket_state.
150
+ *
151
+ * Can be used directly in cases where the cache should be bypassed, such as from a compact job.
152
+ *
153
+ * Internally, we do calculations in smaller batches of buckets as appropriate.
154
+ *
155
+ * For large buckets, this can be slow, but should not time out as the underlying queries are performed in
156
+ * smaller batches.
157
+ */
158
+ public async computePartialChecksumsDirect(batch: FetchPartialBucketChecksum[]): Promise<PartialChecksumMap> {
159
+ // Limit the number of buckets we query for at a time.
160
+ const bucketBatchLimit = this.options?.bucketBatchLimit ?? DEFAULT_BUCKET_BATCH_LIMIT;
161
+
162
+ if (batch.length < bucketBatchLimit) {
163
+ // Single batch - no need for splitting the batch and merging results
164
+ return await this.computePartialChecksumsInternal(batch);
165
+ }
166
+ // Split the batch and merge results
167
+ let results = new Map<string, PartialOrFullChecksum>();
168
+ for (let i = 0; i < batch.length; i += bucketBatchLimit) {
169
+ const bucketBatch = batch.slice(i, i + bucketBatchLimit);
170
+ const batchResults = await this.computePartialChecksumsInternal(bucketBatch);
171
+ for (let r of batchResults.values()) {
172
+ results.set(r.bucket, r);
173
+ }
174
+ }
175
+ return results;
176
+ }
177
+
178
+ /**
179
+ * Query a batch of checksums.
180
+ *
181
+ * We limit the number of operations that the query aggregates in each sub-batch, to avoid potential query timeouts.
182
+ *
183
+ * `batch` must be limited to DEFAULT_BUCKET_BATCH_LIMIT buckets before calling this.
184
+ */
185
+ private async computePartialChecksumsInternal(batch: FetchPartialBucketChecksum[]): Promise<PartialChecksumMap> {
186
+ const batchLimit = this.options?.operationBatchLimit ?? DEFAULT_OPERATION_BATCH_LIMIT;
187
+
188
+ // Map requests by bucket. We adjust this as we get partial results.
189
+ let requests = new Map<string, FetchPartialBucketChecksum>();
190
+ for (let request of batch) {
191
+ requests.set(request.bucket, request);
192
+ }
193
+
194
+ const partialChecksums = new Map<string, PartialOrFullChecksum>();
195
+
196
+ while (requests.size > 0) {
197
+ const filters: any[] = [];
198
+ for (let request of requests.values()) {
199
+ filters.push({
200
+ _id: {
201
+ $gt: {
202
+ g: this.group_id,
203
+ b: request.bucket,
204
+ o: request.start ?? new bson.MinKey()
205
+ },
206
+ $lte: {
207
+ g: this.group_id,
208
+ b: request.bucket,
209
+ o: request.end
210
+ }
211
+ }
212
+ });
213
+ }
214
+
215
+ // Aggregate over a max of `batchLimit` operations at a time.
216
+ // Let's say we have 3 buckets (A, B, C), each with 10 operations, and our batch limit is 12.
217
+ // Then we'll do three batches:
218
+ // 1. Query: A[1-end], B[1-end], C[1-end]
219
+ // Returns: A[1-10], B[1-2]
220
+ // 2. Query: B[3-end], C[1-end]
221
+ // Returns: B[3-10], C[1-4]
222
+ // 3. Query: C[5-end]
223
+ // Returns: C[5-10]
224
+ const aggregate = await this.db.bucket_data
225
+ .aggregate(
226
+ [
227
+ {
228
+ $match: {
229
+ $or: filters
230
+ }
231
+ },
232
+ // sort and limit _before_ grouping
233
+ { $sort: { _id: 1 } },
234
+ { $limit: batchLimit },
235
+ {
236
+ $group: {
237
+ _id: '$_id.b',
238
+ // Historically, checksum may be stored as 'int' or 'double'.
239
+ // More recently, this should be a 'long'.
240
+ // $toLong ensures that we always sum it as a long, avoiding inaccuracies in the calculations.
241
+ checksum_total: { $sum: { $toLong: '$checksum' } },
242
+ count: { $sum: 1 },
243
+ has_clear_op: {
244
+ $max: {
245
+ $cond: [{ $eq: ['$op', 'CLEAR'] }, 1, 0]
246
+ }
247
+ },
248
+ last_op: { $max: '$_id.o' }
249
+ }
250
+ }
251
+ ],
252
+ { session: undefined, readConcern: 'snapshot', maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS }
253
+ )
254
+ .toArray()
255
+ .catch((e) => {
256
+ throw lib_mongo.mapQueryError(e, 'while reading checksums');
257
+ });
258
+
259
+ let batchCount = 0;
260
+ let limitReached = false;
261
+ for (let doc of aggregate) {
262
+ const bucket = doc._id;
263
+ const checksum = checksumFromAggregate(doc);
264
+
265
+ const existing = partialChecksums.get(bucket);
266
+ if (existing != null) {
267
+ partialChecksums.set(bucket, addPartialChecksums(bucket, existing, checksum));
268
+ } else {
269
+ partialChecksums.set(bucket, checksum);
270
+ }
271
+
272
+ batchCount += doc.count;
273
+ if (batchCount == batchLimit) {
274
+ // Limit reached. Request more in the next batch.
275
+ // Note that this only affects the _last_ bucket in a batch.
276
+ limitReached = true;
277
+ const req = requests.get(bucket);
278
+ requests.set(bucket, {
279
+ bucket,
280
+ start: doc.last_op,
281
+ end: req!.end
282
+ });
283
+ } else {
284
+ // All done for this bucket
285
+ requests.delete(bucket);
286
+ }
287
+ batchCount++;
288
+ }
289
+ if (!limitReached) {
290
+ break;
291
+ }
292
+ }
293
+
294
+ return new Map<string, PartialOrFullChecksum>(
295
+ batch.map((request) => {
296
+ const bucket = request.bucket;
297
+ // Could be null if we got no data
298
+ let partialChecksum = partialChecksums.get(bucket);
299
+ if (partialChecksum == null) {
300
+ partialChecksum = {
301
+ bucket,
302
+ partialCount: 0,
303
+ partialChecksum: 0
304
+ };
305
+ }
306
+ if (request.start == null && isPartialChecksum(partialChecksum)) {
307
+ partialChecksum = {
308
+ bucket,
309
+ count: partialChecksum.partialCount,
310
+ checksum: partialChecksum.partialChecksum
311
+ };
312
+ }
313
+
314
+ return [bucket, partialChecksum];
315
+ })
316
+ );
317
+ }
318
+ }
319
+
320
+ /**
321
+ * Convert output of the $group stage into a checksum.
322
+ */
323
+ function checksumFromAggregate(doc: bson.Document): PartialOrFullChecksum {
324
+ const partialChecksum = Number(BigInt(doc.checksum_total) & 0xffffffffn) & 0xffffffff;
325
+ const bucket = doc._id;
326
+
327
+ if (doc.has_clear_op == 1) {
328
+ return {
329
+ // full checksum - replaces any previous one
330
+ bucket,
331
+ checksum: partialChecksum,
332
+ count: doc.count
333
+ } satisfies BucketChecksum;
334
+ } else {
335
+ return {
336
+ // partial checksum - is added to a previous one
337
+ bucket,
338
+ partialCount: doc.count,
339
+ partialChecksum
340
+ } satisfies PartialChecksum;
341
+ }
342
+ }