@powersync/service-module-mongodb-storage 0.21.0 → 0.22.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 +17 -0
- package/dist/storage/MongoBucketStorage.d.ts +5 -0
- package/dist/storage/MongoBucketStorage.js +7 -1
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +2 -1
- package/dist/storage/implementation/MongoBucketBatch.js +9 -8
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +4 -2
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
- package/dist/storage/implementation/MongoWriteBatch.js +149 -0
- package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +2 -2
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/common/PersistedBatch.d.ts +5 -4
- package/dist/storage/implementation/common/PersistedBatch.js +6 -4
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +5 -0
- package/dist/storage/implementation/db.js +13 -0
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +13 -6
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +2 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.js +23 -17
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
- package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +2 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +17 -14
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +6 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.js +164 -69
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +12 -8
- package/dist/storage/implementation/v3/PersistedBatchV3.js +25 -32
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +8 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +3 -3
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +7 -9
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +3 -2
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +4 -7
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +21 -3
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/types/types.d.ts +5 -0
- package/dist/types/types.js +12 -0
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.js +1 -0
- package/dist/utils/test-utils.js.map +1 -1
- package/package.json +6 -6
- package/src/storage/MongoBucketStorage.ts +12 -1
- package/src/storage/implementation/MongoBucketBatch.ts +13 -10
- package/src/storage/implementation/MongoStorageProvider.ts +12 -2
- package/src/storage/implementation/MongoWriteBatch.ts +172 -0
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +4 -2
- package/src/storage/implementation/common/PersistedBatch.ts +12 -8
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
- package/src/storage/implementation/db.ts +14 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +17 -8
- package/src/storage/implementation/v1/MongoCompactorV1.ts +29 -26
- package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +51 -49
- package/src/storage/implementation/v3/MongoCompactorV3.ts +276 -97
- package/src/storage/implementation/v3/PersistedBatchV3.ts +36 -36
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +8 -1
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +11 -10
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +8 -9
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +23 -3
- package/src/types/types.ts +18 -0
- package/src/utils/test-utils.ts +1 -0
- package/test/src/chunk_compaction_config.test.ts +38 -0
- package/test/src/object_storage_usage.test.ts +16 -4
- package/test/src/storage_compacting.test.ts +257 -5
- package/test/src/storage_s3_reading.test.ts +47 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -8,6 +8,7 @@ import { JSONBig } from '@powersync/service-jsonbig';
|
|
|
8
8
|
import { mongoTableId, replicaIdToSubkey } from '../../../utils/util.js';
|
|
9
9
|
import { currentBucketKey, MAX_ROW_SIZE } from '../MongoBucketBatchShared.js';
|
|
10
10
|
import { MongoIdSequence } from '../MongoIdSequence.js';
|
|
11
|
+
import { MongoWriteBatch } from '../MongoWriteBatch.js';
|
|
11
12
|
import type { VersionedPowerSyncMongo } from '../db.js';
|
|
12
13
|
import { TaggedBucketParameterDocument } from '../models.js';
|
|
13
14
|
import { ObjectStorage } from '../v3/object-storage/ObjectStorage.js';
|
|
@@ -216,13 +217,13 @@ export abstract class PersistedBatch {
|
|
|
216
217
|
|
|
217
218
|
protected abstract get currentDataCount(): number;
|
|
218
219
|
|
|
219
|
-
protected abstract
|
|
220
|
+
protected abstract queueBucketData(writes: MongoWriteBatch): Promise<void>;
|
|
220
221
|
|
|
221
|
-
protected abstract
|
|
222
|
+
protected abstract queueBucketParameters(writes: MongoWriteBatch): void;
|
|
222
223
|
|
|
223
|
-
protected abstract
|
|
224
|
+
protected abstract queueCurrentData(writes: MongoWriteBatch): void;
|
|
224
225
|
|
|
225
|
-
protected abstract
|
|
226
|
+
protected abstract queueBucketStates(writes: MongoWriteBatch): void;
|
|
226
227
|
|
|
227
228
|
protected abstract resetCurrentData(): void;
|
|
228
229
|
|
|
@@ -348,24 +349,27 @@ export abstract class PersistedBatch {
|
|
|
348
349
|
async flush(session: mongo.ClientSession, options?: storage.BucketBatchCommitOptions) {
|
|
349
350
|
const startAt = performance.now();
|
|
350
351
|
let flushedSomething = false;
|
|
352
|
+
const writes = this.db.createWriteBatch(session, { ordered: false });
|
|
351
353
|
if (this.bucketDataCount > 0) {
|
|
352
354
|
flushedSomething = true;
|
|
353
|
-
await this.
|
|
355
|
+
await this.queueBucketData(writes);
|
|
354
356
|
}
|
|
355
357
|
if (this.bucketParameters.length > 0) {
|
|
356
358
|
flushedSomething = true;
|
|
357
|
-
|
|
359
|
+
this.queueBucketParameters(writes);
|
|
358
360
|
}
|
|
359
361
|
if (this.currentDataCount > 0) {
|
|
360
362
|
flushedSomething = true;
|
|
361
|
-
|
|
363
|
+
this.queueCurrentData(writes);
|
|
362
364
|
}
|
|
363
365
|
|
|
364
366
|
if (this.bucketStates.size > 0) {
|
|
365
367
|
flushedSomething = true;
|
|
366
|
-
|
|
368
|
+
this.queueBucketStates(writes);
|
|
367
369
|
}
|
|
368
370
|
|
|
371
|
+
await writes.execute();
|
|
372
|
+
|
|
369
373
|
if (flushedSomething) {
|
|
370
374
|
const duration = Math.round(performance.now() - startAt);
|
|
371
375
|
if (options?.oldestUncommittedChange != null) {
|
|
@@ -58,6 +58,10 @@ export abstract class BaseVersionedPowerSyncMongo {
|
|
|
58
58
|
return this.upstream.notifyCheckpoint();
|
|
59
59
|
}
|
|
60
60
|
|
|
61
|
+
createWriteBatch(session: mongo.ClientSession | undefined, options: { ordered: boolean }) {
|
|
62
|
+
return this.upstream.createWriteBatch(session, options);
|
|
63
|
+
}
|
|
64
|
+
|
|
61
65
|
protected sourceRecordsCollectionName(replicationStreamId: number, sourceTableId: mongo.ObjectId) {
|
|
62
66
|
return this.upstream.sourceRecordsCollectionName(replicationStreamId, sourceTableId);
|
|
63
67
|
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
SyncRuleDocumentBase,
|
|
18
18
|
WriteCheckpointDocument
|
|
19
19
|
} from './models.js';
|
|
20
|
+
import { MongoWriteBatch } from './MongoWriteBatch.js';
|
|
20
21
|
import {
|
|
21
22
|
BucketDataDocumentV1,
|
|
22
23
|
BucketParameterDocument,
|
|
@@ -54,6 +55,19 @@ export class PowerSyncMongo {
|
|
|
54
55
|
|
|
55
56
|
readonly client: mongo.MongoClient;
|
|
56
57
|
readonly db: mongo.Db;
|
|
58
|
+
private get clientBulkWriteSupport(): boolean {
|
|
59
|
+
// The driver exposes topology at runtime, but omits this internal property
|
|
60
|
+
// from its public types. Reuse its handshake results without another command.
|
|
61
|
+
const client = this.client as mongo.MongoClient & { topology?: { description: mongo.TopologyDescription } };
|
|
62
|
+
const servers = [...(client.topology?.description.servers.values() ?? [])];
|
|
63
|
+
// MongoDB 8.0 introduced client bulkWrite at wire version 25. Unknown / mixed-version
|
|
64
|
+
// topologies use collection writes until all known servers support it.
|
|
65
|
+
return servers.length > 0 && servers.every((server) => server.maxWireVersion >= 25);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
createWriteBatch(session: mongo.ClientSession | undefined, options: { ordered: boolean }): MongoWriteBatch {
|
|
69
|
+
return new MongoWriteBatch(this.client, this.clientBulkWriteSupport, session, options);
|
|
70
|
+
}
|
|
57
71
|
|
|
58
72
|
constructor(client: mongo.MongoClient, options?: PowerSyncMongoOptions) {
|
|
59
73
|
this.client = client;
|
|
@@ -55,8 +55,15 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
55
55
|
session: mongo.ClientSession,
|
|
56
56
|
opId: InternalOpId
|
|
57
57
|
): Promise<void> {
|
|
58
|
+
// Only the final checkpoint per user/stream is visible after this transaction.
|
|
59
|
+
const checkpoints = new Map(
|
|
60
|
+
this.write_checkpoint_batch.map((checkpoint) => [
|
|
61
|
+
JSON.stringify([checkpoint.sync_rules_id, checkpoint.user_id]),
|
|
62
|
+
checkpoint
|
|
63
|
+
])
|
|
64
|
+
);
|
|
58
65
|
await this.db.custom_write_checkpoints.bulkWrite(
|
|
59
|
-
|
|
66
|
+
[...checkpoints.values()].map((checkpointOptions) => {
|
|
60
67
|
const set: Record<string, unknown> = {
|
|
61
68
|
checkpoint: checkpointOptions.checkpoint,
|
|
62
69
|
sync_rules_id: checkpointOptions.sync_rules_id,
|
|
@@ -77,7 +84,7 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
77
84
|
}
|
|
78
85
|
};
|
|
79
86
|
}),
|
|
80
|
-
{ session }
|
|
87
|
+
{ session, ordered: false }
|
|
81
88
|
);
|
|
82
89
|
}
|
|
83
90
|
|
|
@@ -462,7 +469,9 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
462
469
|
const ids = tables.map((table) => mongoTableId(table.id));
|
|
463
470
|
|
|
464
471
|
await this.withTransaction(async () => {
|
|
465
|
-
|
|
472
|
+
const writes = this.db.createWriteBatch(session, { ordered: false });
|
|
473
|
+
writes.updateMany(
|
|
474
|
+
this.db.sourceTablesV1(this.replicationStreamId),
|
|
466
475
|
{ _id: { $in: ids } },
|
|
467
476
|
{
|
|
468
477
|
$set: {
|
|
@@ -471,12 +480,12 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
471
480
|
$unset: {
|
|
472
481
|
snapshot_status: 1
|
|
473
482
|
}
|
|
474
|
-
}
|
|
475
|
-
{ session }
|
|
483
|
+
}
|
|
476
484
|
);
|
|
477
485
|
|
|
478
486
|
if (no_checkpoint_before_lsn != null) {
|
|
479
|
-
|
|
487
|
+
writes.updateOne(
|
|
488
|
+
this.db.sync_rules,
|
|
480
489
|
{
|
|
481
490
|
_id: this.replicationStreamId
|
|
482
491
|
},
|
|
@@ -487,10 +496,10 @@ export class MongoBucketBatchV1 extends MongoBucketBatch {
|
|
|
487
496
|
$max: {
|
|
488
497
|
no_checkpoint_before: no_checkpoint_before_lsn
|
|
489
498
|
}
|
|
490
|
-
}
|
|
491
|
-
{ session: this.session }
|
|
499
|
+
}
|
|
492
500
|
);
|
|
493
501
|
}
|
|
502
|
+
await writes.execute();
|
|
494
503
|
});
|
|
495
504
|
return tables.map((table) => {
|
|
496
505
|
const copy = table.clone();
|
|
@@ -13,6 +13,7 @@ import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
|
13
13
|
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
14
14
|
import { BucketStateDocumentBase, LEGACY_BUCKET_DATA_DEFINITION_ID } from '../models.js';
|
|
15
15
|
import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
|
|
16
|
+
import { MongoWriteBatch } from '../MongoWriteBatch.js';
|
|
16
17
|
import { cacheKey } from '../OperationBatch.js';
|
|
17
18
|
import { BucketDataDocumentV1, BucketStateDocumentV1 } from './models.js';
|
|
18
19
|
import type { MongoSyncBucketStorageV1 } from './MongoSyncBucketStorageV1.js';
|
|
@@ -296,11 +297,10 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
296
297
|
}
|
|
297
298
|
|
|
298
299
|
private async flushBucketStateUpdates() {
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
}
|
|
300
|
+
const writes = this.db.createWriteBatch(undefined, { ordered: false });
|
|
301
|
+
this.writeBucketStateUpdates(writes);
|
|
302
|
+
await writes.execute();
|
|
303
|
+
this.bucketStateUpdates = [];
|
|
304
304
|
}
|
|
305
305
|
|
|
306
306
|
private async updateChecksumsBatch(buckets: Pick<DirtyBucket, 'bucket' | 'definitionId'>[]) {
|
|
@@ -340,10 +340,12 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
340
340
|
await this.flushBucketStateUpdates();
|
|
341
341
|
}
|
|
342
342
|
|
|
343
|
-
protected
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
343
|
+
protected writeBucketStateUpdates(writes: MongoWriteBatch): void {
|
|
344
|
+
if (this.bucketStateUpdates.length === 0) return;
|
|
345
|
+
this.logger.info(`Updating ${this.bucketStateUpdates.length} bucket states`);
|
|
346
|
+
writes.bulkWriteUnordered(
|
|
347
|
+
this.db.bucketStateV1,
|
|
348
|
+
this.bucketStateUpdates as mongo.AnyBulkWriteOperation<BucketStateDocumentV1>[]
|
|
347
349
|
);
|
|
348
350
|
}
|
|
349
351
|
|
|
@@ -541,17 +543,19 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
541
543
|
}
|
|
542
544
|
|
|
543
545
|
private async flush(bucketContext: SingleBucketStoreV1) {
|
|
546
|
+
// Persist data changes before advertising the resulting bucket state.
|
|
547
|
+
const writes = this.db.createWriteBatch(undefined, { ordered: true });
|
|
544
548
|
if (this.updates.length > 0) {
|
|
545
549
|
this.logger.info(`Compacting ${this.updates.length} ops`);
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
ordered: false
|
|
550
|
-
});
|
|
551
|
-
this.updates = [];
|
|
550
|
+
// Order is not important. Since checksums are not affected, these operations can happen in any order,
|
|
551
|
+
// and it's fine if the operations are partially applied. Each individual operation is atomic.
|
|
552
|
+
writes.bulkWriteUnordered(bucketContext.collection, this.updates);
|
|
552
553
|
}
|
|
553
554
|
|
|
554
|
-
|
|
555
|
+
this.writeBucketStateUpdates(writes);
|
|
556
|
+
await writes.execute();
|
|
557
|
+
this.updates = [];
|
|
558
|
+
this.bucketStateUpdates = [];
|
|
555
559
|
}
|
|
556
560
|
|
|
557
561
|
/**
|
|
@@ -622,15 +626,13 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
622
626
|
}
|
|
623
627
|
|
|
624
628
|
this.logger.info(`Flushing CLEAR for ${numberOfOpsToClear} ops at ${lastOp?.o}`);
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
{ session }
|
|
633
|
-
);
|
|
629
|
+
const writes = this.db.createWriteBatch(session, { ordered: true });
|
|
630
|
+
writes.deleteMany(bucketContext.collection, {
|
|
631
|
+
_id: {
|
|
632
|
+
$gte: bucketContext.minId,
|
|
633
|
+
$lte: bucketContext.docId(lastOp!.o)
|
|
634
|
+
}
|
|
635
|
+
});
|
|
634
636
|
|
|
635
637
|
const op = bucketContext.toPersistedDocument({
|
|
636
638
|
o: lastOp!.o,
|
|
@@ -639,7 +641,8 @@ export class MongoCompactorV1 extends MongoCompactor {
|
|
|
639
641
|
data: null,
|
|
640
642
|
target_op: targetOp
|
|
641
643
|
});
|
|
642
|
-
|
|
644
|
+
writes.insertOne(bucketContext.collection, op);
|
|
645
|
+
await writes.execute();
|
|
643
646
|
|
|
644
647
|
opCountDiff = -numberOfOpsToClear + 1;
|
|
645
648
|
},
|
|
@@ -6,6 +6,7 @@ import * as bson from 'bson';
|
|
|
6
6
|
import { BucketDataSource, BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
7
7
|
import { mongoTableId } from '../../../utils/util.js';
|
|
8
8
|
import { EMPTY_DATA } from '../MongoBucketBatchShared.js';
|
|
9
|
+
import { MongoWriteBatch } from '../MongoWriteBatch.js';
|
|
9
10
|
import {
|
|
10
11
|
BucketStateUpdate,
|
|
11
12
|
PersistedBatch,
|
|
@@ -25,7 +26,9 @@ import {
|
|
|
25
26
|
export class PersistedBatchV1 extends PersistedBatch {
|
|
26
27
|
declare protected readonly db: VersionedPowerSyncMongoV1;
|
|
27
28
|
|
|
28
|
-
|
|
29
|
+
// Each upsert supplies the complete source-record state. Only its final
|
|
30
|
+
// state matters within a flush; bucket and parameter history is kept separately.
|
|
31
|
+
currentData = new Map<string, mongo.AnyBulkWriteOperation<CurrentDataDocument>>();
|
|
29
32
|
|
|
30
33
|
protected checkDefinitionId(_definitionId: BucketDefinitionId | null): BucketDefinitionId {
|
|
31
34
|
// V1 storage doesn't persist the id, and we don't use it.
|
|
@@ -94,7 +97,7 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
94
97
|
}
|
|
95
98
|
|
|
96
99
|
hardDeleteCurrentData(sourceTableId: bson.ObjectId, replicaId: storage.ReplicaId) {
|
|
97
|
-
this.currentData.
|
|
100
|
+
this.currentData.set(this.currentDataKey(sourceTableId, replicaId), {
|
|
98
101
|
deleteOne: {
|
|
99
102
|
filter: { _id: this.currentDataId(sourceTableId, replicaId) }
|
|
100
103
|
}
|
|
@@ -124,7 +127,7 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
124
127
|
return lookup.lookup;
|
|
125
128
|
});
|
|
126
129
|
|
|
127
|
-
this.currentData.
|
|
130
|
+
this.currentData.set(this.currentDataKey(values.sourceTableId, values.replicaId), {
|
|
128
131
|
updateOne: {
|
|
129
132
|
filter: { _id: this.currentDataId(values.sourceTableId, values.replicaId) },
|
|
130
133
|
update: {
|
|
@@ -141,57 +144,45 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
141
144
|
}
|
|
142
145
|
|
|
143
146
|
protected get currentDataCount() {
|
|
144
|
-
return this.currentData.
|
|
147
|
+
return this.currentData.size;
|
|
145
148
|
}
|
|
146
149
|
|
|
147
|
-
protected async
|
|
148
|
-
|
|
150
|
+
protected async queueBucketData(writes: MongoWriteBatch) {
|
|
151
|
+
writes.bulkWriteUnordered(
|
|
152
|
+
this.db.bucketDataV1,
|
|
149
153
|
this.bucketData.map((document) => ({
|
|
150
154
|
insertOne: {
|
|
151
155
|
document: serializeBucketDataV1(document)
|
|
152
156
|
}
|
|
153
|
-
}))
|
|
154
|
-
{
|
|
155
|
-
session,
|
|
156
|
-
ordered: false
|
|
157
|
-
}
|
|
157
|
+
}))
|
|
158
158
|
);
|
|
159
159
|
}
|
|
160
160
|
|
|
161
|
-
protected
|
|
162
|
-
|
|
161
|
+
protected queueBucketParameters(writes: MongoWriteBatch): void {
|
|
162
|
+
writes.bulkWriteUnordered(
|
|
163
|
+
this.db.parameterIndexV1,
|
|
163
164
|
this.bucketParameters.map((document) => ({
|
|
164
165
|
insertOne: {
|
|
165
166
|
document: taggedBucketParameterDocumentToV1(document)
|
|
166
167
|
}
|
|
167
|
-
}))
|
|
168
|
-
{
|
|
169
|
-
session,
|
|
170
|
-
ordered: false
|
|
171
|
-
}
|
|
168
|
+
}))
|
|
172
169
|
);
|
|
173
170
|
}
|
|
174
171
|
|
|
175
|
-
protected
|
|
176
|
-
if (this.currentData.
|
|
172
|
+
protected queueCurrentData(writes: MongoWriteBatch): void {
|
|
173
|
+
if (this.currentData.size == 0) {
|
|
177
174
|
return;
|
|
178
175
|
}
|
|
179
176
|
|
|
180
|
-
|
|
181
|
-
session,
|
|
182
|
-
ordered: true
|
|
183
|
-
});
|
|
177
|
+
writes.bulkWriteUnordered(this.db.sourceRecordsV1, [...this.currentData.values()]);
|
|
184
178
|
}
|
|
185
179
|
|
|
186
|
-
protected
|
|
187
|
-
|
|
188
|
-
session,
|
|
189
|
-
ordered: false
|
|
190
|
-
});
|
|
180
|
+
protected queueBucketStates(writes: MongoWriteBatch): void {
|
|
181
|
+
writes.bulkWriteUnordered(this.db.bucketStateV1, this.getBucketStateUpdates());
|
|
191
182
|
}
|
|
192
183
|
|
|
193
184
|
protected resetCurrentData() {
|
|
194
|
-
this.currentData
|
|
185
|
+
this.currentData.clear();
|
|
195
186
|
}
|
|
196
187
|
|
|
197
188
|
private getBucketStateUpdates(): mongo.AnyBulkWriteOperation<BucketStateDocumentV1>[] {
|
|
@@ -219,6 +210,10 @@ export class PersistedBatchV1 extends PersistedBatch {
|
|
|
219
210
|
});
|
|
220
211
|
}
|
|
221
212
|
|
|
213
|
+
private currentDataKey(sourceTableId: bson.ObjectId, replicaId: storage.ReplicaId): string {
|
|
214
|
+
return Buffer.from(bson.serialize({ t: sourceTableId, k: replicaId })).toString('base64');
|
|
215
|
+
}
|
|
216
|
+
|
|
222
217
|
private currentDataId(sourceTableId: bson.ObjectId, replicaId: storage.ReplicaId): SourceKey {
|
|
223
218
|
return {
|
|
224
219
|
g: this.group_id,
|
|
@@ -7,6 +7,7 @@ import { mongoTableId } from '../../../utils/util.js';
|
|
|
7
7
|
import { canCheckpointState } from '../CheckpointState.js';
|
|
8
8
|
import { MongoBucketBatch, MongoBucketBatchOptions } from '../MongoBucketBatch.js';
|
|
9
9
|
import { MongoParsedSyncConfigSet } from '../MongoParsedSyncConfigSet.js';
|
|
10
|
+
import { MongoWriteBatch } from '../MongoWriteBatch.js';
|
|
10
11
|
import { stopReplicationStreamPipeline } from '../SyncRuleStateUpdate.js';
|
|
11
12
|
import { PersistedBatch } from '../common/PersistedBatch.js';
|
|
12
13
|
import { SourceRecordStore } from '../common/SourceRecordStore.js';
|
|
@@ -60,14 +61,12 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
60
61
|
return this.store;
|
|
61
62
|
}
|
|
62
63
|
|
|
63
|
-
protected override
|
|
64
|
-
session: lib_mongo.mongo.ClientSession,
|
|
65
|
-
lastOp: bigint
|
|
66
|
-
): Promise<void> {
|
|
64
|
+
protected override onReplicationTransactionFlush(writes: MongoWriteBatch, lastOp: bigint): void {
|
|
67
65
|
// Durably advance the stream-level head of persisted ops within the flush transaction.
|
|
68
66
|
// This ensures a checkpoint created later (even by an empty commit, or by a freshly-appended
|
|
69
67
|
// config that replicates nothing) covers all ops persisted before a potential crash.
|
|
70
|
-
|
|
68
|
+
writes.updateOne(
|
|
69
|
+
this.db.sync_rules,
|
|
71
70
|
{
|
|
72
71
|
_id: this.replicationStreamId
|
|
73
72
|
},
|
|
@@ -75,8 +74,7 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
75
74
|
$max: {
|
|
76
75
|
last_persisted_op: lastOp
|
|
77
76
|
}
|
|
78
|
-
}
|
|
79
|
-
{ session }
|
|
77
|
+
}
|
|
80
78
|
);
|
|
81
79
|
}
|
|
82
80
|
|
|
@@ -652,7 +650,9 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
652
650
|
const syncConfigIds = this.relevantSyncConfigIdsForTables(tables);
|
|
653
651
|
|
|
654
652
|
await this.withTransaction(async () => {
|
|
655
|
-
|
|
653
|
+
const writes = this.db.createWriteBatch(session, { ordered: false });
|
|
654
|
+
writes.updateMany(
|
|
655
|
+
this.db.sourceTables(this.replicationStreamId),
|
|
656
656
|
{ _id: { $in: ids } },
|
|
657
657
|
{
|
|
658
658
|
$set: {
|
|
@@ -661,12 +661,12 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
661
661
|
$unset: {
|
|
662
662
|
snapshot_status: 1
|
|
663
663
|
}
|
|
664
|
-
}
|
|
665
|
-
{ session }
|
|
664
|
+
}
|
|
666
665
|
);
|
|
667
666
|
|
|
668
667
|
if (no_checkpoint_before_lsn != null && syncConfigIds.length > 0) {
|
|
669
|
-
|
|
668
|
+
writes.updateOne(
|
|
669
|
+
this.db.sync_rules,
|
|
670
670
|
{
|
|
671
671
|
_id: this.replicationStreamId,
|
|
672
672
|
'sync_configs._id': { $in: syncConfigIds }
|
|
@@ -680,12 +680,12 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
680
680
|
}
|
|
681
681
|
},
|
|
682
682
|
{
|
|
683
|
-
session: this.session,
|
|
684
683
|
// Only set for sync configs that use this table
|
|
685
684
|
arrayFilters: [{ 'config._id': { $in: syncConfigIds } }]
|
|
686
685
|
}
|
|
687
686
|
);
|
|
688
687
|
}
|
|
688
|
+
await writes.execute();
|
|
689
689
|
});
|
|
690
690
|
return tables.map((table) => {
|
|
691
691
|
const copy = table.clone();
|
|
@@ -706,9 +706,11 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
706
706
|
this.validateCustomCheckpointEventId(checkpoint.event_id)
|
|
707
707
|
);
|
|
708
708
|
|
|
709
|
+
const writes = this.db.createWriteBatch(session, { ordered: false });
|
|
709
710
|
for (const [eventId, checkpoints] of checkpointsByEvent) {
|
|
710
|
-
|
|
711
|
+
this.batchCreateEventCustomWriteCheckpoints(writes, opId, eventId, checkpoints);
|
|
711
712
|
}
|
|
713
|
+
await writes.execute();
|
|
712
714
|
}
|
|
713
715
|
|
|
714
716
|
protected override async prepareCustomWriteCheckpoints(): Promise<void> {
|
|
@@ -741,48 +743,48 @@ export class MongoBucketBatchV3 extends MongoBucketBatch {
|
|
|
741
743
|
return eventId;
|
|
742
744
|
}
|
|
743
745
|
|
|
744
|
-
private
|
|
745
|
-
|
|
746
|
+
private batchCreateEventCustomWriteCheckpoints(
|
|
747
|
+
writes: MongoWriteBatch,
|
|
746
748
|
opId: InternalOpId,
|
|
747
749
|
eventId: EventDefinitionId,
|
|
748
750
|
checkpoints: storage.CustomWriteCheckpointOptions[]
|
|
749
|
-
):
|
|
750
|
-
|
|
751
|
-
|
|
751
|
+
): void {
|
|
752
|
+
// A repeated user within an event replaces the complete checkpoint state.
|
|
753
|
+
const uniqueCheckpoints = new Map(checkpoints.map((checkpoint) => [checkpoint.user_id, checkpoint]));
|
|
754
|
+
writes.bulkWriteUnordered(
|
|
755
|
+
this.db.customCheckpointRequests({
|
|
752
756
|
eventId,
|
|
753
757
|
replicationStreamId: this.replicationStreamId
|
|
754
|
-
})
|
|
755
|
-
.
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
|
|
763
|
-
|
|
764
|
-
}
|
|
758
|
+
}),
|
|
759
|
+
[...uniqueCheckpoints.values()].map((checkpointOptions) => {
|
|
760
|
+
const set: Partial<CustomCheckpointRequestDocumentV3> = {
|
|
761
|
+
user_id: checkpointOptions.user_id,
|
|
762
|
+
checkpoint: checkpointOptions.checkpoint,
|
|
763
|
+
op_id: opId
|
|
764
|
+
};
|
|
765
|
+
if (checkpointOptions.checkpoint_requested_at != null) {
|
|
766
|
+
set.checkpoint_requested_at = checkpointOptions.checkpoint_requested_at;
|
|
767
|
+
}
|
|
765
768
|
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
}
|
|
769
|
+
return {
|
|
770
|
+
updateOne: {
|
|
771
|
+
filter: {
|
|
772
|
+
user_id: checkpointOptions.user_id
|
|
773
|
+
},
|
|
774
|
+
update: {
|
|
775
|
+
$set: set,
|
|
776
|
+
...(checkpointOptions.checkpoint_requested_at == null
|
|
777
|
+
? {
|
|
778
|
+
$unset: {
|
|
779
|
+
checkpoint_requested_at: 1
|
|
778
780
|
}
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
}
|
|
784
|
-
}
|
|
785
|
-
|
|
786
|
-
|
|
781
|
+
}
|
|
782
|
+
: {})
|
|
783
|
+
},
|
|
784
|
+
upsert: true
|
|
785
|
+
}
|
|
786
|
+
};
|
|
787
|
+
})
|
|
788
|
+
);
|
|
787
789
|
}
|
|
788
790
|
}
|