Skip to content

Commit f1b4cef

Browse files
authored
More checksum calculation fixes (#354)
* Add failing tests. * Remove wrong count increment. * Sort group results. * Changeset.
1 parent 9681b4c commit f1b4cef

File tree

4 files changed

+115
-2
lines changed

4 files changed

+115
-2
lines changed
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
---
2+
'@powersync/service-module-mongodb-storage': patch
3+
'@powersync/service-core-tests': patch
4+
'@powersync/service-core': patch
5+
'@powersync/service-image': patch
6+
---
7+
8+
Fix checksum calculation issues with large buckets.

modules/module-mongodb-storage/src/storage/implementation/MongoChecksums.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -247,7 +247,10 @@ export class MongoChecksums {
247247
},
248248
last_op: { $max: '$_id.o' }
249249
}
250-
}
250+
},
251+
// Sort the aggregated results (100 max, so should be fast).
252+
// This is important to identify which buckets we have partial data for.
253+
{ $sort: { _id: 1 } }
251254
],
252255
{ session: undefined, readConcern: 'snapshot', maxTimeMS: lib_mongo.MONGO_CHECKSUM_TIMEOUT_MS }
253256
)
@@ -284,7 +287,6 @@ export class MongoChecksums {
284287
// All done for this bucket
285288
requests.delete(bucket);
286289
}
287-
batchCount++;
288290
}
289291
if (!limitReached) {
290292
break;

modules/module-mongodb-storage/test/src/storage.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import { describe } from 'vitest';
33
import { INITIALIZED_MONGO_STORAGE_FACTORY } from './util.js';
44
import { env } from './env.js';
55
import { MongoTestStorageFactoryGenerator } from '@module/storage/implementation/MongoTestStorageFactoryGenerator.js';
6+
import { MongoChecksumOptions } from '@module/storage/implementation/MongoChecksums.js';
67

78
describe('Mongo Sync Bucket Storage - Parameters', () =>
89
register.registerDataStorageParameterTests(INITIALIZED_MONGO_STORAGE_FACTORY));
@@ -42,3 +43,50 @@ describe('Mongo Sync Bucket Storage - split buckets', () =>
4243
}
4344
})
4445
));
46+
47+
describe('Mongo Sync Bucket Storage - checksum calculations', () => {
48+
// This test tests 4 buckets x 4 operations in each.
49+
// We specifically use operationBatchLimit that does not have factors in common with 4,
50+
// as well some that do.
51+
const params: MongoChecksumOptions[] = [
52+
{
53+
bucketBatchLimit: 100,
54+
operationBatchLimit: 3
55+
},
56+
57+
{
58+
bucketBatchLimit: 10,
59+
operationBatchLimit: 7
60+
},
61+
62+
{
63+
bucketBatchLimit: 3,
64+
operationBatchLimit: 1
65+
},
66+
{
67+
bucketBatchLimit: 1,
68+
operationBatchLimit: 3
69+
},
70+
{
71+
bucketBatchLimit: 2,
72+
operationBatchLimit: 4
73+
},
74+
{
75+
bucketBatchLimit: 4,
76+
operationBatchLimit: 12
77+
}
78+
];
79+
for (let options of params) {
80+
describe(`${options.bucketBatchLimit}|${options.operationBatchLimit}`, () => {
81+
register.testChecksumBatching(
82+
MongoTestStorageFactoryGenerator({
83+
url: env.MONGO_TEST_URL,
84+
isCI: env.CI,
85+
internalOptions: {
86+
checksumOptions: options
87+
}
88+
})
89+
);
90+
});
91+
}
92+
});

packages/service-core-tests/src/tests/register-data-storage-data-tests.ts

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1235,4 +1235,59 @@ bucket_definitions:
12351235
const checksums2 = [...(await bucketStorage.getChecksums(checkpoint + 1n, ['global[]'])).values()];
12361236
expect(checksums2).toEqual([{ bucket: 'global[]', checksum: 1917136889, count: 1 }]);
12371237
});
1238+
1239+
testChecksumBatching(generateStorageFactory);
1240+
}
1241+
1242+
/**
1243+
* This specifically tests an issue we ran into with MongoDB storage.
1244+
*
1245+
* Exposed as a separate test so we can test with more storage parameters.
1246+
*/
1247+
export function testChecksumBatching(generateStorageFactory: storage.TestStorageFactory) {
1248+
test('checksums for multiple buckets', async () => {
1249+
await using factory = await generateStorageFactory();
1250+
const syncRules = await factory.updateSyncRules({
1251+
content: `
1252+
bucket_definitions:
1253+
user:
1254+
parameters: select request.user_id() as user_id
1255+
data:
1256+
- select id, description from test where user_id = bucket.user_id
1257+
`
1258+
});
1259+
const bucketStorage = factory.getInstance(syncRules);
1260+
1261+
const sourceTable = TEST_TABLE;
1262+
await bucketStorage.startBatch(test_utils.BATCH_OPTIONS, async (batch) => {
1263+
for (let u of ['u1', 'u2', 'u3', 'u4']) {
1264+
for (let t of ['t1', 't2', 't3', 't4']) {
1265+
const id = `${t}_${u}`;
1266+
await batch.save({
1267+
sourceTable,
1268+
tag: storage.SaveOperationTag.INSERT,
1269+
after: {
1270+
id,
1271+
description: `${t} description`,
1272+
user_id: u
1273+
},
1274+
afterReplicaId: test_utils.rid(id)
1275+
});
1276+
}
1277+
}
1278+
await batch.commit('1/1');
1279+
});
1280+
const { checkpoint } = await bucketStorage.getCheckpoint();
1281+
1282+
bucketStorage.clearChecksumCache();
1283+
const buckets = ['user["u1"]', 'user["u2"]', 'user["u3"]', 'user["u4"]'];
1284+
const checksums = [...(await bucketStorage.getChecksums(checkpoint, buckets)).values()];
1285+
checksums.sort((a, b) => a.bucket.localeCompare(b.bucket));
1286+
expect(checksums).toEqual([
1287+
{ bucket: 'user["u1"]', count: 4, checksum: 346204588 },
1288+
{ bucket: 'user["u2"]', count: 4, checksum: 5261081 },
1289+
{ bucket: 'user["u3"]', count: 4, checksum: 134760718 },
1290+
{ bucket: 'user["u4"]', count: 4, checksum: -302639724 }
1291+
]);
1292+
});
12381293
}

0 commit comments

Comments
 (0)