Zum Inhalt springen
Deutsch

Object Storage im Überblick

Das Object-Storage-Backend des Frameworks ist eine S3-kompatible Persistenz-Schicht. Zwei Implementierungen werden mitgeliefert:

BackendVerwendung
FilesystemObjectStorageBackendLokale Dateien; Dev + Tests.
S3ObjectStorageBackendAlles S3-kompatible (AWS S3, MinIO, R2, B2).

Verwendet von:

  • ObjectStorageDurableStateStore — Durable State im Cloud-Storage.
  • ObjectStorageSnapshotStore — Snapshots im Cloud-Storage.

Gebaut auf einem kleinen Interface (PUT / GET / DELETE / LIST + CAS-Unterstützung); beide Backends sprechen dieselbe Oberfläche.

Du solltest Object Storage verwenden, wenn…
Du Cloud-native bist und S3 (oder ähnlich) deine Storage-Plattform ist.
Du geteilte Persistenz über Cluster-Nodes willst, ohne Cassandra zu betreiben.
Du Server-Side-Encryption über Cloud-KMS willst.
Du unendliches Scaling willst, ohne Storage-Kapazität zu verwalten.

Für Single-Node-Deployments ist SQLite einfacher. Für High-Throughput-Multi-Node-Persistenz ist Cassandra schneller. Object Storage sitzt dazwischen — cloud-freundlich, anständige Performance, viele 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));

Der State von Cart lebt in S3 unter cart-<userId> als Object-Key. Reads und Writes gehen durch die S3-API.

Object-Keys folgen einem vorhersagbaren Layout:

state/
cart-user-42 # ein Object pro persistenceId
cart-user-43
snapshots/
account-42/
seq-100 # Snapshots indexiert nach seqNr
seq-200
seq-300

Das Framework verwaltet dieses Layout; du baust keine Keys manuell.

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[]>;
}

Kleine Oberfläche — passt zu AWS S3, MinIO, Cloudflare R2, Backblaze B2, Wasabi, etc. Die meisten S3-kompatiblen APIs passen exakt.

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

ifMatch lässt Aufrufer Compare-and-Set-Writes machen — wenn das aktuelle ETag abweicht, schlägt das Put mit ObjectStorageConcurrencyError fehl. Verwendet von ObjectStorageDurableStateStore, um nebenläufige Writer ohne separate Koordination zu erkennen.

ifNoneMatch: '*' ist Create-only — gelingt nur, wenn der Key noch nicht existiert.

Einige ältere S3-kompatible Stores beachten diese Header nicht richtig. Die Backend-Implementierungen des Frameworks werfen klar in diesem Fall, anstatt still zu ignorieren; prüfe die CAS-Unterstützung deines Providers, bevor du dich darauf verlässt.

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

Speichert Objects als Dateien unter rootDir. Kein Netzwerk, keine S3-Kosten. Richtig für:

  • Tests — gleicher Code-Pfad wie Produktion, mit lokalen Dateien.
  • Lokales Dev — kein MinIO-Container erforderlich.
  • Kleine Single-Node-Deployments — wenn du speziell das Object-Storage-Interface ohne S3 willst.
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') // optionaler Override
.withCredentials({
accessKeyId: process.env.AWS_ACCESS_KEY_ID!,
secretAccessKey: process.env.AWS_SECRET_ACCESS_KEY!,
});
const backend = new S3ObjectStorageBackend(s3ObjectStorageOptions);

Funktioniert mit jedem S3-kompatiblen Service. Für Nicht-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);
FeatureSeite
Kompression (gzip / zstd)Kompression
Verschlüsselung at rest (AES-GCM)Verschlüsselung
Master-Key-RotationSchlüsselrotation
Per-Actor-Kompressions- / Verschlüsselungs-PoliciesPer-Actor-Policies
Snapshot-Store-BackendSnapshot-Store-Backend

Alle optional — starte ohne; schichte nach Bedarf darauf.

Grobe Zahlen für S3:

  • Put (kleines Objekt): 20-50 ms.
  • Get: 10-30 ms.
  • Delete: 30-50 ms.

Filesystem-Backend: Sub-Millisekunde.

Object Storage ist langsamer als SQLite / Cassandra für einzelne Operationen. Kompensiere mit:

  • Snapshot-Policies, die die Recovery begrenzen.
  • CachedSnapshotStore-Decorator für Read-Through-Caching.
  • Batching, wo immer das Framework es erlaubt.