Aller au contenu
Français

Object storage overview

Ce contenu n’est pas encore disponible dans votre langue.

The framework’s object-storage backend is an S3-compatible persistence layer. Two implementations ship:

BackendUse
FilesystemObjectStorageBackendLocal files; dev + tests.
S3ObjectStorageBackendAnything S3-compatible (AWS S3, MinIO, R2, B2).

Used by:

  • ObjectStorageDurableStateStore — durable state in cloud storage.
  • ObjectStorageSnapshotStore — snapshots in cloud storage.

Built on a small interface (PUT / GET / DELETE / LIST + CAS support); both backends speak the same surface.

You should use object storage when…
You’re cloud-native and S3 (or similar) is your storage platform.
You want shared persistence across cluster nodes without running Cassandra.
You want server-side encryption via cloud KMS.
You want infinite scaling without managing storage capacity.

For single-node deployments, SQLite is simpler. For high-throughput multi-node persistence, Cassandra is faster. Object storage sits in between — cloud-friendly, decent performance, lots of features.

import {
DurableStateOptions,
ObjectStorageDurableStateStore,
ObjectStorageDurableStateStoreOptions,
S3ObjectStorageBackend,
S3ObjectStorageOptions,
} from 'actor-ts';
const s3ObjectStorageOptions = S3ObjectStorageOptions.create()
.withRegion('eu-west-1')
.withBucket('my-app-state');
const backend = new S3ObjectStorageBackend(s3ObjectStorageOptions);
const objectStorageDurableStateStoreOptions = ObjectStorageDurableStateStoreOptions.create().withBackend(backend);
const stateStore = new ObjectStorageDurableStateStore(
objectStorageDurableStateStoreOptions,
);
const durableStateOptions = DurableStateOptions.create<State>()
.withPersistenceId(`cart-${userId}`)
.withStore(stateStore)
.withEmptyState(() => ({ items: [] }));
const cart = system.spawnAnonymous(() => new Cart(durableStateOptions));

Cart’s state lives in S3 under cart-<userId> as the object key. Reads and writes go through the S3 API.

Object keys follow a predictable layout:

state/
cart-user-42 # one object per persistenceId
cart-user-43
snapshots/
account-42/
seq-100 # snapshots indexed by seqNr
seq-200
seq-300

The framework manages this layout; you don’t construct keys manually.

interface ObjectStorageBackend {
put(key: string, body: Uint8Array, options?: PutOptions): Promise<{ etag: string }>;
get(key: string): Promise<Option<ObjectFetched>>;
delete(key: string): Promise<void>;
list(options: { prefix: string; limit?: number }): Promise<ObjectInfo[]>;
}

Small surface — fits AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, etc. Most S3-compatible APIs match exactly.

await backend.put('state/cart-42', body, {
ifMatch: 'previous-etag',
});

ifMatch lets callers do compare-and-set writes — if the current ETag differs, the put fails with ObjectStorageConcurrencyError. Used by ObjectStorageDurableStateStore to detect concurrent writers without separate coordination.

ifNoneMatch: '*' is create-only — succeed only if the key doesn’t exist yet.

Some older S3-compatible stores don’t honor these headers properly. The framework’s backend implementations throw clearly in that case rather than silently ignoring; check your provider’s CAS support before relying on it.

import { FilesystemObjectStorageBackend, FilesystemObjectStorageOptions } from 'actor-ts';
const filesystemObjectStorageOptions = FilesystemObjectStorageOptions.create().withDir('/var/lib/actor-ts');
const backend = new FilesystemObjectStorageBackend(
filesystemObjectStorageOptions,
);

Stores objects as files under rootDir. No network, no S3 cost. Right for:

  • Tests — same code path as production, with local files.
  • Local dev — no Minio container required.
  • Small single-node deployments — if you specifically want the object-storage interface without S3.
import { S3ObjectStorageBackend, S3ObjectStorageOptions } from 'actor-ts';
const s3ObjectStorageOptions = S3ObjectStorageOptions.create()
.withRegion('eu-west-1')
.withBucket('my-app-state')
.withEndpoint('https://s3.eu-west-1.amazonaws.com') // optional override
.withCredentials({
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
});
const backend = new S3ObjectStorageBackend(s3ObjectStorageOptions);

Works with any S3-compatible service. For non-AWS:

// MinIO:
const s3ObjectStorageOptions = S3ObjectStorageOptions.create()
.withRegion('us-east-1')
.withBucket('my-bucket')
.withEndpoint('http://minio:9000')
.withForcePathStyle(true);
new S3ObjectStorageBackend(s3ObjectStorageOptions);
// Cloudflare R2:
const s3ObjectStorage2Options = S3ObjectStorageOptions.create()
.withRegion('auto')
.withBucket('my-bucket')
.withEndpoint('https://<account-id>.r2.cloudflarestorage.com');
new S3ObjectStorageBackend(s3ObjectStorage2Options);
// Backblaze B2:
const s3ObjectStorage3Options = S3ObjectStorageOptions.create()
.withRegion('us-west-002')
.withBucket('my-bucket')
.withEndpoint('https://s3.us-west-002.backblazeb2.com');
new S3ObjectStorageBackend(s3ObjectStorage3Options);
FeaturePage
Compression (gzip / zstd)Compression
Encryption at rest (AES-GCM)Encryption
Master-key rotationKey rotation
Per-actor compression / encryption policiesPer-actor policies
Snapshot store backendSnapshot store backend

All optional — start without; layer on as needed.

Rough numbers for S3:

  • Put (small object): 20-50 ms.
  • Get: 10-30 ms.
  • Delete: 30-50 ms.

Filesystem backend: sub-millisecond.

Object-storage is slower than SQLite / Cassandra for single operations. Compensate with:

  • Snapshot policies that bound recovery.
  • CachedSnapshotStore decorator for read-through caching.
  • Batching wherever the framework allows.