@powersync/service-module-mongodb-storage 0.17.0 → 0.18.1
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 +27 -0
- package/dist/storage/MongoBucketStorage.d.ts +34 -16
- package/dist/storage/MongoBucketStorage.js +219 -70
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +42 -23
- package/dist/storage/implementation/MongoBucketBatch.js +61 -44
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +10 -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 +2 -0
- 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 +123 -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 +41 -23
- package/dist/storage/implementation/MongoSyncBucketStorage.js +51 -43
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +3 -3
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +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 +1 -2
- package/dist/storage/implementation/common/PersistedBatch.js +5 -0
- package/dist/storage/implementation/common/PersistedBatch.js.map +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.js +9 -14
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +4 -13
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +9 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +42 -26
- 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/MongoSyncBucketStorageV1.d.ts +15 -17
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +47 -45
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.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 +10 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -4
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +286 -259
- 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 +19 -2
- 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 +40 -28
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +232 -116
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +1 -1
- 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 +13 -12
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +29 -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 +64 -21
- 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/storage/MongoBucketStorage.ts +334 -87
- package/src/storage/implementation/MongoBucketBatch.ts +86 -58
- package/src/storage/implementation/MongoChecksums.ts +6 -138
- package/src/storage/implementation/MongoCompactor.ts +19 -9
- 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 +154 -0
- package/src/storage/implementation/MongoStorageProvider.ts +5 -2
- package/src/storage/implementation/MongoSyncBucketStorage.ts +70 -56
- package/src/storage/implementation/MongoSyncRulesLock.ts +7 -4
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +7 -0
- package/src/storage/implementation/common/PersistedBatch.ts +7 -3
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +1 -1
- package/src/storage/implementation/createMongoSyncBucketStorage.ts +21 -7
- package/src/storage/implementation/db.ts +11 -19
- package/src/storage/implementation/models.ts +4 -14
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +55 -34
- package/src/storage/implementation/v1/MongoChecksumsV1.ts +137 -4
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +70 -64
- package/src/storage/implementation/v1/VersionedPowerSyncMongoV1.ts +11 -1
- package/src/storage/implementation/v1/models.ts +11 -1
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +358 -314
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +262 -26
- package/src/storage/implementation/v3/MongoCompactorV3.ts +646 -29
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +480 -0
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +315 -157
- package/src/storage/implementation/v3/PersistedBatchV3.ts +62 -36
- 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 +41 -36
- 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 +93 -55
- 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 +1077 -68
- package/test/src/storeCurrentData.test.ts +17 -7
- 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 -15
- 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 -67
- package/dist/storage/implementation/MongoPersistedSyncRules.js.map +0 -1
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.d.ts +0 -29
- package/dist/storage/implementation/MongoPersistedSyncRulesContent.js +0 -76
- 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 -4
- 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 -75
- package/src/storage/implementation/MongoPersistedSyncRules.ts +0 -82
- package/src/storage/implementation/MongoPersistedSyncRulesContent.ts +0 -101
- 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 -11
|
@@ -6,9 +6,9 @@ import {
|
|
|
6
6
|
GetCheckpointChangesOptions,
|
|
7
7
|
InternalOpId,
|
|
8
8
|
internalToExternalOpId,
|
|
9
|
-
maxLsn,
|
|
10
9
|
ParameterSetLimitExceededError,
|
|
11
10
|
ProtocolOpId,
|
|
11
|
+
SingleSyncConfigBucketDefinitionMapping,
|
|
12
12
|
storage,
|
|
13
13
|
utils
|
|
14
14
|
} from '@powersync/service-core';
|
|
@@ -17,57 +17,103 @@ import { ParameterLookupRows, ScopedParameterLookup, SqliteJsonRow } from '@powe
|
|
|
17
17
|
import * as bson from 'bson';
|
|
18
18
|
import { mapOpEntry, readSingleBatch, setSessionSnapshotTime } from '../../../utils/util.js';
|
|
19
19
|
import { MongoBucketStorage } from '../../MongoBucketStorage.js';
|
|
20
|
-
import {
|
|
21
|
-
|
|
22
|
-
MongoSyncBucketStorageContext
|
|
23
|
-
} from '../common/MongoSyncBucketStorageContext.js';
|
|
24
|
-
import { MongoBucketBatchOptions } from '../MongoBucketBatch.js';
|
|
20
|
+
import { BucketDataDoc } from '../common/BucketDataDoc.js';
|
|
21
|
+
import { MongoSyncBucketStorageCheckpoint } from '../common/MongoSyncBucketStorageCheckpoint.js';
|
|
25
22
|
import { MongoChecksums } from '../MongoChecksums.js';
|
|
26
23
|
import { MongoCompactOptions, MongoCompactor } from '../MongoCompactor.js';
|
|
27
24
|
import { MongoParameterCompactor } from '../MongoParameterCompactor.js';
|
|
28
|
-
import {
|
|
29
|
-
MongoPersistedSyncRulesContentV1,
|
|
30
|
-
MongoPersistedSyncRulesContentV3
|
|
31
|
-
} from '../MongoPersistedSyncRulesContent.js';
|
|
25
|
+
import { MongoPersistedReplicationStream } from '../MongoPersistedReplicationStream.js';
|
|
32
26
|
import { MongoSyncBucketStorage, MongoSyncBucketStorageOptions } from '../MongoSyncBucketStorage.js';
|
|
27
|
+
import { loadBucketDataDocument } from './bucket-format.js';
|
|
33
28
|
import {
|
|
34
29
|
BucketDataDocumentV3,
|
|
35
30
|
BucketParameterDocumentV3,
|
|
36
|
-
|
|
31
|
+
deserializeParameterLookup,
|
|
37
32
|
ReplicationStreamDocumentV3,
|
|
33
|
+
serializeParameterLookup,
|
|
38
34
|
SyncRuleConfigStateV3
|
|
39
35
|
} from './models.js';
|
|
40
36
|
import { MongoBucketBatchV3 } from './MongoBucketBatchV3.js';
|
|
41
37
|
import { MongoChecksumsV3 } from './MongoChecksumsV3.js';
|
|
42
38
|
import { MongoCompactorV3 } from './MongoCompactorV3.js';
|
|
43
|
-
import {
|
|
44
|
-
import { deserializeParameterLookupV3, serializeParameterLookupV3 } from './MongoParameterLookupV3.js';
|
|
39
|
+
import { MongoStoppedSyncConfigCleanup } from './MongoStoppedSyncConfigCleanup.js';
|
|
45
40
|
import { VersionedPowerSyncMongoV3 } from './VersionedPowerSyncMongoV3.js';
|
|
46
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
|
+
|
|
47
97
|
export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
48
|
-
// Declare types to be more specific
|
|
49
98
|
declare readonly db: VersionedPowerSyncMongoV3;
|
|
50
99
|
declare readonly checksums: MongoChecksumsV3;
|
|
51
100
|
|
|
52
|
-
private readonly syncRulesV3: MongoPersistedSyncRulesContentV3;
|
|
53
|
-
|
|
54
101
|
constructor(
|
|
55
102
|
factory: MongoBucketStorage,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
103
|
+
replicationStreamId: number,
|
|
104
|
+
replicationStream: MongoPersistedReplicationStream,
|
|
105
|
+
replicationStreamName: string,
|
|
59
106
|
writeCheckpointMode: storage.WriteCheckpointMode | undefined,
|
|
60
107
|
options: MongoSyncBucketStorageOptions
|
|
61
108
|
) {
|
|
62
|
-
super(factory,
|
|
63
|
-
if (
|
|
109
|
+
super(factory, replicationStreamId, replicationStream, replicationStreamName, writeCheckpointMode, options);
|
|
110
|
+
if (replicationStream.syncConfigIds.length == 0) {
|
|
64
111
|
throw new ServiceAssertionError('Missing sync config id for storage v3');
|
|
65
112
|
}
|
|
66
|
-
this.syncRulesV3 = sync_rules;
|
|
67
113
|
}
|
|
68
114
|
|
|
69
|
-
private get
|
|
70
|
-
return this.
|
|
115
|
+
private get syncConfigIds(): bson.ObjectId[] {
|
|
116
|
+
return this.replicationStream.syncConfigIds;
|
|
71
117
|
}
|
|
72
118
|
|
|
73
119
|
private get syncRulesCollection(): mongo.Collection<ReplicationStreamDocumentV3> {
|
|
@@ -76,10 +122,10 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
76
122
|
|
|
77
123
|
private syncConfigMatch(extra: mongo.Document = {}): mongo.Filter<ReplicationStreamDocumentV3> {
|
|
78
124
|
return {
|
|
79
|
-
_id: this.
|
|
125
|
+
_id: this.replicationStreamId,
|
|
80
126
|
sync_configs: {
|
|
81
127
|
$elemMatch: {
|
|
82
|
-
_id: this.
|
|
128
|
+
_id: { $in: this.syncConfigIds },
|
|
83
129
|
...extra
|
|
84
130
|
}
|
|
85
131
|
}
|
|
@@ -89,44 +135,27 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
89
135
|
private syncConfigProjection(extra: mongo.Document = {}): mongo.Document {
|
|
90
136
|
return {
|
|
91
137
|
...extra,
|
|
92
|
-
sync_configs:
|
|
93
|
-
$elemMatch: {
|
|
94
|
-
_id: this.syncConfigId
|
|
95
|
-
}
|
|
96
|
-
}
|
|
138
|
+
sync_configs: 1
|
|
97
139
|
};
|
|
98
140
|
}
|
|
99
141
|
|
|
100
|
-
private
|
|
101
|
-
return
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
/**
|
|
105
|
-
* For now, we only support a single sync config per replication stream.
|
|
106
|
-
*
|
|
107
|
-
* In the future we'll add support for multiple.
|
|
108
|
-
*/
|
|
109
|
-
private selectedSyncConfig(
|
|
110
|
-
doc: Pick<ReplicationStreamDocumentV3, 'sync_configs'> | null
|
|
111
|
-
): SyncRuleConfigStateV3 | null {
|
|
112
|
-
return doc?.sync_configs?.[0] ?? null;
|
|
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))) ?? [];
|
|
113
144
|
}
|
|
114
145
|
|
|
115
146
|
protected async initializeVersionStorage(): Promise<void> {
|
|
116
|
-
const
|
|
117
|
-
for (
|
|
118
|
-
const collection = this.db.
|
|
119
|
-
await this.db.db
|
|
120
|
-
.
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
throw error;
|
|
126
|
-
});
|
|
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
|
+
});
|
|
127
156
|
}
|
|
128
|
-
for (
|
|
129
|
-
await this.db.
|
|
157
|
+
for (const indexId of storageIds.parameterIndexIds) {
|
|
158
|
+
await this.db.parameterIndex(this.replicationStreamId, indexId).createIndex(
|
|
130
159
|
{
|
|
131
160
|
lookup: 1,
|
|
132
161
|
key: 1,
|
|
@@ -140,10 +169,10 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
140
169
|
}
|
|
141
170
|
|
|
142
171
|
protected createMongoChecksums(options: MongoSyncBucketStorageOptions): MongoChecksums {
|
|
143
|
-
return new MongoChecksumsV3(this.db, this.
|
|
172
|
+
return new MongoChecksumsV3(this.db, this.replicationStreamId, {
|
|
144
173
|
...options.checksumOptions,
|
|
145
174
|
storageConfig: options?.storageConfig,
|
|
146
|
-
|
|
175
|
+
syncConfigMapping: () => this.singleSyncConfigMapping()
|
|
147
176
|
});
|
|
148
177
|
}
|
|
149
178
|
|
|
@@ -155,11 +184,36 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
155
184
|
checkpoint: InternalOpId,
|
|
156
185
|
options: storage.CompactOptions
|
|
157
186
|
): MongoParameterCompactor {
|
|
158
|
-
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
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
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);
|
|
159
202
|
}
|
|
160
203
|
|
|
161
|
-
protected createWriterImpl(
|
|
162
|
-
|
|
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
|
+
});
|
|
163
217
|
}
|
|
164
218
|
|
|
165
219
|
protected async fetchCheckpointState(
|
|
@@ -174,8 +228,20 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
174
228
|
projection: this.syncConfigProjection()
|
|
175
229
|
}
|
|
176
230
|
);
|
|
177
|
-
|
|
178
|
-
|
|
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) {
|
|
179
245
|
return null;
|
|
180
246
|
}
|
|
181
247
|
return {
|
|
@@ -184,24 +250,10 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
184
250
|
};
|
|
185
251
|
}
|
|
186
252
|
|
|
187
|
-
protected async getWriterSyncState() {
|
|
188
|
-
const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
|
|
189
|
-
projection: this.syncConfigProjection({ snapshot_lsn: 1 })
|
|
190
|
-
});
|
|
191
|
-
const syncConfig = this.selectedSyncConfig(doc);
|
|
192
|
-
const checkpointLsn = syncConfig?.last_checkpoint_lsn ?? null;
|
|
193
|
-
return {
|
|
194
|
-
lastCheckpointLsn: checkpointLsn,
|
|
195
|
-
resumeFromLsn: maxLsn(checkpointLsn, doc?.snapshot_lsn),
|
|
196
|
-
keepaliveOp: syncConfig?.keepalive_op ?? null,
|
|
197
|
-
syncConfigId: this.syncConfigId
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
|
|
201
253
|
protected async terminateSyncRuleState(): Promise<void> {
|
|
202
254
|
await this.db.sync_rules.updateOne(
|
|
203
255
|
{
|
|
204
|
-
_id: this.
|
|
256
|
+
_id: this.replicationStreamId
|
|
205
257
|
},
|
|
206
258
|
{
|
|
207
259
|
$set: {
|
|
@@ -213,52 +265,80 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
213
265
|
);
|
|
214
266
|
}
|
|
215
267
|
|
|
216
|
-
protected async getStatusImpl(): Promise<storage.
|
|
268
|
+
protected async getStatusImpl(): Promise<storage.ReplicationStreamStatus> {
|
|
217
269
|
const doc = await this.syncRulesCollection.findOne(this.syncConfigMatch(), {
|
|
218
|
-
projection: this.syncConfigProjection({
|
|
270
|
+
projection: this.syncConfigProjection({ resume_lsn: 1 })
|
|
219
271
|
});
|
|
220
|
-
const
|
|
221
|
-
if (doc == null ||
|
|
272
|
+
const syncConfigs = this.selectedSyncConfigs(doc);
|
|
273
|
+
if (doc == null || syncConfigs.length == 0) {
|
|
222
274
|
throw new ServiceAssertionError('Cannot find replication stream status');
|
|
223
275
|
}
|
|
224
276
|
|
|
225
277
|
return {
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
keepalive_op: syncConfig.keepalive_op ?? null
|
|
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
|
|
231
282
|
};
|
|
232
283
|
}
|
|
233
284
|
|
|
234
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.
|
|
235
290
|
await this.syncRulesCollection.updateOne(
|
|
236
|
-
this.
|
|
291
|
+
{ _id: this.replicationStreamId },
|
|
237
292
|
{
|
|
238
293
|
$set: {
|
|
239
294
|
persisted_lsn: null,
|
|
240
|
-
'sync_configs.$[
|
|
241
|
-
'sync_configs.$[
|
|
242
|
-
'sync_configs.$[
|
|
243
|
-
'sync_configs.$[
|
|
244
|
-
'sync_configs.$[config].keepalive_op': 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
|
|
245
299
|
},
|
|
246
300
|
$unset: {
|
|
247
|
-
|
|
301
|
+
resume_lsn: 1,
|
|
302
|
+
last_persisted_op: 1
|
|
248
303
|
}
|
|
249
304
|
},
|
|
250
305
|
{
|
|
251
|
-
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
|
|
252
|
-
arrayFilters: this.syncConfigArrayFilters()
|
|
306
|
+
maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS
|
|
253
307
|
}
|
|
254
308
|
);
|
|
255
309
|
}
|
|
256
310
|
|
|
257
|
-
|
|
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;
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
protected get versionContext(): MongoSyncBucketStorageContextV3 {
|
|
335
|
+
const self = this;
|
|
258
336
|
return {
|
|
259
337
|
db: this.db,
|
|
260
|
-
|
|
261
|
-
mapping
|
|
338
|
+
replicationStreamId: this.replicationStreamId,
|
|
339
|
+
get mapping() {
|
|
340
|
+
return self.singleSyncConfigMapping();
|
|
341
|
+
}
|
|
262
342
|
};
|
|
263
343
|
}
|
|
264
344
|
|
|
@@ -279,26 +359,26 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
279
359
|
}
|
|
280
360
|
|
|
281
361
|
protected async clearBucketData(_signal?: AbortSignal): Promise<void> {
|
|
282
|
-
for (const collection of await this.db.
|
|
362
|
+
for (const collection of await this.db.listBucketDataCollections(this.replicationStreamId)) {
|
|
283
363
|
await collection.drop();
|
|
284
364
|
}
|
|
285
365
|
}
|
|
286
366
|
|
|
287
367
|
protected async clearParameterIndexes(_signal?: AbortSignal): Promise<void> {
|
|
288
|
-
for (const collection of await this.db.
|
|
368
|
+
for (const collection of await this.db.listParameterIndexCollections(this.replicationStreamId)) {
|
|
289
369
|
await collection.collection.drop();
|
|
290
370
|
}
|
|
291
371
|
}
|
|
292
372
|
|
|
293
373
|
protected async clearSourceRecords(_signal?: AbortSignal): Promise<void> {
|
|
294
|
-
for (const collection of await this.db.
|
|
374
|
+
for (const collection of await this.db.listSourceRecordCollections(this.replicationStreamId)) {
|
|
295
375
|
await collection.drop();
|
|
296
376
|
}
|
|
297
377
|
}
|
|
298
378
|
|
|
299
379
|
protected async clearBucketState(_signal?: AbortSignal): Promise<void> {
|
|
300
380
|
await this.db
|
|
301
|
-
.
|
|
381
|
+
.bucketState(this.replicationStreamId)
|
|
302
382
|
.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
|
|
303
383
|
.catch((error) => {
|
|
304
384
|
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
|
|
@@ -310,7 +390,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
310
390
|
|
|
311
391
|
protected async clearSourceTables(_signal?: AbortSignal): Promise<void> {
|
|
312
392
|
await this.db
|
|
313
|
-
.
|
|
393
|
+
.sourceTables(this.replicationStreamId)
|
|
314
394
|
.drop({ maxTimeMS: lib_mongo.db.MONGO_CLEAR_OPERATION_TIMEOUT_MS })
|
|
315
395
|
.catch((error) => {
|
|
316
396
|
if (lib_mongo.isMongoServerError(error) && error.codeName === 'NamespaceNotFound') {
|
|
@@ -320,6 +400,19 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
320
400
|
});
|
|
321
401
|
}
|
|
322
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
|
+
|
|
323
416
|
protected getDataBucketChangesImpl(
|
|
324
417
|
options: GetCheckpointChangesOptions
|
|
325
418
|
): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
|
|
@@ -334,7 +427,7 @@ export class MongoSyncBucketStorageV3 extends MongoSyncBucketStorage {
|
|
|
334
427
|
}
|
|
335
428
|
|
|
336
429
|
export async function getParameterSetsV3(
|
|
337
|
-
ctx:
|
|
430
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
338
431
|
checkpoint: MongoSyncBucketStorageCheckpoint,
|
|
339
432
|
lookups: ScopedParameterLookup[],
|
|
340
433
|
limit: number
|
|
@@ -350,8 +443,8 @@ export async function getParameterSetsV3(
|
|
|
350
443
|
pipeline: mongo.Document[];
|
|
351
444
|
} => {
|
|
352
445
|
const indexId = lookup.indexId;
|
|
353
|
-
const collection = ctx.db.
|
|
354
|
-
const lookupFilter =
|
|
446
|
+
const collection = ctx.db.parameterIndex(ctx.replicationStreamId, indexId);
|
|
447
|
+
const lookupFilter = serializeParameterLookup(lookup);
|
|
355
448
|
|
|
356
449
|
return {
|
|
357
450
|
collection,
|
|
@@ -434,7 +527,7 @@ export async function getParameterSetsV3(
|
|
|
434
527
|
}
|
|
435
528
|
|
|
436
529
|
export async function* getBucketDataBatchV3(
|
|
437
|
-
ctx:
|
|
530
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
438
531
|
checkpoint: utils.InternalOpId,
|
|
439
532
|
dataBuckets: storage.BucketDataRequest[],
|
|
440
533
|
options?: storage.BucketDataBatchOptions
|
|
@@ -461,61 +554,125 @@ export async function* getBucketDataBatchV3(
|
|
|
461
554
|
}
|
|
462
555
|
|
|
463
556
|
const definitionGroups = Array.from(requestsByDefinition.entries());
|
|
464
|
-
for (
|
|
465
|
-
|
|
557
|
+
for (const [groupIndex, [definitionId, requests]] of definitionGroups.entries()) {
|
|
558
|
+
if (remainingLimit <= 0) {
|
|
559
|
+
break;
|
|
560
|
+
}
|
|
466
561
|
const hasLaterDefinitionGroups = groupIndex < definitionGroups.length - 1;
|
|
467
562
|
const bucketMap = new Map(requests.map((request) => [request.bucket, request.start]));
|
|
468
|
-
const filters
|
|
563
|
+
const filters = Array.from(bucketMap.entries()).map(([bucket, start]) => ({
|
|
469
564
|
_id: {
|
|
470
|
-
$gt: {
|
|
471
|
-
|
|
472
|
-
o: start
|
|
473
|
-
},
|
|
474
|
-
$lte: {
|
|
475
|
-
b: bucket,
|
|
476
|
-
o: end as any
|
|
477
|
-
}
|
|
478
|
-
}
|
|
479
|
-
}));
|
|
480
|
-
|
|
481
|
-
const cursor = ctx.db.bucketDataV3(ctx.group_id, definitionId).find(
|
|
482
|
-
{
|
|
483
|
-
$or: filters
|
|
565
|
+
$gt: { b: bucket, o: start },
|
|
566
|
+
$lte: { b: bucket, o: new bson.MaxKey() }
|
|
484
567
|
},
|
|
485
|
-
{
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
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) => {
|
|
496
591
|
throw lib_mongo.mapQueryError(e, 'while reading bucket data');
|
|
497
592
|
});
|
|
498
|
-
|
|
499
|
-
|
|
593
|
+
|
|
594
|
+
if (cursorOptions.limit != null && rawData.length >= cursorOptions.limit) {
|
|
595
|
+
hasMore = true;
|
|
500
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
|
+
}
|
|
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
|
+
|
|
501
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
|
+
}
|
|
502
663
|
continue;
|
|
503
664
|
}
|
|
504
665
|
|
|
505
666
|
remainingLimit -= data.length;
|
|
506
667
|
|
|
507
|
-
let
|
|
668
|
+
let currentChunkSizeBytes = 0;
|
|
508
669
|
let currentChunk: utils.SyncBucketData | null = null;
|
|
509
670
|
let targetOp: InternalOpId | null = null;
|
|
510
671
|
|
|
511
|
-
for (
|
|
512
|
-
const row = loadBucketDataDocumentV3(
|
|
513
|
-
{ replicationStreamId: ctx.group_id, definitionId },
|
|
514
|
-
bson.deserialize(rawData, storage.BSON_DESERIALIZE_INTERNAL_OPTIONS) as BucketDataDocumentV3
|
|
515
|
-
);
|
|
672
|
+
for (const { row, docIndex, isLastOpInDocument } of walkDocumentOps(data, documentOpCounts, documentSizes)) {
|
|
516
673
|
const bucket = row.bucketKey.bucket;
|
|
517
674
|
|
|
518
|
-
if (currentChunk == null || currentChunk.bucket != bucket ||
|
|
675
|
+
if (currentChunk == null || currentChunk.bucket != bucket || currentChunkSizeBytes >= chunkSizeLimitBytes) {
|
|
519
676
|
let start: ProtocolOpId | undefined = undefined;
|
|
520
677
|
if (currentChunk != null) {
|
|
521
678
|
if (currentChunk.bucket == bucket) {
|
|
@@ -525,8 +682,8 @@ export async function* getBucketDataBatchV3(
|
|
|
525
682
|
|
|
526
683
|
const yieldChunk = currentChunk;
|
|
527
684
|
currentChunk = null;
|
|
528
|
-
|
|
529
|
-
yield { chunkData: yieldChunk, targetOp
|
|
685
|
+
currentChunkSizeBytes = 0;
|
|
686
|
+
yield { chunkData: yieldChunk, targetOp };
|
|
530
687
|
targetOp = null;
|
|
531
688
|
}
|
|
532
689
|
|
|
@@ -553,13 +710,16 @@ export async function* getBucketDataBatchV3(
|
|
|
553
710
|
|
|
554
711
|
currentChunk.data.push(entry);
|
|
555
712
|
currentChunk.next_after = entry.op_id;
|
|
556
|
-
|
|
713
|
+
|
|
714
|
+
if (isLastOpInDocument) {
|
|
715
|
+
currentChunkSizeBytes += documentSizes[docIndex];
|
|
716
|
+
}
|
|
557
717
|
}
|
|
558
718
|
|
|
559
719
|
if (currentChunk != null) {
|
|
560
720
|
const yieldChunk = currentChunk;
|
|
561
721
|
yieldChunk.has_more = batchHasMore || (remainingLimit <= 0 && hasLaterDefinitionGroups);
|
|
562
|
-
yield { chunkData: yieldChunk, targetOp
|
|
722
|
+
yield { chunkData: yieldChunk, targetOp };
|
|
563
723
|
}
|
|
564
724
|
|
|
565
725
|
if (batchHasMore || remainingLimit <= 0) {
|
|
@@ -569,12 +729,12 @@ export async function* getBucketDataBatchV3(
|
|
|
569
729
|
}
|
|
570
730
|
|
|
571
731
|
export async function getDataBucketChangesV3(
|
|
572
|
-
ctx:
|
|
732
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
573
733
|
options: GetCheckpointChangesOptions
|
|
574
734
|
): Promise<Pick<CheckpointChanges, 'updatedDataBuckets' | 'invalidateDataBuckets'>> {
|
|
575
735
|
const limit = 1000;
|
|
576
736
|
const bucketStateUpdates = await ctx.db
|
|
577
|
-
.
|
|
737
|
+
.bucketState(ctx.replicationStreamId)
|
|
578
738
|
.aggregate<{ _id: string; last_op: bigint }>(
|
|
579
739
|
[
|
|
580
740
|
{
|
|
@@ -611,14 +771,14 @@ export async function getDataBucketChangesV3(
|
|
|
611
771
|
}
|
|
612
772
|
|
|
613
773
|
export async function getParameterBucketChangesV3(
|
|
614
|
-
ctx:
|
|
774
|
+
ctx: MongoSyncBucketStorageContextV3,
|
|
615
775
|
options: GetCheckpointChangesOptions
|
|
616
776
|
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
|
|
617
777
|
const limit = 1000;
|
|
618
778
|
const indexIds = ctx.mapping.allParameterIndexIds();
|
|
619
779
|
const collections = indexIds.map((indexId) => ({
|
|
620
780
|
indexId,
|
|
621
|
-
collection: ctx.db.
|
|
781
|
+
collection: ctx.db.parameterIndex(ctx.replicationStreamId, indexId)
|
|
622
782
|
}));
|
|
623
783
|
if (collections.length == 0) {
|
|
624
784
|
return {
|
|
@@ -671,8 +831,6 @@ export async function getParameterBucketChangesV3(
|
|
|
671
831
|
invalidateParameterBuckets: invalidateParameterUpdates,
|
|
672
832
|
updatedParameterLookups: invalidateParameterUpdates
|
|
673
833
|
? new Set<string>()
|
|
674
|
-
: new Set<string>(
|
|
675
|
-
parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookupV3(p.lookup, p.indexId)))
|
|
676
|
-
)
|
|
834
|
+
: new Set<string>(parameterUpdates.map((p) => JSONBig.stringify(deserializeParameterLookup(p.lookup, p.indexId))))
|
|
677
835
|
};
|
|
678
836
|
}
|