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