@powersync/service-module-mongodb-storage 0.0.0-dev-20260803144812 → 0.0.0-dev-20260929095716
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 +129 -7
- package/dist/storage/MongoBucketStorage.d.ts +42 -5
- package/dist/storage/MongoBucketStorage.js +404 -215
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +39 -14
- package/dist/storage/implementation/MongoBucketBatch.js +169 -98
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +21 -79
- package/dist/storage/implementation/MongoCompactor.js +27 -238
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoIdSequence.d.ts +10 -1
- package/dist/storage/implementation/MongoIdSequence.js +14 -1
- package/dist/storage/implementation/MongoIdSequence.js.map +1 -1
- package/dist/storage/implementation/MongoOpIdAllocator.d.ts +27 -0
- package/dist/storage/implementation/MongoOpIdAllocator.js +97 -0
- package/dist/storage/implementation/MongoOpIdAllocator.js.map +1 -0
- 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 +35 -2
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +2 -1
- package/dist/storage/implementation/MongoPersistedReplicationStream.js +7 -4
- package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +3 -3
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +10 -8
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +8 -4
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +32 -11
- package/dist/storage/implementation/MongoSyncBucketStorage.js +95 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +26 -2
- package/dist/storage/implementation/MongoSyncRulesLock.js +59 -10
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
- package/dist/storage/implementation/MongoWriteBatch.js +149 -0
- package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
- 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 +32 -4
- package/dist/storage/implementation/common/PersistedBatch.js +57 -14
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +9 -1
- package/dist/storage/implementation/db.js +28 -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 +11 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +227 -221
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
- package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
- 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 +66 -17
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
- package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/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 +14 -2
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +261 -194
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +83 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +702 -159
- 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 +92 -32
- 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 +14 -8
- package/dist/storage/implementation/v3/PersistedBatchV3.js +77 -45
- 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 +108 -0
- package/dist/storage/implementation/v3/compact-utils.js +223 -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 +90 -6
- 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 +16 -5
- 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 +6 -8
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -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 +197 -25
- 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 +28 -1
- package/dist/types/types.js +28 -7
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.d.ts +4 -2
- package/dist/utils/test-utils.js +7 -4
- package/dist/utils/test-utils.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 +545 -277
- package/src/storage/implementation/MongoBucketBatch.ts +258 -124
- package/src/storage/implementation/MongoCompactor.ts +37 -346
- package/src/storage/implementation/MongoIdSequence.ts +21 -1
- package/src/storage/implementation/MongoOpIdAllocator.ts +105 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +46 -1
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +12 -4
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +48 -29
- package/src/storage/implementation/MongoStorageProvider.ts +16 -4
- package/src/storage/implementation/MongoSyncBucketStorage.ts +133 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +78 -12
- package/src/storage/implementation/MongoWriteBatch.ts +172 -0
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +76 -20
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
- package/src/storage/implementation/db.ts +36 -2
- package/src/storage/implementation/models.ts +39 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +272 -254
- package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +88 -28
- package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
- 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 +339 -247
- package/src/storage/implementation/v3/MongoCompactorV3.ts +955 -225
- 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 +112 -42
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +94 -51
- 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 +329 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +97 -4
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
- package/src/storage/implementation/v3/source-table-utils.ts +82 -76
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +42 -7
- package/src/utils/test-utils.ts +9 -5
- 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 +794 -205
- package/test/src/chunk_compaction_config.test.ts +38 -0
- package/test/src/cleanup-stopped-sync-configs.test.ts +59 -26
- package/test/src/compact-utils.test.ts +251 -0
- package/test/src/concurrent_writers.test.ts +599 -0
- package/test/src/connection_config.test.ts +159 -0
- package/test/src/helpers/MemoryObjectStorage.ts +14 -6
- package/test/src/helpers/concurrentWriter.mjs +29 -0
- package/test/src/helpers/s3TestFactory.ts +12 -12
- package/test/src/object_storage_usage.test.ts +268 -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 +280 -24
- package/test/src/storage_compacting.test.ts +1097 -253
- package/test/src/storage_object_storage_inline_threshold.test.ts +3 -3
- package/test/src/storage_s3_checksums.test.ts +6 -7
- package/test/src/storage_s3_compaction_lifecycle.test.ts +143 -17
- package/test/src/storage_s3_reading.test.ts +294 -6
- package/test/src/storage_s3_writing.test.ts +4 -3
- package/test/src/storage_sync.test.ts +412 -80
- package/test/src/storeCurrentData.test.ts +5 -5
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -5,7 +5,13 @@ import {
|
|
|
5
5
|
storage
|
|
6
6
|
} from '@powersync/service-core';
|
|
7
7
|
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
DO_NOT_LOG,
|
|
10
|
+
ErrorCode,
|
|
11
|
+
logger,
|
|
12
|
+
ReplicationAssertionError,
|
|
13
|
+
ServiceError
|
|
14
|
+
} from '@powersync/lib-services-framework';
|
|
9
15
|
import { v4 as uuid } from 'uuid';
|
|
10
16
|
|
|
11
17
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
@@ -16,18 +22,22 @@ import {
|
|
|
16
22
|
formatIncrementalSyncConfigUpdateLog,
|
|
17
23
|
isCompatible
|
|
18
24
|
} from '@powersync/service-core';
|
|
25
|
+
import { Semaphore } from 'async-mutex';
|
|
19
26
|
import { ObjectId } from 'bson';
|
|
20
|
-
import { DEFAULT_CLEAR_BATCH_THROTTLE_RATE } from '../types/types.js';
|
|
27
|
+
import { DEFAULT_CLEAR_BATCH_THROTTLE_RATE, normalizeChunkCompactionConcurrency } from '../types/types.js';
|
|
21
28
|
import { generateReplicationStreamName } from '../utils/util.js';
|
|
22
29
|
import type { MongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
23
30
|
import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
24
31
|
import { PowerSyncMongo } from './implementation/db.js';
|
|
25
32
|
import { getMongoStorageConfig, StorageConfig, SyncRuleDocumentBase } from './implementation/models.js';
|
|
26
33
|
import { MongoChecksumOptions } from './implementation/MongoChecksums.js';
|
|
34
|
+
import { MongoOpIdAllocator } from './implementation/MongoOpIdAllocator.js';
|
|
27
35
|
import { MongoPersistedReplicationStream } from './implementation/MongoPersistedReplicationStream.js';
|
|
28
|
-
import {
|
|
36
|
+
import { MongoSyncRulesLock } from './implementation/MongoSyncRulesLock.js';
|
|
37
|
+
import { stopReplicationStreamPipeline } from './implementation/SyncRuleStateUpdate.js';
|
|
29
38
|
import { SyncRuleDocumentV1 } from './implementation/v1/models.js';
|
|
30
39
|
import { ObjectStorage } from './implementation/v3/object-storage/ObjectStorage.js';
|
|
40
|
+
import { ObjectStorageUsage } from './implementation/v3/object-storage/ObjectStorageUsage.js';
|
|
31
41
|
import { VersionedPowerSyncMongoV3 } from './implementation/v3/VersionedPowerSyncMongoV3.js';
|
|
32
42
|
import { ReplicationStreamDocumentV3, SyncConfigDefinition, SyncRuleConfigStateV3 } from './storage-index.js';
|
|
33
43
|
|
|
@@ -35,6 +45,8 @@ export interface MongoBucketStorageOptions {
|
|
|
35
45
|
checksumOptions?: Omit<MongoChecksumOptions, 'storageConfig'>;
|
|
36
46
|
objectStorage?: ObjectStorage;
|
|
37
47
|
inlineThresholdBytes?: number;
|
|
48
|
+
/** Shared across chunk-compaction jobs. Default: 4 with object storage, otherwise 2. */
|
|
49
|
+
chunkCompactionConcurrency?: number;
|
|
38
50
|
/**
|
|
39
51
|
* Prefix for replication stream name and Postgres logical replication slot name.
|
|
40
52
|
*/
|
|
@@ -42,6 +54,7 @@ export interface MongoBucketStorageOptions {
|
|
|
42
54
|
readPreference?: mongo.ReadPreference;
|
|
43
55
|
checksumCacheTtlMs?: number;
|
|
44
56
|
clearBatchThrottleRate?: number;
|
|
57
|
+
defaultStorageVersion?: number;
|
|
45
58
|
/**
|
|
46
59
|
* Reuse a compatible active replication stream by appending a new sync config.
|
|
47
60
|
*
|
|
@@ -56,28 +69,50 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
56
69
|
[DO_NOT_LOG] = true;
|
|
57
70
|
|
|
58
71
|
private readonly client: mongo.MongoClient;
|
|
59
|
-
private readonly session: mongo.ClientSession;
|
|
60
72
|
public readonly replicationStreamNamePrefix: string;
|
|
61
73
|
|
|
74
|
+
private readonly opIdAllocators = new Map<number, { lock: MongoSyncRulesLock; allocator: MongoOpIdAllocator }>();
|
|
75
|
+
|
|
76
|
+
discardOpIdAllocator(streamId: number) {
|
|
77
|
+
this.opIdAllocators.get(streamId)?.allocator.discard();
|
|
78
|
+
this.opIdAllocators.delete(streamId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
getOpIdAllocator(stream: MongoPersistedReplicationStream, lock: MongoSyncRulesLock): MongoOpIdAllocator {
|
|
82
|
+
lock.throwIfAborted();
|
|
83
|
+
const previous = this.opIdAllocators.get(stream.replicationStreamId);
|
|
84
|
+
if (previous?.lock === lock) {
|
|
85
|
+
return previous.allocator;
|
|
86
|
+
}
|
|
87
|
+
previous?.allocator.discard();
|
|
88
|
+
const allocator = new MongoOpIdAllocator(this.db.versioned(stream.getStorageConfig()));
|
|
89
|
+
this.opIdAllocators.set(stream.replicationStreamId, { lock, allocator });
|
|
90
|
+
return allocator;
|
|
91
|
+
}
|
|
92
|
+
|
|
62
93
|
private activeStorageCache: MongoSyncBucketStorage | undefined;
|
|
63
94
|
|
|
64
95
|
public readonly db: PowerSyncMongo;
|
|
96
|
+
public readonly chunkCompactionConcurrency: number;
|
|
97
|
+
public readonly chunkCompactionSlots: Semaphore;
|
|
65
98
|
|
|
66
99
|
constructor(
|
|
67
100
|
db: PowerSyncMongo,
|
|
101
|
+
syncConfigParser: storage.SyncConfigParser,
|
|
68
102
|
private options: MongoBucketStorageOptions
|
|
69
103
|
) {
|
|
70
|
-
super();
|
|
104
|
+
super({ syncConfigParser });
|
|
105
|
+
this.chunkCompactionConcurrency = normalizeChunkCompactionConcurrency(
|
|
106
|
+
options.chunkCompactionConcurrency,
|
|
107
|
+
options.objectStorage != null
|
|
108
|
+
);
|
|
109
|
+
// All replication streams created by this factory share the configured limit.
|
|
110
|
+
this.chunkCompactionSlots = new Semaphore(this.chunkCompactionConcurrency);
|
|
71
111
|
this.client = db.client;
|
|
72
112
|
this.db = db;
|
|
73
|
-
this.session = this.client.startSession();
|
|
74
113
|
this.replicationStreamNamePrefix = options.replicationStreamNamePrefix;
|
|
75
114
|
}
|
|
76
115
|
|
|
77
|
-
async [Symbol.asyncDispose]() {
|
|
78
|
-
// No-op
|
|
79
|
-
}
|
|
80
|
-
|
|
81
116
|
getInstance(
|
|
82
117
|
replicationStream: storage.PersistedReplicationStream,
|
|
83
118
|
options?: GetIntanceOptions
|
|
@@ -90,6 +125,15 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
90
125
|
replicationStreamId = Number(replicationStreamId);
|
|
91
126
|
}
|
|
92
127
|
const storageConfig = replicationStream.getStorageConfig();
|
|
128
|
+
if (options?.replicationLock != null) {
|
|
129
|
+
if (
|
|
130
|
+
!(options.replicationLock instanceof MongoSyncRulesLock) ||
|
|
131
|
+
options.replicationLock.sync_rules_id !== replicationStream.replicationStreamId
|
|
132
|
+
) {
|
|
133
|
+
throw new ReplicationAssertionError('Replication lock does not belong to this MongoDB stream');
|
|
134
|
+
}
|
|
135
|
+
replicationStream.current_lock = options.replicationLock;
|
|
136
|
+
}
|
|
93
137
|
const syncRuleStorage = createMongoSyncBucketStorage(
|
|
94
138
|
this,
|
|
95
139
|
replicationStreamId,
|
|
@@ -138,187 +182,256 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
138
182
|
}
|
|
139
183
|
|
|
140
184
|
async restartReplication(replicationStreamId: number) {
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
185
|
+
await this.withTransaction(async (session) => {
|
|
186
|
+
const next = await this.getDeployingSyncConfigInternal(session);
|
|
187
|
+
const active = await this.getActiveSyncConfigInternal(session);
|
|
188
|
+
|
|
189
|
+
if (next != null && next.content.replicationStreamId == replicationStreamId) {
|
|
190
|
+
// We need to redo the "next" replication stream.
|
|
191
|
+
// This creates a new stream, and stops the existing PROCESSING one.
|
|
192
|
+
await this.updateSyncRulesInTransaction(
|
|
193
|
+
next.content.asUpdateOptions({ forceNewReplicationStream: true }),
|
|
194
|
+
session
|
|
195
|
+
);
|
|
196
|
+
const sharedActiveStream = active?.content.replicationStreamId == replicationStreamId;
|
|
197
|
+
if (sharedActiveStream) {
|
|
198
|
+
// The same stream was also used for the ACTIVE config. Transition that to ERRORED
|
|
199
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
200
|
+
}
|
|
201
|
+
} else if (next == null && active?.content.replicationStreamId == replicationStreamId) {
|
|
202
|
+
// Slot removed for "active" replication stream, while there is no "next" one.
|
|
203
|
+
await this.updateSyncRulesInTransaction(
|
|
204
|
+
active.content.asUpdateOptions({ forceNewReplicationStream: true }),
|
|
205
|
+
session
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
// In this case we keep the old one as active for clients, so that that existing clients
|
|
209
|
+
// can still get the latest data while we replicate the new ones.
|
|
210
|
+
// It will however not replicate anymore.
|
|
211
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
212
|
+
} else if (next != null && active?.content.replicationStreamId == replicationStreamId) {
|
|
213
|
+
// Already have next replication stream, but need to stop replicating the active one.
|
|
214
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
215
|
+
} else {
|
|
216
|
+
// replicationStreamId does not match the ACTIVE or PROCESSING streams - no-op.
|
|
217
|
+
}
|
|
218
|
+
});
|
|
219
|
+
await this.db.notifyCheckpoint();
|
|
220
|
+
}
|
|
163
221
|
|
|
164
|
-
|
|
222
|
+
/**
|
|
223
|
+
* Keep an active config readable after its replication stream is invalidated.
|
|
224
|
+
*
|
|
225
|
+
* Incremental V3 streams can retain stopped historical configs and embed a deploying config.
|
|
226
|
+
* Read-side storage requires exactly one ACTIVE or ERRORED config, so only the active config
|
|
227
|
+
* transitions to ERRORED; every other embedded config remains or becomes STOP.
|
|
228
|
+
*/
|
|
229
|
+
private async errorActiveStreamForReplacement(active: storage.ResolvedSyncConfig, session: mongo.ClientSession) {
|
|
230
|
+
const activeSyncConfigId = active.content.syncConfigId;
|
|
231
|
+
if (activeSyncConfigId == null) {
|
|
232
|
+
// V1 documents
|
|
233
|
+
const result = await this.db.sync_rules.updateOne(
|
|
165
234
|
{
|
|
166
235
|
_id: active.content.replicationStreamId,
|
|
167
|
-
state: storage.SyncRuleState.ACTIVE
|
|
236
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
168
237
|
},
|
|
169
|
-
syncRuleStateUpdatePipeline(storage.SyncRuleState.ERRORED)
|
|
170
|
-
);
|
|
171
|
-
await this.db.notifyCheckpoint();
|
|
172
|
-
} else if (next != null && active?.content.replicationStreamId == replicationStreamId) {
|
|
173
|
-
// Already have next replication stream, but need to stop replicating the active one.
|
|
174
|
-
|
|
175
|
-
await this.db.sync_rules.updateOne(
|
|
176
238
|
{
|
|
177
|
-
|
|
178
|
-
|
|
239
|
+
$set: {
|
|
240
|
+
state: storage.SyncRuleState.ERRORED
|
|
241
|
+
}
|
|
179
242
|
},
|
|
180
|
-
|
|
243
|
+
{ session }
|
|
244
|
+
);
|
|
245
|
+
if (result.matchedCount != 1) {
|
|
246
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
247
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
248
|
+
throw new ReplicationAssertionError(
|
|
249
|
+
`Active replication stream ${active.content.replicationStreamId} changed during restart`
|
|
250
|
+
);
|
|
251
|
+
}
|
|
252
|
+
return;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const activeConfigObjectId = new ObjectId(activeSyncConfigId);
|
|
256
|
+
const result = await this.db.sync_rules.updateOne(
|
|
257
|
+
{
|
|
258
|
+
_id: active.content.replicationStreamId,
|
|
259
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
260
|
+
sync_configs: {
|
|
261
|
+
// Confirm that this is still the ACTIVE/ERRORED one.
|
|
262
|
+
$elemMatch: {
|
|
263
|
+
_id: activeConfigObjectId,
|
|
264
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
},
|
|
268
|
+
{
|
|
269
|
+
$set: {
|
|
270
|
+
state: storage.SyncRuleState.ERRORED,
|
|
271
|
+
// There must be exactly one config with an ERRORED state: This will be used to serve
|
|
272
|
+
// clients until there is a new ACTIVE one.
|
|
273
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.ERRORED,
|
|
274
|
+
// PROCESSING configs must stop. Historical STOP configs remain unchanged.
|
|
275
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.STOP
|
|
276
|
+
}
|
|
277
|
+
},
|
|
278
|
+
{
|
|
279
|
+
session,
|
|
280
|
+
arrayFilters: [
|
|
281
|
+
{
|
|
282
|
+
'activeConfig._id': activeConfigObjectId,
|
|
283
|
+
'activeConfig.state': { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
284
|
+
},
|
|
285
|
+
{
|
|
286
|
+
'processingConfig._id': { $ne: activeConfigObjectId },
|
|
287
|
+
'processingConfig.state': storage.SyncRuleState.PROCESSING
|
|
288
|
+
}
|
|
289
|
+
]
|
|
290
|
+
}
|
|
291
|
+
);
|
|
292
|
+
if (result.matchedCount != 1) {
|
|
293
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
294
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
295
|
+
throw new ReplicationAssertionError(
|
|
296
|
+
`Active replication stream ${active.content.replicationStreamId} changed during restart`
|
|
181
297
|
);
|
|
182
|
-
await this.db.notifyCheckpoint();
|
|
183
298
|
}
|
|
184
299
|
}
|
|
185
300
|
|
|
186
|
-
private async
|
|
301
|
+
private async updateSyncRulesInTransaction(options: storage.UpdateSyncRulesOptions, session: mongo.ClientSession) {
|
|
302
|
+
const storageVersion =
|
|
303
|
+
options.storageVersion ??
|
|
304
|
+
options.config.parsed.config.storageVersion ??
|
|
305
|
+
this.options.defaultStorageVersion ??
|
|
306
|
+
storage.CURRENT_STORAGE_VERSION;
|
|
307
|
+
const storageConfig = getMongoStorageConfig(storageVersion);
|
|
308
|
+
if (storageConfig.incrementalReprocessing) {
|
|
309
|
+
return this.updateSyncConfigV3InTransaction(options, storageVersion, storageConfig, session);
|
|
310
|
+
}
|
|
311
|
+
return this.updateSyncConfigV1InTransaction(options, storageVersion, session);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
/**
|
|
315
|
+
* Update to a V3 sync config.
|
|
316
|
+
*
|
|
317
|
+
* Does support cases where existing replication streams are on V1.
|
|
318
|
+
*/
|
|
319
|
+
private async updateSyncConfigV3InTransaction(
|
|
187
320
|
options: storage.UpdateSyncRulesOptions,
|
|
188
321
|
storageVersion: number,
|
|
189
|
-
storageConfig: StorageConfig
|
|
322
|
+
storageConfig: StorageConfig,
|
|
323
|
+
session: mongo.ClientSession
|
|
190
324
|
): Promise<MongoPersistedReplicationStream> {
|
|
191
|
-
let rules: MongoPersistedReplicationStream | undefined = undefined;
|
|
192
325
|
const versioned = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
|
|
193
326
|
|
|
194
|
-
const
|
|
327
|
+
const active = await this.db.sync_rules.findOne<ReplicationStreamDocumentV3>(
|
|
328
|
+
{
|
|
329
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
330
|
+
storage_version: storageVersion
|
|
331
|
+
},
|
|
332
|
+
{ session, sort: { _id: -1 }, limit: 1 }
|
|
333
|
+
);
|
|
334
|
+
if (active != null) {
|
|
335
|
+
const existingConfigDocs = await this.loadSyncConfigDefinitions(versioned, active, session);
|
|
336
|
+
|
|
337
|
+
if (
|
|
338
|
+
!options.forceNewReplicationStream &&
|
|
339
|
+
this.options.supportsMultipleSyncConfigs &&
|
|
340
|
+
isCompatible(
|
|
341
|
+
existingConfigDocs.map((d) => d.serialized_plan ?? null),
|
|
342
|
+
options.config,
|
|
343
|
+
logger
|
|
344
|
+
)
|
|
345
|
+
) {
|
|
346
|
+
logger.info(`Using incremental reprocessing`);
|
|
347
|
+
await this.stopExistingProcessingWork(session);
|
|
348
|
+
return await this.appendSyncConfigToStream({
|
|
349
|
+
versioned,
|
|
350
|
+
existing: active,
|
|
351
|
+
existingConfigDocs,
|
|
352
|
+
options,
|
|
353
|
+
storageVersion,
|
|
354
|
+
session
|
|
355
|
+
});
|
|
356
|
+
}
|
|
357
|
+
}
|
|
195
358
|
|
|
196
|
-
await
|
|
197
|
-
const active = await this.db.sync_rules.findOne<ReplicationStreamDocumentV3>(
|
|
198
|
-
{
|
|
199
|
-
state: storage.SyncRuleState.ACTIVE,
|
|
200
|
-
storage_version: storageVersion
|
|
201
|
-
},
|
|
202
|
-
{ session, sort: { _id: -1 }, limit: 1 }
|
|
203
|
-
);
|
|
204
|
-
if (active != null) {
|
|
205
|
-
const existingConfigDocs = await this.loadSyncConfigDefinitions(versioned, active, session);
|
|
359
|
+
await this.stopExistingProcessingWork(session);
|
|
206
360
|
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
logger.info(`Using incremental reprocessing`);
|
|
216
|
-
await this.db.sync_rules.updateMany(
|
|
217
|
-
{
|
|
218
|
-
state: storage.SyncRuleState.PROCESSING
|
|
219
|
-
},
|
|
220
|
-
syncRuleStateUpdatePipeline(storage.SyncRuleState.STOP),
|
|
221
|
-
{ session }
|
|
222
|
-
);
|
|
223
|
-
await this.stopEmbeddedDeployingConfigs(active, session);
|
|
224
|
-
rules = await this.appendSyncConfigToStream({
|
|
225
|
-
versioned,
|
|
226
|
-
existing: active,
|
|
227
|
-
existingConfigDocs,
|
|
228
|
-
options,
|
|
229
|
-
storageVersion,
|
|
230
|
-
session
|
|
231
|
-
});
|
|
232
|
-
return;
|
|
361
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
|
|
362
|
+
{
|
|
363
|
+
// Uses the legacy id "sync_rules" for backwards-compatibility. See AGENTS.md.
|
|
364
|
+
_id: 'sync_rules'
|
|
365
|
+
},
|
|
366
|
+
{
|
|
367
|
+
$inc: {
|
|
368
|
+
op_id: 1n
|
|
233
369
|
}
|
|
234
|
-
|
|
235
|
-
|
|
370
|
+
},
|
|
371
|
+
{
|
|
372
|
+
upsert: true,
|
|
373
|
+
returnDocument: 'after',
|
|
374
|
+
session
|
|
236
375
|
}
|
|
376
|
+
);
|
|
237
377
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
378
|
+
const id = Number(id_doc!.op_id);
|
|
379
|
+
const replicationStreamName = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
380
|
+
|
|
381
|
+
const mapping =
|
|
382
|
+
options.config.plan == null
|
|
383
|
+
? // For legacy Sync Rules and Streams, use the parsed config directly to create a mapping
|
|
384
|
+
SingleSyncConfigBucketDefinitionMapping.fromParsedSyncConfig(options.config.parsed)
|
|
385
|
+
: // For new Sync Streams, always use the serialized version
|
|
386
|
+
SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingFromSerializedPlans(
|
|
387
|
+
[],
|
|
388
|
+
options.config.plan.plan,
|
|
389
|
+
[]
|
|
390
|
+
);
|
|
246
391
|
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
392
|
+
const syncConfigDoc: SyncConfigDefinition = {
|
|
393
|
+
_id: new ObjectId(),
|
|
394
|
+
replication_stream_id: id,
|
|
395
|
+
created_at: new Date(),
|
|
396
|
+
storage_version: storageVersion,
|
|
397
|
+
content: options.config.yaml,
|
|
398
|
+
version_label: options.version_label,
|
|
399
|
+
serialized_plan: options.config.plan,
|
|
400
|
+
rule_mapping: mapping.serialize()
|
|
401
|
+
};
|
|
402
|
+
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
403
|
+
|
|
404
|
+
const doc: ReplicationStreamDocumentV3 = {
|
|
405
|
+
_id: id,
|
|
406
|
+
storage_version: storageVersion,
|
|
407
|
+
sync_configs: [
|
|
256
408
|
{
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
409
|
+
_id: syncConfigDoc._id,
|
|
410
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
411
|
+
last_checkpoint: null,
|
|
412
|
+
last_checkpoint_lsn: null,
|
|
413
|
+
no_checkpoint_before: null,
|
|
414
|
+
snapshot_done: false
|
|
260
415
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
: // For new sync streams, always use the serialized version
|
|
271
|
-
SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingFromSerializedPlans(
|
|
272
|
-
[],
|
|
273
|
-
options.config.plan.plan,
|
|
274
|
-
[]
|
|
275
|
-
);
|
|
276
|
-
|
|
277
|
-
const syncConfigDoc: SyncConfigDefinition = {
|
|
278
|
-
_id: new ObjectId(),
|
|
279
|
-
replication_stream_id: id,
|
|
280
|
-
created_at: new Date(),
|
|
281
|
-
storage_version: storageVersion,
|
|
282
|
-
content: options.config.yaml,
|
|
283
|
-
serialized_plan: options.config.plan,
|
|
284
|
-
rule_mapping: mapping.serialize()
|
|
285
|
-
};
|
|
286
|
-
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
287
|
-
|
|
288
|
-
const doc: ReplicationStreamDocumentV3 = {
|
|
289
|
-
_id: id,
|
|
290
|
-
storage_version: storageVersion,
|
|
291
|
-
sync_configs: [
|
|
292
|
-
{
|
|
293
|
-
_id: syncConfigDoc._id,
|
|
294
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
295
|
-
last_checkpoint: null,
|
|
296
|
-
last_checkpoint_lsn: null,
|
|
297
|
-
no_checkpoint_before: null,
|
|
298
|
-
snapshot_done: false
|
|
299
|
-
}
|
|
300
|
-
],
|
|
301
|
-
snapshot_lsn: undefined,
|
|
302
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
303
|
-
slot_name: replicationStreamName,
|
|
304
|
-
last_checkpoint_ts: null,
|
|
305
|
-
last_fatal_error: null,
|
|
306
|
-
last_fatal_error_ts: null,
|
|
307
|
-
last_keepalive_ts: null
|
|
308
|
-
};
|
|
309
|
-
|
|
310
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
311
|
-
rules = new MongoPersistedReplicationStream(this.db, doc, [syncConfigDoc]);
|
|
312
|
-
if (options.lock) {
|
|
313
|
-
// The lock is persisted on rules.current_lock
|
|
314
|
-
await rules.lock(session);
|
|
315
|
-
}
|
|
316
|
-
});
|
|
317
|
-
|
|
318
|
-
// Notify only after the transaction has committed, so listeners cannot observe pre-commit state.
|
|
319
|
-
await this.db.notifyCheckpoint();
|
|
416
|
+
],
|
|
417
|
+
snapshot_lsn: undefined,
|
|
418
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
419
|
+
slot_name: replicationStreamName,
|
|
420
|
+
last_checkpoint_ts: null,
|
|
421
|
+
last_fatal_error: null,
|
|
422
|
+
last_fatal_error_ts: null,
|
|
423
|
+
last_keepalive_ts: null
|
|
424
|
+
};
|
|
320
425
|
|
|
321
|
-
|
|
426
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
427
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc, [syncConfigDoc], this.syncConfigParser);
|
|
428
|
+
if (options.lock) {
|
|
429
|
+
// We only lock when creating a new stream - otherwise we'll likely get lock contention
|
|
430
|
+
// from an existing job on the stream, which would fail the entire transaction.
|
|
431
|
+
// The lock is persisted on rules.current_lock
|
|
432
|
+
await rules.lock(session);
|
|
433
|
+
}
|
|
434
|
+
return rules;
|
|
322
435
|
}
|
|
323
436
|
|
|
324
437
|
private async loadSyncConfigDefinitions(
|
|
@@ -363,31 +476,6 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
363
476
|
.toArray();
|
|
364
477
|
}
|
|
365
478
|
|
|
366
|
-
private async stopEmbeddedDeployingConfigs(existing: ReplicationStreamDocumentV3, session: mongo.ClientSession) {
|
|
367
|
-
const deployingConfigs = existing.sync_configs
|
|
368
|
-
.filter((config) => config.state == storage.SyncRuleState.PROCESSING)
|
|
369
|
-
.map((config) => config._id);
|
|
370
|
-
if (deployingConfigs.length == 0) {
|
|
371
|
-
return;
|
|
372
|
-
}
|
|
373
|
-
|
|
374
|
-
await this.db.sync_rules.updateOne(
|
|
375
|
-
{
|
|
376
|
-
_id: existing._id,
|
|
377
|
-
'sync_configs._id': { $in: deployingConfigs }
|
|
378
|
-
},
|
|
379
|
-
{
|
|
380
|
-
$set: {
|
|
381
|
-
'sync_configs.$[config].state': storage.SyncRuleState.STOP
|
|
382
|
-
}
|
|
383
|
-
},
|
|
384
|
-
{
|
|
385
|
-
session,
|
|
386
|
-
arrayFilters: [{ 'config._id': { $in: deployingConfigs } }]
|
|
387
|
-
}
|
|
388
|
-
);
|
|
389
|
-
}
|
|
390
|
-
|
|
391
479
|
private logIncrementalDefinitionChanges(changes: ReturnType<typeof describeIncrementalSyncConfigUpdate>) {
|
|
392
480
|
logger.info(`Incremental reprocessing sync config update:\n${formatIncrementalSyncConfigUpdateLog(changes)}`);
|
|
393
481
|
}
|
|
@@ -432,6 +520,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
432
520
|
created_at: new Date(),
|
|
433
521
|
storage_version: storageVersion,
|
|
434
522
|
content: updateOptions.config.yaml,
|
|
523
|
+
version_label: updateOptions.version_label,
|
|
435
524
|
serialized_plan: updateOptions.config.plan,
|
|
436
525
|
rule_mapping: mapping.serialize()
|
|
437
526
|
};
|
|
@@ -468,7 +557,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
468
557
|
...existing,
|
|
469
558
|
sync_configs: syncConfigStates
|
|
470
559
|
},
|
|
471
|
-
[...existingConfigDocs, syncConfigDoc]
|
|
560
|
+
[...existingConfigDocs, syncConfigDoc],
|
|
561
|
+
this.syncConfigParser
|
|
472
562
|
);
|
|
473
563
|
// The stream already exists, so an active replication job may already hold the stream lock.
|
|
474
564
|
// Deployment only persists the appended sync config; replication job locking is handled by
|
|
@@ -477,92 +567,144 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
477
567
|
}
|
|
478
568
|
|
|
479
569
|
async updateSyncRules(options: storage.UpdateSyncRulesOptions): Promise<MongoPersistedReplicationStream> {
|
|
480
|
-
|
|
481
|
-
|
|
570
|
+
let rules: MongoPersistedReplicationStream | undefined;
|
|
571
|
+
await this.withTransaction(async (session) => {
|
|
572
|
+
rules = await this.updateSyncRulesInTransaction(options, session);
|
|
573
|
+
});
|
|
574
|
+
await this.db.notifyCheckpoint();
|
|
575
|
+
return rules!;
|
|
576
|
+
}
|
|
482
577
|
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
578
|
+
private async withTransaction<T>(callback: (session: mongo.ClientSession) => Promise<T>): Promise<T> {
|
|
579
|
+
return this.client.withSession((session) => session.withTransaction(callback));
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
/**
|
|
583
|
+
* Update to a V1 sync config.
|
|
584
|
+
*
|
|
585
|
+
* Does support cases where existing replication streams are on V3.
|
|
586
|
+
*/
|
|
587
|
+
private async updateSyncConfigV1InTransaction(
|
|
588
|
+
options: storage.UpdateSyncRulesOptions,
|
|
589
|
+
storageVersion: number,
|
|
590
|
+
session: mongo.ClientSession
|
|
591
|
+
): Promise<MongoPersistedReplicationStream> {
|
|
592
|
+
await this.stopExistingProcessingWork(session);
|
|
487
593
|
|
|
488
|
-
|
|
594
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate(
|
|
595
|
+
{
|
|
596
|
+
// Uses the legacy id "sync_rules" for backwards-compatibility. See AGENTS.md.
|
|
597
|
+
_id: 'sync_rules'
|
|
598
|
+
},
|
|
599
|
+
{
|
|
600
|
+
$inc: {
|
|
601
|
+
op_id: 1n
|
|
602
|
+
}
|
|
603
|
+
},
|
|
604
|
+
{
|
|
605
|
+
upsert: true,
|
|
606
|
+
returnDocument: 'after',
|
|
607
|
+
session
|
|
608
|
+
}
|
|
609
|
+
);
|
|
489
610
|
|
|
490
|
-
const
|
|
611
|
+
const id = Number(id_doc!.op_id);
|
|
612
|
+
const slot_name = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
491
613
|
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
{ session }
|
|
500
|
-
);
|
|
614
|
+
// All V1 replication streams share both the `main` op id sequence and the `bucket_parameters`
|
|
615
|
+
// collection, so every parameter entry this stream writes gets an op id above the current
|
|
616
|
+
// head. Seeding the parameter compaction cursor with that head keeps the stream's first
|
|
617
|
+
// compaction from scanning other streams' history, which would otherwise be repeated for
|
|
618
|
+
// every new deployment. A concurrent replication flush can only advance the head after this
|
|
619
|
+
// read, which makes the seed conservative, never too high.
|
|
620
|
+
const opSequence = await this.db.op_id_sequence.findOne({ _id: 'main' }, { session });
|
|
501
621
|
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
622
|
+
const doc: SyncRuleDocumentV1 = {
|
|
623
|
+
_id: id,
|
|
624
|
+
storage_version: storageVersion,
|
|
625
|
+
content: options.config.yaml,
|
|
626
|
+
version_label: options.version_label,
|
|
627
|
+
serialized_plan: options.config.plan,
|
|
628
|
+
last_checkpoint: null,
|
|
629
|
+
last_checkpoint_lsn: null,
|
|
630
|
+
no_checkpoint_before: null,
|
|
631
|
+
keepalive_op: null,
|
|
632
|
+
snapshot_done: false,
|
|
633
|
+
snapshot_lsn: undefined,
|
|
634
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
635
|
+
slot_name: slot_name,
|
|
636
|
+
last_checkpoint_ts: null,
|
|
637
|
+
last_fatal_error: null,
|
|
638
|
+
last_fatal_error_ts: null,
|
|
639
|
+
last_keepalive_ts: null,
|
|
640
|
+
parameter_compaction: { compacted_before: opSequence?.op_id ?? 0n }
|
|
641
|
+
};
|
|
642
|
+
|
|
643
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
644
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc, [], this.syncConfigParser);
|
|
645
|
+
if (options.lock) {
|
|
646
|
+
// The lock is persisted on rules.current_lock
|
|
647
|
+
await rules.lock(session);
|
|
648
|
+
}
|
|
649
|
+
return rules;
|
|
650
|
+
}
|
|
517
651
|
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
last_keepalive_ts: null
|
|
538
|
-
};
|
|
539
|
-
|
|
540
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
541
|
-
await this.db.notifyCheckpoint();
|
|
542
|
-
rules = new MongoPersistedReplicationStream(this.db, doc);
|
|
543
|
-
if (options.lock) {
|
|
544
|
-
// The lock is persisted on rules.current_lock
|
|
545
|
-
await rules.lock(session);
|
|
652
|
+
/**
|
|
653
|
+
* Stop top-level and embedded processing work before creating a replacement stream.
|
|
654
|
+
*
|
|
655
|
+
* Use in the same transaction that the replacement stream is created.
|
|
656
|
+
*/
|
|
657
|
+
private async stopExistingProcessingWork(session: mongo.ClientSession) {
|
|
658
|
+
await this.db.sync_rules.updateMany(
|
|
659
|
+
{
|
|
660
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
661
|
+
'sync_configs.state': storage.SyncRuleState.PROCESSING
|
|
662
|
+
},
|
|
663
|
+
{
|
|
664
|
+
$set: {
|
|
665
|
+
'sync_configs.$[config].state': storage.SyncRuleState.STOP
|
|
666
|
+
}
|
|
667
|
+
},
|
|
668
|
+
{
|
|
669
|
+
session,
|
|
670
|
+
arrayFilters: [{ 'config.state': storage.SyncRuleState.PROCESSING }]
|
|
546
671
|
}
|
|
547
|
-
|
|
672
|
+
);
|
|
548
673
|
|
|
549
|
-
|
|
674
|
+
await this.db.sync_rules.updateMany(
|
|
675
|
+
{
|
|
676
|
+
state: storage.SyncRuleState.PROCESSING
|
|
677
|
+
},
|
|
678
|
+
stopReplicationStreamPipeline(),
|
|
679
|
+
{ session }
|
|
680
|
+
);
|
|
550
681
|
}
|
|
551
682
|
|
|
552
683
|
async getActiveSyncConfig(): Promise<storage.ResolvedSyncConfig | null> {
|
|
684
|
+
return this.getActiveSyncConfigInternal();
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
private async getActiveSyncConfigInternal(session?: mongo.ClientSession): Promise<storage.ResolvedSyncConfig | null> {
|
|
553
688
|
const doc = await this.db.sync_rules.findOne(
|
|
554
689
|
{
|
|
555
690
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
556
691
|
},
|
|
557
|
-
{ sort: { _id: -1 }, limit: 1 }
|
|
692
|
+
{ session, sort: { _id: -1 }, limit: 1 }
|
|
558
693
|
);
|
|
559
694
|
|
|
560
|
-
return this.resolvedSyncConfigFromDoc(
|
|
561
|
-
|
|
562
|
-
|
|
695
|
+
return this.resolvedSyncConfigFromDoc(
|
|
696
|
+
doc,
|
|
697
|
+
[storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED],
|
|
698
|
+
{ cacheActiveStorage: session == null },
|
|
699
|
+
session
|
|
700
|
+
);
|
|
563
701
|
}
|
|
564
702
|
|
|
565
|
-
private async replicationStreamFromDoc(
|
|
703
|
+
private async replicationStreamFromDoc(
|
|
704
|
+
doc: SyncRuleDocumentBase | null,
|
|
705
|
+
stateFilter: storage.SyncRuleState[],
|
|
706
|
+
session?: mongo.ClientSession
|
|
707
|
+
) {
|
|
566
708
|
if (doc == null) {
|
|
567
709
|
return null;
|
|
568
710
|
}
|
|
@@ -580,29 +722,38 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
580
722
|
// It is not a major bottleneck though, since it only runs once every couple of seconds at most.
|
|
581
723
|
const db = this.db.versioned(storageConfig) as VersionedPowerSyncMongoV3;
|
|
582
724
|
const syncConfigDocs = await db.syncConfigDefinitions
|
|
583
|
-
.find(
|
|
584
|
-
|
|
585
|
-
|
|
725
|
+
.find(
|
|
726
|
+
{
|
|
727
|
+
_id: { $in: matching.map((config) => config._id) }
|
|
728
|
+
},
|
|
729
|
+
{ session }
|
|
730
|
+
)
|
|
586
731
|
.toArray();
|
|
587
732
|
|
|
588
733
|
if (syncConfigDocs.length == 0) {
|
|
589
734
|
return null;
|
|
590
735
|
}
|
|
591
|
-
return new MongoPersistedReplicationStream(this.db, v3, syncConfigDocs);
|
|
736
|
+
return new MongoPersistedReplicationStream(this.db, v3, syncConfigDocs, this.syncConfigParser);
|
|
592
737
|
}
|
|
593
738
|
|
|
594
|
-
return new MongoPersistedReplicationStream(this.db, doc as SyncRuleDocumentV1);
|
|
739
|
+
return new MongoPersistedReplicationStream(this.db, doc as SyncRuleDocumentV1, [], this.syncConfigParser);
|
|
595
740
|
}
|
|
596
741
|
|
|
597
742
|
async getDeployingSyncConfig(): Promise<storage.ResolvedSyncConfig | null> {
|
|
743
|
+
return this.getDeployingSyncConfigInternal();
|
|
744
|
+
}
|
|
745
|
+
|
|
746
|
+
private async getDeployingSyncConfigInternal(
|
|
747
|
+
session?: mongo.ClientSession
|
|
748
|
+
): Promise<storage.ResolvedSyncConfig | null> {
|
|
598
749
|
const doc = await this.db.sync_rules.findOne(
|
|
599
750
|
{
|
|
600
751
|
$or: [{ state: storage.SyncRuleState.PROCESSING }, { 'sync_configs.state': storage.SyncRuleState.PROCESSING }]
|
|
601
752
|
},
|
|
602
|
-
{ sort: { _id: -1 }, limit: 1 }
|
|
753
|
+
{ session, sort: { _id: -1 }, limit: 1 }
|
|
603
754
|
);
|
|
604
755
|
|
|
605
|
-
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.PROCESSING]);
|
|
756
|
+
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.PROCESSING], {}, session);
|
|
606
757
|
}
|
|
607
758
|
|
|
608
759
|
async getReplicatingReplicationStreams(): Promise<storage.PersistedReplicationStream[]> {
|
|
@@ -640,9 +791,10 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
640
791
|
private async resolvedSyncConfigFromDoc(
|
|
641
792
|
doc: SyncRuleDocumentBase | null,
|
|
642
793
|
stateFilter: storage.SyncRuleState[],
|
|
643
|
-
options: { cacheActiveStorage?: boolean } = {}
|
|
794
|
+
options: { cacheActiveStorage?: boolean } = {},
|
|
795
|
+
session?: mongo.ClientSession
|
|
644
796
|
): Promise<storage.ResolvedSyncConfig | null> {
|
|
645
|
-
const stream = await this.replicationStreamFromDoc(doc, stateFilter);
|
|
797
|
+
const stream = await this.replicationStreamFromDoc(doc, stateFilter, session);
|
|
646
798
|
if (stream == null) {
|
|
647
799
|
return null;
|
|
648
800
|
}
|
|
@@ -675,7 +827,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
675
827
|
|
|
676
828
|
async getStorageMetrics(): Promise<storage.StorageMetrics> {
|
|
677
829
|
const ignoreNotExisting = (e: unknown) => {
|
|
678
|
-
if (lib_mongo.
|
|
830
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(e)) {
|
|
679
831
|
// Collection doesn't exist - return 0
|
|
680
832
|
return [{ storageStats: { size: 0 } }];
|
|
681
833
|
} else {
|
|
@@ -683,9 +835,6 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
683
835
|
}
|
|
684
836
|
};
|
|
685
837
|
|
|
686
|
-
// For now, we get storage metrics over all v1 and v3 collections.
|
|
687
|
-
// In the future, we may split these metrics to report separately for active replication streams versus processing streams.
|
|
688
|
-
|
|
689
838
|
const aggregateStaticCollection = async <T extends mongo.Document>(collection: mongo.Collection<T>) => {
|
|
690
839
|
// We check whether the collection exists before getting the statistics. This avoids repeated
|
|
691
840
|
// errors in the MongoDB logs if the collection hasn't been created yet.
|
|
@@ -709,8 +858,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
709
858
|
};
|
|
710
859
|
|
|
711
860
|
const operations_aggregate = await aggregateStaticCollection(this.db.bucket_data);
|
|
861
|
+
const v3OperationCollections = await this.db.listBucketDataCollectionsV3();
|
|
712
862
|
const v3_operation_aggregates = await Promise.all(
|
|
713
|
-
|
|
863
|
+
v3OperationCollections.map((collection) =>
|
|
714
864
|
collection
|
|
715
865
|
.aggregate([
|
|
716
866
|
{
|
|
@@ -726,8 +876,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
726
876
|
|
|
727
877
|
const parameters_aggregate = await aggregateStaticCollection(this.db.bucket_parameters);
|
|
728
878
|
|
|
879
|
+
const v3ParameterCollections = await this.db.listAllParameterIndexCollectionsV3();
|
|
729
880
|
const v3_parameter_aggregates = await Promise.all(
|
|
730
|
-
|
|
881
|
+
v3ParameterCollections.map((collection) =>
|
|
731
882
|
collection
|
|
732
883
|
.aggregate([
|
|
733
884
|
{
|
|
@@ -743,8 +894,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
743
894
|
|
|
744
895
|
const v1_source_record_aggregate = await aggregateStaticCollection(this.db.current_data);
|
|
745
896
|
|
|
897
|
+
const v3SourceRecordCollections = await this.db.listAllSourceRecordCollectionsV3();
|
|
746
898
|
const source_record_aggregates = await Promise.all(
|
|
747
|
-
|
|
899
|
+
v3SourceRecordCollections.map((collection) =>
|
|
748
900
|
collection
|
|
749
901
|
.aggregate([
|
|
750
902
|
{
|
|
@@ -757,6 +909,120 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
757
909
|
.catch(ignoreNotExisting)
|
|
758
910
|
)
|
|
759
911
|
);
|
|
912
|
+
|
|
913
|
+
const v3StorageConfig = getMongoStorageConfig(storage.STORAGE_VERSION_3) as StorageConfig & {
|
|
914
|
+
incrementalReprocessing: true;
|
|
915
|
+
};
|
|
916
|
+
const v3Db = this.db.versioned(v3StorageConfig) as VersionedPowerSyncMongoV3;
|
|
917
|
+
const objectStorageDefinitionUsage = await ObjectStorageUsage.readAllDefinitionUsage(v3Db);
|
|
918
|
+
|
|
919
|
+
const v3StreamDocs = (await this.db.sync_rules
|
|
920
|
+
.find({ storage_version: { $gte: storage.STORAGE_VERSION_3 } }, { projection: { _id: 1, sync_configs: 1 } })
|
|
921
|
+
.toArray()) as unknown as Pick<ReplicationStreamDocumentV3, '_id' | 'sync_configs'>[];
|
|
922
|
+
|
|
923
|
+
const collectionSizes = new Map<string, number>();
|
|
924
|
+
const addCollectionSize = (
|
|
925
|
+
collection: { collectionName: string },
|
|
926
|
+
aggregate: { storageStats?: { size?: number | bigint } }[],
|
|
927
|
+
prefix: string,
|
|
928
|
+
sizes: Map<string, number>
|
|
929
|
+
) => {
|
|
930
|
+
const match = collection.collectionName.match(new RegExp(`^${prefix}(\\d+)_(.+)$`));
|
|
931
|
+
if (match == null) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
sizes.set(`${prefix}${match[1]}:${match[2]}`, Number(aggregate[0]?.storageStats?.size ?? 0));
|
|
935
|
+
};
|
|
936
|
+
|
|
937
|
+
v3OperationCollections.forEach((collection, index) =>
|
|
938
|
+
addCollectionSize(collection, v3_operation_aggregates[index], 'bucket_data_', collectionSizes)
|
|
939
|
+
);
|
|
940
|
+
v3ParameterCollections.forEach((collection, index) =>
|
|
941
|
+
addCollectionSize(collection, v3_parameter_aggregates[index], 'parameter_index_', collectionSizes)
|
|
942
|
+
);
|
|
943
|
+
v3SourceRecordCollections.forEach((collection, index) =>
|
|
944
|
+
addCollectionSize(collection, source_record_aggregates[index], 'source_records_', collectionSizes)
|
|
945
|
+
);
|
|
946
|
+
|
|
947
|
+
const syncConfigIds = v3StreamDocs.flatMap((stream) => (stream.sync_configs ?? []).map((config) => config._id));
|
|
948
|
+
const syncConfigDefinitions =
|
|
949
|
+
syncConfigIds.length == 0
|
|
950
|
+
? []
|
|
951
|
+
: await v3Db.syncConfigDefinitions
|
|
952
|
+
.find({ _id: { $in: syncConfigIds } }, { projection: { _id: 1, rule_mapping: 1, version_label: 1 } })
|
|
953
|
+
.toArray();
|
|
954
|
+
const syncConfigDefinitionsById = new Map(
|
|
955
|
+
syncConfigDefinitions.map((definition) => [definition._id.toHexString(), definition])
|
|
956
|
+
);
|
|
957
|
+
|
|
958
|
+
const sourceTablesByStream = new Map<
|
|
959
|
+
number,
|
|
960
|
+
{ _id: ObjectId; bucket_data_source_ids: string[]; parameter_lookup_source_ids: string[] }[]
|
|
961
|
+
>();
|
|
962
|
+
await Promise.all(
|
|
963
|
+
v3StreamDocs.map(async (stream) => {
|
|
964
|
+
const sourceTables = await v3Db
|
|
965
|
+
.sourceTables(stream._id)
|
|
966
|
+
.find({}, { projection: { _id: 1, bucket_data_source_ids: 1, parameter_lookup_source_ids: 1 } })
|
|
967
|
+
.toArray();
|
|
968
|
+
sourceTablesByStream.set(stream._id, sourceTables);
|
|
969
|
+
})
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
const objectStorageSizeByDefinition = new Map<string, number>();
|
|
973
|
+
for (const usage of objectStorageDefinitionUsage) {
|
|
974
|
+
objectStorageSizeByDefinition.set(
|
|
975
|
+
`${usage.replication_stream_id}:${usage.definition_id}`,
|
|
976
|
+
Number(usage.active_bytes)
|
|
977
|
+
);
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
const sumCollectionSizes = (prefix: string, streamId: number, ids: ReadonlySet<string>) =>
|
|
981
|
+
[...ids].reduce((total, id) => total + (collectionSizes.get(`${prefix}${streamId}:${id}`) ?? 0), 0);
|
|
982
|
+
|
|
983
|
+
const syncConfigMetrics: storage.StorageSyncConfigMetrics[] = [];
|
|
984
|
+
for (const stream of v3StreamDocs) {
|
|
985
|
+
const sourceTables = sourceTablesByStream.get(stream._id) ?? [];
|
|
986
|
+
for (const syncConfig of stream.sync_configs ?? []) {
|
|
987
|
+
const definition = syncConfigDefinitionsById.get(syncConfig._id.toHexString());
|
|
988
|
+
if (definition == null) {
|
|
989
|
+
continue;
|
|
990
|
+
}
|
|
991
|
+
|
|
992
|
+
const mapping = SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(definition.rule_mapping);
|
|
993
|
+
const bucketDefinitionIds = mapping.allBucketDefinitionIds();
|
|
994
|
+
const parameterIndexIds = mapping.allParameterIndexIds();
|
|
995
|
+
const bucketDefinitionIdSet = new Set(bucketDefinitionIds);
|
|
996
|
+
const parameterIndexIdSet = new Set(parameterIndexIds);
|
|
997
|
+
const replicationSize = sourceTables.reduce((total, sourceTable) => {
|
|
998
|
+
if (
|
|
999
|
+
!(sourceTable.bucket_data_source_ids ?? []).some((id) => bucketDefinitionIdSet.has(id)) &&
|
|
1000
|
+
!(sourceTable.parameter_lookup_source_ids ?? []).some((id) => parameterIndexIdSet.has(id))
|
|
1001
|
+
) {
|
|
1002
|
+
return total;
|
|
1003
|
+
}
|
|
1004
|
+
return total + (collectionSizes.get(`source_records_${stream._id}:${sourceTable._id.toHexString()}`) ?? 0);
|
|
1005
|
+
}, 0);
|
|
1006
|
+
|
|
1007
|
+
syncConfigMetrics.push({
|
|
1008
|
+
sync_config_id: syncConfig._id.toHexString(),
|
|
1009
|
+
sync_config_state: String(syncConfig.state),
|
|
1010
|
+
version_label: definition.version_label,
|
|
1011
|
+
attributed_bucket_data_bytes: sumCollectionSizes('bucket_data_', stream._id, bucketDefinitionIdSet),
|
|
1012
|
+
attributed_parameter_indexes_bytes: sumCollectionSizes('parameter_index_', stream._id, parameterIndexIdSet),
|
|
1013
|
+
attributed_source_records_bytes: replicationSize,
|
|
1014
|
+
attributed_object_storage_bytes: [...bucketDefinitionIdSet].reduce(
|
|
1015
|
+
(total, definitionId) => total + (objectStorageSizeByDefinition.get(`${stream._id}:${definitionId}`) ?? 0),
|
|
1016
|
+
0
|
|
1017
|
+
)
|
|
1018
|
+
});
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
const totalObjectStorageSize = objectStorageDefinitionUsage.reduce(
|
|
1023
|
+
(total, usage) => total + usage.active_bytes,
|
|
1024
|
+
0n
|
|
1025
|
+
);
|
|
760
1026
|
return {
|
|
761
1027
|
operations_size_bytes:
|
|
762
1028
|
Number(operations_aggregate[0].storageStats.size) +
|
|
@@ -766,7 +1032,9 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
766
1032
|
v3_parameter_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
|
|
767
1033
|
replication_size_bytes:
|
|
768
1034
|
Number(v1_source_record_aggregate[0]?.storageStats?.size ?? 0) +
|
|
769
|
-
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0)
|
|
1035
|
+
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0),
|
|
1036
|
+
object_storage_size_bytes: Number(totalObjectStorageSize),
|
|
1037
|
+
sync_config_metrics: syncConfigMetrics
|
|
770
1038
|
};
|
|
771
1039
|
}
|
|
772
1040
|
|