@powersync/service-module-mongodb-storage 0.18.2 → 0.19.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 +48 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.d.ts +3 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js +41 -0
- package/dist/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.js.map +1 -0
- package/dist/storage/MongoBucketStorage.d.ts +5 -1
- package/dist/storage/MongoBucketStorage.js +5 -1
- package/dist/storage/MongoBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoBucketBatch.d.ts +3 -0
- package/dist/storage/implementation/MongoBucketBatch.js.map +1 -1
- package/dist/storage/implementation/MongoChecksums.d.ts +2 -0
- package/dist/storage/implementation/MongoChecksums.js +3 -2
- package/dist/storage/implementation/MongoChecksums.js.map +1 -1
- package/dist/storage/implementation/MongoCompactor.d.ts +15 -19
- package/dist/storage/implementation/MongoCompactor.js +68 -260
- package/dist/storage/implementation/MongoCompactor.js.map +1 -1
- package/dist/storage/implementation/MongoStorageProvider.js +26 -3
- package/dist/storage/implementation/MongoStorageProvider.js.map +1 -1
- package/dist/storage/implementation/MongoSyncBucketStorage.d.ts +12 -5
- package/dist/storage/implementation/MongoSyncBucketStorage.js +25 -14
- package/dist/storage/implementation/MongoSyncBucketStorage.js.map +1 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.d.ts +2 -1
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js +146 -34
- package/dist/storage/implementation/MongoWriteCheckpointAPI.js.map +1 -1
- package/dist/storage/implementation/common/MongoSyncBucketStorageCheckpoint.d.ts +2 -0
- package/dist/storage/implementation/common/PersistedBatch.d.ts +6 -0
- package/dist/storage/implementation/common/PersistedBatch.js +15 -8
- package/dist/storage/implementation/common/PersistedBatch.js.map +1 -1
- package/dist/storage/implementation/models.d.ts +16 -0
- package/dist/storage/implementation/models.js.map +1 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.d.ts +11 -2
- package/dist/storage/implementation/v1/MongoCompactorV1.js +250 -1
- package/dist/storage/implementation/v1/MongoCompactorV1.js.map +1 -1
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js +20 -11
- package/dist/storage/implementation/v1/MongoSyncBucketStorageV1.js.map +1 -1
- package/dist/storage/implementation/v1/SingleBucketStoreV1.d.ts +9 -10
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js +2 -8
- package/dist/storage/implementation/v1/SingleBucketStoreV1.js.map +1 -1
- package/dist/storage/implementation/v3/BucketDataContextV3.d.ts +13 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js +23 -0
- package/dist/storage/implementation/v3/BucketDataContextV3.js.map +1 -0
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js +3 -1
- package/dist/storage/implementation/v3/MongoBucketBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoChecksumsV3.d.ts +1 -0
- package/dist/storage/implementation/v3/MongoChecksumsV3.js +59 -103
- package/dist/storage/implementation/v3/MongoChecksumsV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoCompactorV3.d.ts +23 -2
- package/dist/storage/implementation/v3/MongoCompactorV3.js +354 -219
- package/dist/storage/implementation/v3/MongoCompactorV3.js.map +1 -1
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.d.ts +5 -0
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js +12 -11
- package/dist/storage/implementation/v3/MongoStoppedSyncConfigCleanup.js.map +1 -1
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.d.ts +5 -3
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js +109 -106
- package/dist/storage/implementation/v3/MongoSyncBucketStorageV3.js.map +1 -1
- package/dist/storage/implementation/v3/PersistedBatchV3.d.ts +5 -2
- package/dist/storage/implementation/v3/PersistedBatchV3.js +53 -15
- package/dist/storage/implementation/v3/PersistedBatchV3.js.map +1 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.d.ts +2 -1
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js +3 -0
- package/dist/storage/implementation/v3/VersionedPowerSyncMongoV3.js.map +1 -1
- package/dist/storage/implementation/v3/bucket-format.d.ts +10 -1
- package/dist/storage/implementation/v3/bucket-format.js +28 -9
- package/dist/storage/implementation/v3/bucket-format.js.map +1 -1
- package/dist/storage/implementation/v3/chunking.js +3 -1
- package/dist/storage/implementation/v3/chunking.js.map +1 -1
- package/dist/storage/implementation/v3/models.d.ts +38 -1
- package/dist/storage/implementation/v3/models.js.map +1 -1
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.d.ts +21 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js +44 -0
- package/dist/storage/implementation/v3/object-storage/BucketDataObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.d.ts +30 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js +12 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorage.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.d.ts +70 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js +146 -0
- package/dist/storage/implementation/v3/object-storage/ObjectStorageLifecycle.js.map +1 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.d.ts +42 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js +285 -0
- package/dist/storage/implementation/v3/object-storage/S3ObjectStorage.js.map +1 -0
- package/dist/types/types.d.ts +35 -0
- package/dist/types/types.js +35 -1
- package/dist/types/types.js.map +1 -1
- package/dist/utils/test-utils.js +3 -1
- package/dist/utils/test-utils.js.map +1 -1
- package/dist/utils/util.d.ts +8 -1
- package/dist/utils/util.js +125 -2
- package/dist/utils/util.js.map +1 -1
- package/package.json +10 -7
- package/src/migrations/db/migrations/1782950400000-checkpoint-requested-at-index.ts +55 -0
- package/src/storage/MongoBucketStorage.ts +10 -2
- package/src/storage/implementation/MongoBucketBatch.ts +4 -0
- package/src/storage/implementation/MongoChecksums.ts +5 -2
- package/src/storage/implementation/MongoCompactor.ts +90 -314
- package/src/storage/implementation/MongoStorageProvider.ts +32 -3
- package/src/storage/implementation/MongoSyncBucketStorage.ts +34 -24
- package/src/storage/implementation/MongoWriteCheckpointAPI.ts +164 -37
- package/src/storage/implementation/common/MongoSyncBucketStorageCheckpoint.ts +2 -0
- package/src/storage/implementation/common/PersistedBatch.ts +22 -10
- package/src/storage/implementation/models.ts +16 -0
- package/src/storage/implementation/v1/MongoCompactorV1.ts +292 -8
- package/src/storage/implementation/v1/MongoSyncBucketStorageV1.ts +54 -40
- package/src/storage/implementation/v1/SingleBucketStoreV1.ts +18 -27
- package/src/storage/implementation/v3/BucketDataContextV3.ts +32 -0
- package/src/storage/implementation/v3/MongoBucketBatchV3.ts +3 -1
- package/src/storage/implementation/v3/MongoChecksumsV3.ts +75 -105
- package/src/storage/implementation/v3/MongoCompactorV3.ts +456 -258
- package/src/storage/implementation/v3/MongoStoppedSyncConfigCleanup.ts +21 -19
- package/src/storage/implementation/v3/MongoSyncBucketStorageV3.ts +123 -126
- package/src/storage/implementation/v3/PersistedBatchV3.ts +69 -16
- package/src/storage/implementation/v3/VersionedPowerSyncMongoV3.ts +5 -0
- package/src/storage/implementation/v3/bucket-format.ts +36 -9
- package/src/storage/implementation/v3/chunking.ts +3 -1
- package/src/storage/implementation/v3/models.ts +40 -1
- package/src/storage/implementation/v3/object-storage/BucketDataObjectStorage.ts +54 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorage.ts +29 -0
- package/src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts +171 -0
- package/src/storage/implementation/v3/object-storage/S3ObjectStorage.ts +343 -0
- package/src/types/types.ts +42 -1
- package/src/utils/test-utils.ts +3 -1
- package/src/utils/util.ts +182 -4
- package/test/src/__snapshots__/storage_sync.test.ts.snap +1 -583
- package/test/src/cleanup-stopped-sync-configs.test.ts +24 -1
- package/test/src/helpers/MemoryObjectStorage.ts +53 -0
- package/test/src/helpers/s3TestFactory.ts +69 -0
- package/test/src/object_storage_lifecycle.test.ts +59 -0
- package/test/src/setup.ts +6 -1
- package/test/src/storage.test.ts +216 -2
- package/test/src/storage_compacting.test.ts +176 -187
- package/test/src/storage_object_storage_inline_threshold.test.ts +116 -0
- package/test/src/storage_s3_checksums.test.ts +191 -0
- package/test/src/storage_s3_compaction_lifecycle.test.ts +417 -0
- package/test/src/storage_s3_reading.test.ts +526 -0
- package/test/src/storage_s3_writing.test.ts +135 -0
- package/test/src/storage_sync.test.ts +95 -5
- package/tsconfig.tsbuildinfo +1 -1
- package/dist/storage/implementation/common/SingleBucketStore.d.ts +0 -54
- package/dist/storage/implementation/common/SingleBucketStore.js +0 -3
- package/dist/storage/implementation/common/SingleBucketStore.js.map +0 -1
- package/dist/storage/implementation/v3/SingleBucketStoreV3.d.ts +0 -18
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js +0 -84
- package/dist/storage/implementation/v3/SingleBucketStoreV3.js.map +0 -1
- package/src/storage/implementation/common/SingleBucketStore.ts +0 -63
- package/src/storage/implementation/v3/SingleBucketStoreV3.ts +0 -108
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
import { mongo } from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { Logger } from '@powersync/lib-services-framework';
|
|
3
|
+
import * as bson from 'bson';
|
|
4
|
+
import { VersionedPowerSyncMongoV3 } from '../VersionedPowerSyncMongoV3.js';
|
|
5
|
+
import { BucketDataObjectStorage } from './BucketDataObjectStorage.js';
|
|
6
|
+
import { ObjectStorage } from './ObjectStorage.js';
|
|
7
|
+
export declare const OBJECT_STORAGE_UPLOAD_LEASE_MS: number;
|
|
8
|
+
export declare const OBJECT_STORAGE_PUBLICATION_SAFETY_MARGIN_MS: number;
|
|
9
|
+
export declare const OBJECT_STORAGE_REFERENCE_GRACE_MS: number;
|
|
10
|
+
export interface PreparedObjectStorageUpload {
|
|
11
|
+
markerId: bson.ObjectId;
|
|
12
|
+
path: string;
|
|
13
|
+
deleteAfter: Date;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Coordinates the MongoDB outbox used to make object-storage changes recoverable.
|
|
17
|
+
*
|
|
18
|
+
* A MongoDB collection is used to track files that will be uploaded, or should be deleted.
|
|
19
|
+
* By using a MongoDB collection, we can use the same transaction that creates or removes
|
|
20
|
+
* references to these files, making the operations atomic.
|
|
21
|
+
*
|
|
22
|
+
* These markers indicate which files should be deleted - either by being orphaned during create,
|
|
23
|
+
* or after references to them are removed.
|
|
24
|
+
*/
|
|
25
|
+
export declare class ObjectStorageLifecycle {
|
|
26
|
+
private readonly db;
|
|
27
|
+
private readonly replicationStreamId;
|
|
28
|
+
private readonly objectStorage;
|
|
29
|
+
readonly bucketData: BucketDataObjectStorage;
|
|
30
|
+
constructor(db: VersionedPowerSyncMongoV3, replicationStreamId: number, objectStorage: ObjectStorage);
|
|
31
|
+
streamPrefix(): string;
|
|
32
|
+
definitionPrefix(definitionId: string): string;
|
|
33
|
+
allocatePath(definitionId: string, bucket: string, minOp: bigint, maxOp: bigint): string;
|
|
34
|
+
/**
|
|
35
|
+
* Indicate that files will be uploaded to S3.
|
|
36
|
+
*
|
|
37
|
+
* These markers are expected to be removed using publishUploads(). If that does not happen within
|
|
38
|
+
* the grace period, the files will be deleted.
|
|
39
|
+
*/
|
|
40
|
+
prepareUploads(paths: string[], now?: Date): Promise<PreparedObjectStorageUpload[]>;
|
|
41
|
+
canPublish(upload: PreparedObjectStorageUpload, now?: Date): boolean;
|
|
42
|
+
/**
|
|
43
|
+
* Remove markers for files that have finished uploading, and are now referenced from other documents.
|
|
44
|
+
*
|
|
45
|
+
* Call this in the same transaction as the one creating the references.
|
|
46
|
+
*/
|
|
47
|
+
publishUploads(uploads: PreparedObjectStorageUpload[], session: mongo.ClientSession): Promise<void>;
|
|
48
|
+
/**
|
|
49
|
+
* Mark old paths for deletion.
|
|
50
|
+
*
|
|
51
|
+
* By marking for deletion instead of immediately deleting:
|
|
52
|
+
* 1. This avoids inconsistencies/orphaned files between MongoDB and S3, by allowing the markers to
|
|
53
|
+
* be persisted transactionally, while the deletes happen asynchronously and can be retried.
|
|
54
|
+
* 2. The deletes are delayed, giving clients a grace period to continue using the files for
|
|
55
|
+
* in-progress requests.
|
|
56
|
+
*
|
|
57
|
+
* Call this in the same transaction as the one removing the references to these files.
|
|
58
|
+
*/
|
|
59
|
+
retire(paths: Iterable<string>, session: mongo.ClientSession, now?: Date): Promise<void>;
|
|
60
|
+
/**
|
|
61
|
+
* Discover and delete objects by their storage prefix
|
|
62
|
+
* without touching the database.
|
|
63
|
+
*/
|
|
64
|
+
deletePrefix(prefix: string, options?: {
|
|
65
|
+
signal?: AbortSignal;
|
|
66
|
+
}): Promise<{
|
|
67
|
+
objectCount: number;
|
|
68
|
+
}>;
|
|
69
|
+
cleanup(logger: Logger): Promise<void>;
|
|
70
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import * as bson from 'bson';
|
|
2
|
+
import { createHash, randomUUID } from 'node:crypto';
|
|
3
|
+
import { BucketDataObjectStorage } from './BucketDataObjectStorage.js';
|
|
4
|
+
export const OBJECT_STORAGE_UPLOAD_LEASE_MS = 60 * 60 * 1000;
|
|
5
|
+
export const OBJECT_STORAGE_PUBLICATION_SAFETY_MARGIN_MS = 2 * 60 * 1000;
|
|
6
|
+
export const OBJECT_STORAGE_REFERENCE_GRACE_MS = 15 * 60 * 1000;
|
|
7
|
+
/**
|
|
8
|
+
* Protect against arbitrary-long bucket names and parameters exceeding the S3 key length limits.
|
|
9
|
+
*
|
|
10
|
+
* Any bucket name over this length is hashed.
|
|
11
|
+
*/
|
|
12
|
+
const MAX_READABLE_BUCKET_SEGMENT_BYTES = 128;
|
|
13
|
+
/**
|
|
14
|
+
* Coordinates the MongoDB outbox used to make object-storage changes recoverable.
|
|
15
|
+
*
|
|
16
|
+
* A MongoDB collection is used to track files that will be uploaded, or should be deleted.
|
|
17
|
+
* By using a MongoDB collection, we can use the same transaction that creates or removes
|
|
18
|
+
* references to these files, making the operations atomic.
|
|
19
|
+
*
|
|
20
|
+
* These markers indicate which files should be deleted - either by being orphaned during create,
|
|
21
|
+
* or after references to them are removed.
|
|
22
|
+
*/
|
|
23
|
+
export class ObjectStorageLifecycle {
|
|
24
|
+
db;
|
|
25
|
+
replicationStreamId;
|
|
26
|
+
objectStorage;
|
|
27
|
+
bucketData;
|
|
28
|
+
constructor(db, replicationStreamId, objectStorage) {
|
|
29
|
+
this.db = db;
|
|
30
|
+
this.replicationStreamId = replicationStreamId;
|
|
31
|
+
this.objectStorage = objectStorage;
|
|
32
|
+
this.bucketData = new BucketDataObjectStorage(objectStorage);
|
|
33
|
+
}
|
|
34
|
+
streamPrefix() {
|
|
35
|
+
return `bucket-data/${this.replicationStreamId}/`;
|
|
36
|
+
}
|
|
37
|
+
definitionPrefix(definitionId) {
|
|
38
|
+
return `${this.streamPrefix()}${definitionId}/`;
|
|
39
|
+
}
|
|
40
|
+
allocatePath(definitionId, bucket, minOp, maxOp) {
|
|
41
|
+
// Preserve readable paths for normal buckets, but bound user-controlled
|
|
42
|
+
// parameter values so they cannot consume the S3 key-length budget.
|
|
43
|
+
const escapedBucket = encodeURIComponent(bucket);
|
|
44
|
+
const bucketSegment = Buffer.byteLength(escapedBucket, 'utf8') <= MAX_READABLE_BUCKET_SEGMENT_BYTES
|
|
45
|
+
? escapedBucket
|
|
46
|
+
: `sha256-${createHash('sha256').update(bucket).digest('base64url')}`;
|
|
47
|
+
return `${this.definitionPrefix(definitionId)}${bucketSegment}/${minOp}-${maxOp}-${randomUUID()}.bson`;
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* Indicate that files will be uploaded to S3.
|
|
51
|
+
*
|
|
52
|
+
* These markers are expected to be removed using publishUploads(). If that does not happen within
|
|
53
|
+
* the grace period, the files will be deleted.
|
|
54
|
+
*/
|
|
55
|
+
async prepareUploads(paths, now = new Date()) {
|
|
56
|
+
const deleteAfter = new Date(now.getTime() + OBJECT_STORAGE_UPLOAD_LEASE_MS);
|
|
57
|
+
const uploads = paths.map((path) => ({ markerId: new bson.ObjectId(), path, deleteAfter }));
|
|
58
|
+
if (uploads.length) {
|
|
59
|
+
await this.db.pendingObjectStorageDeletes(this.replicationStreamId).insertMany(uploads.map((upload) => ({
|
|
60
|
+
_id: upload.markerId,
|
|
61
|
+
path: upload.path,
|
|
62
|
+
delete_after: upload.deleteAfter
|
|
63
|
+
})));
|
|
64
|
+
}
|
|
65
|
+
return uploads;
|
|
66
|
+
}
|
|
67
|
+
canPublish(upload, now = new Date()) {
|
|
68
|
+
return now.getTime() < upload.deleteAfter.getTime() - OBJECT_STORAGE_PUBLICATION_SAFETY_MARGIN_MS;
|
|
69
|
+
}
|
|
70
|
+
/**
|
|
71
|
+
* Remove markers for files that have finished uploading, and are now referenced from other documents.
|
|
72
|
+
*
|
|
73
|
+
* Call this in the same transaction as the one creating the references.
|
|
74
|
+
*/
|
|
75
|
+
async publishUploads(uploads, session) {
|
|
76
|
+
for (const upload of uploads) {
|
|
77
|
+
if (!this.canPublish(upload)) {
|
|
78
|
+
throw new Error(`Object storage publication lease expired for ${upload.path}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
if (uploads.length === 0) {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
const result = await this.db
|
|
85
|
+
.pendingObjectStorageDeletes(this.replicationStreamId)
|
|
86
|
+
.deleteMany({ _id: { $in: uploads.map((upload) => upload.markerId) } }, { session });
|
|
87
|
+
if (result.deletedCount !== uploads.length) {
|
|
88
|
+
throw new Error(`Missing object storage publication markers`);
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Mark old paths for deletion.
|
|
93
|
+
*
|
|
94
|
+
* By marking for deletion instead of immediately deleting:
|
|
95
|
+
* 1. This avoids inconsistencies/orphaned files between MongoDB and S3, by allowing the markers to
|
|
96
|
+
* be persisted transactionally, while the deletes happen asynchronously and can be retried.
|
|
97
|
+
* 2. The deletes are delayed, giving clients a grace period to continue using the files for
|
|
98
|
+
* in-progress requests.
|
|
99
|
+
*
|
|
100
|
+
* Call this in the same transaction as the one removing the references to these files.
|
|
101
|
+
*/
|
|
102
|
+
async retire(paths, session, now = new Date()) {
|
|
103
|
+
const markers = Array.from(paths, (path) => ({
|
|
104
|
+
_id: new bson.ObjectId(),
|
|
105
|
+
path,
|
|
106
|
+
delete_after: new Date(now.getTime() + OBJECT_STORAGE_REFERENCE_GRACE_MS)
|
|
107
|
+
}));
|
|
108
|
+
if (markers.length) {
|
|
109
|
+
await this.db.pendingObjectStorageDeletes(this.replicationStreamId).insertMany(markers, { session });
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Discover and delete objects by their storage prefix
|
|
114
|
+
* without touching the database.
|
|
115
|
+
*/
|
|
116
|
+
async deletePrefix(prefix, options) {
|
|
117
|
+
return this.objectStorage.deletePrefix(prefix, { signal: options?.signal });
|
|
118
|
+
}
|
|
119
|
+
async cleanup(logger) {
|
|
120
|
+
const markers = this.db.pendingObjectStorageDeletes(this.replicationStreamId);
|
|
121
|
+
const cursor = markers.find({ delete_after: { $lte: new Date() } });
|
|
122
|
+
let batch = [];
|
|
123
|
+
const deleteBatch = async () => {
|
|
124
|
+
if (batch.length === 0) {
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
const deleting = batch;
|
|
128
|
+
batch = [];
|
|
129
|
+
try {
|
|
130
|
+
await this.bucketData.delete(deleting.map((marker) => marker.path));
|
|
131
|
+
await markers.deleteMany({ _id: { $in: deleting.map((marker) => marker._id) } });
|
|
132
|
+
}
|
|
133
|
+
catch (error) {
|
|
134
|
+
logger.warn('Failed to clean up object storage deletion markers; will retry during the next compaction', error);
|
|
135
|
+
}
|
|
136
|
+
};
|
|
137
|
+
for await (const marker of cursor) {
|
|
138
|
+
batch.push(marker);
|
|
139
|
+
if (batch.length === 500) {
|
|
140
|
+
await deleteBatch();
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
await deleteBatch();
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=ObjectStorageLifecycle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ObjectStorageLifecycle.js","sourceRoot":"","sources":["../../../../../src/storage/implementation/v3/object-storage/ObjectStorageLifecycle.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { type ObjectStorage, type ObjectStoragePutMetadata } from './ObjectStorage.js';
|
|
3
|
+
export interface S3ObjectStorageOptions {
|
|
4
|
+
bucket: string;
|
|
5
|
+
region?: string;
|
|
6
|
+
prefix?: string;
|
|
7
|
+
endpoint?: string;
|
|
8
|
+
forcePathStyle?: boolean;
|
|
9
|
+
accessKeyId?: string;
|
|
10
|
+
secretAccessKey?: string;
|
|
11
|
+
concurrencyLimit?: number;
|
|
12
|
+
}
|
|
13
|
+
export declare class S3ObjectStorage implements ObjectStorage {
|
|
14
|
+
/**
|
|
15
|
+
* Public for tests only.
|
|
16
|
+
*/
|
|
17
|
+
readonly client: S3Client;
|
|
18
|
+
private bucket;
|
|
19
|
+
private prefix;
|
|
20
|
+
private readonly operationSemaphore;
|
|
21
|
+
constructor(options: S3ObjectStorageOptions);
|
|
22
|
+
put(path: string, data: Uint8Array, metadata: ObjectStoragePutMetadata): Promise<void>;
|
|
23
|
+
get(path: string, options?: {
|
|
24
|
+
signal?: AbortSignal;
|
|
25
|
+
}): Promise<{
|
|
26
|
+
data: Uint8Array;
|
|
27
|
+
metadata: ObjectStoragePutMetadata;
|
|
28
|
+
}>;
|
|
29
|
+
list(prefix: string, options?: {
|
|
30
|
+
signal?: AbortSignal;
|
|
31
|
+
}): AsyncIterable<string>;
|
|
32
|
+
delete(paths: string[]): Promise<void>;
|
|
33
|
+
deletePrefix(prefix: string, options?: {
|
|
34
|
+
signal?: AbortSignal;
|
|
35
|
+
}): Promise<{
|
|
36
|
+
objectCount: number;
|
|
37
|
+
}>;
|
|
38
|
+
private listPage;
|
|
39
|
+
private deleteFullPaths;
|
|
40
|
+
private fullPath;
|
|
41
|
+
private withOperation;
|
|
42
|
+
}
|
|
@@ -0,0 +1,285 @@
|
|
|
1
|
+
import { DeleteObjectsCommand, GetObjectCommand, ListObjectsV2Command, PutObjectCommand, S3Client } from '@aws-sdk/client-s3';
|
|
2
|
+
import { acquireSemaphoreAbortable, isAbortError } from '@powersync/service-core';
|
|
3
|
+
import { isThrottlingError, isTransientError } from '@smithy/core/retry';
|
|
4
|
+
import { Semaphore } from 'async-mutex';
|
|
5
|
+
import { ObjectStorageError } from './ObjectStorage.js';
|
|
6
|
+
const DEFAULT_S3_OPERATION_CONCURRENCY = 16;
|
|
7
|
+
const S3_DELETE_PREFIX_BATCH_SIZE = 1000;
|
|
8
|
+
/**
|
|
9
|
+
* Slightly smaller than DEFAULT_S3_OPERATION_CONCURRENCY.
|
|
10
|
+
*/
|
|
11
|
+
const S3_DELETE_PREFIX_CONCURRENCY = 12;
|
|
12
|
+
const MAX_S3_PREFIX_BYTES = 256;
|
|
13
|
+
const SAFE_S3_KEY_BYTES = 896;
|
|
14
|
+
export class S3ObjectStorage {
|
|
15
|
+
/**
|
|
16
|
+
* Public for tests only.
|
|
17
|
+
*/
|
|
18
|
+
client;
|
|
19
|
+
bucket;
|
|
20
|
+
prefix;
|
|
21
|
+
operationSemaphore;
|
|
22
|
+
constructor(options) {
|
|
23
|
+
const concurrencyLimit = options.concurrencyLimit ?? DEFAULT_S3_OPERATION_CONCURRENCY;
|
|
24
|
+
if (!Number.isInteger(concurrencyLimit) || concurrencyLimit <= 0) {
|
|
25
|
+
throw new Error('S3 object storage concurrencyLimit must be a positive integer');
|
|
26
|
+
}
|
|
27
|
+
if ((options.accessKeyId == null) !== (options.secretAccessKey == null)) {
|
|
28
|
+
throw new Error('S3 object storage accessKeyId and secretAccessKey must be configured together');
|
|
29
|
+
}
|
|
30
|
+
const prefix = options.prefix ?? '';
|
|
31
|
+
if (Buffer.byteLength(prefix, 'utf8') > MAX_S3_PREFIX_BYTES) {
|
|
32
|
+
throw new Error(`S3 object storage prefix must be at most ${MAX_S3_PREFIX_BYTES} UTF-8 bytes`);
|
|
33
|
+
}
|
|
34
|
+
if (prefix.endsWith('/')) {
|
|
35
|
+
throw new Error('S3 object storage prefix must not end with "/"');
|
|
36
|
+
}
|
|
37
|
+
this.bucket = options.bucket;
|
|
38
|
+
this.prefix = prefix;
|
|
39
|
+
this.operationSemaphore = new Semaphore(concurrencyLimit);
|
|
40
|
+
this.client = new S3Client({
|
|
41
|
+
region: options.region,
|
|
42
|
+
endpoint: options.endpoint,
|
|
43
|
+
forcePathStyle: options.forcePathStyle,
|
|
44
|
+
credentials: options.accessKeyId && options.secretAccessKey
|
|
45
|
+
? { accessKeyId: options.accessKeyId, secretAccessKey: options.secretAccessKey }
|
|
46
|
+
: undefined
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async put(path, data, metadata) {
|
|
50
|
+
const fullPath = this.fullPath(path);
|
|
51
|
+
await using _ = await this.withOperation();
|
|
52
|
+
try {
|
|
53
|
+
await this.client.send(new PutObjectCommand({
|
|
54
|
+
Bucket: this.bucket,
|
|
55
|
+
Key: fullPath,
|
|
56
|
+
Body: data,
|
|
57
|
+
ContentType: metadata.contentType,
|
|
58
|
+
ContentEncoding: metadata.contentEncoding ?? undefined
|
|
59
|
+
}));
|
|
60
|
+
}
|
|
61
|
+
catch (error) {
|
|
62
|
+
throw s3OperationError('upload', fullPath, error);
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
async get(path, options) {
|
|
66
|
+
const fullPath = this.fullPath(path);
|
|
67
|
+
const signal = options?.signal;
|
|
68
|
+
await using _ = await this.withOperation(signal);
|
|
69
|
+
try {
|
|
70
|
+
const response = await this.client.send(new GetObjectCommand({
|
|
71
|
+
Bucket: this.bucket,
|
|
72
|
+
Key: fullPath
|
|
73
|
+
}), { abortSignal: signal });
|
|
74
|
+
const contentLength = response.ContentLength;
|
|
75
|
+
if (contentLength == null) {
|
|
76
|
+
throw new Error(`S3 download response for ${fullPath} is missing ContentLength`);
|
|
77
|
+
}
|
|
78
|
+
if (!Number.isSafeInteger(contentLength) || contentLength < 0) {
|
|
79
|
+
throw new Error(`S3 download response for ${fullPath} has invalid ContentLength ${contentLength}`);
|
|
80
|
+
}
|
|
81
|
+
const data = new Uint8Array(contentLength);
|
|
82
|
+
let offset = 0;
|
|
83
|
+
const stream = response.Body;
|
|
84
|
+
for await (const chunk of stream) {
|
|
85
|
+
signal?.throwIfAborted();
|
|
86
|
+
const nextOffset = offset + chunk.byteLength;
|
|
87
|
+
if (nextOffset > contentLength) {
|
|
88
|
+
throw new Error(`S3 download ContentLength mismatch for ${fullPath}: expected ${contentLength} bytes, received at least ${nextOffset}`);
|
|
89
|
+
}
|
|
90
|
+
data.set(chunk, offset);
|
|
91
|
+
offset = nextOffset;
|
|
92
|
+
}
|
|
93
|
+
if (offset !== contentLength) {
|
|
94
|
+
throw new Error(`S3 download ContentLength mismatch for ${fullPath}: expected ${contentLength} bytes, received ${offset}`);
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
data,
|
|
98
|
+
metadata: {
|
|
99
|
+
contentType: response.ContentType ?? 'application/octet-stream',
|
|
100
|
+
contentEncoding: response.ContentEncoding ?? null
|
|
101
|
+
}
|
|
102
|
+
};
|
|
103
|
+
}
|
|
104
|
+
catch (err) {
|
|
105
|
+
if (err.name === 'NoSuchKey' || err.Code === 'NoSuchKey') {
|
|
106
|
+
throw new ObjectStorageError(`S3 object not found: ${fullPath}`, { cause: err, retryable: false });
|
|
107
|
+
}
|
|
108
|
+
throw s3OperationError('download', fullPath, err);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
async *list(prefix, options) {
|
|
112
|
+
const fullPrefix = this.fullPath(prefix);
|
|
113
|
+
let continuationToken;
|
|
114
|
+
const signal = options?.signal;
|
|
115
|
+
do {
|
|
116
|
+
signal?.throwIfAborted();
|
|
117
|
+
const response = await this.listPage(fullPrefix, continuationToken, signal);
|
|
118
|
+
for (const object of response.Contents ?? []) {
|
|
119
|
+
if (object.Key == null) {
|
|
120
|
+
continue;
|
|
121
|
+
}
|
|
122
|
+
yield this.prefix ? object.Key.slice(this.prefix.length + 1) : object.Key;
|
|
123
|
+
}
|
|
124
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;
|
|
125
|
+
if (response.IsTruncated && continuationToken == null) {
|
|
126
|
+
throw new Error(`S3 listing for ${fullPrefix} was truncated without a continuation token`);
|
|
127
|
+
}
|
|
128
|
+
} while (continuationToken != null);
|
|
129
|
+
}
|
|
130
|
+
async delete(paths) {
|
|
131
|
+
const fullPaths = paths.map((path) => ({ Key: this.fullPath(path) }));
|
|
132
|
+
await this.deleteFullPaths(fullPaths);
|
|
133
|
+
}
|
|
134
|
+
async deletePrefix(prefix, options) {
|
|
135
|
+
const fullPrefix = this.fullPath(prefix);
|
|
136
|
+
const signal = options?.signal;
|
|
137
|
+
let continuationToken;
|
|
138
|
+
let objectCount = 0;
|
|
139
|
+
const deleting = new Set();
|
|
140
|
+
let deleteError;
|
|
141
|
+
const scheduleDelete = (fullPaths) => {
|
|
142
|
+
let tracked;
|
|
143
|
+
tracked = this.deleteFullPaths(fullPaths, signal)
|
|
144
|
+
.catch((error) => {
|
|
145
|
+
deleteError ??= { error };
|
|
146
|
+
})
|
|
147
|
+
.finally(() => deleting.delete(tracked));
|
|
148
|
+
deleting.add(tracked);
|
|
149
|
+
};
|
|
150
|
+
const throwDeleteError = () => {
|
|
151
|
+
if (deleteError != null) {
|
|
152
|
+
throw deleteError.error;
|
|
153
|
+
}
|
|
154
|
+
};
|
|
155
|
+
let operationError;
|
|
156
|
+
try {
|
|
157
|
+
do {
|
|
158
|
+
signal?.throwIfAborted();
|
|
159
|
+
const response = await this.listPage(fullPrefix, continuationToken, signal);
|
|
160
|
+
throwDeleteError();
|
|
161
|
+
const fullPaths = [];
|
|
162
|
+
for (const object of response.Contents ?? []) {
|
|
163
|
+
if (object.Key != null) {
|
|
164
|
+
fullPaths.push({ Key: object.Key });
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
objectCount += fullPaths.length;
|
|
168
|
+
if (fullPaths.length !== 0) {
|
|
169
|
+
while (deleting.size >= S3_DELETE_PREFIX_CONCURRENCY) {
|
|
170
|
+
await Promise.race(deleting);
|
|
171
|
+
throwDeleteError();
|
|
172
|
+
}
|
|
173
|
+
scheduleDelete(fullPaths);
|
|
174
|
+
}
|
|
175
|
+
continuationToken = response.IsTruncated ? response.NextContinuationToken : undefined;
|
|
176
|
+
if (response.IsTruncated && continuationToken == null) {
|
|
177
|
+
throw new Error(`S3 listing for ${fullPrefix} was truncated without a continuation token`);
|
|
178
|
+
}
|
|
179
|
+
} while (continuationToken != null);
|
|
180
|
+
}
|
|
181
|
+
catch (error) {
|
|
182
|
+
operationError = { error };
|
|
183
|
+
}
|
|
184
|
+
await Promise.all(deleting);
|
|
185
|
+
if (operationError != null) {
|
|
186
|
+
throw operationError.error;
|
|
187
|
+
}
|
|
188
|
+
throwDeleteError();
|
|
189
|
+
return { objectCount };
|
|
190
|
+
}
|
|
191
|
+
async listPage(fullPrefix, continuationToken, signal) {
|
|
192
|
+
try {
|
|
193
|
+
await using _ = await this.withOperation(signal);
|
|
194
|
+
return await this.client.send(new ListObjectsV2Command({
|
|
195
|
+
Bucket: this.bucket,
|
|
196
|
+
Prefix: fullPrefix,
|
|
197
|
+
ContinuationToken: continuationToken,
|
|
198
|
+
MaxKeys: S3_DELETE_PREFIX_BATCH_SIZE
|
|
199
|
+
}), { abortSignal: signal });
|
|
200
|
+
}
|
|
201
|
+
catch (error) {
|
|
202
|
+
throw s3OperationError('list', fullPrefix, error);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
205
|
+
async deleteFullPaths(fullPaths, signal) {
|
|
206
|
+
if (fullPaths.length === 0)
|
|
207
|
+
return;
|
|
208
|
+
await using _ = await this.withOperation(signal);
|
|
209
|
+
let response;
|
|
210
|
+
try {
|
|
211
|
+
response = await this.client.send(new DeleteObjectsCommand({
|
|
212
|
+
Bucket: this.bucket,
|
|
213
|
+
Delete: { Objects: fullPaths, Quiet: true }
|
|
214
|
+
}), { abortSignal: signal });
|
|
215
|
+
}
|
|
216
|
+
catch (error) {
|
|
217
|
+
throw s3OperationError('delete', `${fullPaths.length} objects`, error);
|
|
218
|
+
}
|
|
219
|
+
if (response.Errors?.length) {
|
|
220
|
+
const errors = response.Errors.map((error) => `${error.Key}: ${error.Code}`).join(', ');
|
|
221
|
+
throw new ObjectStorageError(`Failed to delete S3 objects: ${errors}`, {
|
|
222
|
+
cause: response.Errors,
|
|
223
|
+
retryable: response.Errors.some((error) => isTransientS3Error({ name: error.Code }))
|
|
224
|
+
});
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
fullPath(path) {
|
|
228
|
+
const fullPath = this.prefix ? `${this.prefix}/${path}` : path;
|
|
229
|
+
const size = Buffer.byteLength(fullPath, 'utf8');
|
|
230
|
+
if (size > SAFE_S3_KEY_BYTES) {
|
|
231
|
+
throw new Error(`S3 object key is ${size} bytes, exceeding the safe limit of ${SAFE_S3_KEY_BYTES} bytes`);
|
|
232
|
+
}
|
|
233
|
+
return fullPath;
|
|
234
|
+
}
|
|
235
|
+
async withOperation(signal) {
|
|
236
|
+
signal?.throwIfAborted();
|
|
237
|
+
const acquired = signal
|
|
238
|
+
? await acquireSemaphoreAbortable(this.operationSemaphore, signal)
|
|
239
|
+
: await this.operationSemaphore.acquire();
|
|
240
|
+
if (acquired === 'aborted') {
|
|
241
|
+
signal.throwIfAborted();
|
|
242
|
+
throw new Error('S3 operation aborted while waiting for a concurrency slot');
|
|
243
|
+
}
|
|
244
|
+
const [, release] = acquired;
|
|
245
|
+
if (signal?.aborted) {
|
|
246
|
+
release();
|
|
247
|
+
signal.throwIfAborted();
|
|
248
|
+
}
|
|
249
|
+
return {
|
|
250
|
+
[Symbol.asyncDispose]: async () => release()
|
|
251
|
+
};
|
|
252
|
+
}
|
|
253
|
+
}
|
|
254
|
+
function s3OperationError(operation, target, error) {
|
|
255
|
+
if (error instanceof ObjectStorageError) {
|
|
256
|
+
return error;
|
|
257
|
+
}
|
|
258
|
+
// Preserve abort errors so callers can distinguish cancellation from an S3
|
|
259
|
+
// failure and avoid retrying work after shutdown.
|
|
260
|
+
if (isAbortError(error)) {
|
|
261
|
+
return error;
|
|
262
|
+
}
|
|
263
|
+
const detail = error instanceof Error ? `: ${error.message}` : '';
|
|
264
|
+
return new ObjectStorageError(`S3 ${operation} failed for ${target}${detail}`, {
|
|
265
|
+
cause: error,
|
|
266
|
+
retryable: isTransientS3Error(error)
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
function isTransientS3Error(error) {
|
|
270
|
+
if (typeof error != 'object' || error == null) {
|
|
271
|
+
return false;
|
|
272
|
+
}
|
|
273
|
+
if (isAbortError(error)) {
|
|
274
|
+
return false;
|
|
275
|
+
}
|
|
276
|
+
const sdkError = error;
|
|
277
|
+
// Use the same classification as the AWS SDK retry middleware. The SDK
|
|
278
|
+
// returns the final error after its own attempt/quota limits are exhausted;
|
|
279
|
+
// this tells the compactor whether restarting the larger bucket operation is
|
|
280
|
+
// still appropriate.
|
|
281
|
+
// One exception is clock skew errors: We don't treat that as retryable by us,
|
|
282
|
+
// although the SDK may internally retry them.
|
|
283
|
+
return isThrottlingError(sdkError) || isTransientError(sdkError);
|
|
284
|
+
}
|
|
285
|
+
//# sourceMappingURL=S3ObjectStorage.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"S3ObjectStorage.js","sourceRoot":"","sources":["../../../../../src/storage/implementation/v3/object-storage/S3ObjectStorage.ts"],"names":[],"mappings":"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"}
|
package/dist/types/types.d.ts
CHANGED
|
@@ -25,7 +25,42 @@ export declare const MongoStorageConfig: t.Intersection<t.Codec<{
|
|
|
25
25
|
* This is an experimental option, and may be removed in a future release.
|
|
26
26
|
*/
|
|
27
27
|
bulk_read_preference: t.OptionalCodec<t.Codec<"primary" | "primaryPreferred" | "secondary" | "secondaryPreferred" | "nearest", "primary" | "primaryPreferred" | "secondary" | "secondaryPreferred" | "nearest", string, t.CodecProps>>;
|
|
28
|
+
/**
|
|
29
|
+
* Throttle clear operations after sync config deploys, by pausing between batches.
|
|
30
|
+
* The rate produces a pause proportional to the previous batch duration.
|
|
31
|
+
*
|
|
32
|
+
* Increase this to reduce impact of clear operations on the storage cluster.
|
|
33
|
+
* Use 0 to clear as fast as possible.
|
|
34
|
+
*
|
|
35
|
+
* Defaults to 0.2.
|
|
36
|
+
*/
|
|
37
|
+
clear_batch_throttle_rate: t.OptionalCodec<t.Codec<number, number, string, t.CodecProps>>;
|
|
38
|
+
object_storage: t.OptionalCodec<t.Codec<{
|
|
39
|
+
type: "s3";
|
|
40
|
+
bucket: string;
|
|
41
|
+
region?: string | undefined;
|
|
42
|
+
prefix?: string | undefined;
|
|
43
|
+
endpoint?: string | undefined;
|
|
44
|
+
force_path_style?: boolean | undefined;
|
|
45
|
+
access_key_id?: string | undefined;
|
|
46
|
+
secret_access_key?: string | undefined;
|
|
47
|
+
concurrency_limit?: number | undefined;
|
|
48
|
+
inline_threshold_bytes?: number | undefined;
|
|
49
|
+
}, {
|
|
50
|
+
type: "s3";
|
|
51
|
+
bucket: string;
|
|
52
|
+
region?: string | undefined;
|
|
53
|
+
prefix?: string | undefined;
|
|
54
|
+
endpoint?: string | undefined;
|
|
55
|
+
force_path_style?: boolean | undefined;
|
|
56
|
+
access_key_id?: string | undefined;
|
|
57
|
+
secret_access_key?: string | undefined;
|
|
58
|
+
concurrency_limit?: number | undefined;
|
|
59
|
+
inline_threshold_bytes?: number | undefined;
|
|
60
|
+
}, string, t.CodecProps>>;
|
|
28
61
|
}>>;
|
|
29
62
|
export type MongoStorageConfig = t.Encoded<typeof MongoStorageConfig>;
|
|
30
63
|
export type MongoStorageConfigDecoded = t.Decoded<typeof MongoStorageConfig>;
|
|
64
|
+
export declare const DEFAULT_CLEAR_BATCH_THROTTLE_RATE = 0.2;
|
|
65
|
+
export declare function normalizeClearBatchThrottleRate(value: number | undefined): number;
|
|
31
66
|
export declare function isMongoStorageConfig(config: service_types.configFile.GenericStorageConfig): config is MongoStorageConfig;
|
package/dist/types/types.js
CHANGED
|
@@ -1,5 +1,20 @@
|
|
|
1
1
|
import * as lib_mongo from '@powersync/lib-service-mongodb';
|
|
2
|
+
import { ErrorCode, ServiceError } from '@powersync/lib-services-framework';
|
|
2
3
|
import * as t from 'ts-codec';
|
|
4
|
+
const S3ObjectStorageConfig = t.object({
|
|
5
|
+
type: t.literal('s3'),
|
|
6
|
+
bucket: t.string,
|
|
7
|
+
region: t.string.optional(),
|
|
8
|
+
prefix: t.string.optional(),
|
|
9
|
+
endpoint: t.string.optional(),
|
|
10
|
+
force_path_style: t.boolean.optional(),
|
|
11
|
+
access_key_id: t.string.optional(),
|
|
12
|
+
secret_access_key: t.string.optional(),
|
|
13
|
+
concurrency_limit: t.number.optional(),
|
|
14
|
+
// Chunks whose BSON-serialized size falls below this byte threshold
|
|
15
|
+
// stay inline in MongoDB instead of being offloaded to S3. Default 1024.
|
|
16
|
+
inline_threshold_bytes: t.number.optional()
|
|
17
|
+
});
|
|
3
18
|
export const MongoStorageReadPreference = t
|
|
4
19
|
.literal('primary')
|
|
5
20
|
.or(t.literal('primaryPreferred'))
|
|
@@ -14,8 +29,27 @@ export const MongoStorageConfig = lib_mongo.BaseMongoConfig.and(t.object({
|
|
|
14
29
|
*
|
|
15
30
|
* This is an experimental option, and may be removed in a future release.
|
|
16
31
|
*/
|
|
17
|
-
bulk_read_preference: MongoStorageReadPreference.optional()
|
|
32
|
+
bulk_read_preference: MongoStorageReadPreference.optional(),
|
|
33
|
+
/**
|
|
34
|
+
* Throttle clear operations after sync config deploys, by pausing between batches.
|
|
35
|
+
* The rate produces a pause proportional to the previous batch duration.
|
|
36
|
+
*
|
|
37
|
+
* Increase this to reduce impact of clear operations on the storage cluster.
|
|
38
|
+
* Use 0 to clear as fast as possible.
|
|
39
|
+
*
|
|
40
|
+
* Defaults to 0.2.
|
|
41
|
+
*/
|
|
42
|
+
clear_batch_throttle_rate: t.number.optional(),
|
|
43
|
+
object_storage: S3ObjectStorageConfig.optional()
|
|
18
44
|
}));
|
|
45
|
+
export const DEFAULT_CLEAR_BATCH_THROTTLE_RATE = 0.2;
|
|
46
|
+
export function normalizeClearBatchThrottleRate(value) {
|
|
47
|
+
const rate = value ?? DEFAULT_CLEAR_BATCH_THROTTLE_RATE;
|
|
48
|
+
if (!Number.isFinite(rate) || rate < 0 || rate > 20) {
|
|
49
|
+
throw new ServiceError(ErrorCode.PSYNC_S3201, 'storage.clear_batch_throttle_rate must be a finite number between 0 and 20');
|
|
50
|
+
}
|
|
51
|
+
return rate;
|
|
52
|
+
}
|
|
19
53
|
export function isMongoStorageConfig(config) {
|
|
20
54
|
return config.type == lib_mongo.MONGO_CONNECTION_TYPE;
|
|
21
55
|
}
|