@powersync/service-module-mongodb-storage 0.0.0-dev-20260803144812 → 0.0.0-dev-20260929095716
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +129 -7
- package/dist/storage/MongoBucketStorage.d.ts +42 -5
- package/dist/storage/MongoBucketStorage.js +404 -215
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +39 -14
- package/dist/storage/implementation/MongoBucketBatch.js +169 -98
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +21 -79
- package/dist/storage/implementation/MongoCompactor.js +27 -238
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoIdSequence.d.ts +10 -1
- package/dist/storage/implementation/MongoIdSequence.js +14 -1
- package/dist/storage/implementation/MongoIdSequence.js.map +1 -1
- package/dist/storage/implementation/MongoOpIdAllocator.d.ts +27 -0
- package/dist/storage/implementation/MongoOpIdAllocator.js +97 -0
- package/dist/storage/implementation/MongoOpIdAllocator.js.map +1 -0
- package/dist/storage/implementation/MongoParameterCompactor.d.ts +83 -10
- package/dist/storage/implementation/MongoParameterCompactor.js +309 -96
- package/dist/storage/implementation/MongoParameterCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.d.ts +9 -1
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js +35 -2
- package/dist/storage/implementation/MongoParsedSyncConfigSet.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedReplicationStream.d.ts +2 -1
- package/dist/storage/implementation/MongoPersistedReplicationStream.js +7 -4
- package/dist/storage/implementation/MongoPersistedReplicationStream.js.map +1 -1
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.d.ts +3 -3
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js +10 -8
- package/dist/storage/implementation/MongoPersistedSyncConfigContent.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +8 -4
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +32 -11
- package/dist/storage/implementation/MongoSyncBucketStorage.js +95 -31
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoSyncRulesLock.d.ts +26 -2
- package/dist/storage/implementation/MongoSyncRulesLock.js +59 -10
- package/dist/storage/implementation/MongoSyncRulesLock.js.map +1 -1
- package/dist/storage/implementation/MongoWriteBatch.d.ts +49 -0
- package/dist/storage/implementation/MongoWriteBatch.js +149 -0
- package/dist/storage/implementation/MongoWriteBatch.js.map +1 -0
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +9 -7
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +9 -30
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/SyncRuleStateUpdate.d.ts +11 -5
- package/dist/storage/implementation/SyncRuleStateUpdate.js +14 -6
- package/dist/storage/implementation/SyncRuleStateUpdate.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +22 -2
- package/dist/storage/implementation/common/PersistedBatch.d.ts +32 -4
- package/dist/storage/implementation/common/PersistedBatch.js +57 -14
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.d.ts +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js +3 -0
- package/dist/storage/implementation/common/VersionedPowerSyncMongoBase.js.map +1 -1
- package/dist/storage/implementation/db.d.ts +9 -1
- package/dist/storage/implementation/db.js +28 -1
- package/dist/storage/implementation/db.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +35 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoBucketBatchV1.d.ts +11 -0
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js +227 -221
- package/dist/storage/implementation/v1/MongoBucketBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +38 -6
- package/dist/storage/implementation/v1/MongoCompactorV1.js +247 -20
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.d.ts +16 -2
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js +20 -9
- package/dist/storage/implementation/v1/MongoParameterCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.d.ts +18 -11
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +66 -17
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/PersistedBatchV1.d.ts +7 -5
- package/dist/storage/implementation/v1/PersistedBatchV1.js +22 -28
- package/dist/storage/implementation/v1/PersistedBatchV1.js.map +1 -1
- package/dist/storage/implementation/v1/models.js +1 -0
- package/dist/storage/implementation/v1/models.js.map +1 -1
- package/dist/storage/implementation/v3/CompactionLease.d.ts +50 -0
- package/dist/storage/implementation/v3/CompactionLease.js +131 -0
- package/dist/storage/implementation/v3/CompactionLease.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.d.ts +14 -2
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +261 -194
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +83 -25
- package/dist/storage/implementation/v3/MongoCompactorV3.js +702 -159
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.d.ts +18 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js +23 -0
- package/dist/storage/implementation/v3/MongoParameterCompactorV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +2 -15
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +60 -92
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +19 -9
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +92 -32
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.d.ts +26 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js +82 -0
- package/dist/storage/implementation/v3/MongoWriteCheckpointAPIV3.js.map +1 -0
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +14 -8
- package/dist/storage/implementation/v3/PersistedBatchV3.js +77 -45
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +12 -2
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +42 -3
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.js +2 -0
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- package/dist/storage/implementation/v3/compact-utils.d.ts +108 -0
- package/dist/storage/implementation/v3/compact-utils.js +223 -0
- package/dist/storage/implementation/v3/compact-utils.js.map +1 -0
- package/dist/storage/implementation/v3/compaction-constants.d.ts +5 -0
- package/dist/storage/implementation/v3/compaction-constants.js +6 -0
- package/dist/storage/implementation/v3/compaction-constants.js.map +1 -0
- package/dist/storage/implementation/v3/models.d.ts +90 -6
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +7 -8
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +16 -5
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +14 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +6 -8
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +17 -11
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.d.ts +38 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js +164 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageUsage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +37 -12
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +197 -25
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -1
- package/dist/storage/implementation/v3/source-table-utils.d.ts +15 -7
- package/dist/storage/implementation/v3/source-table-utils.js +49 -57
- package/dist/storage/implementation/v3/source-table-utils.js.map +1 -1
- package/dist/storage/storage-index.d.ts +2 -1
- package/dist/storage/storage-index.js +1 -0
- package/dist/storage/storage-index.js.map +1 -1
- package/dist/types/types.d.ts +28 -1
- package/dist/types/types.js +28 -7
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.d.ts +4 -2
- package/dist/utils/test-utils.js +7 -4
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.js +3 -1
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -9
- package/src/storage/MongoBucketStorage.ts +545 -277
- package/src/storage/implementation/MongoBucketBatch.ts +258 -124
- package/src/storage/implementation/MongoCompactor.ts +37 -346
- package/src/storage/implementation/MongoIdSequence.ts +21 -1
- package/src/storage/implementation/MongoOpIdAllocator.ts +105 -0
- package/src/storage/implementation/MongoParameterCompactor.ts +388 -102
- package/src/storage/implementation/MongoParsedSyncConfigSet.ts +46 -1
- package/src/storage/implementation/MongoPersistedReplicationStream.ts +12 -4
- package/src/storage/implementation/MongoPersistedSyncConfigContent.ts +48 -29
- package/src/storage/implementation/MongoStorageProvider.ts +16 -4
- package/src/storage/implementation/MongoSyncBucketStorage.ts +133 -40
- package/src/storage/implementation/MongoSyncRulesLock.ts +78 -12
- package/src/storage/implementation/MongoWriteBatch.ts +172 -0
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +16 -46
- package/src/storage/implementation/SyncRuleStateUpdate.ts +13 -6
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +24 -2
- package/src/storage/implementation/common/PersistedBatch.ts +76 -20
- package/src/storage/implementation/common/VersionedPowerSyncMongoBase.ts +4 -0
- package/src/storage/implementation/db.ts +36 -2
- package/src/storage/implementation/models.ts +39 -0
- package/src/storage/implementation/v1/MongoBucketBatchV1.ts +272 -254
- package/src/storage/implementation/v1/MongoCompactorV1.ts +327 -40
- package/src/storage/implementation/v1/MongoParameterCompactorV1.ts +25 -9
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +88 -28
- package/src/storage/implementation/v1/PersistedBatchV1.ts +25 -30
- package/src/storage/implementation/v1/models.ts +1 -0
- package/src/storage/implementation/v3/CompactionLease.ts +152 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +339 -247
- package/src/storage/implementation/v3/MongoCompactorV3.ts +955 -225
- package/src/storage/implementation/v3/MongoParameterCompactorV3.ts +34 -0
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +100 -109
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +112 -42
- package/src/storage/implementation/v3/MongoWriteCheckpointAPIV3.ts +111 -0
- package/src/storage/implementation/v3/PersistedBatchV3.ts +94 -51
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +55 -4
- package/src/storage/implementation/v3/bucket-format.ts +2 -0
- package/src/storage/implementation/v3/compact-utils.ts +329 -0
- package/src/storage/implementation/v3/compaction-constants.ts +5 -0
- package/src/storage/implementation/v3/models.ts +97 -4
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +24 -9
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +20 -5
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +26 -14
- package/src/storage/implementation/v3/object-storage/ObjectStorageUsage.ts +229 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +279 -29
- package/src/storage/implementation/v3/source-table-utils.ts +82 -76
- package/src/storage/storage-index.ts +2 -0
- package/src/types/types.ts +42 -7
- package/src/utils/test-utils.ts +9 -5
- package/src/utils/util.ts +3 -1
- package/test/src/__snapshots__/storage.test.ts.snap +25 -0
- package/test/src/__snapshots__/storage_sync.test.ts.snap +794 -205
- package/test/src/chunk_compaction_config.test.ts +38 -0
- package/test/src/cleanup-stopped-sync-configs.test.ts +59 -26
- package/test/src/compact-utils.test.ts +251 -0
- package/test/src/concurrent_writers.test.ts +599 -0
- package/test/src/connection_config.test.ts +159 -0
- package/test/src/helpers/MemoryObjectStorage.ts +14 -6
- package/test/src/helpers/concurrentWriter.mjs +29 -0
- package/test/src/helpers/s3TestFactory.ts +12 -12
- package/test/src/object_storage_usage.test.ts +268 -0
- package/test/src/parameter_compacting_v1.test.ts +120 -0
- package/test/src/parameter_compacting_v3.test.ts +192 -0
- package/test/src/parameter_compaction_fence.test.ts +276 -0
- package/test/src/storage.test.ts +280 -24
- package/test/src/storage_compacting.test.ts +1097 -253
- package/test/src/storage_object_storage_inline_threshold.test.ts +3 -3
- package/test/src/storage_s3_checksums.test.ts +6 -7
- package/test/src/storage_s3_compaction_lifecycle.test.ts +143 -17
- package/test/src/storage_s3_reading.test.ts +294 -6
- package/test/src/storage_s3_writing.test.ts +4 -3
- package/test/src/storage_sync.test.ts +412 -80
- package/test/src/storeCurrentData.test.ts +5 -5
- package/test/src/util.ts +1 -1
- package/tsconfig.tsbuildinfo +1 -1
|
@@ -1,11 +1,11 @@
|
|
|
1
|
-
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
1
|
import { Logger } from '@powersync/lib-services-framework';
|
|
3
2
|
import * as bson from 'bson';
|
|
4
3
|
import { createHash, randomUUID } from 'node:crypto';
|
|
4
|
+
import { MongoWriteBatch } from '../../MongoWriteBatch.js';
|
|
5
5
|
import { ObjectStorageDeletionMarker } from '../models.js';
|
|
6
6
|
import { VersionedPowerSyncMongoV3 } from '../VersionedPowerSyncMongoV3.js';
|
|
7
7
|
import { BucketDataObjectStorage } from './BucketDataObjectStorage.js';
|
|
8
|
-
import { ObjectStorage } from './ObjectStorage.js';
|
|
8
|
+
import { ObjectStorage, ObjectStorageOperationOptions } from './ObjectStorage.js';
|
|
9
9
|
|
|
10
10
|
export const OBJECT_STORAGE_UPLOAD_LEASE_MS = 60 * 60 * 1000;
|
|
11
11
|
export const OBJECT_STORAGE_PUBLICATION_SAFETY_MARGIN_MS = 2 * 60 * 1000;
|
|
@@ -93,7 +93,7 @@ export class ObjectStorageLifecycle {
|
|
|
93
93
|
*
|
|
94
94
|
* Call this in the same transaction as the one creating the references.
|
|
95
95
|
*/
|
|
96
|
-
|
|
96
|
+
publishUploads(uploads: PreparedObjectStorageUpload[], writes: MongoWriteBatch): void {
|
|
97
97
|
for (const upload of uploads) {
|
|
98
98
|
if (!this.canPublish(upload)) {
|
|
99
99
|
throw new Error(`Object storage publication lease expired for ${upload.path}`);
|
|
@@ -103,12 +103,13 @@ export class ObjectStorageLifecycle {
|
|
|
103
103
|
return;
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
|
|
107
|
-
.pendingObjectStorageDeletes(this.replicationStreamId)
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
106
|
+
writes.deleteMany(
|
|
107
|
+
this.db.pendingObjectStorageDeletes(this.replicationStreamId),
|
|
108
|
+
{ _id: { $in: uploads.map((upload) => upload.markerId) } },
|
|
109
|
+
(deletedCount) => {
|
|
110
|
+
if (deletedCount !== uploads.length) throw new Error('Missing object storage publication markers');
|
|
111
|
+
}
|
|
112
|
+
);
|
|
112
113
|
}
|
|
113
114
|
|
|
114
115
|
/**
|
|
@@ -122,14 +123,14 @@ export class ObjectStorageLifecycle {
|
|
|
122
123
|
*
|
|
123
124
|
* Call this in the same transaction as the one removing the references to these files.
|
|
124
125
|
*/
|
|
125
|
-
|
|
126
|
+
retire(paths: Iterable<string>, writes: MongoWriteBatch, now = new Date()): void {
|
|
126
127
|
const markers: ObjectStorageDeletionMarker[] = Array.from(paths, (path) => ({
|
|
127
128
|
_id: new bson.ObjectId(),
|
|
128
129
|
path,
|
|
129
130
|
delete_after: new Date(now.getTime() + OBJECT_STORAGE_REFERENCE_GRACE_MS)
|
|
130
131
|
}));
|
|
131
132
|
if (markers.length) {
|
|
132
|
-
|
|
133
|
+
writes.insertMany(this.db.pendingObjectStorageDeletes(this.replicationStreamId), markers);
|
|
133
134
|
}
|
|
134
135
|
}
|
|
135
136
|
|
|
@@ -137,11 +138,12 @@ export class ObjectStorageLifecycle {
|
|
|
137
138
|
* Discover and delete objects by their storage prefix
|
|
138
139
|
* without touching the database.
|
|
139
140
|
*/
|
|
140
|
-
async deletePrefix(prefix: string, options?:
|
|
141
|
+
async deletePrefix(prefix: string, options?: ObjectStorageOperationOptions): Promise<{ objectCount: number }> {
|
|
141
142
|
return this.objectStorage.deletePrefix(prefix, { signal: options?.signal });
|
|
142
143
|
}
|
|
143
144
|
|
|
144
|
-
async cleanup(logger: Logger): Promise<void> {
|
|
145
|
+
async cleanup(logger: Logger, options?: ObjectStorageOperationOptions): Promise<void> {
|
|
146
|
+
const signal = options?.signal;
|
|
145
147
|
const markers = this.db.pendingObjectStorageDeletes(this.replicationStreamId);
|
|
146
148
|
const cursor = markers.find({ delete_after: { $lte: new Date() } });
|
|
147
149
|
let batch: ObjectStorageDeletionMarker[] = [];
|
|
@@ -153,14 +155,24 @@ export class ObjectStorageLifecycle {
|
|
|
153
155
|
const deleting = batch;
|
|
154
156
|
batch = [];
|
|
155
157
|
try {
|
|
156
|
-
await this.bucketData.delete(
|
|
158
|
+
await this.bucketData.delete(
|
|
159
|
+
deleting.map((marker) => marker.path),
|
|
160
|
+
{ signal }
|
|
161
|
+
);
|
|
157
162
|
await markers.deleteMany({ _id: { $in: deleting.map((marker) => marker._id) } });
|
|
158
163
|
} catch (error) {
|
|
164
|
+
if (signal?.aborted) {
|
|
165
|
+
// Shutting down: stop instead of working through the remaining markers. They stay
|
|
166
|
+
// pending, and are cleaned up during the next compaction. Checked on the signal rather
|
|
167
|
+
// than the error, since a caller may abort with any reason.
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
159
170
|
logger.warn('Failed to clean up object storage deletion markers; will retry during the next compaction', error);
|
|
160
171
|
}
|
|
161
172
|
};
|
|
162
173
|
|
|
163
174
|
for await (const marker of cursor) {
|
|
175
|
+
signal?.throwIfAborted();
|
|
164
176
|
batch.push(marker);
|
|
165
177
|
if (batch.length === 500) {
|
|
166
178
|
await deleteBatch();
|
|
@@ -0,0 +1,229 @@
|
|
|
1
|
+
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
3
|
+
import { ReplicationAssertionError } from '@powersync/lib-services-framework';
|
|
4
|
+
import { BucketDefinitionId } from '@powersync/service-sync-rules';
|
|
5
|
+
import { randomUUID } from 'node:crypto';
|
|
6
|
+
import { MongoWriteBatch } from '../../MongoWriteBatch.js';
|
|
7
|
+
import { BucketDataDocumentV3, ObjectStorageUsageDocument } from '../models.js';
|
|
8
|
+
import { VersionedPowerSyncMongoV3 } from '../VersionedPowerSyncMongoV3.js';
|
|
9
|
+
|
|
10
|
+
export const OBJECT_STORAGE_USAGE_BASE_WRITER_ID = '__base__';
|
|
11
|
+
export const DEFAULT_OBJECT_STORAGE_USAGE_STALE_WRITER_MS = 30 * 60 * 1000;
|
|
12
|
+
export const DEFAULT_OBJECT_STORAGE_USAGE_FOLD_LIMIT = 100;
|
|
13
|
+
|
|
14
|
+
export interface ReplicationStreamObjectStorageDefinitionUsageResult {
|
|
15
|
+
replication_stream_id: number;
|
|
16
|
+
definition_id: BucketDefinitionId;
|
|
17
|
+
active_bytes: bigint;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function createObjectStorageUsageWriterId(): string {
|
|
21
|
+
return randomUUID();
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/**
|
|
25
|
+
* Tracks active S3 references. Values are signed writer-local deltas, so reference changes and
|
|
26
|
+
* accounting updates can be committed together without making replication and compaction share a
|
|
27
|
+
* hot counter document.
|
|
28
|
+
*/
|
|
29
|
+
export class ObjectStorageUsage {
|
|
30
|
+
constructor(
|
|
31
|
+
private readonly db: VersionedPowerSyncMongoV3,
|
|
32
|
+
private readonly replicationStreamId: number,
|
|
33
|
+
readonly writerId?: string
|
|
34
|
+
) {
|
|
35
|
+
if (writerId === OBJECT_STORAGE_USAGE_BASE_WRITER_ID) {
|
|
36
|
+
throw new ReplicationAssertionError('The reserved object-storage usage writer id cannot be used by a writer');
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
static bytes(document: Pick<BucketDataDocumentV3, 'storage_ref'>): bigint {
|
|
41
|
+
return BigInt(document.storage_ref?.file_size ?? 0);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
static async readAllDefinitionUsage(
|
|
45
|
+
db: VersionedPowerSyncMongoV3
|
|
46
|
+
): Promise<ReplicationStreamObjectStorageDefinitionUsageResult[]> {
|
|
47
|
+
return db.client.withSession({ snapshot: true }, async (session) => {
|
|
48
|
+
// Deliberately read the whole usage collection. The number of replication streams is
|
|
49
|
+
// expected to stay low, so a collection scan is cheaper than issuing one _id range query
|
|
50
|
+
// per stream (and the usage collection is bounded by streams and writers, not buckets).
|
|
51
|
+
const entries = await db.objectStorageUsage
|
|
52
|
+
.aggregate<{
|
|
53
|
+
_id: { replication_stream_id: number; definition_id: BucketDefinitionId };
|
|
54
|
+
active_bytes: bigint;
|
|
55
|
+
}>(
|
|
56
|
+
[
|
|
57
|
+
{ $project: { replication_stream_id: '$_id.g', definitions: { $objectToArray: '$definitions' } } },
|
|
58
|
+
{ $unwind: '$definitions' },
|
|
59
|
+
{
|
|
60
|
+
$group: {
|
|
61
|
+
_id: {
|
|
62
|
+
replication_stream_id: '$replication_stream_id',
|
|
63
|
+
definition_id: '$definitions.k'
|
|
64
|
+
},
|
|
65
|
+
active_bytes: { $sum: '$definitions.v' }
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
],
|
|
69
|
+
{ session, readConcern: 'snapshot' }
|
|
70
|
+
)
|
|
71
|
+
.toArray()
|
|
72
|
+
.catch((error) => {
|
|
73
|
+
if (lib_mongo.isMongoNamespaceNotFoundError(error)) {
|
|
74
|
+
return [];
|
|
75
|
+
}
|
|
76
|
+
throw error;
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
return entries.map((entry) => {
|
|
80
|
+
const activeBytes = BigInt(entry.active_bytes ?? 0);
|
|
81
|
+
if (activeBytes < 0n) {
|
|
82
|
+
return {
|
|
83
|
+
replication_stream_id: entry._id.replication_stream_id,
|
|
84
|
+
definition_id: entry._id.definition_id,
|
|
85
|
+
active_bytes: 0n
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
return {
|
|
89
|
+
replication_stream_id: entry._id.replication_stream_id,
|
|
90
|
+
definition_id: entry._id.definition_id,
|
|
91
|
+
active_bytes: activeBytes
|
|
92
|
+
};
|
|
93
|
+
});
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
applyDelta(definitionId: BucketDefinitionId, delta: bigint, writes: MongoWriteBatch): void {
|
|
98
|
+
this.applyDeltas(new Map([[definitionId, delta]]), writes);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
applyDeltas(deltas: ReadonlyMap<BucketDefinitionId, bigint>, writes: MongoWriteBatch): void {
|
|
102
|
+
if (this.writerId == null) {
|
|
103
|
+
throw new ReplicationAssertionError('A writer id is required to apply object-storage usage');
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
const increments: Record<string, bigint> = {};
|
|
107
|
+
for (const [definitionId, delta] of deltas) {
|
|
108
|
+
if (delta === 0n) {
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
increments[this.definitionPath(definitionId)] = delta;
|
|
112
|
+
}
|
|
113
|
+
if (Object.keys(increments).length === 0) {
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
writes.updateOne(
|
|
118
|
+
this.db.objectStorageUsage,
|
|
119
|
+
{ _id: this.documentId() },
|
|
120
|
+
{ $inc: increments, $currentDate: { updated_at: true } },
|
|
121
|
+
{ upsert: true }
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
async removeDefinition(definitionId: BucketDefinitionId, session: mongo.ClientSession): Promise<void> {
|
|
126
|
+
await this.db.objectStorageUsage.updateMany(
|
|
127
|
+
{ '_id.g': this.replicationStreamId },
|
|
128
|
+
{ $unset: { [this.definitionPath(definitionId)]: 1 } },
|
|
129
|
+
{ session }
|
|
130
|
+
);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async removeStream(session: mongo.ClientSession): Promise<void> {
|
|
134
|
+
await this.db.objectStorageUsage.deleteMany({ '_id.g': this.replicationStreamId }, { session });
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
async foldStaleWriterDeltas(
|
|
138
|
+
options: {
|
|
139
|
+
staleWriterMs?: number;
|
|
140
|
+
limit?: number;
|
|
141
|
+
} = {}
|
|
142
|
+
): Promise<void> {
|
|
143
|
+
const staleWriterMs = options.staleWriterMs ?? DEFAULT_OBJECT_STORAGE_USAGE_STALE_WRITER_MS;
|
|
144
|
+
const limit = options.limit ?? DEFAULT_OBJECT_STORAGE_USAGE_FOLD_LIMIT;
|
|
145
|
+
if (limit <= 0) {
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
await this.db.client.withSession((session) =>
|
|
150
|
+
session.withTransaction(
|
|
151
|
+
async () => {
|
|
152
|
+
const staleDocuments = await this.db.objectStorageUsage
|
|
153
|
+
.find(
|
|
154
|
+
{
|
|
155
|
+
'_id.g': this.replicationStreamId,
|
|
156
|
+
'_id.w': { $ne: OBJECT_STORAGE_USAGE_BASE_WRITER_ID },
|
|
157
|
+
$expr: {
|
|
158
|
+
$lt: [
|
|
159
|
+
'$updated_at',
|
|
160
|
+
{ $dateSubtract: { startDate: '$$NOW', unit: 'millisecond', amount: staleWriterMs } }
|
|
161
|
+
]
|
|
162
|
+
}
|
|
163
|
+
},
|
|
164
|
+
{ session, sort: { updated_at: 1 }, limit }
|
|
165
|
+
)
|
|
166
|
+
.toArray();
|
|
167
|
+
if (staleDocuments.length === 0) {
|
|
168
|
+
return;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const deltas = new Map<BucketDefinitionId, bigint>();
|
|
172
|
+
for (const document of staleDocuments) {
|
|
173
|
+
for (const [definitionId, delta] of Object.entries(document.definitions ?? {})) {
|
|
174
|
+
this.validateDefinitionId(definitionId);
|
|
175
|
+
deltas.set(definitionId, (deltas.get(definitionId) ?? 0n) + BigInt(delta));
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
const increments: Record<string, bigint> = {};
|
|
180
|
+
for (const [definitionId, delta] of deltas) {
|
|
181
|
+
if (delta !== 0n) {
|
|
182
|
+
increments[this.definitionPath(definitionId)] = delta;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
if (Object.keys(increments).length > 0) {
|
|
186
|
+
await this.db.objectStorageUsage.updateOne(
|
|
187
|
+
{ _id: this.documentId(OBJECT_STORAGE_USAGE_BASE_WRITER_ID) },
|
|
188
|
+
{
|
|
189
|
+
$inc: increments,
|
|
190
|
+
$currentDate: { updated_at: true }
|
|
191
|
+
},
|
|
192
|
+
{ upsert: true, session }
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
await this.db.objectStorageUsage.deleteMany(
|
|
197
|
+
{
|
|
198
|
+
'_id.g': this.replicationStreamId,
|
|
199
|
+
_id: { $in: staleDocuments.map((document) => document._id) }
|
|
200
|
+
},
|
|
201
|
+
{ session }
|
|
202
|
+
);
|
|
203
|
+
},
|
|
204
|
+
{ readConcern: { level: 'snapshot' }, writeConcern: { w: 'majority' } }
|
|
205
|
+
)
|
|
206
|
+
);
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
private definitionPath(definitionId: BucketDefinitionId): string {
|
|
210
|
+
this.validateDefinitionId(definitionId);
|
|
211
|
+
return `definitions.${definitionId}`;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
private documentId(writerId = this.writerId): ObjectStorageUsageDocument['_id'] {
|
|
215
|
+
if (writerId == null) {
|
|
216
|
+
throw new ReplicationAssertionError('A writer id is required to identify object-storage usage');
|
|
217
|
+
}
|
|
218
|
+
return {
|
|
219
|
+
g: this.replicationStreamId,
|
|
220
|
+
w: writerId
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
private validateDefinitionId(definitionId: BucketDefinitionId): void {
|
|
225
|
+
if (definitionId.length === 0 || definitionId.includes('.') || definitionId.includes('$')) {
|
|
226
|
+
throw new ReplicationAssertionError(`Invalid bucket definition id for object-storage usage: ${definitionId}`);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|