@powersync/service-module-mongodb-storage 0.16.0 → 0.18.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 +53 -0
- package/dist/storage/MongoBucketStorage.d.ts +35 -15
- package/dist/storage/MongoBucketStorage.js +309 -86
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/CheckpointState.d.ts +20 -0
- package/dist/storage/implementation/CheckpointState.js +31 -0
- package/dist/storage/implementation/CheckpointState.js.map +1 -0
- package/dist/storage/implementation/MongoBucketBatch.d.ts +69 -39
- package/dist/storage/implementation/MongoBucketBatch.js +104 -313
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +11 -9
- package/dist/storage/implementation/MongoChecksums.js +3 -111
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +3 -1
- package/dist/storage/implementation/MongoCompactor.js +18 -9
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoHydrationState.d.ts +11 -0
- package/dist/storage/implementation/MongoHydrationState.js +49 -0
- package/dist/storage/implementation/MongoHydrationState.js.map +1 -0
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +6 -5
- package/dist/storage/implementation/MongoParameterCompactor.js +24 -1
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +13 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +64 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +36 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js +90 -0
- package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +25 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +121 -0
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -0
- package/dist/storage/implementation/MongoStorageProvider.js +5 -2
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +50 -21
- package/dist/storage/implementation/MongoSyncBucketStorage.js +62 -199
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +5 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +9 -5
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +14 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js +36 -0
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -0
- package/dist/storage/implementation/common/BucketDataDoc.d.ts +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +6 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js +2 -0
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.js.map +1 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +2 -3
- package/dist/storage/implementation/common/PersistedBatch.js +5 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/SourceRecordStore.d.ts +1 -2
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/createMongoSyncBucketStorage.d.ts +2 -2
- package/dist/storage/implementation/createMongoSyncBucketStorage.js +4 -4
- package/dist/storage/implementation/createMongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +10 -2
- package/dist/storage/implementation/db.js +9 -14
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +25 -50
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +23 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +403 -2
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoChecksumsV1.js +114 -3
- package/dist/storage/implementation/v1/MongoChecksumsV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +22 -15
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +100 -27
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +1 -2
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.d.ts +2 -1
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js +3 -0
- package/dist/storage/implementation/v1/VersionedPowerSyncMongoV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.d.ts +21 -1
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +21 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +459 -3
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +27 -7
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +202 -18
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +20 -3
- package/dist/storage/implementation/v3/MongoCompactorV3.js +511 -22
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +62 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +350 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -0
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +50 -16
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +292 -75
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -3
- package/dist/storage/implementation/v3/PersistedBatchV3.js +43 -20
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +52 -16
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/SourceRecordStoreV3.d.ts +3 -3
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js +13 -11
- package/dist/storage/implementation/v3/SourceRecordStoreV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +16 -14
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +32 -27
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +4 -0
- package/dist/storage/implementation/v3/bucket-format.js +58 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -0
- package/dist/storage/implementation/v3/chunking.d.ts +10 -0
- package/dist/storage/implementation/v3/chunking.js +28 -0
- package/dist/storage/implementation/v3/chunking.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +111 -10
- package/dist/storage/implementation/v3/models.js +11 -28
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +55 -0
- package/dist/storage/implementation/v3/source-table-utils.js +214 -0
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -0
- package/dist/storage/storage-index.d.ts +4 -4
- package/dist/storage/storage-index.js +4 -4
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +2 -2
- package/dist/utils/test-utils.d.ts +1 -2
- package/dist/utils/test-utils.js +5 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +1 -1
- package/dist/utils/util.js +3 -2
- package/dist/utils/util.js.map +1 -1
- package/package.json +9 -19
- package/src/migrations/db/migrations/1702295701188-sync-rule-state.ts +1 -1
- package/src/storage/MongoBucketStorage.ts +464 -95
- package/src/storage/implementation/CheckpointState.ts +59 -0
- package/src/storage/implementation/MongoBucketBatch.ts +159 -405
- package/src/storage/implementation/MongoChecksums.ts +7 -138
- package/src/storage/implementation/MongoCompactor.ts +20 -10
- package/src/storage/implementation/MongoHydrationState.ts +61 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +24 -5
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +88 -0
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +118 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +152 -0
- package/src/storage/implementation/MongoStorageProvider.ts +5 -2
- package/src/storage/implementation/MongoSyncBucketStorage.ts +89 -250
- package/src/storage/implementation/MongoSyncRulesLock.ts +14 -5
- package/src/storage/implementation/SyncRuleStateUpdate.ts +38 -0
- package/src/storage/implementation/common/BucketDataDoc.ts +1 -1
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
- package/src/storage/implementation/common/PersistedBatch.ts +8 -4
- package/src/storage/implementation/common/SourceRecordStore.ts +1 -2
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +16 -21
- package/src/storage/implementation/models.ts +28 -61
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +503 -5
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
- package/src/storage/implementation/v1/MongoCompactorV1.ts +1 -1
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +150 -49
- package/src/storage/implementation/v1/PersistedBatchV1.ts +1 -2
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +25 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +611 -4
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
- package/src/storage/implementation/v3/MongoCompactorV3.ts +647 -30
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +397 -111
- package/src/storage/implementation/v3/PersistedBatchV3.ts +63 -38
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +59 -19
- package/src/storage/implementation/v3/SourceRecordStoreV3.ts +12 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +48 -38
- package/src/storage/implementation/v3/bucket-format.ts +70 -0
- package/src/storage/implementation/v3/chunking.ts +38 -0
- package/src/storage/implementation/v3/models.ts +150 -44
- package/src/storage/implementation/v3/source-table-utils.ts +392 -0
- package/src/storage/storage-index.ts +12 -4
- package/src/utils/test-utils.ts +6 -3
- package/src/utils/util.ts +3 -2
- package/test/src/__snapshots__/storage.test.ts.snap +0 -75
- package/test/src/__snapshots__/storage_sync.test.ts.snap +48 -48
- package/test/src/cleanup-stopped-sync-configs.test.ts +648 -0
- package/test/src/storage.test.ts +11 -11
- package/test/src/storage_compacting.test.ts +1956 -6
- package/test/src/storage_sync.test.ts +1429 -42
- package/test/src/storeCurrentData.test.ts +221 -0
- package/test/src/util.ts +2 -1
- package/tsconfig.json +1 -4
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/BucketDefinitionMapping.d.ts +0 -17
- package/dist/storage/implementation/BucketDefinitionMapping.js +0 -58
- package/dist/storage/implementation/BucketDefinitionMapping.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRules.d.ts +0 -14
- package/dist/storage/implementation/MongoPersistedSyncRules.js +0 -64
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -15
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -42
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js.map +0 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.d.ts +0 -13
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js +0 -2
- package/dist/storage/implementation/common/MongoSyncBucketStorageContext.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +0 -9
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +0 -18
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +0 -1
- package/dist/storage/implementation/v3/MongoParameterLookupV3.d.ts +0 -5
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js +0 -9
- package/dist/storage/implementation/v3/MongoParameterLookupV3.js.map +0 -1
- package/src/storage/implementation/BucketDefinitionMapping.ts +0 -72
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -76
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -56
- package/src/storage/implementation/common/MongoSyncBucketStorageContext.ts +0 -15
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +0 -24
- package/src/storage/implementation/v3/MongoParameterLookupV3.ts +0 -12
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
2
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
|
+
import { ServiceAssertionError } from '@powersync/lib-services-framework';
|
|
3
4
|
import {
|
|
4
5
|
CheckpointChanges,
|
|
5
6
|
GetCheckpointChangesOptions,
|
|
@@ -7,6 +8,7 @@ import {
|
|
|
7
8
|
internalToExternalOpId,
|
|
8
9
|
ParameterSetLimitExceededError,
|
|
9
10
|
ProtocolOpId,
|
|
11
|
+
SingleSyncConfigBucketDefinitionMapping,
|
|
10
12
|
storage,
|
|
11
13
|
utils
|
|
12
14
|
} from '@powersync/service-core';
|
|
@@ -15,56 +17,145 @@ import { ParameterLookupRows, ScopedParameterLookup, SqliteJsonRow } from '@powe
|
|
|
15
17
|
import * as bson from 'bson';
|
|
16
18
|
import { mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../../utils/util.js';
|
|
17
19
|
import { MongoBucketStorage } from '../../MongoBucketStorage.js';
|
|
18
|
-
import {
|
|
19
|
-
|
|
20
|
-
MongoSyncBucketStorageContext
|
|
21
|
-
} from '../common/MongoSyncBucketStorageContext.js';
|
|
22
|
-
import { CommonSourceTableDocument } from '../models.js';
|
|
23
|
-
import { MongoBucketBatchOptions } from '../MongoBucketBatch.js';
|
|
20
|
+
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
21
|
+
import { MongoSyncBucketStorageCheckpoint } from '../common/MongoSyncBucketStorageCheckpoint.js';
|
|
24
22
|
import { MongoChecksums } from '../MongoChecksums.js';
|
|
25
23
|
import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
|
|
26
24
|
import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
|
|
27
|
-
import {
|
|
25
|
+
import { MongoPersistedReplicationStream } from '../MongoPersistedReplicationStream.js';
|
|
28
26
|
import { MongoSyncBucketStorage, MongoSyncBucketStorageOptions } from '../MongoSyncBucketStorage.js';
|
|
29
|
-
import {
|
|
27
|
+
import { loadBucketDataDocument } from './bucket-format.js';
|
|
28
|
+
import {
|
|
29
|
+
BucketDataDocumentV3,
|
|
30
|
+
BucketParameterDocumentV3,
|
|
31
|
+
deserializeParameterLookup,
|
|
32
|
+
ReplicationStreamDocumentV3,
|
|
33
|
+
serializeParameterLookup,
|
|
34
|
+
SyncRuleConfigStateV3
|
|
35
|
+
} from './models.js';
|
|
30
36
|
import { MongoBucketBatchV3 } from './MongoBucketBatchV3.js';
|
|
31
37
|
import { MongoChecksumsV3 } from './MongoChecksumsV3.js';
|
|
32
38
|
import { MongoCompactorV3 } from './MongoCompactorV3.js';
|
|
33
|
-
import {
|
|
34
|
-
import { deserializeParameterLookupV3, serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
|
|
39
|
+
import { MongoStoppedSyncConfigCleanup } from './MongoStoppedSyncConfigCleanup.js';
|
|
35
40
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
36
41
|
|
|
42
|
+
export interface MongoSyncBucketStorageContextV3 {
|
|
43
|
+
db: VersionedPowerSyncMongoV3;
|
|
44
|
+
replicationStreamId: number;
|
|
45
|
+
/**
|
|
46
|
+
* Persisted mapping of the single sync config that read operations are served from.
|
|
47
|
+
*
|
|
48
|
+
* Implemented as a lazy getter: accessing it on a storage instance with multiple sync
|
|
49
|
+
* configs throws, but operations that don't use it remain unaffected.
|
|
50
|
+
*/
|
|
51
|
+
readonly mapping: SingleSyncConfigBucketDefinitionMapping;
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function* walkDocumentOps(
|
|
55
|
+
data: BucketDataDoc[],
|
|
56
|
+
documentOpCounts: number[],
|
|
57
|
+
documentSizes: number[]
|
|
58
|
+
): Generator<{ row: BucketDataDoc; docIndex: number; isLastOpInDocument: boolean }> {
|
|
59
|
+
let opIndex = 0;
|
|
60
|
+
for (const [docIndex, opCount] of documentOpCounts.entries()) {
|
|
61
|
+
for (let i = 0; i < opCount; i++) {
|
|
62
|
+
yield { row: data[opIndex++], docIndex, isLastOpInDocument: i === opCount - 1 };
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function extractRowsFromDocument(
|
|
68
|
+
doc: BucketDataDocumentV3,
|
|
69
|
+
context: { replicationStreamId: number; definitionId: string },
|
|
70
|
+
bucketMap: Map<string, InternalOpId>,
|
|
71
|
+
endOpId: InternalOpId,
|
|
72
|
+
remainingLimit: number
|
|
73
|
+
): { rows: BucketDataDoc[]; remainingLimit: number; limitReached: boolean } {
|
|
74
|
+
const rows: BucketDataDoc[] = [];
|
|
75
|
+
for (const row of loadBucketDataDocument(context, doc)) {
|
|
76
|
+
const bucket = row.bucketKey.bucket;
|
|
77
|
+
const bucketStart = bucketMap.get(bucket);
|
|
78
|
+
if (bucketStart == null) {
|
|
79
|
+
throw new Error(`data for unexpected bucket: ${bucket}`);
|
|
80
|
+
}
|
|
81
|
+
if (row.o <= bucketStart) {
|
|
82
|
+
continue;
|
|
83
|
+
}
|
|
84
|
+
if (row.o > endOpId) {
|
|
85
|
+
continue;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
rows.push(row);
|
|
89
|
+
remainingLimit--;
|
|
90
|
+
if (remainingLimit <= 0) {
|
|
91
|
+
return { rows, remainingLimit, limitReached: true };
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
return { rows, remainingLimit, limitReached: false };
|
|
95
|
+
}
|
|
96
|
+
|
|
37
97
|
export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
38
|
-
// Declare types to be more specific
|
|
39
98
|
declare readonly db: VersionedPowerSyncMongoV3;
|
|
40
99
|
declare readonly checksums: MongoChecksumsV3;
|
|
41
100
|
|
|
42
101
|
constructor(
|
|
43
102
|
factory: MongoBucketStorage,
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
103
|
+
replicationStreamId: number,
|
|
104
|
+
replicationStream: MongoPersistedReplicationStream,
|
|
105
|
+
replicationStreamName: string,
|
|
47
106
|
writeCheckpointMode: storage.WriteCheckpointMode | undefined,
|
|
48
107
|
options: MongoSyncBucketStorageOptions
|
|
49
108
|
) {
|
|
50
|
-
super(factory,
|
|
109
|
+
super(factory, replicationStreamId, replicationStream, replicationStreamName, writeCheckpointMode, options);
|
|
110
|
+
if (replicationStream.syncConfigIds.length == 0) {
|
|
111
|
+
throw new ServiceAssertionError('Missing sync config id for storage v3');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private get syncConfigIds(): bson.ObjectId[] {
|
|
116
|
+
return this.replicationStream.syncConfigIds;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
private get syncRulesCollection(): mongo.Collection<ReplicationStreamDocumentV3> {
|
|
120
|
+
return this.db.sync_rules as unknown as mongo.Collection<ReplicationStreamDocumentV3>;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
private syncConfigMatch(extra: mongo.Document = {}): mongo.Filter<ReplicationStreamDocumentV3> {
|
|
124
|
+
return {
|
|
125
|
+
_id: this.replicationStreamId,
|
|
126
|
+
sync_configs: {
|
|
127
|
+
$elemMatch: {
|
|
128
|
+
_id: { $in: this.syncConfigIds },
|
|
129
|
+
...extra
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
};
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
private syncConfigProjection(extra: mongo.Document = {}): mongo.Document {
|
|
136
|
+
return {
|
|
137
|
+
...extra,
|
|
138
|
+
sync_configs: 1
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
private selectedSyncConfigs(doc: Pick<ReplicationStreamDocumentV3, 'sync_configs'> | null): SyncRuleConfigStateV3[] {
|
|
143
|
+
return doc?.sync_configs?.filter((config) => this.syncConfigIds.some((id) => id.equals(config._id))) ?? [];
|
|
51
144
|
}
|
|
52
145
|
|
|
53
146
|
protected async initializeVersionStorage(): Promise<void> {
|
|
54
|
-
const
|
|
55
|
-
for (
|
|
56
|
-
const collection = this.db.
|
|
57
|
-
await this.db.db
|
|
58
|
-
.
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
throw error;
|
|
64
|
-
});
|
|
147
|
+
const storageIds = this.storageIds;
|
|
148
|
+
for (const source of storageIds.bucketDefinitionIds) {
|
|
149
|
+
const collection = this.db.bucketData(this.replicationStreamId, source).collectionName;
|
|
150
|
+
await this.db.db.createCollection(collection, {}).catch((error) => {
|
|
151
|
+
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceExists') {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
throw error;
|
|
155
|
+
});
|
|
65
156
|
}
|
|
66
|
-
for (
|
|
67
|
-
await this.db.
|
|
157
|
+
for (const indexId of storageIds.parameterIndexIds) {
|
|
158
|
+
await this.db.parameterIndex(this.replicationStreamId, indexId).createIndex(
|
|
68
159
|
{
|
|
69
160
|
lookup: 1,
|
|
70
161
|
key: 1,
|
|
@@ -78,10 +169,10 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
78
169
|
}
|
|
79
170
|
|
|
80
171
|
protected createMongoChecksums(options: MongoSyncBucketStorageOptions): MongoChecksums {
|
|
81
|
-
return new MongoChecksumsV3(this.db, this.
|
|
172
|
+
return new MongoChecksumsV3(this.db, this.replicationStreamId, {
|
|
82
173
|
...options.checksumOptions,
|
|
83
174
|
storageConfig: options?.storageConfig,
|
|
84
|
-
|
|
175
|
+
syncConfigMapping: () => this.singleSyncConfigMapping()
|
|
85
176
|
});
|
|
86
177
|
}
|
|
87
178
|
|
|
@@ -93,44 +184,161 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
93
184
|
checkpoint: InternalOpId,
|
|
94
185
|
options: storage.CompactOptions
|
|
95
186
|
): MongoParameterCompactor {
|
|
96
|
-
return new
|
|
187
|
+
return new MongoParameterCompactor(this.db, this.replicationStreamId, checkpoint, options, () =>
|
|
188
|
+
this.db
|
|
189
|
+
.listParameterIndexCollections(this.replicationStreamId)
|
|
190
|
+
.then((collections) =>
|
|
191
|
+
collections.map((c) => c.collection as unknown as lib_mongo.mongo.Collection<lib_mongo.mongo.Document>)
|
|
192
|
+
)
|
|
193
|
+
);
|
|
97
194
|
}
|
|
98
195
|
|
|
99
|
-
protected
|
|
100
|
-
|
|
196
|
+
protected async fetchPersistedOpHead(): Promise<InternalOpId | null> {
|
|
197
|
+
const doc = await this.syncRulesCollection.findOne(
|
|
198
|
+
{ _id: this.replicationStreamId },
|
|
199
|
+
{ projection: { last_persisted_op: 1 } }
|
|
200
|
+
);
|
|
201
|
+
return doc?.last_persisted_op == null ? null : BigInt(doc.last_persisted_op);
|
|
101
202
|
}
|
|
102
203
|
|
|
103
|
-
protected
|
|
104
|
-
|
|
204
|
+
protected async createWriterImpl(options: storage.CreateWriterOptions): Promise<storage.BucketStorageBatch> {
|
|
205
|
+
const doc = await this.syncRulesCollection.findOne(
|
|
206
|
+
{ _id: this.replicationStreamId },
|
|
207
|
+
{ projection: { resume_lsn: 1 } }
|
|
208
|
+
);
|
|
209
|
+
|
|
210
|
+
return new MongoBucketBatchV3({
|
|
211
|
+
...this.writerBatchOptions(options),
|
|
212
|
+
// The stream-level replication position - per-config checkpoint LSNs are consistency
|
|
213
|
+
// markers and do not affect where replication resumes.
|
|
214
|
+
resumeFromLsn: doc?.resume_lsn ?? null,
|
|
215
|
+
syncConfigIds: this.syncConfigIds
|
|
216
|
+
});
|
|
105
217
|
}
|
|
106
218
|
|
|
107
|
-
protected
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
219
|
+
protected async fetchCheckpointState(
|
|
220
|
+
session: mongo.ClientSession
|
|
221
|
+
): Promise<{ checkpoint: bigint; lsn: string | null } | null> {
|
|
222
|
+
const doc = await this.syncRulesCollection.findOne(
|
|
223
|
+
this.syncConfigMatch({
|
|
224
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
225
|
+
}),
|
|
226
|
+
{
|
|
227
|
+
session,
|
|
228
|
+
projection: this.syncConfigProjection()
|
|
229
|
+
}
|
|
230
|
+
);
|
|
231
|
+
// Checkpoints are served from the single active config. A PROCESSING config in the same
|
|
232
|
+
// stream (incremental reprocessing) does not affect checkpoints until it is activated.
|
|
233
|
+
const syncConfigs = this.selectedSyncConfigs(doc).filter((config) =>
|
|
234
|
+
[storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED].includes(config.state)
|
|
235
|
+
);
|
|
236
|
+
if (syncConfigs.length > 1) {
|
|
237
|
+
// Activation atomically replaces the previous active config, so this cannot happen unless
|
|
238
|
+
// the stored state is corrupt.
|
|
239
|
+
throw new ServiceAssertionError(
|
|
240
|
+
`Expected a single active sync config, got ${syncConfigs.map((config) => config._id.toHexString()).join(', ')}`
|
|
241
|
+
);
|
|
242
|
+
}
|
|
243
|
+
const syncConfig = syncConfigs[0];
|
|
244
|
+
if (syncConfig == null || !syncConfig.snapshot_done) {
|
|
245
|
+
return null;
|
|
246
|
+
}
|
|
247
|
+
return {
|
|
248
|
+
checkpoint: syncConfig.last_checkpoint ?? 0n,
|
|
249
|
+
lsn: syncConfig.last_checkpoint_lsn ?? null
|
|
250
|
+
};
|
|
251
|
+
}
|
|
118
252
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
253
|
+
protected async terminateSyncRuleState(): Promise<void> {
|
|
254
|
+
await this.db.sync_rules.updateOne(
|
|
255
|
+
{
|
|
256
|
+
_id: this.replicationStreamId
|
|
257
|
+
},
|
|
258
|
+
{
|
|
259
|
+
$set: {
|
|
260
|
+
state: storage.SyncRuleState.TERMINATED,
|
|
261
|
+
persisted_lsn: null,
|
|
262
|
+
sync_configs: []
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
);
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
protected async getStatusImpl(): Promise<storage.ReplicationStreamStatus> {
|
|
269
|
+
const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
|
|
270
|
+
projection: this.syncConfigProjection({ resume_lsn: 1 })
|
|
122
271
|
});
|
|
272
|
+
const syncConfigs = this.selectedSyncConfigs(doc);
|
|
273
|
+
if (doc == null || syncConfigs.length == 0) {
|
|
274
|
+
throw new ServiceAssertionError('Cannot find replication stream status');
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
return {
|
|
278
|
+
snapshotDone:
|
|
279
|
+
syncConfigs.every((config) => config.snapshot_done ?? false) &&
|
|
280
|
+
syncConfigs.every((config) => config.last_checkpoint_lsn != null),
|
|
281
|
+
resumeLsn: doc.resume_lsn ?? null
|
|
282
|
+
};
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
protected async clearSyncRuleState(): Promise<void> {
|
|
286
|
+
// Clearing resets the entire replication stream (bucket data and the op sequence), so reset
|
|
287
|
+
// the checkpoint state for _all_ embedded sync configs, not only the ones selected for this
|
|
288
|
+
// storage instance. This maintains the invariant that no config has a last_checkpoint past
|
|
289
|
+
// the stream-level last_persisted_op.
|
|
290
|
+
await this.syncRulesCollection.updateOne(
|
|
291
|
+
{ _id: this.replicationStreamId },
|
|
292
|
+
{
|
|
293
|
+
$set: {
|
|
294
|
+
persisted_lsn: null,
|
|
295
|
+
'sync_configs.$[].snapshot_done': false,
|
|
296
|
+
'sync_configs.$[].last_checkpoint_lsn': null,
|
|
297
|
+
'sync_configs.$[].last_checkpoint': null,
|
|
298
|
+
'sync_configs.$[].no_checkpoint_before': null
|
|
299
|
+
},
|
|
300
|
+
$unset: {
|
|
301
|
+
resume_lsn: 1,
|
|
302
|
+
last_persisted_op: 1
|
|
303
|
+
}
|
|
304
|
+
},
|
|
305
|
+
{
|
|
306
|
+
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
|
|
307
|
+
}
|
|
308
|
+
);
|
|
123
309
|
}
|
|
124
310
|
|
|
125
|
-
|
|
126
|
-
|
|
311
|
+
/**
|
|
312
|
+
* The persisted mapping of the single sync config that read operations are served from.
|
|
313
|
+
*
|
|
314
|
+
* Reads always operate on a single sync config: read-path storage instances are
|
|
315
|
+
* constructed for the active sync config only (see MongoBucketStorage.getActiveSyncConfig),
|
|
316
|
+
* and checkpoints are served from the single active config (see fetchCheckpointState).
|
|
317
|
+
* Within a single sync config, unique names are the persistence key of its rule mapping,
|
|
318
|
+
* so its name-keyed {@link SingleSyncConfigBucketDefinitionMapping} resolves sources from
|
|
319
|
+
* any parse of that config unambiguously - no parsed-set identity is required.
|
|
320
|
+
*
|
|
321
|
+
* Throws on storage instances with multiple sync configs (replication-side instances),
|
|
322
|
+
* which must not serve reads.
|
|
323
|
+
*/
|
|
324
|
+
private singleSyncConfigMapping(): SingleSyncConfigBucketDefinitionMapping {
|
|
325
|
+
const content = this.replicationStream.syncConfigContent;
|
|
326
|
+
if (content.length != 1) {
|
|
327
|
+
throw new ServiceAssertionError(
|
|
328
|
+
`Read operations require a storage instance with a single sync config, got ${content.length}`
|
|
329
|
+
);
|
|
330
|
+
}
|
|
331
|
+
return content[0].mapping;
|
|
127
332
|
}
|
|
128
333
|
|
|
129
|
-
protected
|
|
334
|
+
protected get versionContext(): MongoSyncBucketStorageContextV3 {
|
|
335
|
+
const self = this;
|
|
130
336
|
return {
|
|
131
337
|
db: this.db,
|
|
132
|
-
|
|
133
|
-
mapping
|
|
338
|
+
replicationStreamId: this.replicationStreamId,
|
|
339
|
+
get mapping() {
|
|
340
|
+
return self.singleSyncConfigMapping();
|
|
341
|
+
}
|
|
134
342
|
};
|
|
135
343
|
}
|
|
136
344
|
|
|
@@ -151,26 +359,26 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
151
359
|
}
|
|
152
360
|
|
|
153
361
|
protected async clearBucketData(_signal?: AbortSignal): Promise<void> {
|
|
154
|
-
for (const collection of await this.db.
|
|
362
|
+
for (const collection of await this.db.listBucketDataCollections(this.replicationStreamId)) {
|
|
155
363
|
await collection.drop();
|
|
156
364
|
}
|
|
157
365
|
}
|
|
158
366
|
|
|
159
367
|
protected async clearParameterIndexes(_signal?: AbortSignal): Promise<void> {
|
|
160
|
-
for (const collection of await this.db.
|
|
368
|
+
for (const collection of await this.db.listParameterIndexCollections(this.replicationStreamId)) {
|
|
161
369
|
await collection.collection.drop();
|
|
162
370
|
}
|
|
163
371
|
}
|
|
164
372
|
|
|
165
373
|
protected async clearSourceRecords(_signal?: AbortSignal): Promise<void> {
|
|
166
|
-
for (const collection of await this.db.
|
|
374
|
+
for (const collection of await this.db.listSourceRecordCollections(this.replicationStreamId)) {
|
|
167
375
|
await collection.drop();
|
|
168
376
|
}
|
|
169
377
|
}
|
|
170
378
|
|
|
171
379
|
protected async clearBucketState(_signal?: AbortSignal): Promise<void> {
|
|
172
380
|
await this.db
|
|
173
|
-
.
|
|
381
|
+
.bucketState(this.replicationStreamId)
|
|
174
382
|
.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
|
|
175
383
|
.catch((error) => {
|
|
176
384
|
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
|
|
@@ -182,7 +390,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
182
390
|
|
|
183
391
|
protected async clearSourceTables(_signal?: AbortSignal): Promise<void> {
|
|
184
392
|
await this.db
|
|
185
|
-
.
|
|
393
|
+
.sourceTables(this.replicationStreamId)
|
|
186
394
|
.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
|
|
187
395
|
.catch((error) => {
|
|
188
396
|
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
|
|
@@ -192,6 +400,19 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
192
400
|
});
|
|
193
401
|
}
|
|
194
402
|
|
|
403
|
+
async cleanupStoppedSyncConfigs(
|
|
404
|
+
options: storage.CleanupStoppedSyncConfigsOptions
|
|
405
|
+
): Promise<storage.CleanupStoppedSyncConfigsResult> {
|
|
406
|
+
return new MongoStoppedSyncConfigCleanup({
|
|
407
|
+
db: this.db,
|
|
408
|
+
replicationStreamId: this.replicationStreamId,
|
|
409
|
+
signal: options.signal,
|
|
410
|
+
logger: options.logger ?? this.logger,
|
|
411
|
+
defaultSchema: options.defaultSchema,
|
|
412
|
+
sourceConnectionTag: options.sourceConnectionTag
|
|
413
|
+
}).run();
|
|
414
|
+
}
|
|
415
|
+
|
|
195
416
|
protected getDataBucketChangesImpl(
|
|
196
417
|
options: GetCheckpointChangesOptions
|
|
197
418
|
): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
|
|
@@ -206,7 +427,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
206
427
|
}
|
|
207
428
|
|
|
208
429
|
export async function getParameterSetsV3(
|
|
209
|
-
ctx:
|
|
430
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
210
431
|
checkpoint: MongoSyncBucketStorageCheckpoint,
|
|
211
432
|
lookups: ScopedParameterLookup[],
|
|
212
433
|
limit: number
|
|
@@ -222,8 +443,8 @@ export async function getParameterSetsV3(
|
|
|
222
443
|
pipeline: mongo.Document[];
|
|
223
444
|
} => {
|
|
224
445
|
const indexId = lookup.indexId;
|
|
225
|
-
const collection = ctx.db.
|
|
226
|
-
const lookupFilter =
|
|
446
|
+
const collection = ctx.db.parameterIndex(ctx.replicationStreamId, indexId);
|
|
447
|
+
const lookupFilter = serializeParameterLookup(lookup);
|
|
227
448
|
|
|
228
449
|
return {
|
|
229
450
|
collection,
|
|
@@ -306,7 +527,7 @@ export async function getParameterSetsV3(
|
|
|
306
527
|
}
|
|
307
528
|
|
|
308
529
|
export async function* getBucketDataBatchV3(
|
|
309
|
-
ctx:
|
|
530
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
310
531
|
checkpoint: utils.InternalOpId,
|
|
311
532
|
dataBuckets: storage.BucketDataRequest[],
|
|
312
533
|
options?: storage.BucketDataBatchOptions
|
|
@@ -333,61 +554,125 @@ export async function* getBucketDataBatchV3(
|
|
|
333
554
|
}
|
|
334
555
|
|
|
335
556
|
const definitionGroups = Array.from(requestsByDefinition.entries());
|
|
336
|
-
for (
|
|
337
|
-
|
|
557
|
+
for (const [groupIndex, [definitionId, requests]] of definitionGroups.entries()) {
|
|
558
|
+
if (remainingLimit <= 0) {
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
338
561
|
const hasLaterDefinitionGroups = groupIndex < definitionGroups.length - 1;
|
|
339
562
|
const bucketMap = new Map(requests.map((request) => [request.bucket, request.start]));
|
|
340
|
-
const filters
|
|
563
|
+
const filters = Array.from(bucketMap.entries()).map(([bucket, start]) => ({
|
|
341
564
|
_id: {
|
|
342
|
-
$gt: {
|
|
343
|
-
|
|
344
|
-
o: start
|
|
345
|
-
},
|
|
346
|
-
$lte: {
|
|
347
|
-
b: bucket,
|
|
348
|
-
o: end as any
|
|
349
|
-
}
|
|
350
|
-
}
|
|
351
|
-
}));
|
|
352
|
-
|
|
353
|
-
const cursor = ctx.db.bucketDataV3(ctx.group_id, definitionId).find(
|
|
354
|
-
{
|
|
355
|
-
$or: filters
|
|
565
|
+
$gt: { b: bucket, o: start },
|
|
566
|
+
$lte: { b: bucket, o: new bson.MaxKey() }
|
|
356
567
|
},
|
|
357
|
-
{
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
568
|
+
min_op: { $lte: end }
|
|
569
|
+
// MongoDB Filter<T> doesn't accept compound _id ranges or dotted field paths in its type.
|
|
570
|
+
})) as unknown as mongo.Filter<BucketDataDocumentV3>[];
|
|
571
|
+
|
|
572
|
+
const collection = ctx.db.bucketData(ctx.replicationStreamId, definitionId);
|
|
573
|
+
// MongoDB Filter<T> doesn't accept the $or operator in its type.
|
|
574
|
+
const filter = { $or: filters } as unknown as mongo.Filter<BucketDataDocumentV3>;
|
|
575
|
+
const context = { replicationStreamId: ctx.replicationStreamId, definitionId };
|
|
576
|
+
const limit = remainingLimit;
|
|
577
|
+
|
|
578
|
+
const cursorOptions = { limit: remainingLimit, batchSize: remainingLimit + 1 };
|
|
579
|
+
|
|
580
|
+
// raw: true returns Buffers, but the driver typing doesn't reflect that
|
|
581
|
+
// without an explicit cast to FindCursor<Buffer>.
|
|
582
|
+
const cursor = collection.find(filter, {
|
|
583
|
+
session: undefined,
|
|
584
|
+
sort: { _id: 1 },
|
|
585
|
+
raw: true,
|
|
586
|
+
maxTimeMS: lib_mongo.db.MONGO_OPERATION_TIMEOUT_MS,
|
|
587
|
+
...cursorOptions
|
|
588
|
+
}) as unknown as mongo.FindCursor<Buffer>;
|
|
589
|
+
|
|
590
|
+
let { data: rawData, hasMore } = await readSingleBatch(cursor).catch((e: unknown) => {
|
|
368
591
|
throw lib_mongo.mapQueryError(e, 'while reading bucket data');
|
|
369
592
|
});
|
|
370
|
-
|
|
371
|
-
|
|
593
|
+
|
|
594
|
+
if (cursorOptions.limit != null && rawData.length >= cursorOptions.limit) {
|
|
595
|
+
hasMore = true;
|
|
596
|
+
}
|
|
597
|
+
|
|
598
|
+
const data: BucketDataDoc[] = [];
|
|
599
|
+
const documentOpCounts: number[] = [];
|
|
600
|
+
const documentSizes: number[] = [];
|
|
601
|
+
let sharedRemainingLimit = limit;
|
|
602
|
+
let limitReached = false;
|
|
603
|
+
// Buckets whose matched document contributed no rows after filtering.
|
|
604
|
+
const completeEmptyBuckets = new Set<string>();
|
|
605
|
+
|
|
606
|
+
for (const raw of rawData) {
|
|
607
|
+
const doc = bson.deserialize(raw, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS) as BucketDataDocumentV3;
|
|
608
|
+
const {
|
|
609
|
+
rows,
|
|
610
|
+
remainingLimit,
|
|
611
|
+
limitReached: docLimitReached
|
|
612
|
+
} = extractRowsFromDocument(doc, context, bucketMap, end, sharedRemainingLimit);
|
|
613
|
+
if (rows.length == 0) {
|
|
614
|
+
// The document straddles the requested (start, end] window: it matched the
|
|
615
|
+
// query, but none of its ops are in range. Since its _id.o (max op) must be
|
|
616
|
+
// > end (any op <= end would have been > start, and thus in range), and
|
|
617
|
+
// document ranges per bucket are disjoint, no later document for this bucket
|
|
618
|
+
// can match either. The bucket is complete through the checkpoint.
|
|
619
|
+
completeEmptyBuckets.add(doc._id.b);
|
|
620
|
+
}
|
|
621
|
+
data.push(...rows);
|
|
622
|
+
documentOpCounts.push(rows.length);
|
|
623
|
+
documentSizes.push(raw.byteLength);
|
|
624
|
+
sharedRemainingLimit = remainingLimit;
|
|
625
|
+
if (docLimitReached) {
|
|
626
|
+
limitReached = true;
|
|
627
|
+
break;
|
|
628
|
+
}
|
|
372
629
|
}
|
|
630
|
+
|
|
631
|
+
const batchHasMore = hasMore || limitReached;
|
|
632
|
+
|
|
633
|
+
// Empty chunks are not forwarded to clients, but report progress to the caller:
|
|
634
|
+
// the bucket's position advances to the checkpoint, so it is not re-requested.
|
|
635
|
+
// If the batch produced no data at all, the last empty chunk also carries the
|
|
636
|
+
// has_more signal, so the caller re-requests the remaining buckets instead of
|
|
637
|
+
// treating an all-filtered batch as the end of the stream.
|
|
638
|
+
const emptyBuckets = Array.from(completeEmptyBuckets);
|
|
639
|
+
for (const [index, bucket] of emptyBuckets.entries()) {
|
|
640
|
+
const startOpId = bucketMap.get(bucket);
|
|
641
|
+
if (startOpId == null) {
|
|
642
|
+
throw new ServiceAssertionError(`data for unexpected bucket: ${bucket}`);
|
|
643
|
+
}
|
|
644
|
+
const isLastChunkOfBatch = data.length == 0 && index == emptyBuckets.length - 1;
|
|
645
|
+
yield {
|
|
646
|
+
chunkData: {
|
|
647
|
+
bucket,
|
|
648
|
+
after: internalToExternalOpId(startOpId),
|
|
649
|
+
has_more: isLastChunkOfBatch && batchHasMore,
|
|
650
|
+
data: [],
|
|
651
|
+
next_after: internalToExternalOpId(end)
|
|
652
|
+
},
|
|
653
|
+
targetOp: null
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
373
657
|
if (data.length == 0) {
|
|
658
|
+
if (batchHasMore) {
|
|
659
|
+
// The remaining documents are read in the next round, after the caller has
|
|
660
|
+
// advanced the positions of the empty buckets above.
|
|
661
|
+
return;
|
|
662
|
+
}
|
|
374
663
|
continue;
|
|
375
664
|
}
|
|
376
665
|
|
|
377
666
|
remainingLimit -= data.length;
|
|
378
667
|
|
|
379
|
-
let
|
|
668
|
+
let currentChunkSizeBytes = 0;
|
|
380
669
|
let currentChunk: utils.SyncBucketData | null = null;
|
|
381
670
|
let targetOp: InternalOpId | null = null;
|
|
382
671
|
|
|
383
|
-
for (
|
|
384
|
-
const row = loadBucketDataDocumentV3(
|
|
385
|
-
{ replicationStreamId: ctx.group_id, definitionId },
|
|
386
|
-
bson.deserialize(rawData, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS) as BucketDataDocumentV3
|
|
387
|
-
);
|
|
672
|
+
for (const { row, docIndex, isLastOpInDocument } of walkDocumentOps(data, documentOpCounts, documentSizes)) {
|
|
388
673
|
const bucket = row.bucketKey.bucket;
|
|
389
674
|
|
|
390
|
-
if (currentChunk == null || currentChunk.bucket != bucket ||
|
|
675
|
+
if (currentChunk == null || currentChunk.bucket != bucket || currentChunkSizeBytes >= chunkSizeLimitBytes) {
|
|
391
676
|
let start: ProtocolOpId | undefined = undefined;
|
|
392
677
|
if (currentChunk != null) {
|
|
393
678
|
if (currentChunk.bucket == bucket) {
|
|
@@ -397,8 +682,8 @@ export async function* getBucketDataBatchV3(
|
|
|
397
682
|
|
|
398
683
|
const yieldChunk = currentChunk;
|
|
399
684
|
currentChunk = null;
|
|
400
|
-
|
|
401
|
-
yield { chunkData: yieldChunk, targetOp
|
|
685
|
+
currentChunkSizeBytes = 0;
|
|
686
|
+
yield { chunkData: yieldChunk, targetOp };
|
|
402
687
|
targetOp = null;
|
|
403
688
|
}
|
|
404
689
|
|
|
@@ -425,13 +710,16 @@ export async function* getBucketDataBatchV3(
|
|
|
425
710
|
|
|
426
711
|
currentChunk.data.push(entry);
|
|
427
712
|
currentChunk.next_after = entry.op_id;
|
|
428
|
-
|
|
713
|
+
|
|
714
|
+
if (isLastOpInDocument) {
|
|
715
|
+
currentChunkSizeBytes += documentSizes[docIndex];
|
|
716
|
+
}
|
|
429
717
|
}
|
|
430
718
|
|
|
431
719
|
if (currentChunk != null) {
|
|
432
720
|
const yieldChunk = currentChunk;
|
|
433
721
|
yieldChunk.has_more = batchHasMore || (remainingLimit <= 0 && hasLaterDefinitionGroups);
|
|
434
|
-
yield { chunkData: yieldChunk, targetOp
|
|
722
|
+
yield { chunkData: yieldChunk, targetOp };
|
|
435
723
|
}
|
|
436
724
|
|
|
437
725
|
if (batchHasMore || remainingLimit <= 0) {
|
|
@@ -441,12 +729,12 @@ export async function* getBucketDataBatchV3(
|
|
|
441
729
|
}
|
|
442
730
|
|
|
443
731
|
export async function getDataBucketChangesV3(
|
|
444
|
-
ctx:
|
|
732
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
445
733
|
options: GetCheckpointChangesOptions
|
|
446
734
|
): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
|
|
447
735
|
const limit = 1000;
|
|
448
736
|
const bucketStateUpdates = await ctx.db
|
|
449
|
-
.
|
|
737
|
+
.bucketState(ctx.replicationStreamId)
|
|
450
738
|
.aggregate<{ _id: string; last_op: bigint }>(
|
|
451
739
|
[
|
|
452
740
|
{
|
|
@@ -483,14 +771,14 @@ export async function getDataBucketChangesV3(
|
|
|
483
771
|
}
|
|
484
772
|
|
|
485
773
|
export async function getParameterBucketChangesV3(
|
|
486
|
-
ctx:
|
|
774
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
487
775
|
options: GetCheckpointChangesOptions
|
|
488
776
|
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
|
|
489
777
|
const limit = 1000;
|
|
490
778
|
const indexIds = ctx.mapping.allParameterIndexIds();
|
|
491
779
|
const collections = indexIds.map((indexId) => ({
|
|
492
780
|
indexId,
|
|
493
|
-
collection: ctx.db.
|
|
781
|
+
collection: ctx.db.parameterIndex(ctx.replicationStreamId, indexId)
|
|
494
782
|
}));
|
|
495
783
|
if (collections.length == 0) {
|
|
496
784
|
return {
|
|
@@ -543,8 +831,6 @@ export async function getParameterBucketChangesV3(
|
|
|
543
831
|
invalidateParameterBuckets: invalidateParameterUpdates,
|
|
544
832
|
updatedParameterLookups: invalidateParameterUpdates
|
|
545
833
|
? new Set<string>()
|
|
546
|
-
: new Set<string>(
|
|
547
|
-
parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookupV3(p.lookup, p.indexId)))
|
|
548
|
-
)
|
|
834
|
+
: new Set<string>(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup, p.indexId))))
|
|
549
835
|
};
|
|
550
836
|
}
|