@powersync/service-module-mongodb-storage 0.19.0 → 0.21.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 +64 -0
- package/dist/storage/MongoBucketStorage.d.ts +31 -3
- package/dist/storage/MongoBucketStorage.js +365 -206
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +15 -4
- package/dist/storage/implementation/MongoBucketBatch.js +35 -9
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +19 -79
- package/dist/storage/implementation/MongoCompactor.js +27 -238
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
- package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +29 -0
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +4 -2
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +3 -1
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +27 -11
- package/dist/storage/implementation/MongoSyncBucketStorage.js +78 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.js +21 -9
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +7 -28
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
- package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
- package/dist/storage/implementation/common/PersistedBatch.d.ts +27 -0
- package/dist/storage/implementation/common/PersistedBatch.js +43 -2
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +4 -1
- package/dist/storage/implementation/db.js +14 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +35 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +6 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +80 -84
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +36 -5
- package/dist/storage/implementation/v1/MongoCompactorV1.js +229 -8
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +65 -16
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
- package/dist/storage/implementation/v3/CompactionLease.js +131 -0
- package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +8 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +120 -39
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +77 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +585 -141
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +91 -31
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +2 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.js +52 -11
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.js +2 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- package/dist/storage/implementation/v3/compact-utils.d.ts +107 -0
- package/dist/storage/implementation/v3/compact-utils.js +216 -0
- package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
- package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
- package/dist/storage/implementation/v3/compaction-constants.js +6 -0
- package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +89 -5
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +8 -4
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +3 -5
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +10 -2
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +37 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +167 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +177 -23
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +15 -7
- package/dist/storage/implementation/v3/source-table-utils.js +49 -57
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
- package/dist/storage/storage-index.d.ts +2 -1
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +23 -1
- package/dist/types/types.js +16 -7
- package/dist/types/types.js.map +1 -1
- package/dist/utils/util.js +3 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -9
- package/src/storage/MongoBucketStorage.ts +495 -268
- package/src/storage/implementation/MongoBucketBatch.ts +54 -14
- package/src/storage/implementation/MongoCompactor.ts +35 -346
- package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +34 -0
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +4 -2
- package/src/storage/implementation/MongoStorageProvider.ts +3 -1
- package/src/storage/implementation/MongoSyncBucketStorage.ts +114 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +22 -9
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +12 -44
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +54 -2
- package/src/storage/implementation/db.ts +21 -2
- package/src/storage/implementation/models.ts +38 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +107 -96
- package/src/storage/implementation/v1/MongoCompactorV1.ts +303 -19
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +87 -27
- package/src/storage/implementation/v1/models.ts +1 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +159 -42
- package/src/storage/implementation/v3/MongoCompactorV3.ts +744 -190
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +111 -41
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +57 -12
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
- package/src/storage/implementation/v3/bucket-format.ts +2 -0
- package/src/storage/implementation/v3/compact-utils.ts +320 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +96 -3
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +16 -8
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +15 -4
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +230 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +257 -27
- package/src/storage/implementation/v3/source-table-utils.ts +82 -76
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +24 -7
- package/src/utils/util.ts +3 -1
- package/test/src/__snapshots__/storage.test.ts.snap +25 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +605 -16
- package/test/src/cleanup-stopped-sync-configs.test.ts +40 -13
- package/test/src/compact-utils.test.ts +226 -0
- package/test/src/helpers/MemoryObjectStorage.ts +14 -6
- package/test/src/object_storage_usage.test.ts +255 -0
- package/test/src/parameter_compacting_v1.test.ts +120 -0
- package/test/src/parameter_compacting_v3.test.ts +192 -0
- package/test/src/parameter_compaction_fence.test.ts +276 -0
- package/test/src/storage.test.ts +273 -22
- package/test/src/storage_compacting.test.ts +714 -244
- package/test/src/storage_s3_checksums.test.ts +3 -4
- package/test/src/storage_s3_compaction_lifecycle.test.ts +138 -12
- package/test/src/storage_s3_reading.test.ts +243 -1
- package/test/src/storage_s3_writing.test.ts +1 -0
- package/test/src/storage_sync.test.ts +365 -41
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -2,6 +2,7 @@ import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
|
2
2
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
3
|
import {
|
|
4
4
|
BaseObserver,
|
|
5
|
+
logger as defaultLogger,
|
|
5
6
|
DO_NOT_LOG,
|
|
6
7
|
Logger,
|
|
7
8
|
ReplicationAbortedError,
|
|
@@ -11,14 +12,15 @@ import {
|
|
|
11
12
|
BroadcastIterable,
|
|
12
13
|
CHECKPOINT_INVALIDATE_ALL,
|
|
13
14
|
CheckpointChanges,
|
|
15
|
+
CompactInitialReplicationOptions,
|
|
16
|
+
CompactInitialReplicationResults,
|
|
14
17
|
GetCheckpointChangesOptions,
|
|
15
18
|
InternalOpId,
|
|
16
19
|
mergeAsyncIterables,
|
|
17
|
-
PopulateChecksumCacheOptions,
|
|
18
|
-
PopulateChecksumCacheResults,
|
|
19
20
|
ReplicationCheckpoint,
|
|
20
21
|
ReplicationStreamStorageIds,
|
|
21
22
|
storage,
|
|
23
|
+
SyncRuleState,
|
|
22
24
|
utils,
|
|
23
25
|
WatchWriteCheckpointOptions
|
|
24
26
|
} from '@powersync/service-core';
|
|
@@ -28,6 +30,10 @@ import { LRUCache } from 'lru-cache';
|
|
|
28
30
|
import * as timers from 'timers/promises';
|
|
29
31
|
import { DEFAULT_CLEAR_BATCH_THROTTLE_RATE } from '../../types/types.js';
|
|
30
32
|
import { MongoBucketStorage } from '../MongoBucketStorage.js';
|
|
33
|
+
import {
|
|
34
|
+
MongoGetCheckpointChangesOptions,
|
|
35
|
+
MongoSyncBucketStorageCheckpoint
|
|
36
|
+
} from './common/MongoSyncBucketStorageCheckpoint.js';
|
|
31
37
|
import { DEFAULT_INLINE_THRESHOLD_BYTES } from './common/PersistedBatch.js';
|
|
32
38
|
import type { VersionedPowerSyncMongo } from './db.js';
|
|
33
39
|
import { StorageConfig } from './models.js';
|
|
@@ -37,7 +43,7 @@ import { MongoCompactOptions, MongoCompactor } from './MongoCompactor.js';
|
|
|
37
43
|
import { MongoParameterCompactor } from './MongoParameterCompactor.js';
|
|
38
44
|
import { MongoParsedSyncConfigSet } from './MongoParsedSyncConfigSet.js';
|
|
39
45
|
import { MongoPersistedReplicationStream } from './MongoPersistedReplicationStream.js';
|
|
40
|
-
import { MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
46
|
+
import { MongoCheckpointAPIOptions, MongoWriteCheckpointAPI } from './MongoWriteCheckpointAPI.js';
|
|
41
47
|
import { ObjectStorage } from './v3/object-storage/ObjectStorage.js';
|
|
42
48
|
|
|
43
49
|
export interface MongoSyncBucketStorageOptions {
|
|
@@ -50,6 +56,16 @@ export interface MongoSyncBucketStorageOptions {
|
|
|
50
56
|
inlineThresholdBytes?: number;
|
|
51
57
|
}
|
|
52
58
|
|
|
59
|
+
/**
|
|
60
|
+
* The stream state read for a checkpoint. All fields must come from the same snapshot.
|
|
61
|
+
*/
|
|
62
|
+
export interface MongoCheckpointState {
|
|
63
|
+
checkpoint: InternalOpId;
|
|
64
|
+
lsn: string | null;
|
|
65
|
+
/** See {@link MongoSyncBucketStorageCheckpoint.parameterChangesInvalidBefore}. */
|
|
66
|
+
parameterChangesInvalidBefore: InternalOpId;
|
|
67
|
+
}
|
|
68
|
+
|
|
53
69
|
interface InternalCheckpointChanges extends CheckpointChanges {
|
|
54
70
|
updatedWriteCheckpoints: Map<string, bigint>;
|
|
55
71
|
invalidateWriteCheckpoints: boolean;
|
|
@@ -112,12 +128,27 @@ export abstract class MongoSyncBucketStorage
|
|
|
112
128
|
this.clearBatchThrottleRate = options.clearBatchThrottleRate ?? DEFAULT_CLEAR_BATCH_THROTTLE_RATE;
|
|
113
129
|
this.db = factory.db.versioned(this.storageConfig);
|
|
114
130
|
this.checksums = this.createMongoChecksums(options);
|
|
115
|
-
this.writeCheckpointAPI =
|
|
131
|
+
this.writeCheckpointAPI = this.createWriteCheckpointAPI({
|
|
116
132
|
db: this.db,
|
|
117
|
-
mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED,
|
|
118
|
-
|
|
133
|
+
writeCheckpointMode: { mode: writeCheckpointMode ?? storage.WriteCheckpointMode.MANAGED },
|
|
134
|
+
replicationStreamId
|
|
119
135
|
});
|
|
120
|
-
|
|
136
|
+
|
|
137
|
+
// Include both replication stream name and sync config version labels in log prefix.
|
|
138
|
+
// Examples:
|
|
139
|
+
// No sync config with a label: [stream]
|
|
140
|
+
// All sync configs have a label: [stream][v5,v6]
|
|
141
|
+
// Mixed: [stream][?,v6]
|
|
142
|
+
const hasVersionLabel = replicationStream.syncConfigContent.some((config) => config.version_label != null);
|
|
143
|
+
const versionLabels = hasVersionLabel
|
|
144
|
+
? replicationStream.syncConfigContent.map((config) => config.version_label ?? '?')
|
|
145
|
+
: [];
|
|
146
|
+
this.logger =
|
|
147
|
+
versionLabels.length == 0
|
|
148
|
+
? replicationStream.logger
|
|
149
|
+
: defaultLogger.child({
|
|
150
|
+
prefix: `[${replicationStreamName}][${versionLabels.join(',')}] `
|
|
151
|
+
});
|
|
121
152
|
}
|
|
122
153
|
|
|
123
154
|
/**
|
|
@@ -128,11 +159,20 @@ export abstract class MongoSyncBucketStorage
|
|
|
128
159
|
abstract createMongoCompactor(options: MongoCompactOptions): MongoCompactor;
|
|
129
160
|
|
|
130
161
|
protected abstract createMongoChecksums(options: MongoSyncBucketStorageOptions): MongoChecksums;
|
|
162
|
+
protected createWriteCheckpointAPI(options: MongoCheckpointAPIOptions): MongoWriteCheckpointAPI {
|
|
163
|
+
return new MongoWriteCheckpointAPI(options);
|
|
164
|
+
}
|
|
165
|
+
|
|
131
166
|
protected abstract createMongoParameterCompactor(
|
|
132
167
|
checkpoint: InternalOpId,
|
|
133
168
|
options: storage.CompactOptions
|
|
134
169
|
): MongoParameterCompactor;
|
|
135
170
|
|
|
171
|
+
/** MongoDB parameter compaction uses a persisted operation-id cursor. */
|
|
172
|
+
public supportsIncrementalParameterCompaction(): boolean {
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
|
|
136
176
|
get writeCheckpointMode() {
|
|
137
177
|
return this.writeCheckpointAPI.writeCheckpointMode;
|
|
138
178
|
}
|
|
@@ -144,8 +184,8 @@ export abstract class MongoSyncBucketStorage
|
|
|
144
184
|
return this.replicationStream.storageIds;
|
|
145
185
|
}
|
|
146
186
|
|
|
147
|
-
setWriteCheckpointMode(
|
|
148
|
-
this.writeCheckpointAPI.setWriteCheckpointMode(
|
|
187
|
+
setWriteCheckpointMode(config: storage.WriteCheckpointModeConfig): void {
|
|
188
|
+
this.writeCheckpointAPI.setWriteCheckpointMode(config);
|
|
149
189
|
}
|
|
150
190
|
|
|
151
191
|
createManagedWriteCheckpoints(
|
|
@@ -178,9 +218,7 @@ export abstract class MongoSyncBucketStorage
|
|
|
178
218
|
return (await this.getCheckpointInternal()) ?? new EmptyReplicationCheckpoint();
|
|
179
219
|
}
|
|
180
220
|
|
|
181
|
-
protected abstract fetchCheckpointState(
|
|
182
|
-
session: mongo.ClientSession
|
|
183
|
-
): Promise<{ checkpoint: bigint; lsn: string | null } | null>;
|
|
221
|
+
protected abstract fetchCheckpointState(session: mongo.ClientSession): Promise<MongoCheckpointState | null>;
|
|
184
222
|
|
|
185
223
|
async getCheckpointInternal(): Promise<storage.ReplicationCheckpoint | null> {
|
|
186
224
|
return await this.db.client.withSession({ snapshot: true }, async (session) => {
|
|
@@ -197,7 +235,14 @@ export abstract class MongoSyncBucketStorage
|
|
|
197
235
|
if (clusterTime == null) {
|
|
198
236
|
throw new ServiceAssertionError('Missing clusterTime in getCheckpoint()');
|
|
199
237
|
}
|
|
200
|
-
return new MongoReplicationCheckpoint(
|
|
238
|
+
return new MongoReplicationCheckpoint(
|
|
239
|
+
this,
|
|
240
|
+
state.checkpoint,
|
|
241
|
+
state.lsn,
|
|
242
|
+
snapshotTime,
|
|
243
|
+
clusterTime,
|
|
244
|
+
state.parameterChangesInvalidBefore
|
|
245
|
+
);
|
|
201
246
|
});
|
|
202
247
|
}
|
|
203
248
|
|
|
@@ -235,6 +280,7 @@ export abstract class MongoSyncBucketStorage
|
|
|
235
280
|
markRecordUnavailable: options.markRecordUnavailable,
|
|
236
281
|
hooks: options.hooks,
|
|
237
282
|
tracer: options.tracer,
|
|
283
|
+
signal: options.signal,
|
|
238
284
|
objectStorage: this.objectStorage,
|
|
239
285
|
inlineThresholdBytes: this.inlineThresholdBytes
|
|
240
286
|
};
|
|
@@ -329,6 +375,7 @@ export abstract class MongoSyncBucketStorage
|
|
|
329
375
|
protected abstract clearBucketState(signal?: AbortSignal): Promise<void>;
|
|
330
376
|
|
|
331
377
|
protected abstract clearSourceTables(signal?: AbortSignal): Promise<void>;
|
|
378
|
+
protected async clearCustomCheckpointRequests(_signal?: AbortSignal): Promise<void> {}
|
|
332
379
|
protected abstract clearSyncRuleState(): Promise<void>;
|
|
333
380
|
|
|
334
381
|
async clear(options?: storage.ClearStorageOptions): Promise<void> {
|
|
@@ -345,6 +392,7 @@ export abstract class MongoSyncBucketStorage
|
|
|
345
392
|
await this.clearSourceRecords(signal);
|
|
346
393
|
await this.clearBucketState(signal);
|
|
347
394
|
await this.clearSourceTables(signal);
|
|
395
|
+
await this.clearCustomCheckpointRequests(signal);
|
|
348
396
|
|
|
349
397
|
this.#storageInitialized = false;
|
|
350
398
|
}
|
|
@@ -367,43 +415,30 @@ export abstract class MongoSyncBucketStorage
|
|
|
367
415
|
async compact(options?: storage.CompactOptions) {
|
|
368
416
|
let maxOpId = options?.maxOpId;
|
|
369
417
|
if (maxOpId == null) {
|
|
418
|
+
// For PROCESSING streams, this will be undefined.
|
|
370
419
|
const checkpoint = await this.getCheckpointInternal();
|
|
371
420
|
maxOpId = checkpoint?.checkpoint ?? undefined;
|
|
372
421
|
}
|
|
373
422
|
await this.createMongoCompactor({ ...options, maxOpId, logger: this.logger }).compact();
|
|
374
423
|
|
|
375
|
-
if (maxOpId != null && options?.compactParameterData) {
|
|
376
|
-
|
|
424
|
+
if (maxOpId != null && options?.compactParameterData && this.replicationStream.state == SyncRuleState.ACTIVE) {
|
|
425
|
+
// Use the stream-scoped logger, matching bucket compaction above.
|
|
426
|
+
await this.createMongoParameterCompactor(maxOpId, { ...options, logger: this.logger }).compact();
|
|
377
427
|
}
|
|
378
428
|
}
|
|
379
429
|
|
|
430
|
+
abstract compactInitialReplication(
|
|
431
|
+
options: CompactInitialReplicationOptions
|
|
432
|
+
): Promise<CompactInitialReplicationResults>;
|
|
433
|
+
|
|
380
434
|
/**
|
|
381
435
|
* The highest op id persisted for this stream, whether or not covered by a checkpoint.
|
|
382
436
|
*
|
|
383
|
-
* Used as the default `maxOpId` for {@link
|
|
437
|
+
* Used as the default `maxOpId` for {@link compactInitialReplication}, which runs after
|
|
384
438
|
* initial replication but before the first checkpoint exists.
|
|
385
439
|
*/
|
|
386
440
|
protected abstract fetchPersistedOpHead(): Promise<InternalOpId | null>;
|
|
387
441
|
|
|
388
|
-
async populatePersistentChecksumCache(options: PopulateChecksumCacheOptions): Promise<PopulateChecksumCacheResults> {
|
|
389
|
-
this.logger.info(`Populating persistent checksum cache...`);
|
|
390
|
-
const start = Date.now();
|
|
391
|
-
const maxOpId = options.maxOpId ?? (await this.fetchPersistedOpHead()) ?? undefined;
|
|
392
|
-
const compactor = this.createMongoCompactor({
|
|
393
|
-
...options,
|
|
394
|
-
maxOpId,
|
|
395
|
-
memoryLimitMB: 0,
|
|
396
|
-
logger: this.logger
|
|
397
|
-
});
|
|
398
|
-
|
|
399
|
-
const result = await compactor.populateChecksums({
|
|
400
|
-
minBucketChanges: options.minBucketChanges ?? 10
|
|
401
|
-
});
|
|
402
|
-
const duration = Date.now() - start;
|
|
403
|
-
this.logger.info(`Populated persistent checksum cache in ${(duration / 1000).toFixed(1)}s`);
|
|
404
|
-
return result;
|
|
405
|
-
}
|
|
406
|
-
|
|
407
442
|
private async *watchActiveCheckpoint(signal: AbortSignal): AsyncIterable<ReplicationCheckpoint> {
|
|
408
443
|
if (signal.aborted) {
|
|
409
444
|
return;
|
|
@@ -580,13 +615,31 @@ export abstract class MongoSyncBucketStorage
|
|
|
580
615
|
}
|
|
581
616
|
|
|
582
617
|
protected abstract getParameterBucketChangesImpl(
|
|
583
|
-
options:
|
|
618
|
+
options: MongoGetCheckpointChangesOptions
|
|
584
619
|
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>>;
|
|
585
620
|
|
|
586
621
|
private async getParameterBucketChanges(
|
|
587
622
|
options: GetCheckpointChangesOptions
|
|
588
623
|
): Promise<Pick<CheckpointChanges, 'updatedParameterLookups' | 'invalidateParameterBuckets'>> {
|
|
589
|
-
|
|
624
|
+
const nextCheckpoint = requireMongoCheckpoint(options.nextCheckpoint);
|
|
625
|
+
if (options.lastCheckpoint.checkpoint < nextCheckpoint.parameterChangesInvalidBefore) {
|
|
626
|
+
// Parameter compaction may have deleted parameter entries in the range we'd have to query
|
|
627
|
+
// to find the individual changed lookups. Invalidate all parameter buckets instead.
|
|
628
|
+
//
|
|
629
|
+
// The fence is committed before the first delete of a compaction pass, and captured in the
|
|
630
|
+
// same snapshot as the checkpoint, so a checkpoint that could miss a deleted entry always
|
|
631
|
+
// observes the fence as well.
|
|
632
|
+
return {
|
|
633
|
+
invalidateParameterBuckets: true,
|
|
634
|
+
updatedParameterLookups: new Set<string>()
|
|
635
|
+
};
|
|
636
|
+
}
|
|
637
|
+
// The query below runs at the checkpoint snapshot, which still sees entries deleted by a
|
|
638
|
+
// compaction pass that started after the snapshot.
|
|
639
|
+
return this.getParameterBucketChangesImpl({
|
|
640
|
+
lastCheckpoint: options.lastCheckpoint,
|
|
641
|
+
nextCheckpoint
|
|
642
|
+
});
|
|
590
643
|
}
|
|
591
644
|
|
|
592
645
|
private checkpointChangesCache = new LRUCache<
|
|
@@ -611,7 +664,10 @@ export abstract class MongoSyncBucketStorage
|
|
|
611
664
|
});
|
|
612
665
|
|
|
613
666
|
async getCheckpointChanges(options: GetCheckpointChangesOptions): Promise<InternalCheckpointChanges> {
|
|
614
|
-
|
|
667
|
+
// The invalidation fence is part of the identity: the same checkpoint pair read before and
|
|
668
|
+
// after a compaction pass produces different results (specific lookups vs. invalidate-all).
|
|
669
|
+
const fence = requireMongoCheckpoint(options.nextCheckpoint).parameterChangesInvalidBefore;
|
|
670
|
+
const key = `${options.lastCheckpoint.checkpoint}_${options.lastCheckpoint.lsn}__${options.nextCheckpoint.checkpoint}_${options.nextCheckpoint.lsn}_${fence}`;
|
|
615
671
|
const result = await this.checkpointChangesCache.fetch(key, { context: { options } });
|
|
616
672
|
return result!;
|
|
617
673
|
}
|
|
@@ -629,7 +685,19 @@ export abstract class MongoSyncBucketStorage
|
|
|
629
685
|
}
|
|
630
686
|
}
|
|
631
687
|
|
|
632
|
-
|
|
688
|
+
/**
|
|
689
|
+
* We don't support any other constructions of ReplicationCheckpoint.
|
|
690
|
+
*/
|
|
691
|
+
function requireMongoCheckpoint(checkpoint: ReplicationCheckpoint): MongoReplicationCheckpoint {
|
|
692
|
+
if (!(checkpoint instanceof MongoReplicationCheckpoint)) {
|
|
693
|
+
throw new ServiceAssertionError(
|
|
694
|
+
`Checkpoint changes require a checkpoint from getCheckpointInternal(), got ${checkpoint.constructor.name}`
|
|
695
|
+
);
|
|
696
|
+
}
|
|
697
|
+
return checkpoint;
|
|
698
|
+
}
|
|
699
|
+
|
|
700
|
+
class MongoReplicationCheckpoint implements MongoSyncBucketStorageCheckpoint {
|
|
633
701
|
#storage: MongoSyncBucketStorage;
|
|
634
702
|
|
|
635
703
|
constructor(
|
|
@@ -637,7 +705,9 @@ class MongoReplicationCheckpoint implements ReplicationCheckpoint {
|
|
|
637
705
|
public readonly checkpoint: InternalOpId,
|
|
638
706
|
public readonly lsn: string | null,
|
|
639
707
|
public snapshotTime: mongo.Timestamp,
|
|
640
|
-
public clusterTime: mongo.ClusterTime
|
|
708
|
+
public clusterTime: mongo.ClusterTime,
|
|
709
|
+
/** Captured in the same snapshot as the checkpoint. */
|
|
710
|
+
public readonly parameterChangesInvalidBefore: InternalOpId
|
|
641
711
|
) {
|
|
642
712
|
this.#storage = storage;
|
|
643
713
|
}
|
|
@@ -647,6 +717,10 @@ class MongoReplicationCheckpoint implements ReplicationCheckpoint {
|
|
|
647
717
|
}
|
|
648
718
|
}
|
|
649
719
|
|
|
720
|
+
/**
|
|
721
|
+
* Used when no checkpoint has been persisted yet. This has no snapshot or invalidation fence, so
|
|
722
|
+
* it cannot be used for checkpoint change detection - see {@link requireMongoCheckpoint}.
|
|
723
|
+
*/
|
|
650
724
|
class EmptyReplicationCheckpoint implements ReplicationCheckpoint {
|
|
651
725
|
readonly checkpoint: InternalOpId = 0n;
|
|
652
726
|
readonly lsn: string | null = null;
|
|
@@ -5,6 +5,8 @@ import { ErrorCode, Logger, ServiceError } from '@powersync/lib-services-framewo
|
|
|
5
5
|
import { storage } from '@powersync/service-core';
|
|
6
6
|
import { VersionedPowerSyncMongo } from './db.js';
|
|
7
7
|
|
|
8
|
+
const LOCK_DURATION_MS = 60 * 1000;
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Manages a lock on a replication stream document, so that only one process
|
|
10
12
|
* processes that replication stream at a time.
|
|
@@ -22,15 +24,20 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
|
|
|
22
24
|
): Promise<MongoSyncRulesLock> {
|
|
23
25
|
const lockId = crypto.randomBytes(8).toString('hex');
|
|
24
26
|
const doc = await db.sync_rules.findOneAndUpdate(
|
|
25
|
-
{ _id: sync_rules.replicationStreamId, $or: [{ lock: null }, { 'lock.expires_at': { $lt: new Date() } }] },
|
|
26
27
|
{
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
28
|
+
_id: sync_rules.replicationStreamId,
|
|
29
|
+
$or: [{ lock: null }, { $expr: { $lt: ['$lock.expires_at', '$$NOW'] } }]
|
|
30
|
+
},
|
|
31
|
+
[
|
|
32
|
+
{
|
|
33
|
+
$set: {
|
|
34
|
+
lock: {
|
|
35
|
+
id: lockId,
|
|
36
|
+
expires_at: { $dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: LOCK_DURATION_MS } }
|
|
37
|
+
}
|
|
31
38
|
}
|
|
32
39
|
}
|
|
33
|
-
|
|
40
|
+
],
|
|
34
41
|
{
|
|
35
42
|
projection: { lock: 1 },
|
|
36
43
|
returnDocument: 'before',
|
|
@@ -96,9 +103,15 @@ export class MongoSyncRulesLock implements storage.ReplicationLock {
|
|
|
96
103
|
_id: this.sync_rules_id,
|
|
97
104
|
'lock.id': this.lock_id
|
|
98
105
|
},
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
106
|
+
[
|
|
107
|
+
{
|
|
108
|
+
$set: {
|
|
109
|
+
'lock.expires_at': {
|
|
110
|
+
$dateAdd: { startDate: '$$NOW', unit: 'millisecond', amount: LOCK_DURATION_MS }
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
],
|
|
102
115
|
{ returnDocument: 'after' }
|
|
103
116
|
);
|
|
104
117
|
if (result == null) {
|
|
@@ -1,29 +1,31 @@
|
|
|
1
|
-
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
1
|
import * as framework from '@powersync/lib-services-framework';
|
|
3
|
-
import { GetCheckpointChangesOptions,
|
|
2
|
+
import { GetCheckpointChangesOptions, storage } from '@powersync/service-core';
|
|
4
3
|
import { VersionedPowerSyncMongo } from './db.js';
|
|
5
4
|
|
|
6
5
|
export type MongoCheckpointAPIOptions = {
|
|
7
6
|
db: VersionedPowerSyncMongo;
|
|
8
|
-
|
|
9
|
-
|
|
7
|
+
writeCheckpointMode: storage.WriteCheckpointModeConfig;
|
|
8
|
+
replicationStreamId: number;
|
|
10
9
|
};
|
|
11
10
|
|
|
12
11
|
export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
13
12
|
readonly db: VersionedPowerSyncMongo;
|
|
13
|
+
readonly replicationStreamId: number;
|
|
14
|
+
|
|
14
15
|
private _mode: storage.WriteCheckpointMode;
|
|
15
16
|
|
|
16
17
|
constructor(options: MongoCheckpointAPIOptions) {
|
|
17
18
|
this.db = options.db;
|
|
18
|
-
this._mode = options.mode;
|
|
19
|
+
this._mode = options.writeCheckpointMode.mode;
|
|
20
|
+
this.replicationStreamId = options.replicationStreamId;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
23
|
get writeCheckpointMode() {
|
|
22
24
|
return this._mode;
|
|
23
25
|
}
|
|
24
26
|
|
|
25
|
-
setWriteCheckpointMode(
|
|
26
|
-
this._mode = mode;
|
|
27
|
+
setWriteCheckpointMode(config: storage.WriteCheckpointModeConfig): void {
|
|
28
|
+
this._mode = config.mode;
|
|
27
29
|
}
|
|
28
30
|
|
|
29
31
|
async createManagedWriteCheckpoints(
|
|
@@ -247,6 +249,7 @@ export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
|
247
249
|
}
|
|
248
250
|
|
|
249
251
|
protected async lastCustomWriteCheckpoint(filters: storage.CustomWriteCheckpointFilters) {
|
|
252
|
+
// Legacy V1/V2 implementation.
|
|
250
253
|
const { user_id, sync_rules_id } = filters;
|
|
251
254
|
const lastWriteCheckpoint = await this.db.custom_write_checkpoints.findOne({
|
|
252
255
|
user_id,
|
|
@@ -301,8 +304,9 @@ export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
|
301
304
|
};
|
|
302
305
|
}
|
|
303
306
|
|
|
304
|
-
|
|
307
|
+
protected async getCustomWriteCheckpointChanges(options: GetCheckpointChangesOptions) {
|
|
305
308
|
const limit = 1000;
|
|
309
|
+
// Legacy V1/V2 implementation.
|
|
306
310
|
const changes = await this.db.custom_write_checkpoints
|
|
307
311
|
.find(
|
|
308
312
|
{
|
|
@@ -332,39 +336,3 @@ export class MongoWriteCheckpointAPI implements storage.WriteCheckpointAPI {
|
|
|
332
336
|
};
|
|
333
337
|
}
|
|
334
338
|
}
|
|
335
|
-
|
|
336
|
-
export async function batchCreateCustomWriteCheckpoints(
|
|
337
|
-
db: VersionedPowerSyncMongo,
|
|
338
|
-
session: mongo.ClientSession,
|
|
339
|
-
checkpoints: storage.CustomWriteCheckpointOptions[],
|
|
340
|
-
opId: InternalOpId
|
|
341
|
-
): Promise<void> {
|
|
342
|
-
if (checkpoints.length == 0) {
|
|
343
|
-
return;
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
await db.custom_write_checkpoints.bulkWrite(
|
|
347
|
-
checkpoints.map((checkpointOptions) => {
|
|
348
|
-
const set: Record<string, unknown> = {
|
|
349
|
-
checkpoint: checkpointOptions.checkpoint,
|
|
350
|
-
sync_rules_id: checkpointOptions.sync_rules_id,
|
|
351
|
-
op_id: opId
|
|
352
|
-
};
|
|
353
|
-
if (checkpointOptions.checkpoint_requested_at != null) {
|
|
354
|
-
set.checkpoint_requested_at = checkpointOptions.checkpoint_requested_at;
|
|
355
|
-
}
|
|
356
|
-
|
|
357
|
-
return {
|
|
358
|
-
updateOne: {
|
|
359
|
-
filter: { user_id: checkpointOptions.user_id, sync_rules_id: checkpointOptions.sync_rules_id },
|
|
360
|
-
update: {
|
|
361
|
-
$set: set,
|
|
362
|
-
...(checkpointOptions.checkpoint_requested_at == null ? { $unset: { checkpoint_requested_at: 1 } } : {})
|
|
363
|
-
},
|
|
364
|
-
upsert: true
|
|
365
|
-
}
|
|
366
|
-
};
|
|
367
|
-
}),
|
|
368
|
-
{ session }
|
|
369
|
-
);
|
|
370
|
-
}
|
|
@@ -2,21 +2,28 @@ import { mongo } from '@powersync/lib-service-mongodb';
|
|
|
2
2
|
import { storage } from '@powersync/service-core';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Update pipeline to
|
|
5
|
+
* Update pipeline to stop a deploying replication stream, covering all storage versions.
|
|
6
6
|
*
|
|
7
7
|
* Roughly equivalent to:
|
|
8
8
|
* $set: {
|
|
9
|
-
* state:
|
|
10
|
-
* 'sync_configs.$[].state':
|
|
9
|
+
* state: STOP,
|
|
10
|
+
* 'sync_configs.$[].state': STOP
|
|
11
11
|
* }
|
|
12
12
|
*
|
|
13
13
|
* The difference is that this also handles v1 storage cases, where `sync_configs` is not present.
|
|
14
|
+
*
|
|
15
|
+
* This can be used for specific use cases:
|
|
16
|
+
* 1. Stop an existing PROCESSING stream, when it is being replaced by a new PROCESSING one.
|
|
17
|
+
* 2. Stop an existing ACTIVE or ERRORED stream, when it is being replaced by a new ACTIVE one.
|
|
18
|
+
*
|
|
19
|
+
* Do not use this when an ACTIVE stream transitions to ERRORED, and is being replaced by a new PROCESSING one.
|
|
20
|
+
* In that case, we need to maintain the ERRORED state, which this pipeline does not cover.
|
|
14
21
|
*/
|
|
15
|
-
export function
|
|
22
|
+
export function stopReplicationStreamPipeline(): mongo.Document[] {
|
|
16
23
|
return [
|
|
17
24
|
{
|
|
18
25
|
$set: {
|
|
19
|
-
state,
|
|
26
|
+
state: storage.SyncRuleState.STOP,
|
|
20
27
|
sync_configs: {
|
|
21
28
|
$cond: [
|
|
22
29
|
{ $isArray: '$sync_configs' },
|
|
@@ -25,7 +32,7 @@ export function syncRuleStateUpdatePipeline(state: storage.SyncRuleState): mongo
|
|
|
25
32
|
input: '$sync_configs',
|
|
26
33
|
as: 'config',
|
|
27
34
|
in: {
|
|
28
|
-
$mergeObjects: ['$$config', { state }]
|
|
35
|
+
$mergeObjects: ['$$config', { state: storage.SyncRuleState.STOP }]
|
|
29
36
|
}
|
|
30
37
|
}
|
|
31
38
|
},
|
|
@@ -1,9 +1,31 @@
|
|
|
1
1
|
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
-
import { InternalOpId } from '@powersync/service-core';
|
|
2
|
+
import { InternalOpId, ReplicationCheckpoint } from '@powersync/service-core';
|
|
3
3
|
import * as bson from 'bson';
|
|
4
4
|
|
|
5
|
-
export interface MongoSyncBucketStorageCheckpoint {
|
|
5
|
+
export interface MongoSyncBucketStorageCheckpoint extends ReplicationCheckpoint {
|
|
6
6
|
checkpoint: InternalOpId;
|
|
7
7
|
snapshotTime: bson.Timestamp;
|
|
8
8
|
clusterTime: mongo.ClusterTime;
|
|
9
|
+
|
|
10
|
+
/**
|
|
11
|
+
* The stream's `parameter_compaction.checkpoint_changes_invalid_before` boundary, read in the
|
|
12
|
+
* same snapshot as {@link checkpoint} and {@link snapshotTime}.
|
|
13
|
+
*
|
|
14
|
+
* Parameter compaction may have deleted parameter entries below this boundary, so checkpoint
|
|
15
|
+
* change detection cannot enumerate individual lookup changes from a checkpoint below it.
|
|
16
|
+
*
|
|
17
|
+
* 0n for streams that have never been compacted.
|
|
18
|
+
*/
|
|
19
|
+
parameterChangesInvalidBefore: InternalOpId;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* MongoDB-specific version of GetCheckpointChangesOptions.
|
|
24
|
+
*
|
|
25
|
+
* The next checkpoint carries the snapshot and invalidation boundary that change detection must
|
|
26
|
+
* be evaluated against. Only `checkpoint` is used from the previous one.
|
|
27
|
+
*/
|
|
28
|
+
export interface MongoGetCheckpointChangesOptions {
|
|
29
|
+
lastCheckpoint: ReplicationCheckpoint;
|
|
30
|
+
nextCheckpoint: MongoSyncBucketStorageCheckpoint;
|
|
9
31
|
}
|
|
@@ -65,6 +65,11 @@ export interface PersistedBatchOptions {
|
|
|
65
65
|
logger?: Logger;
|
|
66
66
|
objectStorage?: ObjectStorage;
|
|
67
67
|
inlineThresholdBytes?: number;
|
|
68
|
+
/**
|
|
69
|
+
* Aborts in-flight object storage uploads when replication stops.
|
|
70
|
+
*/
|
|
71
|
+
signal?: AbortSignal;
|
|
72
|
+
objectStorageUsageWriterId?: string;
|
|
68
73
|
}
|
|
69
74
|
|
|
70
75
|
/**
|
|
@@ -81,6 +86,8 @@ export abstract class PersistedBatch {
|
|
|
81
86
|
|
|
82
87
|
protected readonly objectStorage?: ObjectStorage;
|
|
83
88
|
protected readonly inlineThresholdBytes: number = DEFAULT_INLINE_THRESHOLD_BYTES;
|
|
89
|
+
protected readonly signal?: AbortSignal;
|
|
90
|
+
protected readonly objectStorageUsageWriterId?: string;
|
|
84
91
|
|
|
85
92
|
/**
|
|
86
93
|
* For debug logging only.
|
|
@@ -102,6 +109,8 @@ export abstract class PersistedBatch {
|
|
|
102
109
|
this.currentSize = writtenSize;
|
|
103
110
|
this.logger = options?.logger ?? defaultLogger;
|
|
104
111
|
this.objectStorage = options?.objectStorage;
|
|
112
|
+
this.signal = options?.signal;
|
|
113
|
+
this.objectStorageUsageWriterId = options?.objectStorageUsageWriterId;
|
|
105
114
|
if (options?.inlineThresholdBytes != null) {
|
|
106
115
|
this.inlineThresholdBytes = options.inlineThresholdBytes;
|
|
107
116
|
}
|
|
@@ -124,7 +133,8 @@ export abstract class PersistedBatch {
|
|
|
124
133
|
remaining_buckets.set(currentBucketKey(mapped), mapped);
|
|
125
134
|
}
|
|
126
135
|
|
|
127
|
-
const
|
|
136
|
+
const subkey = this.persistedSubkey(options.table.id, options.sourceKey);
|
|
137
|
+
const dchecksum = BigInt(utils.hashDelete(subkey));
|
|
128
138
|
|
|
129
139
|
for (const evaluated of options.evaluated) {
|
|
130
140
|
const definitionId = this.getBucketDefinitionId(evaluated.source);
|
|
@@ -159,6 +169,7 @@ export abstract class PersistedBatch {
|
|
|
159
169
|
bucket: evaluated.bucket,
|
|
160
170
|
sourceTableId: options.table.id,
|
|
161
171
|
sourceKey: options.sourceKey,
|
|
172
|
+
subkey,
|
|
162
173
|
table: evaluated.table,
|
|
163
174
|
rowId: evaluated.id,
|
|
164
175
|
checksum: BigInt(checksum),
|
|
@@ -181,6 +192,7 @@ export abstract class PersistedBatch {
|
|
|
181
192
|
op_id,
|
|
182
193
|
sourceTableId: options.table.id,
|
|
183
194
|
sourceKey: options.sourceKey,
|
|
195
|
+
subkey,
|
|
184
196
|
table: bucket.table,
|
|
185
197
|
rowId: bucket.id,
|
|
186
198
|
checksum: dchecksum
|
|
@@ -217,6 +229,14 @@ export abstract class PersistedBatch {
|
|
|
217
229
|
protected abstract checkDefinitionId(definitionId: BucketDefinitionId | null): BucketDefinitionId;
|
|
218
230
|
protected abstract getBucketDefinitionId(bucketSource: BucketDataSource): BucketDefinitionId;
|
|
219
231
|
|
|
232
|
+
/**
|
|
233
|
+
* New bucket operations persist protocol subkeys during replication. Older
|
|
234
|
+
* V1 documents may not have one, so sync reads retain a fallback.
|
|
235
|
+
*/
|
|
236
|
+
protected persistedSubkey(table: storage.SourceTableId, key: storage.ReplicaId): string {
|
|
237
|
+
return replicaIdToSubkey(table, key);
|
|
238
|
+
}
|
|
239
|
+
|
|
220
240
|
protected get bucketDataCount(): number {
|
|
221
241
|
return this.bucketData.length;
|
|
222
242
|
}
|
|
@@ -234,17 +254,45 @@ export abstract class PersistedBatch {
|
|
|
234
254
|
bucket,
|
|
235
255
|
lastOp: op_id,
|
|
236
256
|
incrementCount: 1,
|
|
237
|
-
incrementBytes: bytes
|
|
257
|
+
incrementBytes: bytes,
|
|
258
|
+
incrementChunks: 0
|
|
238
259
|
});
|
|
239
260
|
}
|
|
240
261
|
}
|
|
241
262
|
|
|
263
|
+
/**
|
|
264
|
+
* V3 persists operations in chunks. Keep this separate from incrementBucket:
|
|
265
|
+
* operation counts are known while evaluating rows, while chunk counts and
|
|
266
|
+
* exact persisted bytes are only known after the writer has chunked a flush.
|
|
267
|
+
*/
|
|
268
|
+
protected incrementBucketPersistedChunk(definitionId: BucketDefinitionId, bucket: string, bytes: number) {
|
|
269
|
+
const key = `${definitionId ?? ''}:${bucket}`;
|
|
270
|
+
const existingState = this.bucketStates.get(key);
|
|
271
|
+
if (existingState != null) {
|
|
272
|
+
existingState.incrementChunks += 1;
|
|
273
|
+
existingState.incrementBytes += bytes;
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
|
|
277
|
+
/**
|
|
278
|
+
* V3's compactor calculates byte deltas from persisted chunk metadata.
|
|
279
|
+
* Replace the writer's per-operation estimate with those exact sizes before
|
|
280
|
+
* flushing the corresponding bucket state.
|
|
281
|
+
*/
|
|
282
|
+
protected resetBucketPersistedBytes(definitionId: BucketDefinitionId, bucket: string) {
|
|
283
|
+
const state = this.bucketStates.get(`${definitionId ?? ''}:${bucket}`);
|
|
284
|
+
if (state != null) {
|
|
285
|
+
state.incrementBytes = 0;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
|
|
242
289
|
protected addBucketDataPut(options: {
|
|
243
290
|
op_id: InternalOpId;
|
|
244
291
|
bucketKey: BucketKey;
|
|
245
292
|
bucket: string;
|
|
246
293
|
sourceTableId: storage.SourceTable['id'];
|
|
247
294
|
sourceKey: storage.ReplicaId;
|
|
295
|
+
subkey: string;
|
|
248
296
|
table: string;
|
|
249
297
|
rowId: string;
|
|
250
298
|
checksum: bigint;
|
|
@@ -256,6 +304,7 @@ export abstract class PersistedBatch {
|
|
|
256
304
|
op: 'PUT',
|
|
257
305
|
source_table: mongoTableId(options.sourceTableId),
|
|
258
306
|
source_key: options.sourceKey,
|
|
307
|
+
subkey: options.subkey,
|
|
259
308
|
table: options.table,
|
|
260
309
|
row_id: options.rowId,
|
|
261
310
|
checksum: options.checksum,
|
|
@@ -268,6 +317,7 @@ export abstract class PersistedBatch {
|
|
|
268
317
|
bucketKey: BucketKey;
|
|
269
318
|
sourceTableId: storage.SourceTable['id'];
|
|
270
319
|
sourceKey: storage.ReplicaId;
|
|
320
|
+
subkey: string;
|
|
271
321
|
table: string;
|
|
272
322
|
rowId: string;
|
|
273
323
|
checksum: bigint;
|
|
@@ -278,6 +328,7 @@ export abstract class PersistedBatch {
|
|
|
278
328
|
op: 'REMOVE',
|
|
279
329
|
source_table: mongoTableId(options.sourceTableId),
|
|
280
330
|
source_key: options.sourceKey,
|
|
331
|
+
subkey: options.subkey,
|
|
281
332
|
table: options.table,
|
|
282
333
|
row_id: options.rowId,
|
|
283
334
|
checksum: options.checksum,
|
|
@@ -377,4 +428,5 @@ export interface BucketStateUpdate {
|
|
|
377
428
|
lastOp: InternalOpId;
|
|
378
429
|
incrementCount: number;
|
|
379
430
|
incrementBytes: number;
|
|
431
|
+
incrementChunks: number;
|
|
380
432
|
}
|