@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
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { LEGACY_STORAGE_VERSION, SingleSyncConfigBucketDefinitionMapping, storage } from '@powersync/service-core';
|
|
2
|
-
import { DO_NOT_LOG, ErrorCode, logger, ServiceError } from '@powersync/lib-services-framework';
|
|
2
|
+
import { DO_NOT_LOG, ErrorCode, logger, ReplicationAssertionError, ServiceError } from '@powersync/lib-services-framework';
|
|
3
3
|
import { v4 as uuid } from 'uuid';
|
|
4
4
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
5
5
|
import { describeIncrementalSyncConfigUpdate, formatIncrementalSyncConfigUpdateLog, isCompatible } from '@powersync/service-core';
|
|
@@ -9,12 +9,12 @@ import { generateReplicationStreamName } from '../utils/util.js';
|
|
|
9
9
|
import { createMongoSyncBucketStorage } from './implementation/createMongoSyncBucketStorage.js';
|
|
10
10
|
import { getMongoStorageConfig } from './implementation/models.js';
|
|
11
11
|
import { MongoPersistedReplicationStream } from './implementation/MongoPersistedReplicationStream.js';
|
|
12
|
-
import {
|
|
12
|
+
import { stopReplicationStreamPipeline } from './implementation/SyncRuleStateUpdate.js';
|
|
13
|
+
import { ObjectStorageUsage } from './implementation/v3/object-storage/ObjectStorageUsage.js';
|
|
13
14
|
export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
14
15
|
options;
|
|
15
16
|
[DO_NOT_LOG] = true;
|
|
16
17
|
client;
|
|
17
|
-
session;
|
|
18
18
|
replicationStreamNamePrefix;
|
|
19
19
|
activeStorageCache;
|
|
20
20
|
db;
|
|
@@ -23,7 +23,6 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
23
23
|
this.options = options;
|
|
24
24
|
this.client = db.client;
|
|
25
25
|
this.db = db;
|
|
26
|
-
this.session = this.client.startSession();
|
|
27
26
|
this.replicationStreamNamePrefix = options.replicationStreamNamePrefix;
|
|
28
27
|
}
|
|
29
28
|
async [Symbol.asyncDispose]() {
|
|
@@ -72,131 +71,200 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
72
71
|
};
|
|
73
72
|
}
|
|
74
73
|
async restartReplication(replicationStreamId) {
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
74
|
+
await this.withTransaction(async (session) => {
|
|
75
|
+
const next = await this.getDeployingSyncConfigInternal(session);
|
|
76
|
+
const active = await this.getActiveSyncConfigInternal(session);
|
|
77
|
+
if (next != null && next.content.replicationStreamId == replicationStreamId) {
|
|
78
|
+
// We need to redo the "next" replication stream.
|
|
79
|
+
// This creates a new stream, and stops the existing PROCESSING one.
|
|
80
|
+
await this.updateSyncRulesInTransaction(next.content.asUpdateOptions({ forceNewReplicationStream: true }), session);
|
|
81
|
+
const sharedActiveStream = active?.content.replicationStreamId == replicationStreamId;
|
|
82
|
+
if (sharedActiveStream) {
|
|
83
|
+
// The same stream was also used for the ACTIVE config. Transition that to ERRORED
|
|
84
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
else if (next == null && active?.content.replicationStreamId == replicationStreamId) {
|
|
88
|
+
// Slot removed for "active" replication stream, while there is no "next" one.
|
|
89
|
+
await this.updateSyncRulesInTransaction(active.content.asUpdateOptions({ forceNewReplicationStream: true }), session);
|
|
90
|
+
// In this case we keep the old one as active for clients, so that that existing clients
|
|
91
|
+
// can still get the latest data while we replicate the new ones.
|
|
92
|
+
// It will however not replicate anymore.
|
|
93
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
94
|
+
}
|
|
95
|
+
else if (next != null && active?.content.replicationStreamId == replicationStreamId) {
|
|
96
|
+
// Already have next replication stream, but need to stop replicating the active one.
|
|
97
|
+
await this.errorActiveStreamForReplacement(active, session);
|
|
98
|
+
}
|
|
99
|
+
else {
|
|
100
|
+
// replicationStreamId does not match the ACTIVE or PROCESSING streams - no-op.
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
await this.db.notifyCheckpoint();
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Keep an active config readable after its replication stream is invalidated.
|
|
107
|
+
*
|
|
108
|
+
* Incremental V3 streams can retain stopped historical configs and embed a deploying config.
|
|
109
|
+
* Read-side storage requires exactly one ACTIVE or ERRORED config, so only the active config
|
|
110
|
+
* transitions to ERRORED; every other embedded config remains or becomes STOP.
|
|
111
|
+
*/
|
|
112
|
+
async errorActiveStreamForReplacement(active, session) {
|
|
113
|
+
const activeSyncConfigId = active.content.syncConfigId;
|
|
114
|
+
if (activeSyncConfigId == null) {
|
|
115
|
+
// V1 documents
|
|
116
|
+
const result = await this.db.sync_rules.updateOne({
|
|
94
117
|
_id: active.content.replicationStreamId,
|
|
95
|
-
state: storage.SyncRuleState.ACTIVE
|
|
96
|
-
},
|
|
97
|
-
|
|
118
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
119
|
+
}, {
|
|
120
|
+
$set: {
|
|
121
|
+
state: storage.SyncRuleState.ERRORED
|
|
122
|
+
}
|
|
123
|
+
}, { session });
|
|
124
|
+
if (result.matchedCount != 1) {
|
|
125
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
126
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
127
|
+
throw new ReplicationAssertionError(`Active replication stream ${active.content.replicationStreamId} changed during restart`);
|
|
128
|
+
}
|
|
129
|
+
return;
|
|
98
130
|
}
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
131
|
+
const activeConfigObjectId = new ObjectId(activeSyncConfigId);
|
|
132
|
+
const result = await this.db.sync_rules.updateOne({
|
|
133
|
+
_id: active.content.replicationStreamId,
|
|
134
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
135
|
+
sync_configs: {
|
|
136
|
+
// Confirm that this is still the ACTIVE/ERRORED one.
|
|
137
|
+
$elemMatch: {
|
|
138
|
+
_id: activeConfigObjectId,
|
|
139
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
}, {
|
|
143
|
+
$set: {
|
|
144
|
+
state: storage.SyncRuleState.ERRORED,
|
|
145
|
+
// There must be exactly one config with an ERRORED state: This will be used to serve
|
|
146
|
+
// clients until there is a new ACTIVE one.
|
|
147
|
+
'sync_configs.$[activeConfig].state': storage.SyncRuleState.ERRORED,
|
|
148
|
+
// PROCESSING configs must stop. Historical STOP configs remain unchanged.
|
|
149
|
+
'sync_configs.$[processingConfig].state': storage.SyncRuleState.STOP
|
|
150
|
+
}
|
|
151
|
+
}, {
|
|
152
|
+
session,
|
|
153
|
+
arrayFilters: [
|
|
154
|
+
{
|
|
155
|
+
'activeConfig._id': activeConfigObjectId,
|
|
156
|
+
'activeConfig.state': { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
157
|
+
},
|
|
158
|
+
{
|
|
159
|
+
'processingConfig._id': { $ne: activeConfigObjectId },
|
|
160
|
+
'processingConfig.state': storage.SyncRuleState.PROCESSING
|
|
161
|
+
}
|
|
162
|
+
]
|
|
163
|
+
});
|
|
164
|
+
if (result.matchedCount != 1) {
|
|
165
|
+
// This should generally not happen, since the entire process runs in a transaction.
|
|
166
|
+
// The filters just double-check that the state is what we expect, and fails hard otherwise.
|
|
167
|
+
throw new ReplicationAssertionError(`Active replication stream ${active.content.replicationStreamId} changed during restart`);
|
|
106
168
|
}
|
|
107
169
|
}
|
|
108
|
-
async
|
|
109
|
-
|
|
170
|
+
async updateSyncRulesInTransaction(options, session) {
|
|
171
|
+
const storageVersion = options.storageVersion ??
|
|
172
|
+
options.config.parsed.config.storageVersion ??
|
|
173
|
+
this.options.defaultStorageVersion ??
|
|
174
|
+
storage.CURRENT_STORAGE_VERSION;
|
|
175
|
+
const storageConfig = getMongoStorageConfig(storageVersion);
|
|
176
|
+
if (storageConfig.incrementalReprocessing) {
|
|
177
|
+
return this.updateSyncConfigV3InTransaction(options, storageVersion, storageConfig, session);
|
|
178
|
+
}
|
|
179
|
+
return this.updateSyncConfigV1InTransaction(options, storageVersion, session);
|
|
180
|
+
}
|
|
181
|
+
/**
|
|
182
|
+
* Update to a V3 sync config.
|
|
183
|
+
*
|
|
184
|
+
* Does support cases where existing replication streams are on V1.
|
|
185
|
+
*/
|
|
186
|
+
async updateSyncConfigV3InTransaction(options, storageVersion, storageConfig, session) {
|
|
110
187
|
const versioned = this.db.versioned(storageConfig);
|
|
111
|
-
const
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
if (
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
options,
|
|
131
|
-
storageVersion,
|
|
132
|
-
session
|
|
133
|
-
});
|
|
134
|
-
return;
|
|
135
|
-
}
|
|
136
|
-
await this.stopEmbeddedDeployingConfigs(active, session);
|
|
188
|
+
const active = await this.db.sync_rules.findOne({
|
|
189
|
+
state: storage.SyncRuleState.ACTIVE,
|
|
190
|
+
storage_version: storageVersion
|
|
191
|
+
}, { session, sort: { _id: -1 }, limit: 1 });
|
|
192
|
+
if (active != null) {
|
|
193
|
+
const existingConfigDocs = await this.loadSyncConfigDefinitions(versioned, active, session);
|
|
194
|
+
if (!options.forceNewReplicationStream &&
|
|
195
|
+
this.options.supportsMultipleSyncConfigs &&
|
|
196
|
+
isCompatible(existingConfigDocs.map((d) => d.serialized_plan ?? null), options.config, logger)) {
|
|
197
|
+
logger.info(`Using incremental reprocessing`);
|
|
198
|
+
await this.stopExistingProcessingWork(session);
|
|
199
|
+
return await this.appendSyncConfigToStream({
|
|
200
|
+
versioned,
|
|
201
|
+
existing: active,
|
|
202
|
+
existingConfigDocs,
|
|
203
|
+
options,
|
|
204
|
+
storageVersion,
|
|
205
|
+
session
|
|
206
|
+
});
|
|
137
207
|
}
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
$inc: {
|
|
146
|
-
op_id: 1n
|
|
147
|
-
}
|
|
148
|
-
}, {
|
|
149
|
-
upsert: true,
|
|
150
|
-
returnDocument: 'after',
|
|
151
|
-
session
|
|
152
|
-
});
|
|
153
|
-
const id = Number(id_doc.op_id);
|
|
154
|
-
const replicationStreamName = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
155
|
-
const mapping = options.config.plan == null
|
|
156
|
-
? // For legacy sync rules and streams, use the parsed config directly to create a mapping
|
|
157
|
-
SingleSyncConfigBucketDefinitionMapping.fromParsedSyncConfig(options.config.parsed)
|
|
158
|
-
: // For new sync streams, always use the serialized version
|
|
159
|
-
SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingFromSerializedPlans([], options.config.plan.plan, []);
|
|
160
|
-
const syncConfigDoc = {
|
|
161
|
-
_id: new ObjectId(),
|
|
162
|
-
replication_stream_id: id,
|
|
163
|
-
created_at: new Date(),
|
|
164
|
-
storage_version: storageVersion,
|
|
165
|
-
content: options.config.yaml,
|
|
166
|
-
serialized_plan: options.config.plan,
|
|
167
|
-
rule_mapping: mapping.serialize()
|
|
168
|
-
};
|
|
169
|
-
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
170
|
-
const doc = {
|
|
171
|
-
_id: id,
|
|
172
|
-
storage_version: storageVersion,
|
|
173
|
-
sync_configs: [
|
|
174
|
-
{
|
|
175
|
-
_id: syncConfigDoc._id,
|
|
176
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
177
|
-
last_checkpoint: null,
|
|
178
|
-
last_checkpoint_lsn: null,
|
|
179
|
-
no_checkpoint_before: null,
|
|
180
|
-
snapshot_done: false
|
|
181
|
-
}
|
|
182
|
-
],
|
|
183
|
-
snapshot_lsn: undefined,
|
|
184
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
185
|
-
slot_name: replicationStreamName,
|
|
186
|
-
last_checkpoint_ts: null,
|
|
187
|
-
last_fatal_error: null,
|
|
188
|
-
last_fatal_error_ts: null,
|
|
189
|
-
last_keepalive_ts: null
|
|
190
|
-
};
|
|
191
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
192
|
-
rules = new MongoPersistedReplicationStream(this.db, doc, [syncConfigDoc]);
|
|
193
|
-
if (options.lock) {
|
|
194
|
-
// The lock is persisted on rules.current_lock
|
|
195
|
-
await rules.lock(session);
|
|
208
|
+
}
|
|
209
|
+
await this.stopExistingProcessingWork(session);
|
|
210
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate({
|
|
211
|
+
_id: 'sync_rules'
|
|
212
|
+
}, {
|
|
213
|
+
$inc: {
|
|
214
|
+
op_id: 1n
|
|
196
215
|
}
|
|
216
|
+
}, {
|
|
217
|
+
upsert: true,
|
|
218
|
+
returnDocument: 'after',
|
|
219
|
+
session
|
|
197
220
|
});
|
|
198
|
-
|
|
199
|
-
|
|
221
|
+
const id = Number(id_doc.op_id);
|
|
222
|
+
const replicationStreamName = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
223
|
+
const mapping = options.config.plan == null
|
|
224
|
+
? // For legacy sync rules and streams, use the parsed config directly to create a mapping
|
|
225
|
+
SingleSyncConfigBucketDefinitionMapping.fromParsedSyncConfig(options.config.parsed)
|
|
226
|
+
: // For new sync streams, always use the serialized version
|
|
227
|
+
SingleSyncConfigBucketDefinitionMapping.constructIncrementalMappingFromSerializedPlans([], options.config.plan.plan, []);
|
|
228
|
+
const syncConfigDoc = {
|
|
229
|
+
_id: new ObjectId(),
|
|
230
|
+
replication_stream_id: id,
|
|
231
|
+
created_at: new Date(),
|
|
232
|
+
storage_version: storageVersion,
|
|
233
|
+
content: options.config.yaml,
|
|
234
|
+
version_label: options.version_label,
|
|
235
|
+
serialized_plan: options.config.plan,
|
|
236
|
+
rule_mapping: mapping.serialize()
|
|
237
|
+
};
|
|
238
|
+
await versioned.syncConfigDefinitions.insertOne(syncConfigDoc, { session });
|
|
239
|
+
const doc = {
|
|
240
|
+
_id: id,
|
|
241
|
+
storage_version: storageVersion,
|
|
242
|
+
sync_configs: [
|
|
243
|
+
{
|
|
244
|
+
_id: syncConfigDoc._id,
|
|
245
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
246
|
+
last_checkpoint: null,
|
|
247
|
+
last_checkpoint_lsn: null,
|
|
248
|
+
no_checkpoint_before: null,
|
|
249
|
+
snapshot_done: false
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
snapshot_lsn: undefined,
|
|
253
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
254
|
+
slot_name: replicationStreamName,
|
|
255
|
+
last_checkpoint_ts: null,
|
|
256
|
+
last_fatal_error: null,
|
|
257
|
+
last_fatal_error_ts: null,
|
|
258
|
+
last_keepalive_ts: null
|
|
259
|
+
};
|
|
260
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
261
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc, [syncConfigDoc]);
|
|
262
|
+
if (options.lock) {
|
|
263
|
+
// We only lock when creating a new stream - otherwise we'll likely get lock contention
|
|
264
|
+
// from an existing job on the stream, which would fail the entire transaction.
|
|
265
|
+
// The lock is persisted on rules.current_lock
|
|
266
|
+
await rules.lock(session);
|
|
267
|
+
}
|
|
200
268
|
return rules;
|
|
201
269
|
}
|
|
202
270
|
async loadSyncConfigDefinitions(versioned, existing, session) {
|
|
@@ -224,25 +292,6 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
224
292
|
})
|
|
225
293
|
.toArray();
|
|
226
294
|
}
|
|
227
|
-
async stopEmbeddedDeployingConfigs(existing, session) {
|
|
228
|
-
const deployingConfigs = existing.sync_configs
|
|
229
|
-
.filter((config) => config.state == storage.SyncRuleState.PROCESSING)
|
|
230
|
-
.map((config) => config._id);
|
|
231
|
-
if (deployingConfigs.length == 0) {
|
|
232
|
-
return;
|
|
233
|
-
}
|
|
234
|
-
await this.db.sync_rules.updateOne({
|
|
235
|
-
_id: existing._id,
|
|
236
|
-
'sync_configs._id': { $in: deployingConfigs }
|
|
237
|
-
}, {
|
|
238
|
-
$set: {
|
|
239
|
-
'sync_configs.$[config].state': storage.SyncRuleState.STOP
|
|
240
|
-
}
|
|
241
|
-
}, {
|
|
242
|
-
session,
|
|
243
|
-
arrayFilters: [{ 'config._id': { $in: deployingConfigs } }]
|
|
244
|
-
});
|
|
245
|
-
}
|
|
246
295
|
logIncrementalDefinitionChanges(changes) {
|
|
247
296
|
logger.info(`Incremental reprocessing sync config update:\n${formatIncrementalSyncConfigUpdateLog(changes)}`);
|
|
248
297
|
}
|
|
@@ -268,6 +317,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
268
317
|
created_at: new Date(),
|
|
269
318
|
storage_version: storageVersion,
|
|
270
319
|
content: updateOptions.config.yaml,
|
|
320
|
+
version_label: updateOptions.version_label,
|
|
271
321
|
serialized_plan: updateOptions.config.plan,
|
|
272
322
|
rule_mapping: mapping.serialize()
|
|
273
323
|
};
|
|
@@ -303,68 +353,102 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
303
353
|
return stream;
|
|
304
354
|
}
|
|
305
355
|
async updateSyncRules(options) {
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
});
|
|
329
|
-
const id = Number(id_doc.op_id);
|
|
330
|
-
const slot_name = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
331
|
-
const doc = {
|
|
332
|
-
_id: id,
|
|
333
|
-
storage_version: storageVersion,
|
|
334
|
-
content: options.config.yaml,
|
|
335
|
-
serialized_plan: options.config.plan,
|
|
336
|
-
last_checkpoint: null,
|
|
337
|
-
last_checkpoint_lsn: null,
|
|
338
|
-
no_checkpoint_before: null,
|
|
339
|
-
keepalive_op: null,
|
|
340
|
-
snapshot_done: false,
|
|
341
|
-
snapshot_lsn: undefined,
|
|
342
|
-
state: storage.SyncRuleState.PROCESSING,
|
|
343
|
-
slot_name: slot_name,
|
|
344
|
-
last_checkpoint_ts: null,
|
|
345
|
-
last_fatal_error: null,
|
|
346
|
-
last_fatal_error_ts: null,
|
|
347
|
-
last_keepalive_ts: null
|
|
348
|
-
};
|
|
349
|
-
await this.db.sync_rules.insertOne(doc, { session });
|
|
350
|
-
await this.db.notifyCheckpoint();
|
|
351
|
-
rules = new MongoPersistedReplicationStream(this.db, doc);
|
|
352
|
-
if (options.lock) {
|
|
353
|
-
// The lock is persisted on rules.current_lock
|
|
354
|
-
await rules.lock(session);
|
|
356
|
+
let rules;
|
|
357
|
+
await this.withTransaction(async (session) => {
|
|
358
|
+
rules = await this.updateSyncRulesInTransaction(options, session);
|
|
359
|
+
});
|
|
360
|
+
await this.db.notifyCheckpoint();
|
|
361
|
+
return rules;
|
|
362
|
+
}
|
|
363
|
+
async withTransaction(callback) {
|
|
364
|
+
return this.client.withSession((session) => session.withTransaction(callback));
|
|
365
|
+
}
|
|
366
|
+
/**
|
|
367
|
+
* Update to a V1 sync config.
|
|
368
|
+
*
|
|
369
|
+
* Does support cases where existing replication streams are on V3.
|
|
370
|
+
*/
|
|
371
|
+
async updateSyncConfigV1InTransaction(options, storageVersion, session) {
|
|
372
|
+
await this.stopExistingProcessingWork(session);
|
|
373
|
+
const id_doc = await this.db.op_id_sequence.findOneAndUpdate({
|
|
374
|
+
_id: 'sync_rules'
|
|
375
|
+
}, {
|
|
376
|
+
$inc: {
|
|
377
|
+
op_id: 1n
|
|
355
378
|
}
|
|
379
|
+
}, {
|
|
380
|
+
upsert: true,
|
|
381
|
+
returnDocument: 'after',
|
|
382
|
+
session
|
|
356
383
|
});
|
|
384
|
+
const id = Number(id_doc.op_id);
|
|
385
|
+
const slot_name = generateReplicationStreamName(this.replicationStreamNamePrefix, id);
|
|
386
|
+
// All V1 replication streams share both the `main` op id sequence and the `bucket_parameters`
|
|
387
|
+
// collection, so every parameter entry this stream writes gets an op id above the current
|
|
388
|
+
// head. Seeding the parameter compaction cursor with that head keeps the stream's first
|
|
389
|
+
// compaction from scanning other streams' history, which would otherwise be repeated for
|
|
390
|
+
// every new deployment. A concurrent replication flush can only advance the head after this
|
|
391
|
+
// read, which makes the seed conservative, never too high.
|
|
392
|
+
const opSequence = await this.db.op_id_sequence.findOne({ _id: 'main' }, { session });
|
|
393
|
+
const doc = {
|
|
394
|
+
_id: id,
|
|
395
|
+
storage_version: storageVersion,
|
|
396
|
+
content: options.config.yaml,
|
|
397
|
+
version_label: options.version_label,
|
|
398
|
+
serialized_plan: options.config.plan,
|
|
399
|
+
last_checkpoint: null,
|
|
400
|
+
last_checkpoint_lsn: null,
|
|
401
|
+
no_checkpoint_before: null,
|
|
402
|
+
keepalive_op: null,
|
|
403
|
+
snapshot_done: false,
|
|
404
|
+
snapshot_lsn: undefined,
|
|
405
|
+
state: storage.SyncRuleState.PROCESSING,
|
|
406
|
+
slot_name: slot_name,
|
|
407
|
+
last_checkpoint_ts: null,
|
|
408
|
+
last_fatal_error: null,
|
|
409
|
+
last_fatal_error_ts: null,
|
|
410
|
+
last_keepalive_ts: null,
|
|
411
|
+
parameter_compaction: { compacted_before: opSequence?.op_id ?? 0n }
|
|
412
|
+
};
|
|
413
|
+
await this.db.sync_rules.insertOne(doc, { session });
|
|
414
|
+
const rules = new MongoPersistedReplicationStream(this.db, doc);
|
|
415
|
+
if (options.lock) {
|
|
416
|
+
// The lock is persisted on rules.current_lock
|
|
417
|
+
await rules.lock(session);
|
|
418
|
+
}
|
|
357
419
|
return rules;
|
|
358
420
|
}
|
|
421
|
+
/**
|
|
422
|
+
* Stop top-level and embedded processing work before creating a replacement stream.
|
|
423
|
+
*
|
|
424
|
+
* Use in the same transaction that the replacement stream is created.
|
|
425
|
+
*/
|
|
426
|
+
async stopExistingProcessingWork(session) {
|
|
427
|
+
await this.db.sync_rules.updateMany({
|
|
428
|
+
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] },
|
|
429
|
+
'sync_configs.state': storage.SyncRuleState.PROCESSING
|
|
430
|
+
}, {
|
|
431
|
+
$set: {
|
|
432
|
+
'sync_configs.$[config].state': storage.SyncRuleState.STOP
|
|
433
|
+
}
|
|
434
|
+
}, {
|
|
435
|
+
session,
|
|
436
|
+
arrayFilters: [{ 'config.state': storage.SyncRuleState.PROCESSING }]
|
|
437
|
+
});
|
|
438
|
+
await this.db.sync_rules.updateMany({
|
|
439
|
+
state: storage.SyncRuleState.PROCESSING
|
|
440
|
+
}, stopReplicationStreamPipeline(), { session });
|
|
441
|
+
}
|
|
359
442
|
async getActiveSyncConfig() {
|
|
443
|
+
return this.getActiveSyncConfigInternal();
|
|
444
|
+
}
|
|
445
|
+
async getActiveSyncConfigInternal(session) {
|
|
360
446
|
const doc = await this.db.sync_rules.findOne({
|
|
361
447
|
state: { $in: [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED] }
|
|
362
|
-
}, { sort: { _id: -1 }, limit: 1 });
|
|
363
|
-
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED], {
|
|
364
|
-
cacheActiveStorage: true
|
|
365
|
-
});
|
|
448
|
+
}, { session, sort: { _id: -1 }, limit: 1 });
|
|
449
|
+
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.ACTIVE, storage.SyncRuleState.ERRORED], { cacheActiveStorage: session == null }, session);
|
|
366
450
|
}
|
|
367
|
-
async replicationStreamFromDoc(doc, stateFilter) {
|
|
451
|
+
async replicationStreamFromDoc(doc, stateFilter, session) {
|
|
368
452
|
if (doc == null) {
|
|
369
453
|
return null;
|
|
370
454
|
}
|
|
@@ -382,7 +466,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
382
466
|
const syncConfigDocs = await db.syncConfigDefinitions
|
|
383
467
|
.find({
|
|
384
468
|
_id: { $in: matching.map((config) => config._id) }
|
|
385
|
-
})
|
|
469
|
+
}, { session })
|
|
386
470
|
.toArray();
|
|
387
471
|
if (syncConfigDocs.length == 0) {
|
|
388
472
|
return null;
|
|
@@ -392,10 +476,13 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
392
476
|
return new MongoPersistedReplicationStream(this.db, doc);
|
|
393
477
|
}
|
|
394
478
|
async getDeployingSyncConfig() {
|
|
479
|
+
return this.getDeployingSyncConfigInternal();
|
|
480
|
+
}
|
|
481
|
+
async getDeployingSyncConfigInternal(session) {
|
|
395
482
|
const doc = await this.db.sync_rules.findOne({
|
|
396
483
|
$or: [{ state: storage.SyncRuleState.PROCESSING }, { 'sync_configs.state': storage.SyncRuleState.PROCESSING }]
|
|
397
|
-
}, { sort: { _id: -1 }, limit: 1 });
|
|
398
|
-
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.PROCESSING]);
|
|
484
|
+
}, { session, sort: { _id: -1 }, limit: 1 });
|
|
485
|
+
return this.resolvedSyncConfigFromDoc(doc, [storage.SyncRuleState.PROCESSING], {}, session);
|
|
399
486
|
}
|
|
400
487
|
async getReplicatingReplicationStreams() {
|
|
401
488
|
const docs = await this.db.sync_rules
|
|
@@ -417,8 +504,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
417
504
|
return this.replicationStreamFromDoc(doc, [storage.SyncRuleState.STOP]);
|
|
418
505
|
}))).filter((d) => d != null);
|
|
419
506
|
}
|
|
420
|
-
async resolvedSyncConfigFromDoc(doc, stateFilter, options = {}) {
|
|
421
|
-
const stream = await this.replicationStreamFromDoc(doc, stateFilter);
|
|
507
|
+
async resolvedSyncConfigFromDoc(doc, stateFilter, options = {}, session) {
|
|
508
|
+
const stream = await this.replicationStreamFromDoc(doc, stateFilter, session);
|
|
422
509
|
if (stream == null) {
|
|
423
510
|
return null;
|
|
424
511
|
}
|
|
@@ -445,7 +532,7 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
445
532
|
}
|
|
446
533
|
async getStorageMetrics() {
|
|
447
534
|
const ignoreNotExisting = (e) => {
|
|
448
|
-
if (lib_mongo.
|
|
535
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(e)) {
|
|
449
536
|
// Collection doesn't exist - return 0
|
|
450
537
|
return [{ storageStats: { size: 0 } }];
|
|
451
538
|
}
|
|
@@ -453,8 +540,6 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
453
540
|
return Promise.reject(e);
|
|
454
541
|
}
|
|
455
542
|
};
|
|
456
|
-
// For now, we get storage metrics over all v1 and v3 collections.
|
|
457
|
-
// In the future, we may split these metrics to report separately for active replication streams versus processing streams.
|
|
458
543
|
const aggregateStaticCollection = async (collection) => {
|
|
459
544
|
// We check whether the collection exists before getting the statistics. This avoids repeated
|
|
460
545
|
// errors in the MongoDB logs if the collection hasn't been created yet.
|
|
@@ -475,7 +560,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
475
560
|
.catch(ignoreNotExisting);
|
|
476
561
|
};
|
|
477
562
|
const operations_aggregate = await aggregateStaticCollection(this.db.bucket_data);
|
|
478
|
-
const
|
|
563
|
+
const v3OperationCollections = await this.db.listBucketDataCollectionsV3();
|
|
564
|
+
const v3_operation_aggregates = await Promise.all(v3OperationCollections.map((collection) => collection
|
|
479
565
|
.aggregate([
|
|
480
566
|
{
|
|
481
567
|
$collStats: {
|
|
@@ -486,7 +572,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
486
572
|
.toArray()
|
|
487
573
|
.catch(ignoreNotExisting)));
|
|
488
574
|
const parameters_aggregate = await aggregateStaticCollection(this.db.bucket_parameters);
|
|
489
|
-
const
|
|
575
|
+
const v3ParameterCollections = await this.db.listAllParameterIndexCollectionsV3();
|
|
576
|
+
const v3_parameter_aggregates = await Promise.all(v3ParameterCollections.map((collection) => collection
|
|
490
577
|
.aggregate([
|
|
491
578
|
{
|
|
492
579
|
$collStats: {
|
|
@@ -497,7 +584,8 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
497
584
|
.toArray()
|
|
498
585
|
.catch(ignoreNotExisting)));
|
|
499
586
|
const v1_source_record_aggregate = await aggregateStaticCollection(this.db.current_data);
|
|
500
|
-
const
|
|
587
|
+
const v3SourceRecordCollections = await this.db.listAllSourceRecordCollectionsV3();
|
|
588
|
+
const source_record_aggregates = await Promise.all(v3SourceRecordCollections.map((collection) => collection
|
|
501
589
|
.aggregate([
|
|
502
590
|
{
|
|
503
591
|
$collStats: {
|
|
@@ -507,13 +595,84 @@ export class MongoBucketStorage extends storage.BucketStorageFactory {
|
|
|
507
595
|
])
|
|
508
596
|
.toArray()
|
|
509
597
|
.catch(ignoreNotExisting)));
|
|
598
|
+
const v3StorageConfig = getMongoStorageConfig(storage.STORAGE_VERSION_3);
|
|
599
|
+
const v3Db = this.db.versioned(v3StorageConfig);
|
|
600
|
+
const objectStorageDefinitionUsage = await ObjectStorageUsage.readAllDefinitionUsage(v3Db);
|
|
601
|
+
const v3StreamDocs = (await this.db.sync_rules
|
|
602
|
+
.find({ storage_version: { $gte: storage.STORAGE_VERSION_3 } }, { projection: { _id: 1, sync_configs: 1 } })
|
|
603
|
+
.toArray());
|
|
604
|
+
const collectionSizes = new Map();
|
|
605
|
+
const addCollectionSize = (collection, aggregate, prefix, sizes) => {
|
|
606
|
+
const match = collection.collectionName.match(new RegExp(`^${prefix}(\\d+)_(.+)$`));
|
|
607
|
+
if (match == null) {
|
|
608
|
+
return;
|
|
609
|
+
}
|
|
610
|
+
sizes.set(`${prefix}${match[1]}:${match[2]}`, Number(aggregate[0]?.storageStats?.size ?? 0));
|
|
611
|
+
};
|
|
612
|
+
v3OperationCollections.forEach((collection, index) => addCollectionSize(collection, v3_operation_aggregates[index], 'bucket_data_', collectionSizes));
|
|
613
|
+
v3ParameterCollections.forEach((collection, index) => addCollectionSize(collection, v3_parameter_aggregates[index], 'parameter_index_', collectionSizes));
|
|
614
|
+
v3SourceRecordCollections.forEach((collection, index) => addCollectionSize(collection, source_record_aggregates[index], 'source_records_', collectionSizes));
|
|
615
|
+
const syncConfigIds = v3StreamDocs.flatMap((stream) => (stream.sync_configs ?? []).map((config) => config._id));
|
|
616
|
+
const syncConfigDefinitions = syncConfigIds.length == 0
|
|
617
|
+
? []
|
|
618
|
+
: await v3Db.syncConfigDefinitions
|
|
619
|
+
.find({ _id: { $in: syncConfigIds } }, { projection: { _id: 1, rule_mapping: 1, version_label: 1 } })
|
|
620
|
+
.toArray();
|
|
621
|
+
const syncConfigDefinitionsById = new Map(syncConfigDefinitions.map((definition) => [definition._id.toHexString(), definition]));
|
|
622
|
+
const sourceTablesByStream = new Map();
|
|
623
|
+
await Promise.all(v3StreamDocs.map(async (stream) => {
|
|
624
|
+
const sourceTables = await v3Db
|
|
625
|
+
.sourceTables(stream._id)
|
|
626
|
+
.find({}, { projection: { _id: 1, bucket_data_source_ids: 1, parameter_lookup_source_ids: 1 } })
|
|
627
|
+
.toArray();
|
|
628
|
+
sourceTablesByStream.set(stream._id, sourceTables);
|
|
629
|
+
}));
|
|
630
|
+
const objectStorageSizeByDefinition = new Map();
|
|
631
|
+
for (const usage of objectStorageDefinitionUsage) {
|
|
632
|
+
objectStorageSizeByDefinition.set(`${usage.replication_stream_id}:${usage.definition_id}`, Number(usage.active_bytes));
|
|
633
|
+
}
|
|
634
|
+
const sumCollectionSizes = (prefix, streamId, ids) => [...ids].reduce((total, id) => total + (collectionSizes.get(`${prefix}${streamId}:${id}`) ?? 0), 0);
|
|
635
|
+
const syncConfigMetrics = [];
|
|
636
|
+
for (const stream of v3StreamDocs) {
|
|
637
|
+
const sourceTables = sourceTablesByStream.get(stream._id) ?? [];
|
|
638
|
+
for (const syncConfig of stream.sync_configs ?? []) {
|
|
639
|
+
const definition = syncConfigDefinitionsById.get(syncConfig._id.toHexString());
|
|
640
|
+
if (definition == null) {
|
|
641
|
+
continue;
|
|
642
|
+
}
|
|
643
|
+
const mapping = SingleSyncConfigBucketDefinitionMapping.fromPersistedMapping(definition.rule_mapping);
|
|
644
|
+
const bucketDefinitionIds = mapping.allBucketDefinitionIds();
|
|
645
|
+
const parameterIndexIds = mapping.allParameterIndexIds();
|
|
646
|
+
const bucketDefinitionIdSet = new Set(bucketDefinitionIds);
|
|
647
|
+
const parameterIndexIdSet = new Set(parameterIndexIds);
|
|
648
|
+
const replicationSize = sourceTables.reduce((total, sourceTable) => {
|
|
649
|
+
if (!(sourceTable.bucket_data_source_ids ?? []).some((id) => bucketDefinitionIdSet.has(id)) &&
|
|
650
|
+
!(sourceTable.parameter_lookup_source_ids ?? []).some((id) => parameterIndexIdSet.has(id))) {
|
|
651
|
+
return total;
|
|
652
|
+
}
|
|
653
|
+
return total + (collectionSizes.get(`source_records_${stream._id}:${sourceTable._id.toHexString()}`) ?? 0);
|
|
654
|
+
}, 0);
|
|
655
|
+
syncConfigMetrics.push({
|
|
656
|
+
sync_config_id: syncConfig._id.toHexString(),
|
|
657
|
+
sync_config_state: String(syncConfig.state),
|
|
658
|
+
version_label: definition.version_label,
|
|
659
|
+
attributed_bucket_data_bytes: sumCollectionSizes('bucket_data_', stream._id, bucketDefinitionIdSet),
|
|
660
|
+
attributed_parameter_indexes_bytes: sumCollectionSizes('parameter_index_', stream._id, parameterIndexIdSet),
|
|
661
|
+
attributed_source_records_bytes: replicationSize,
|
|
662
|
+
attributed_object_storage_bytes: [...bucketDefinitionIdSet].reduce((total, definitionId) => total + (objectStorageSizeByDefinition.get(`${stream._id}:${definitionId}`) ?? 0), 0)
|
|
663
|
+
});
|
|
664
|
+
}
|
|
665
|
+
}
|
|
666
|
+
const totalObjectStorageSize = objectStorageDefinitionUsage.reduce((total, usage) => total + usage.active_bytes, 0n);
|
|
510
667
|
return {
|
|
511
668
|
operations_size_bytes: Number(operations_aggregate[0].storageStats.size) +
|
|
512
669
|
v3_operation_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
|
|
513
670
|
parameters_size_bytes: Number(parameters_aggregate[0].storageStats.size) +
|
|
514
671
|
v3_parameter_aggregates.reduce((total, aggregate) => total + Number(aggregate[0].storageStats.size), 0),
|
|
515
672
|
replication_size_bytes: Number(v1_source_record_aggregate[0]?.storageStats?.size ?? 0) +
|
|
516
|
-
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0)
|
|
673
|
+
source_record_aggregates.reduce((total, aggregate) => total + Number(aggregate[0]?.storageStats?.size ?? 0), 0),
|
|
674
|
+
object_storage_size_bytes: Number(totalObjectStorageSize),
|
|
675
|
+
sync_config_metrics: syncConfigMetrics
|
|
517
676
|
};
|
|
518
677
|
}
|
|
519
678
|
async getPowerSyncInstanceId() {
|