<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Strata Documentation</title><link>https://danchupin.github.io/strata/</link><description>Recent content on Strata Documentation</description><generator>Hugo</generator><language>en</language><atom:link href="https://danchupin.github.io/strata/index.xml" rel="self" type="application/rss+xml"/><item><title>ADR-0001: Skip RADOS omap for bucket index</title><link>https://danchupin.github.io/strata/adr/0001-skip-rados-omap/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/adr/0001-skip-rados-omap/</guid><description>&lt;h1 id="adr-0001-skip-rados-omap-for-bucket-index"&gt;ADR-0001: Skip RADOS omap for bucket index&lt;a class="anchor" href="#adr-0001-skip-rados-omap-for-bucket-index"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="status"&gt;Status&lt;a class="anchor" href="#status"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Accepted — April 2026&lt;/p&gt;
&lt;h2 id="context"&gt;Context&lt;a class="anchor" href="#context"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Strata is positioned as a drop-in replacement for Ceph RGW. RGW stores
the bucket index in a small set of RADOS objects, each carrying an
&lt;code&gt;omap&lt;/code&gt; (ordered key→value map) that lists the bucket&amp;rsquo;s objects in lex
order. The omap is convenient — listing is a native ordered scan —
but it has hard scale ceilings:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;The omap of a single index object lives in a single placement group.
All listing traffic and all index mutations for that shard land on
one OSD, capped by that OSD&amp;rsquo;s IOPS budget.&lt;/li&gt;
&lt;li&gt;RGW&amp;rsquo;s only mitigation is bucket-index resharding (&lt;code&gt;radosgw-admin bucket reshard&lt;/code&gt;). Resharding rewrites the entire omap, must
quiesce or rate-limit write traffic during the cut-over, and the
shard count tops out before the largest production buckets do —
beyond roughly 100M objects a single bucket exhausts the
resharding ceiling and starts taking IOPS hits regardless.&lt;/li&gt;
&lt;li&gt;The contract is opaque to the layer above. We cannot trade
consistency for throughput, partition by a different key, or fan
out across heterogeneous storage tiers without re-implementing the
scan path.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;We considered (a) keeping omap with aggressive resharding and (b)
moving the index to a separate ordered store. (a) inherits RGW&amp;rsquo;s
ceiling; the project goal is to lift it, not match it.&lt;/p&gt;</description></item><item><title>ADR-0002: Derive IsLatest at read time</title><link>https://danchupin.github.io/strata/adr/0002-islatest-read-time/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/adr/0002-islatest-read-time/</guid><description>&lt;h1 id="adr-0002-derive-islatest-at-read-time"&gt;ADR-0002: Derive &lt;code&gt;IsLatest&lt;/code&gt; at read time&lt;a class="anchor" href="#adr-0002-derive-islatest-at-read-time"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="status"&gt;Status&lt;a class="anchor" href="#status"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Accepted — April 2026&lt;/p&gt;
&lt;h2 id="context"&gt;Context&lt;a class="anchor" href="#context"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;S3 versioning requires every object row to expose an &lt;code&gt;IsLatest&lt;/code&gt; bit:
the most recent (un-deleted) version of a key carries &lt;code&gt;IsLatest=true&lt;/code&gt;,
every older version carries &lt;code&gt;IsLatest=false&lt;/code&gt;. The naive
implementation flips the bit on the previous head whenever a new PUT
lands — &lt;code&gt;UPDATE objects SET is_latest=false WHERE bucket_id=? AND key=? AND version_id=&amp;lt;prev&amp;gt;&lt;/code&gt; immediately after inserting the new
version row.&lt;/p&gt;</description></item><item><title>ADR-0003: Single manifest blob column</title><link>https://danchupin.github.io/strata/adr/0003-manifest-blob-column/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/adr/0003-manifest-blob-column/</guid><description>&lt;h1 id="adr-0003-single-manifest-blob-column-for-object-metadata"&gt;ADR-0003: Single &lt;code&gt;manifest&lt;/code&gt; blob column for object metadata&lt;a class="anchor" href="#adr-0003-single-manifest-blob-column-for-object-metadata"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="status"&gt;Status&lt;a class="anchor" href="#status"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Accepted — April 2026&lt;/p&gt;
&lt;h2 id="context"&gt;Context&lt;a class="anchor" href="#context"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Per-object metadata in Strata is rich and growing: chunk OIDs, chunk
sizes, MD5 / SHA256 digests, SSE wrap context, multipart part
boundaries (&lt;code&gt;PartChunkCounts&lt;/code&gt;, &lt;code&gt;PartChunks&lt;/code&gt;), backend routing hints
(&lt;code&gt;BackendRef.Cluster&lt;/code&gt;), and several smaller fields. A normalised
schema would dedicate one column per field, possibly with side
tables for the variable-length arrays (chunks, parts).&lt;/p&gt;
&lt;p&gt;The cost of normalised columns on Cassandra is two-fold:&lt;/p&gt;</description></item><item><title>ADR-0004: One leader lease per background worker</title><link>https://danchupin.github.io/strata/adr/0004-leader-per-worker/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/adr/0004-leader-per-worker/</guid><description>&lt;h1 id="adr-0004-one-leader-lease-per-background-worker"&gt;ADR-0004: One leader lease per background worker&lt;a class="anchor" href="#adr-0004-one-leader-lease-per-background-worker"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;h2 id="status"&gt;Status&lt;a class="anchor" href="#status"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Accepted — April 2026&lt;/p&gt;
&lt;p&gt;(Reconsideration tracked under
&lt;a href="https://github.com/danchupin/strata/blob/main/ROADMAP.md#consolidation--validation"&gt;ROADMAP &lt;code&gt;## Consolidation &amp;amp; validation&lt;/code&gt;&lt;/a&gt;.
If the supervisor collapse lands, this ADR will be marked &lt;code&gt;Superseded by ADR-XXXX&lt;/code&gt;.)&lt;/p&gt;
&lt;h2 id="context"&gt;Context&lt;a class="anchor" href="#context"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;strata server&lt;/code&gt; ships roughly ten background workers — &lt;code&gt;gc&lt;/code&gt;,
&lt;code&gt;lifecycle&lt;/code&gt;, &lt;code&gt;replicator&lt;/code&gt;, &lt;code&gt;notify&lt;/code&gt;, &lt;code&gt;access-log&lt;/code&gt;, &lt;code&gt;inventory&lt;/code&gt;,
&lt;code&gt;audit-export&lt;/code&gt;, &lt;code&gt;manifest-rewriter&lt;/code&gt;, &lt;code&gt;rebalance&lt;/code&gt;,
&lt;code&gt;quota-reconcile&lt;/code&gt;, &lt;code&gt;usage-rollup&lt;/code&gt;. Each of them must run on at most
one replica at a time: GC must not double-delete, lifecycle must
not duplicate transitions, etc. Some shape of singleton election is
required regardless of the deploy footprint.&lt;/p&gt;</description></item><item><title>Auth</title><link>https://danchupin.github.io/strata/architecture/auth/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/auth/</guid><description>&lt;h1 id="auth"&gt;Auth&lt;a class="anchor" href="#auth"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The auth layer lives under &lt;code&gt;internal/auth/&lt;/code&gt;. Every request enters through
&lt;code&gt;auth.Middleware&lt;/code&gt;, which verifies an AWS SigV4 signature, derives a stable
identity, and stamps the result onto the request context. The router and
handlers downstream never re-derive identity — they read it from
&lt;code&gt;auth.FromContext(ctx).Owner&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="sigv4-verification"&gt;SigV4 verification&lt;a class="anchor" href="#sigv4-verification"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;internal/auth/sigv4.go&lt;/code&gt; implements the standard four-step canonicalisation:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;Parse &lt;code&gt;Authorization&lt;/code&gt; (or &lt;code&gt;X-Amz-*&lt;/code&gt; query parameters for presigned URLs)
to extract &lt;code&gt;AccessKey&lt;/code&gt;, &lt;code&gt;Scope&lt;/code&gt;, &lt;code&gt;SignedHeaders&lt;/code&gt;, and &lt;code&gt;Signature&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Look up the secret for the access key via the configured static
credentials store (&lt;code&gt;internal/auth/static.go&lt;/code&gt;) — no IdP federation in
this cycle.&lt;/li&gt;
&lt;li&gt;Build the canonical request string from &lt;code&gt;Method&lt;/code&gt;, &lt;code&gt;URL.Path&lt;/code&gt;, the
sorted &lt;code&gt;SignedHeaders&lt;/code&gt;, and the body hash (&lt;code&gt;x-amz-content-sha256&lt;/code&gt;,
which may be the literal sentinel &lt;code&gt;STREAMING-AWS4-HMAC-SHA256-PAYLOAD&lt;/code&gt;
for chunked uploads — see below).&lt;/li&gt;
&lt;li&gt;Recompute the signature with the derived signing key and constant-time
compare. Mismatch returns &lt;code&gt;ErrSignatureInvalid&lt;/code&gt; (HTTP 403, AWS code
&lt;code&gt;SignatureDoesNotMatch&lt;/code&gt;).&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The middleware MUST run before any URL rewriting. The signed canonical
string includes the original &lt;code&gt;Host&lt;/code&gt; header and the original
&lt;code&gt;URL.Path&lt;/code&gt; — if the router rewrites either before verification, the
signature breaks. See the &lt;a href="https://danchupin.github.io/strata/architecture/router/"&gt;Router page&lt;/a&gt;
for the order.&lt;/p&gt;</description></item><item><title>Binary consolidation</title><link>https://danchupin.github.io/strata/architecture/migrations/binary-consolidation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/migrations/binary-consolidation/</guid><description>&lt;h1 id="migrating-to-the-unified-strata-binary"&gt;Migrating to the unified &lt;code&gt;strata&lt;/code&gt; binary&lt;a class="anchor" href="#migrating-to-the-unified-strata-binary"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata used to ship eleven &lt;code&gt;cmd/*&lt;/code&gt; binaries — one gateway plus a long tail of
single-purpose worker daemons. The codebase now ships exactly one:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;strata&lt;/code&gt; — the gateway plus every long-running background worker (selected
at startup via &lt;code&gt;STRATA_WORKERS=&lt;/code&gt; / &lt;code&gt;--workers=&lt;/code&gt;), with one-shot operator
commands available as &lt;code&gt;strata admin &amp;lt;subcommand&amp;gt;&lt;/code&gt; against the same binary.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide is the operator-facing checklist for upgrading an existing deploy.
There is no schema migration, no data migration, and no env-var rename — the
move is purely a packaging change.&lt;/p&gt;</description></item><item><title>Drain a cluster</title><link>https://danchupin.github.io/strata/operate/drain-cluster/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/drain-cluster/</guid><description>&lt;h1 id="drain-a-cluster"&gt;Drain a cluster&lt;a class="anchor" href="#drain-a-cluster"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Taking a data cluster out of rotation is a five-step workflow:
&lt;strong&gt;preview impact → drain → watch progress → wait for deregister-ready
→ remove from env&lt;/strong&gt;. Strata exposes each step through the admin API
and the operator console so you can drive the workflow from the
browser or from a shell script.&lt;/p&gt;
&lt;p&gt;This page is the day-2 runbook. The
&lt;a href="https://danchupin.github.io/strata/concepts/drain-rebalance/"&gt;Drain &amp;amp; rebalance concept&lt;/a&gt;
covers the lifecycle states; the
&lt;a href="https://danchupin.github.io/strata/best-practices/placement-rebalance/"&gt;Placement + rebalance best-practice&lt;/a&gt;
covers tuning the rebalance throughput and the cluster-state machine
in depth.&lt;/p&gt;</description></item><item><title>GC + Lifecycle scaling</title><link>https://danchupin.github.io/strata/architecture/benchmarks/gc-lifecycle/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/gc-lifecycle/</guid><description>&lt;h1 id="gc--lifecycle-worker-scaling-benchmark-phase-1--phase-2"&gt;GC + Lifecycle worker scaling benchmark (Phase 1 + Phase 2)&lt;a class="anchor" href="#gc--lifecycle-worker-scaling-benchmark-phase-1--phase-2"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;Phase 2 (US-006) lands &lt;strong&gt;multi-leader&lt;/strong&gt; numbers below Phase 1&amp;rsquo;s single-leader
baseline. The two-section structure is intentional — Phase 1 measured the
per-leader concurrency cap, Phase 2 measures the multiplier from running N
replicas in parallel under &lt;code&gt;STRATA_GC_SHARDS=N&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;&lt;h2 id="phase-1--single-leader-concurrency-cap"&gt;Phase 1 — single-leader concurrency cap&lt;a class="anchor" href="#phase-1--single-leader-concurrency-cap"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Quantifies the throughput curve for &lt;code&gt;internal/gc.Worker&lt;/code&gt; and
&lt;code&gt;internal/lifecycle.Worker&lt;/code&gt; across the per-worker bounded-errgroup
fan-out introduced in cycle &lt;code&gt;ralph/gc-lifecycle-scale&lt;/code&gt; (US-001 / US-002).
The harness lands as &lt;code&gt;strata admin bench-gc&lt;/code&gt; + &lt;code&gt;strata admin bench-lifecycle&lt;/code&gt;
(US-003) and is wired into &lt;code&gt;make bench-gc&lt;/code&gt; / &lt;code&gt;make bench-lifecycle&lt;/code&gt; to drive
the canonical lab-tikv stack.&lt;/p&gt;</description></item><item><title>Router</title><link>https://danchupin.github.io/strata/architecture/router/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/router/</guid><description>&lt;h1 id="router"&gt;Router&lt;a class="anchor" href="#router"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The router lives in &lt;code&gt;internal/s3api/server.go&lt;/code&gt;. It is a single
&lt;code&gt;Server.ServeHTTP&lt;/code&gt; method that classifies every request along three axes:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Special prefix&lt;/strong&gt; — &lt;code&gt;/admin/...&lt;/code&gt; is the embedded operator console JSON
API and bypasses the S3 dispatch entirely.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Bucket vs object scope&lt;/strong&gt; — the URL path is split at the first slash
into &lt;code&gt;(bucket, key)&lt;/code&gt;. Empty bucket → service-level (ListBuckets, IAM
actions). Empty key → bucket-scoped. Both populated → object-scoped.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Query-string sub-resource&lt;/strong&gt; — within bucket and object scope, the
sub-operation is dispatched by query parameter (&lt;code&gt;?cors&lt;/code&gt;, &lt;code&gt;?policy&lt;/code&gt;,
&lt;code&gt;?lifecycle&lt;/code&gt;, &lt;code&gt;?uploads&lt;/code&gt;, &lt;code&gt;?uploadId=&lt;/code&gt;, &lt;code&gt;?tagging&lt;/code&gt;, …) plus the HTTP
method. This is the AWS S3 wire shape; we mirror it.&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 id="dispatch-order"&gt;Dispatch order&lt;a class="anchor" href="#dispatch-order"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;ServeHTTP(w, r):
 1. extractAccessPointAlias(r.Host) # alias.&amp;lt;host&amp;gt; -&amp;gt; rewrite to /&amp;lt;bucket&amp;gt;/...
 2. extractVHostBucket(r.Host, ...) # *.s3.local -&amp;gt; rewrite to /&amp;lt;bucket&amp;gt;/...
 3. if path starts with /admin/ -&amp;gt; handleAdmin
 4. splitPath(r.URL.Path) -&amp;gt; (bucket, key)
 5. bucket == &amp;#34;&amp;#34; -&amp;gt; IAM action / ListBuckets / DLQ-audit listings
 key == &amp;#34;&amp;#34; -&amp;gt; handleBucket(...)
 default -&amp;gt; handleObject(...)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Auth middleware runs &lt;strong&gt;before&lt;/strong&gt; the access-point and vhost rewrites because
those rewrites only mutate &lt;code&gt;r.URL.Path&lt;/code&gt; after SigV4 has already validated
the original. See &lt;a href="https://danchupin.github.io/strata/architecture/auth/"&gt;Auth&lt;/a&gt; for the ordering
rationale.&lt;/p&gt;</description></item><item><title>S3 surface</title><link>https://danchupin.github.io/strata/concepts/s3-surface/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/concepts/s3-surface/</guid><description>&lt;h1 id="s3-surface"&gt;S3 surface&lt;a class="anchor" href="#s3-surface"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata speaks the AWS S3 REST API over HTTP. The goal is compatibility with
the Amazon S3 client ecosystem — the same &lt;code&gt;aws s3&lt;/code&gt; / &lt;code&gt;aws s3api&lt;/code&gt; / &lt;code&gt;mc&lt;/code&gt;
commands and the same SDK calls work against Strata with only the endpoint
URL changed. Compatibility is measured against Ceph&amp;rsquo;s upstream &lt;code&gt;s3-tests&lt;/code&gt;
suite; the running pass rate lives on the
&lt;a href="https://danchupin.github.io/strata/s3-compatibility/"&gt;S3 Compatibility&lt;/a&gt; page.&lt;/p&gt;
&lt;p&gt;The rest of this page introduces each major operation family. None of the
details below depend on which metadata or data backend you pick — the S3
surface is identical across backends.&lt;/p&gt;</description></item><item><title>ScyllaDB</title><link>https://danchupin.github.io/strata/architecture/backends/scylla/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/backends/scylla/</guid><description>&lt;h1 id="scylladb-metadata-backend"&gt;ScyllaDB metadata backend&lt;a class="anchor" href="#scylladb-metadata-backend"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;ScyllaDB is supported as a drop-in replacement for Apache Cassandra. Strata&amp;rsquo;s
metadata access path is gocql + CQL — no Cassandra-specific extensions are
used — so the same &lt;code&gt;internal/meta/cassandra&lt;/code&gt; backend talks to either cluster.
The &lt;code&gt;storetest.Run&lt;/code&gt; contract suite (35+ scenarios covering bucket/object/multipart
LWT semantics, sharded &lt;code&gt;objects&lt;/code&gt; listing, GC and notification queues, audit log,
versioning null literal, access points, etc.) passes against both.&lt;/p&gt;
&lt;h2 id="compatibility-status"&gt;Compatibility status&lt;a class="anchor" href="#compatibility-status"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Code&lt;/strong&gt;: zero gateway changes required to switch from Cassandra to ScyllaDB.
Point &lt;code&gt;STRATA_CASSANDRA_HOSTS&lt;/code&gt; (and friends) at a Scylla cluster and bring up
the gateway as usual.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Schema&lt;/strong&gt;: same DDL — &lt;code&gt;internal/meta/cassandra/schema.go::tableDDL&lt;/code&gt;
applies as-is. No &lt;code&gt;ALTER&lt;/code&gt; differences.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Consistency&lt;/strong&gt;: gateway uses &lt;code&gt;LOCAL_QUORUM&lt;/code&gt; for reads/writes and
&lt;code&gt;LOCAL_SERIAL&lt;/code&gt; for LWT (&lt;code&gt;internal/meta/cassandra/session.go&lt;/code&gt;). ScyllaDB
honours both.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;LWT&lt;/strong&gt;: bucket creation, versioning toggle, multipart Complete, GC ack,
storage-class transitions, lifecycle worker leases — all rely on
&lt;code&gt;IF NOT EXISTS&lt;/code&gt; / &lt;code&gt;IF EXISTS&lt;/code&gt;. ScyllaDB 5.0 ships LWT on the Paxos protocol
by default; ScyllaDB 5.4+ optionally enables Raft-backed LWT
(&lt;code&gt;raft_lwt&lt;/code&gt;-enabled tables) which dramatically reduces tail latency. The
storetest contract is agnostic to the underlying coordination protocol; it
only asserts the linearizable observable behaviour, which both Paxos and
Raft modes provide.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Sharded &lt;code&gt;objects&lt;/code&gt; listing&lt;/strong&gt;: Strata fans out &lt;code&gt;N=64&lt;/code&gt; shard partitions in
parallel (&lt;code&gt;cassandra.Store.ListObjects&lt;/code&gt;). ScyllaDB&amp;rsquo;s per-shard CPU pinning is
a natural fit for this access pattern — token-aware queries land on the
right shard with a single hop.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="deployment-notes"&gt;Deployment notes&lt;a class="anchor" href="#deployment-notes"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;h3 id="connection"&gt;Connection&lt;a class="anchor" href="#connection"&gt;#&lt;/a&gt;&lt;/h3&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;STRATA_CASSANDRA_HOSTS=scylla-1.example,scylla-2.example,scylla-3.example
STRATA_CASSANDRA_KEYSPACE=strata
STRATA_CASSANDRA_LOCAL_DC=datacenter1
STRATA_CASSANDRA_REPLICATION=&amp;#39;{&amp;#34;class&amp;#34;:&amp;#34;NetworkTopologyStrategy&amp;#34;,&amp;#34;datacenter1&amp;#34;:&amp;#34;3&amp;#34;}&amp;#39;&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;&lt;code&gt;STRATA_CASSANDRA_LOCAL_DC&lt;/code&gt; should match the Scylla &lt;code&gt;dc&lt;/code&gt; you want token-aware
routing to prefer. Multi-DC deployments work identically to Cassandra:
&lt;code&gt;NetworkTopologyStrategy&lt;/code&gt; keyspace + per-DC RF.&lt;/p&gt;</description></item><item><title>Single-node deployment</title><link>https://danchupin.github.io/strata/deploy/single-node/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/deploy/single-node/</guid><description>&lt;h1 id="single-node-strata"&gt;Single-node Strata&lt;a class="anchor" href="#single-node-strata"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A single-node deployment runs one &lt;code&gt;strata&lt;/code&gt; process. It is the simplest
shape Strata ships and the recommended starting point for labs,
evaluation, and single-tenant pilots. The gateway is HTTP-stateless —
durability lives in the metadata + data backends — so a single replica
is &lt;strong&gt;not&lt;/strong&gt; an HA shape: the box is the SPOF for HTTP traffic. For HA,
see &lt;a href="https://danchupin.github.io/strata/deploy/multi-replica/"&gt;Multi-replica cluster&lt;/a&gt;.&lt;/p&gt;
&lt;h2 id="prerequisites"&gt;Prerequisites&lt;a class="anchor" href="#prerequisites"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;One Linux host (or a macOS workstation for dev).&lt;/li&gt;
&lt;li&gt;Docker (for the full stack) &lt;strong&gt;or&lt;/strong&gt; Go 1.23+ (for the in-memory smoke).&lt;/li&gt;
&lt;li&gt;2 vCPU + 1 GiB RSS baseline (see &lt;em&gt;Sizing&lt;/em&gt; below).&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The pure-memory path needs no other software. The Cassandra-backed
path needs &lt;code&gt;cassandra:5.0&lt;/code&gt; reachable on &lt;code&gt;:9042&lt;/code&gt;. The full stack
(Cassandra + RADOS) is bootstrapped from the bundled compose file.&lt;/p&gt;</description></item><item><title>STRATA_* environment variables</title><link>https://danchupin.github.io/strata/reference/env-vars/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/reference/env-vars/</guid><description>&lt;!--
Maintainer note: source of truth for new entries is the codebase. Grep with
`grep -rhoE 'STRATA_[A-Z_][A-Z0-9_]+' cmd/strata/ internal/ | sort -u` and
cross-reference defaults + clamp ranges at the consuming call sites (koanf
envMap, worker `*FromEnv` helpers, `os.Getenv(...)`). Update this page when
adding a new env var.

The `TOML key` column mirrors `envMap` in `internal/config/config.go`.
Exempt env vars (bootstrap, test-only, retired, build metadata) show `—`.
The drift-lint test `internal/config/env_toml_parity_test.go` fails the
build if a STRATA_* var is added without wiring through Config + envMap +
this page, so keep all three in lockstep.
--&gt;
&lt;h1 id="strata_-environment-variables"&gt;&lt;code&gt;STRATA_*&lt;/code&gt; environment variables&lt;a class="anchor" href="#strata_-environment-variables"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Every &lt;code&gt;STRATA_*&lt;/code&gt; knob, grouped by the layer that consumes it. CLI flags on
&lt;code&gt;strata server&lt;/code&gt; (&lt;code&gt;--listen&lt;/code&gt;, &lt;code&gt;--vhost-pattern&lt;/code&gt;, &lt;code&gt;--log-level&lt;/code&gt;, &lt;code&gt;--workers=&lt;/code&gt;)
override the matching env var.&lt;/p&gt;</description></item><item><title>Web UI (Strata Console)</title><link>https://danchupin.github.io/strata/best-practices/web-ui/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/web-ui/</guid><description>&lt;h1 id="strata-console-web-ui--operator-guide"&gt;Strata Console (Web UI) — Operator Guide&lt;a class="anchor" href="#strata-console-web-ui--operator-guide"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The Strata Console is an embedded read-only web UI for cluster operators.
It ships in the same binary as the gateway (&lt;code&gt;go:embed&lt;/code&gt;) and is served at
&lt;code&gt;/console/&lt;/code&gt; on the gateway HTTP port. No separate process, no separate
deploy.&lt;/p&gt;
&lt;p&gt;This document is the Phase 1 (foundation) operator guide. Phase 2 (admin
write actions) and Phase 3 (debug tooling — heatmaps, slow queries, OTel
trace browser) ship in their own cycles.&lt;/p&gt;</description></item><item><title>PUT flow</title><link>https://danchupin.github.io/strata/architecture/put-flow/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/put-flow/</guid><description>&lt;h1 id="put-flow"&gt;PUT flow&lt;a class="anchor" href="#put-flow"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A single S3 &lt;code&gt;PutObject&lt;/code&gt; traverses the gateway, the metadata store, and the
data backend. The picture below names the components on the hot path so
the prose that follows can stay focused on the interesting choices —
streaming chunk decode, the manifest compare-and-set, and the failure
modes when one tier wins a race against another.&lt;/p&gt;
&lt;h2 id="sequence-diagram"&gt;Sequence diagram&lt;a class="anchor" href="#sequence-diagram"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="mermaid"&gt;sequenceDiagram
 autonumber
 participant C as S3 client
 participant GW as s3api.Server
 participant Auth as auth.Middleware
 participant Meta as meta.Store
 participant Data as data.Backend
 C-&amp;gt;&amp;gt;GW: PUT /bucket/key (+ SigV4 headers, streaming body)
 GW-&amp;gt;&amp;gt;Auth: verify signature (canonical request, chain HMAC if streaming)
 Auth--&amp;gt;&amp;gt;GW: identity{Owner, AccessKeyID}
 GW-&amp;gt;&amp;gt;Meta: LookupBucket(bucket) → policy + placement
 GW-&amp;gt;&amp;gt;Data: PutChunks(stream, placement, classSpec)
 Data-&amp;gt;&amp;gt;Data: split body into 4 MiB chunks, write to selected cluster
 Data--&amp;gt;&amp;gt;GW: manifest{chunks[], etag, size, BackendRef}
 GW-&amp;gt;&amp;gt;Meta: SetObjectManifest(bucket, key, prevVersion, manifest) — compare-and-set
 alt CAS applied
 Meta--&amp;gt;&amp;gt;GW: applied=true, version=v
 GW--&amp;gt;&amp;gt;C: 200 OK, ETag, x-amz-version-id
 else CAS rejected (concurrent writer landed first)
 Meta--&amp;gt;&amp;gt;GW: applied=false
 GW-&amp;gt;&amp;gt;Data: EnqueueChunkDeletion(losingChunks) — GC absorbs the orphan
 GW--&amp;gt;&amp;gt;C: 200 OK with the winning manifest (retry-safe)
 end&lt;/pre&gt;&lt;h2 id="step-by-step"&gt;Step-by-step&lt;a class="anchor" href="#step-by-step"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;
&lt;p&gt;&lt;strong&gt;SigV4 verify.&lt;/strong&gt; &lt;code&gt;auth.Middleware&lt;/code&gt; canonicalises the request, computes
the expected signature against the secret in &lt;code&gt;auth.StaticStore&lt;/code&gt;, and
rejects on mismatch. Streaming chunk uploads (&lt;code&gt;aws-chunked&lt;/code&gt;) carry a
chain HMAC validated incrementally by &lt;code&gt;auth.streamingReader&lt;/code&gt; so a
torn body fails before the manifest is touched. See
&lt;a href="https://danchupin.github.io/strata/architecture/auth/"&gt;Auth&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Multi-cluster routing</title><link>https://danchupin.github.io/strata/architecture/multi-cluster-routing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/multi-cluster-routing/</guid><description>&lt;h1 id="multi-cluster-routing"&gt;Multi-cluster routing&lt;a class="anchor" href="#multi-cluster-routing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata can front multiple data clusters (multiple RADOS pools, multiple
upstream S3 endpoints, or a mix). Every PUT picks exactly one cluster
before any chunks land. The picker is a thin layer over three inputs:
the bucket&amp;rsquo;s placement policy, the per-cluster weights from the
&lt;code&gt;cluster_state&lt;/code&gt; table, and the storage-class spec inferred from the
request headers.&lt;/p&gt;
&lt;h2 id="flowchart"&gt;Flowchart&lt;a class="anchor" href="#flowchart"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="mermaid"&gt;flowchart TD
 PUT[&amp;#34;s3api.Server.putObject&amp;#34;] --&amp;gt; Spec[&amp;#34;Resolve storage-class spec&amp;lt;br/&amp;gt;(default / standard-ia / glacier / @cluster)&amp;#34;]
 Spec --&amp;gt; Pin{&amp;#34;spec.Cluster != &amp;#39;&amp;#39;&amp;lt;br/&amp;gt;(@cluster suffix?)&amp;#34;}
 Pin -- yes --&amp;gt; PickPinned[&amp;#34;clusterForPlacement(pinnedID)&amp;#34;]
 Pin -- no --&amp;gt; BPolicy{&amp;#34;bucket.Placement&amp;lt;br/&amp;gt;!= nil?&amp;#34;}
 BPolicy -- yes --&amp;gt; Effective[&amp;#34;placement.EffectivePolicy(&amp;lt;br/&amp;gt;bucketPolicy, mode, weights, states)&amp;#34;]
 Effective --&amp;gt; EffectiveNonEmpty{&amp;#34;non-empty?&amp;#34;}
 EffectiveNonEmpty -- yes --&amp;gt; Wheel[&amp;#34;placement.PickClusterExcluding(&amp;lt;br/&amp;gt;policy, drainSet)&amp;#34;]
 EffectiveNonEmpty -- no --&amp;gt; StrictCheck{&amp;#34;mode == strict?&amp;#34;}
 StrictCheck -- yes --&amp;gt; Refuse[/&amp;#34;503 DrainRefused&amp;lt;br/&amp;gt;(compliance pin)&amp;#34;/]
 StrictCheck -- no --&amp;gt; Default[&amp;#34;Synthesised default policy&amp;lt;br/&amp;gt;from cluster weights&amp;#34;]
 Default --&amp;gt; Wheel
 BPolicy -- no --&amp;gt; Default
 Wheel --&amp;gt; Drain{&amp;#34;selected cluster&amp;lt;br/&amp;gt;draining?&amp;#34;}
 Drain -- yes --&amp;gt; Refuse
 Drain -- no --&amp;gt; Put[&amp;#34;data.Backend.PutChunks(...)&amp;#34;]
 PickPinned --&amp;gt; DrainPin{&amp;#34;pinned cluster&amp;lt;br/&amp;gt;draining?&amp;#34;}
 DrainPin -- yes --&amp;gt; Refuse
 DrainPin -- no --&amp;gt; Put&lt;/pre&gt;&lt;h2 id="the-picker-contract"&gt;The picker contract&lt;a class="anchor" href="#the-picker-contract"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;placement.PickCluster&lt;/code&gt; (and its drain-aware sibling
&lt;code&gt;PickClusterExcluding&lt;/code&gt;) lives in &lt;code&gt;internal/data/placement/&lt;/code&gt;. The inputs
are deterministic:&lt;/p&gt;</description></item><item><title>Docker Compose</title><link>https://danchupin.github.io/strata/deploy/docker-compose/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/deploy/docker-compose/</guid><description>&lt;h1 id="docker-compose-deployment"&gt;Docker Compose deployment&lt;a class="anchor" href="#docker-compose-deployment"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The bundled &lt;code&gt;deploy/docker/docker-compose.yml&lt;/code&gt; is the canonical
reference shape for a Strata stack on one host. Bare
&lt;code&gt;docker compose up -d&lt;/code&gt; brings up the &lt;strong&gt;TiKV-default 2-replica lab&lt;/strong&gt;
(PD + TiKV + ceph + ceph-b + strata-a + strata-b + nginx LB +
prometheus + grafana). The Cassandra-backed regression lab lives
under &lt;code&gt;--profile cassandra&lt;/code&gt; so an operator can &lt;code&gt;make up-cassandra&lt;/code&gt; to
validate the Cassandra meta backend side by side.&lt;/p&gt;</description></item><item><title>Meta store</title><link>https://danchupin.github.io/strata/architecture/meta-store/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/meta-store/</guid><description>&lt;h1 id="meta-store"&gt;Meta store&lt;a class="anchor" href="#meta-store"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;internal/meta/store.go&lt;/code&gt; defines &lt;code&gt;meta.Store&lt;/code&gt;, the interface every metadata
backend implements. The contract is intentionally narrow: only the
operations the S3 surface needs, and only with the consistency primitives
the backends can all support without bolting a coordinator in front.&lt;/p&gt;
&lt;p&gt;Three production-eligible backends satisfy &lt;code&gt;meta.Store&lt;/code&gt;:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Backend&lt;/th&gt;
 &lt;th&gt;When to pick&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;memory&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Tests, smoke pass, single-process demos&lt;/td&gt;
 &lt;td&gt;In-process tree-map. Naturally ordered. No durability.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;cassandra&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Multi-replica, scale tested against the s3-tests suite&lt;/td&gt;
 &lt;td&gt;ScyllaDB drops in unchanged (CQL-compatible). Sharded objects table, fan-out + heap-merge listing.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;tikv&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Multi-replica, prefer ordered scans&lt;/td&gt;
 &lt;td&gt;Native KV via &lt;code&gt;tikv/client-go&lt;/code&gt;. Implements &lt;code&gt;RangeScanStore&lt;/code&gt; so &lt;code&gt;ListObjects&lt;/code&gt; is a single scan.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;A new backend MUST satisfy &lt;code&gt;meta.Store&lt;/code&gt; and pass the contract suite at
&lt;code&gt;internal/meta/storetest/contract.go&lt;/code&gt;. The suite is shared across all
backends and is the parity oracle.&lt;/p&gt;</description></item><item><title>Drain pipeline</title><link>https://danchupin.github.io/strata/architecture/drain-pipeline/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/drain-pipeline/</guid><description>&lt;h1 id="drain-pipeline"&gt;Drain pipeline&lt;a class="anchor" href="#drain-pipeline"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A draining cluster is one that the operator has flagged stop-write so
it can be taken out of rotation safely. Strata models the lifecycle as
a state machine on the &lt;code&gt;cluster_state&lt;/code&gt; row; the rebalance worker
migrates chunks off in the &lt;code&gt;evacuating&lt;/code&gt; state; the deregister-ready
gate keeps the operator honest before they drop the cluster from
&lt;code&gt;STRATA_RADOS_CLUSTERS&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="state-diagram"&gt;State diagram&lt;a class="anchor" href="#state-diagram"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="mermaid"&gt;stateDiagram-v2
 [*] --&amp;gt; live: cluster_state row created&amp;lt;br/&amp;gt;(weight default 100)
 [*] --&amp;gt; pending: env-only cluster&amp;lt;br/&amp;gt;(no chunks yet)
 pending --&amp;gt; live: POST /activate {weight: N}
 live --&amp;gt; live: PUT /weight {weight: N}
 live --&amp;gt; draining_readonly: POST /drain {mode: &amp;#34;readonly&amp;#34;}
 live --&amp;gt; evacuating: POST /drain {mode: &amp;#34;evacuate&amp;#34;}
 draining_readonly --&amp;gt; evacuating: POST /drain {mode: &amp;#34;evacuate&amp;#34;}&amp;lt;br/&amp;gt;(upgrade — no readonly→evacuate skip)
 draining_readonly --&amp;gt; live: POST /undrain
 evacuating --&amp;gt; live: POST /undrain
 evacuating --&amp;gt; removed: chunks_on_cluster == 0&amp;lt;br/&amp;gt;operator drops env entry
 removed --&amp;gt; [*]: row remains as tombstone&lt;/pre&gt;&lt;h2 id="states"&gt;States&lt;a class="anchor" href="#states"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;State&lt;/th&gt;
 &lt;th&gt;Picker&lt;/th&gt;
 &lt;th&gt;Reads&lt;/th&gt;
 &lt;th&gt;Scan-on-tick&lt;/th&gt;
 &lt;th&gt;Operator entry&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;pending&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;excluded from default wheel; explicit policy still routes&lt;/td&gt;
 &lt;td&gt;works&lt;/td&gt;
 &lt;td&gt;no&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;POST /admin/v1/clusters/{id}/activate {weight: N}&lt;/code&gt; flips to &lt;code&gt;live&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;live&lt;/code&gt; (weight &amp;gt; 0)&lt;/td&gt;
 &lt;td&gt;included proportional to weight&lt;/td&gt;
 &lt;td&gt;works&lt;/td&gt;
 &lt;td&gt;no&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;PUT /admin/v1/clusters/{id}/weight&lt;/code&gt; adjusts in place&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;live&lt;/code&gt; (weight == 0)&lt;/td&gt;
 &lt;td&gt;excluded from default wheel; explicit policy still routes&lt;/td&gt;
 &lt;td&gt;works&lt;/td&gt;
 &lt;td&gt;no&lt;/td&gt;
 &lt;td&gt;legal &amp;ldquo;drained but not draining&amp;rdquo; state — useful for staged decommission&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;draining_readonly&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;excluded&lt;/td&gt;
 &lt;td&gt;works&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;no&lt;/strong&gt; — readonly is stop-write only&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;POST /admin/v1/clusters/{id}/drain {mode: &amp;quot;readonly&amp;quot;}&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;evacuating&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;excluded&lt;/td&gt;
 &lt;td&gt;works&lt;/td&gt;
 &lt;td&gt;&lt;strong&gt;yes&lt;/strong&gt; — rebalance worker migrates chunks off&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;POST /admin/v1/clusters/{id}/drain {mode: &amp;quot;evacuate&amp;quot;}&lt;/code&gt; or upgrade from readonly&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;removed&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;excluded everywhere&lt;/td&gt;
 &lt;td&gt;n/a&lt;/td&gt;
 &lt;td&gt;n/a&lt;/td&gt;
 &lt;td&gt;operator-flow tombstone — set after deregister&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Stop-write semantics: a draining cluster accepts reads, deletes, HEAD,
multipart &lt;code&gt;UploadPart&lt;/code&gt; / &lt;code&gt;Complete&lt;/code&gt; / &lt;code&gt;Abort&lt;/code&gt;, and listings. Only fresh
PUTs are refused with 503 &lt;code&gt;DrainRefused&lt;/code&gt; + &lt;code&gt;Retry-After: 300&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>Admin API surface</title><link>https://danchupin.github.io/strata/reference/admin-api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/reference/admin-api/</guid><description>&lt;!--
Maintainer note: source of truth is the admin API OpenAPI contract. This page
is a derived flat index — rebuild when paths are added/removed from the
OpenAPI document. Audit-verb column mirrors the SetAuditOverride stamp in
each handler. GET/HEAD/OPTIONS are skipped by the audit middleware regardless
of any defensive stamp, so read-only rows show "—".
--&gt;
&lt;h1 id="admin-api-surface"&gt;Admin API surface&lt;a class="anchor" href="#admin-api-surface"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This is the operator index. Authoritative contract lives in the admin API
OpenAPI document — rendered interactively at the
&lt;a href="https://danchupin.github.io/strata/reference/admin-api-viewer/"&gt;Admin API viewer&lt;/a&gt;; raw bytes at
&lt;a href="https://danchupin.github.io/strata/openapi.yaml"&gt;&lt;code&gt;/openapi.yaml&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Data backend</title><link>https://danchupin.github.io/strata/architecture/data-backend/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/data-backend/</guid><description>&lt;h1 id="data-backend"&gt;Data backend&lt;a class="anchor" href="#data-backend"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;internal/data/backend.go&lt;/code&gt; defines the &lt;code&gt;data.Backend&lt;/code&gt; interface every chunk
store implements. The metadata layer keeps the per-object manifest (chunk
list, sizes, content hash); the data backend is responsible for opaque
fixed-size chunks only. The split is what lets us drop in different
backing stores (RADOS, S3, in-memory) without touching the gateway.&lt;/p&gt;
&lt;h2 id="backends"&gt;Backends&lt;a class="anchor" href="#backends"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Backend&lt;/th&gt;
 &lt;th&gt;Build tag&lt;/th&gt;
 &lt;th&gt;Notes&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;memory&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;In-process map. Used by tests and the smoke pass.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;rados&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;ceph&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;RADOS pools via &lt;code&gt;goceph&lt;/code&gt; (cgo, librados). Requires &lt;code&gt;make build&lt;/code&gt; with &lt;code&gt;-tags ceph&lt;/code&gt; or the docker-built image. Multi-cluster routing supported.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;s3&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;none&lt;/td&gt;
 &lt;td&gt;S3-over-S3 — Strata as a transparent gateway in front of an upstream S3 endpoint. Useful for migrating from MinIO / SeaweedFS / AWS without lifting and shifting data.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Selection is via &lt;code&gt;STRATA_DATA_BACKEND&lt;/code&gt; (&lt;code&gt;memory&lt;/code&gt; / &lt;code&gt;rados&lt;/code&gt; / &lt;code&gt;s3&lt;/code&gt;). RADOS
requires the configured pool (&lt;code&gt;[rados] classes&lt;/code&gt;) to exist; the
&lt;a href="https://danchupin.github.io/strata/architecture/storage/"&gt;Storage status page&lt;/a&gt; covers the
operator-facing health surface.&lt;/p&gt;</description></item><item><title>GC + lifecycle Phase 2</title><link>https://danchupin.github.io/strata/architecture/migrations/gc-lifecycle-phase-2/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/migrations/gc-lifecycle-phase-2/</guid><description>&lt;h1 id="migrating-to-gc--lifecycle-phase-2-sharded-leader-election"&gt;Migrating to gc / lifecycle Phase 2 (sharded leader-election)&lt;a class="anchor" href="#migrating-to-gc--lifecycle-phase-2-sharded-leader-election"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Phase 1 (cycle &lt;code&gt;ralph/gc-lifecycle-scale&lt;/code&gt;, commit &lt;code&gt;6561845&lt;/code&gt;) lifted the
per-leader concurrency cap via bounded &lt;code&gt;errgroup&lt;/code&gt; fan-out
(&lt;code&gt;STRATA_GC_CONCURRENCY&lt;/code&gt; / &lt;code&gt;STRATA_LIFECYCLE_CONCURRENCY&lt;/code&gt;). The leader was
still single-replica.&lt;/p&gt;
&lt;p&gt;Phase 2 (cycle &lt;code&gt;ralph/gc-lifecycle-scale-phase-2&lt;/code&gt;) shards the leader-election
space so multiple replicas process disjoint slices of the queue in parallel:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;gc gets &lt;code&gt;gc-leader-0..N-1&lt;/code&gt; lease keys driven by &lt;code&gt;STRATA_GC_SHARDS&lt;/code&gt; (default
&lt;code&gt;1&lt;/code&gt;, range &lt;code&gt;[1, 1024]&lt;/code&gt;). Each replica races for one or more of the
per-shard leases and drains only the entries it owns via
&lt;code&gt;Meta.ListGCEntriesShard&lt;/code&gt;. The legacy global &lt;code&gt;gc-leader&lt;/code&gt; lease is retired.&lt;/li&gt;
&lt;li&gt;lifecycle gets per-bucket leases (&lt;code&gt;lifecycle-leader-&amp;lt;bucketID&amp;gt;&lt;/code&gt;) plus a
distribution gate (&lt;code&gt;fnv32a(bucketID) % STRATA_GC_SHARDS == myReplicaID&lt;/code&gt;,
where &lt;code&gt;myReplicaID = min(GCFanOut.HeldShards())&lt;/code&gt;). The legacy global
&lt;code&gt;lifecycle-leader&lt;/code&gt; lease is retired.&lt;/li&gt;
&lt;li&gt;Backwards-compat: &lt;code&gt;STRATA_GC_SHARDS=1&lt;/code&gt; (default) reproduces Phase 1
behaviour byte-for-byte. Existing &lt;code&gt;make smoke&lt;/code&gt; / &lt;code&gt;make smoke-tikv&lt;/code&gt; /
&lt;code&gt;make smoke-lab-tikv&lt;/code&gt; continue to pass without configuration changes.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This guide is the operator-facing checklist for the schema cutover and the
multi-replica rollout.&lt;/p&gt;</description></item><item><title>Meta-backend comparison</title><link>https://danchupin.github.io/strata/architecture/benchmarks/meta-backend-comparison/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/meta-backend-comparison/</guid><description>&lt;h1 id="meta-backend-benchmarks-tikv-vs-cassandra"&gt;Meta-backend benchmarks: TiKV vs Cassandra&lt;a class="anchor" href="#meta-backend-benchmarks-tikv-vs-cassandra"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This page captures hot-path latency / throughput numbers for Strata&amp;rsquo;s two
production metadata backends (Cassandra and TiKV) and the in-tree memory
reference. The numbers are operator-runnable on a single laptop docker
stack via the harness in &lt;code&gt;internal/meta/storetest/bench.go&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The headline operations (US-018):&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Op&lt;/th&gt;
 &lt;th&gt;Why it matters&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CreateBucket&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;LWT-equivalent create-if-not-exists&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GetObject&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Single Get / latest-version range-scan&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;ListObjects&lt;/code&gt; 100k page=1k&lt;/td&gt;
 &lt;td&gt;Listing throughput on a real-world bucket&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;CompleteMultipartUpload&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;LWT-equivalent status flip (&lt;code&gt;uploading→completing&lt;/code&gt;)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GetIAMAccessKey&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;SigV4 verifier — runs on every request&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;AuditAppend&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Audit log write amplification&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;AuditSweepPartition&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Periodic retention sweep (TiKV emulates Cassandra TTL)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;RangeScanObjects&lt;/code&gt; 100k&lt;/td&gt;
 &lt;td&gt;TiKV-only: native ordered scan vs Cassandra fan-out&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="rig"&gt;Rig&lt;a class="anchor" href="#rig"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Single-laptop docker stack. Both backends run as 3-node clusters so LWT&lt;/p&gt;</description></item><item><title>Monitoring</title><link>https://danchupin.github.io/strata/operate/monitoring/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/monitoring/</guid><description>&lt;h1 id="monitoring"&gt;Monitoring&lt;a class="anchor" href="#monitoring"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata exposes three observability surfaces and one operator console
embedded in the gateway binary:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Prometheus metrics&lt;/strong&gt; at &lt;code&gt;/metrics&lt;/code&gt; (every replica).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Structured slog logs&lt;/strong&gt; to stdout, JSON-shaped, correlated by
&lt;code&gt;request_id&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;OpenTelemetry traces&lt;/strong&gt; exported via OTLP/HTTP, sampled tail-first
with an in-process ring buffer for failed-trace replay.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Audit log&lt;/strong&gt; in the metadata backend (&lt;code&gt;audit_log&lt;/code&gt; table or TiKV
prefix), one row per state-changing request.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;This page covers the wire-up; the
&lt;a href="https://danchupin.github.io/strata/architecture/observability/"&gt;Observability deep dive&lt;/a&gt;
covers the implementation rationale.&lt;/p&gt;</description></item><item><title>Multi-cluster routing</title><link>https://danchupin.github.io/strata/concepts/multi-cluster/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/concepts/multi-cluster/</guid><description>&lt;h1 id="multi-cluster-routing"&gt;Multi-cluster routing&lt;a class="anchor" href="#multi-cluster-routing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A single Strata gateway can fan writes across multiple data clusters. The
clusters can be Ceph RADOS pools, S3-compatible buckets in different regions
or providers, or a mix. The routing decision happens on every &lt;code&gt;PutObject&lt;/code&gt;
and works the same for chunked RADOS writes and S3-over-S3 pass-through.&lt;/p&gt;
&lt;h2 id="the-two-layers"&gt;The two layers&lt;a class="anchor" href="#the-two-layers"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Routing is decided by two independent inputs, evaluated in order:&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;&lt;strong&gt;Bucket placement policy.&lt;/strong&gt; A bucket can carry an explicit
&lt;code&gt;Placement&lt;/code&gt; map — &lt;code&gt;{cluster-a: 70, cluster-b: 30}&lt;/code&gt; — set via
&lt;code&gt;PUT /admin/v1/buckets/{name}/placement&lt;/code&gt;. When present, this policy wins.
The gateway weighted-picks among the live clusters in the policy and
never routes the bucket elsewhere.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cluster weights.&lt;/strong&gt; When the bucket has no explicit policy, the gateway
synthesizes a default policy from per-cluster weights set via
&lt;code&gt;PUT /admin/v1/clusters/{id}/weight {weight: N}&lt;/code&gt;. Weights are integers
in &lt;code&gt;[0, 100]&lt;/code&gt;; clusters with weight &lt;code&gt;0&lt;/code&gt; accept reads and explicit-policy
writes but receive no new default-routed writes.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;The two layers do not combine. If a bucket has an explicit placement, the
cluster-weight wheel is ignored for that bucket.&lt;/p&gt;</description></item><item><title>Multi-replica cluster</title><link>https://danchupin.github.io/strata/deploy/multi-replica/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/deploy/multi-replica/</guid><description>&lt;h1 id="multi-replica-strata-cluster--operator-guide"&gt;Multi-replica Strata cluster — operator guide&lt;a class="anchor" href="#multi-replica-strata-cluster--operator-guide"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata&amp;rsquo;s gateway is &lt;strong&gt;stateless&lt;/strong&gt;. Replicas don&amp;rsquo;t form a quorum among
themselves; the storage layer (TiKV / RADOS) provides durability +
consistency. A single replica is therefore a &lt;strong&gt;single point of failure
for HTTP traffic&lt;/strong&gt; — not for data. Running &lt;strong&gt;≥2 replicas behind a
load balancer&lt;/strong&gt; is the minimum HA shape.&lt;/p&gt;
&lt;p&gt;This page covers the bare-default 2-replica TiKV lab (two TiKV-backed
replicas behind nginx at host &lt;code&gt;:9999&lt;/code&gt;) — the reference shape for
multi-replica deployments. Same wiring works for 3+ replicas in
production.&lt;/p&gt;</description></item><item><title>Rebalance scaling</title><link>https://danchupin.github.io/strata/architecture/benchmarks/rebalance/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/rebalance/</guid><description>&lt;h1 id="rebalance-worker-scaling-benchmark-phase-2"&gt;Rebalance worker scaling benchmark (Phase 2)&lt;a class="anchor" href="#rebalance-worker-scaling-benchmark-phase-2"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Closes ROADMAP P2 &lt;em&gt;&amp;ldquo;Rebalance worker not sharded — single goroutine
bottleneck on large deploys&amp;rdquo;&lt;/em&gt;. Mirrors the gc + lifecycle Phase 2 shape
(see &lt;a href="https://danchupin.github.io/strata/architecture/benchmarks/gc-lifecycle/#phase-2--multi-leader-us-006"&gt;GC + Lifecycle scaling&lt;/a&gt;
for the same fan-out pattern measured against gc and lifecycle workers).&lt;/p&gt;
&lt;p&gt;The single-leader rebalance worker scans every draining bucket per tick on
one goroutine. Phase 2 (&lt;code&gt;STRATA_REBALANCE_SHARDS=N&lt;/code&gt;) shards that scan by
&lt;code&gt;fnv32a(bucketID) % N&lt;/code&gt;; each replica races for one of the
&lt;code&gt;rebalance-leader-0..N-1&lt;/code&gt; leases and processes only its own shard&amp;rsquo;s bucket
subset. &lt;code&gt;STRATA_REBALANCE_SHARDS=1&lt;/code&gt; reproduces Phase 1 byte-for-byte.&lt;/p&gt;</description></item><item><title>TiKV</title><link>https://danchupin.github.io/strata/architecture/backends/tikv/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/backends/tikv/</guid><description>&lt;h1 id="tikv-metadata-backend"&gt;TiKV metadata backend&lt;a class="anchor" href="#tikv-metadata-backend"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;TiKV is a first-class metadata backend for Strata, on equal footing with
Cassandra (and ScyllaDB as the CQL drop-in). The full &lt;code&gt;meta.Store&lt;/code&gt; contract
in &lt;code&gt;internal/meta/storetest/contract.go&lt;/code&gt; runs against a real PD+TiKV cluster
on every PR via &lt;code&gt;.github/workflows/ci-tikv.yml&lt;/code&gt;.&lt;/p&gt;
&lt;p&gt;The fundamental shape difference vs Cassandra is the &lt;code&gt;objects&lt;/code&gt; table: TiKV
keys are a flat ordered byte space, so &lt;code&gt;ListObjects&lt;/code&gt; is a single ordered
range scan instead of Cassandra&amp;rsquo;s 64-way fan-out + heap-merge. The gateway
discovers this at runtime via the optional &lt;code&gt;meta.RangeScanStore&lt;/code&gt; interface
(&lt;code&gt;internal/meta/store.go&lt;/code&gt;) and dispatches to the native scan path
automatically — no operator action required.&lt;/p&gt;</description></item><item><title>Alerts</title><link>https://danchupin.github.io/strata/operate/alerts/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/alerts/</guid><description>&lt;h1 id="alerts"&gt;Alerts&lt;a class="anchor" href="#alerts"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata ships a curated alert rule set in &lt;code&gt;deploy/prometheus/alerts.yml&lt;/code&gt;.
The file declares &lt;strong&gt;47 rules&lt;/strong&gt; — 17 SLO recording rules, 18 single-window
alert rules, and 12 multi-window burn-rate alerts (Google SRE workbook
ch.5). This page is the operator-facing companion: what each alert means,
when it fires, and what to do.&lt;/p&gt;
&lt;p&gt;Validate the file via &lt;code&gt;make promtool-check&lt;/code&gt;. CI installs &lt;code&gt;promtool&lt;/code&gt; and
runs the target on every push; locally the target degrades to WARN when
the binary is missing (mirrors &lt;code&gt;make helm-lint&lt;/code&gt;). Lab Prometheus
auto-loads the file via the &lt;code&gt;rule_files: [alerts.yml]&lt;/code&gt; directive in
&lt;code&gt;deploy/prometheus/prometheus.yml&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>SLO / SLI</title><link>https://danchupin.github.io/strata/operate/slo/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/slo/</guid><description>&lt;h1 id="slo--sli"&gt;SLO / SLI&lt;a class="anchor" href="#slo--sli"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata ships three production SLOs as a starting point. Operators can
re-tune each target by editing one line in
&lt;code&gt;deploy/prometheus/alerts.yml&lt;/code&gt; — every single-window alert and
multi-burn-rate alert from
&lt;a href="https://danchupin.github.io/strata/operate/alerts/"&gt;/operate/alerts&lt;/a&gt; references the same
recording rule so the change ripples cleanly.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;SLO&lt;/th&gt;
 &lt;th style="text-align: right"&gt;Target&lt;/th&gt;
 &lt;th&gt;Window&lt;/th&gt;
 &lt;th&gt;SLI source&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Availability&lt;/td&gt;
 &lt;td style="text-align: right"&gt;99.9%&lt;/td&gt;
 &lt;td&gt;30 days&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;strata:availability:ratio_rate5m&lt;/code&gt; (recording rule)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Latency p99&lt;/td&gt;
 &lt;td style="text-align: right"&gt;GET/PUT &amp;lt; 500 ms, LIST &amp;lt; 2 s, multipart Complete &amp;lt; 1 s&lt;/td&gt;
 &lt;td&gt;30 days&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;strata:latency_get_put:p99_rate5m&lt;/code&gt; + per-op equivalents&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Durability&lt;/td&gt;
 &lt;td style="text-align: right"&gt;0 non-OK terminal GC acks&lt;/td&gt;
 &lt;td&gt;90 days&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;strata:durability:error_rate5m&lt;/code&gt; (always-on; no inventory dep)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="availability"&gt;Availability&lt;a class="anchor" href="#availability"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;blockquote class='book-hint '&gt;
&lt;p&gt;99.9% of S3 requests over a rolling 30-day window return a non-5xx
status. Admin paths (&lt;code&gt;bucket=&amp;quot;_admin&amp;quot;&lt;/code&gt;) excluded — admin error rate is
tracked separately under the per-tenant dashboard.&lt;/p&gt;</description></item><item><title>Profiling</title><link>https://danchupin.github.io/strata/operate/profiling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/profiling/</guid><description>&lt;h1 id="profiling"&gt;Profiling&lt;a class="anchor" href="#profiling"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata exposes the Go runtime&amp;rsquo;s standard &lt;code&gt;/debug/pprof/*&lt;/code&gt; endpoints when
&lt;code&gt;STRATA_PPROF_ENABLED=true&lt;/code&gt;. Profiles are protected by the same auth
chain that guards &lt;code&gt;/admin/v1/*&lt;/code&gt; (session cookie or SigV4). The
endpoints are &lt;strong&gt;opt-in&lt;/strong&gt; — defense-in-depth — because the heap profile
can leak the contents of in-flight buffers in error paths.&lt;/p&gt;
&lt;h2 id="quick-start"&gt;Quick start&lt;a class="anchor" href="#quick-start"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-bash" data-lang="bash"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Boot Strata with pprof attached to the admin listener.&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;STRATA_PPROF_ENABLED&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;STRATA_ADMIN_LISTEN&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;127.0.0.1:9001 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;STRATA_AUTH_MODE&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;required &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="nv"&gt;STRATA_STATIC_CREDENTIALS&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;AKADMIN:SKADMIN:admin &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; strata server
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# Capture a 30s CPU profile (SigV4 from go tool pprof via aws-cli sigv4 wrapper&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="c1"&gt;# OR from a pre-signed URL; for loopback dev use the admin session cookie).&lt;/span&gt;
&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;go tool pprof -seconds&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="m"&gt;30&lt;/span&gt; -http&lt;span class="o"&gt;=&lt;/span&gt;:7070 &lt;span class="se"&gt;\
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt; -url &lt;span class="s1"&gt;&amp;#39;http://127.0.0.1:9001/debug/pprof/profile?seconds=30&amp;#39;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;h2 id="configuration"&gt;Configuration&lt;a class="anchor" href="#configuration"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Env var&lt;/th&gt;
 &lt;th&gt;TOML key&lt;/th&gt;
 &lt;th&gt;Default&lt;/th&gt;
 &lt;th&gt;Description&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;STRATA_PPROF_ENABLED&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pprof.enabled&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;false&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;Master switch. &lt;code&gt;true&lt;/code&gt; registers &lt;code&gt;/debug/pprof/*&lt;/code&gt;.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;STRATA_PPROF_LISTEN&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pprof.listen&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;empty&lt;/td&gt;
 &lt;td&gt;Optional dedicated listener (e.g. &lt;code&gt;127.0.0.1:9002&lt;/code&gt;). Empty → attach to &lt;code&gt;admin_listen.listen&lt;/code&gt;. One of the two MUST be set when enabled — pprof never attaches to the S3 hot path.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;STRATA_PPROF_BLOCK_RATE&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pprof.block_rate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;runtime.SetBlockProfileRate(N)&lt;/code&gt; argument. &lt;code&gt;0&lt;/code&gt; keeps block profile data empty.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;STRATA_PPROF_MUTEX_RATE&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;pprof.mutex_rate&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;0&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;runtime.SetMutexProfileFraction(N)&lt;/code&gt; argument. &lt;code&gt;0&lt;/code&gt; keeps mutex profile data empty.&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;&lt;code&gt;STRATA_PPROF_ENABLED=true&lt;/code&gt; with neither &lt;code&gt;STRATA_PPROF_LISTEN&lt;/code&gt; nor
&lt;code&gt;STRATA_ADMIN_LISTEN&lt;/code&gt; set fails fast at boot — the gateway refuses to
silently expose profiling on the public S3 listener.&lt;/p&gt;</description></item><item><title>Workers</title><link>https://danchupin.github.io/strata/architecture/workers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/workers/</guid><description>&lt;h1 id="workers"&gt;Workers&lt;a class="anchor" href="#workers"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Background loops run inside the same &lt;code&gt;cmd/strata server&lt;/code&gt; binary as the
gateway. &lt;code&gt;STRATA_WORKERS=&lt;/code&gt; (or &lt;code&gt;--workers=&lt;/code&gt;) selects which to run; an
empty list runs the gateway only. Each worker is leader-elected, panic-
recovered, and supervised — one worker&amp;rsquo;s panic or lease loss never affects
the gateway or sibling workers.&lt;/p&gt;
&lt;h2 id="registry-shape"&gt;Registry shape&lt;a class="anchor" href="#registry-shape"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Each worker has a per-worker file under &lt;code&gt;cmd/strata/workers/&amp;lt;name&amp;gt;.go&lt;/code&gt;
that calls &lt;code&gt;workers.Register&lt;/code&gt; from &lt;code&gt;init()&lt;/code&gt;:&lt;/p&gt;
&lt;div class="highlight"&gt;&lt;pre tabindex="0" class="chroma"&gt;&lt;code class="language-go" data-lang="go"&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nf"&gt;init&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;Register&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;workers&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;Worker&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s"&gt;&amp;#34;gc&amp;#34;&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Build&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kd"&gt;func&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;deps&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;Dependencies&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;Runner&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kt"&gt;error&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="o"&gt;...&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nx"&gt;SkipLease&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="kc"&gt;true&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="c1"&gt;// gc fan-out manages its own per-shard leases&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;})&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span class="line"&gt;&lt;span class="cl"&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;Build&lt;/code&gt; constructs the per-worker runner from the shared &lt;code&gt;workers.Dependencies&lt;/code&gt;
struct (&lt;code&gt;Logger&lt;/code&gt;, &lt;code&gt;Meta&lt;/code&gt;, &lt;code&gt;Data&lt;/code&gt;, &lt;code&gt;Tracer&lt;/code&gt;, &lt;code&gt;Locker&lt;/code&gt;, &lt;code&gt;Region&lt;/code&gt;, &lt;code&gt;EmitLeader&lt;/code&gt;).
Per-worker tunables (&lt;code&gt;STRATA_GC_INTERVAL&lt;/code&gt;, &lt;code&gt;STRATA_LIFECYCLE_*&lt;/code&gt;, …) are read
inside &lt;code&gt;Build&lt;/code&gt; directly so the dependency surface stays small.&lt;/p&gt;</description></item><item><title>Worker + leader election</title><link>https://danchupin.github.io/strata/architecture/worker-leader-election/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/worker-leader-election/</guid><description>&lt;h1 id="worker--leader-election"&gt;Worker + leader election&lt;a class="anchor" href="#worker--leader-election"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Every background worker shares a single supervisor shape: one
goroutine per worker, leader-elected on a per-name lease, panic
recovered with exponential backoff, heartbeat chip wired to the
operator console. This page draws the lifecycle end-to-end and
points at the per-worker carve-outs.&lt;/p&gt;
&lt;h2 id="lifecycle-flowchart"&gt;Lifecycle flowchart&lt;a class="anchor" href="#lifecycle-flowchart"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="mermaid"&gt;flowchart TD
 Boot[&amp;#34;cmd/strata server&amp;#34;] --&amp;gt; Resolve[&amp;#34;workers.Resolve(STRATA_WORKERS)&amp;#34;]
 Resolve --&amp;gt;|valid| Build[&amp;#34;workers.Supervisor.Run(ctx, workers)&amp;#34;]
 Resolve --&amp;gt;|unknown name| Exit[/&amp;#34;exit 2&amp;#34;/]
 Build --&amp;gt; Spawn[&amp;#34;spawn 1 goroutine per worker&amp;#34;]
 Spawn --&amp;gt; LeaseGate{&amp;#34;worker.SkipLease?&amp;#34;}
 LeaseGate -- no --&amp;gt; Acquire[&amp;#34;leader.Session.Acquire(&amp;lt;name&amp;gt;-leader)&amp;#34;]
 LeaseGate -- yes --&amp;gt; RunnerBuild[&amp;#34;Build(deps) → Runner&amp;#34;]
 Acquire -- acquired --&amp;gt; Emit[&amp;#34;deps.EmitLeader(name, true)&amp;#34;]
 Emit --&amp;gt; RunnerBuild
 Acquire -- lost / partition --&amp;gt; Acquire
 RunnerBuild --&amp;gt; Run[&amp;#34;runner.Run(ctx)&amp;#34;]
 Run -- clean return --&amp;gt; Release[&amp;#34;release lease&amp;lt;br/&amp;gt;deps.EmitLeader(name, false)&amp;#34;]
 Run -- panic --&amp;gt; PanicMetric[&amp;#34;strata_worker_panic_total{worker, shard}&amp;lt;br/&amp;gt;++&amp;#34;]
 PanicMetric --&amp;gt; Release
 Release --&amp;gt; Backoff{&amp;#34;healthy ≥ 5m?&amp;#34;}
 Backoff -- yes --&amp;gt; Spawn
 Backoff -- no --&amp;gt; Wait[&amp;#34;sleep 1s → 5s → 30s → 2m&amp;#34;]
 Wait --&amp;gt; Spawn&lt;/pre&gt;&lt;h2 id="supervisor-responsibilities"&gt;Supervisor responsibilities&lt;a class="anchor" href="#supervisor-responsibilities"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;workers.Supervisor.Run(ctx, []workers.Worker)&lt;/code&gt; is the entrypoint:&lt;/p&gt;</description></item><item><title>Compose collapse</title><link>https://danchupin.github.io/strata/architecture/migrations/compose-collapse/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/migrations/compose-collapse/</guid><description>&lt;h1 id="migrating-to-the-single-strata-compose-shape"&gt;Migrating to the single-strata compose shape&lt;a class="anchor" href="#migrating-to-the-single-strata-compose-shape"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;docker compose up -d&lt;/code&gt; used to bring up two strata services in parallel on
the same Cassandra metadata: a single-cluster &lt;code&gt;strata&lt;/code&gt; (port 9999, knew
only the &lt;code&gt;default&lt;/code&gt; RADOS cluster) and a profile-gated &lt;code&gt;strata-multi&lt;/code&gt;
(port 9998, knew &lt;code&gt;default&lt;/code&gt; + &lt;code&gt;cephb&lt;/code&gt;). Both raced for the &lt;strong&gt;same&lt;/strong&gt;
global worker leases (&lt;code&gt;gc-leader-N&lt;/code&gt;, &lt;code&gt;rebalance-leader-N&lt;/code&gt;,
&lt;code&gt;lifecycle-leader-&amp;lt;bucketID&amp;gt;&lt;/code&gt;, etc.) on Cassandra. Whichever container
started first won — and when the single-cluster winner popped a GC
entry for &lt;code&gt;cephb&lt;/code&gt;, deletion failed forever (no cephb connection), the
queue piled up, &lt;code&gt;deregister_ready&lt;/code&gt; stuck &lt;code&gt;false&lt;/code&gt;, and drain wedged.&lt;/p&gt;</description></item><item><title>Drain &amp; rebalance</title><link>https://danchupin.github.io/strata/concepts/drain-rebalance/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/concepts/drain-rebalance/</guid><description>&lt;h1 id="drain--rebalance"&gt;Drain &amp;amp; rebalance&lt;a class="anchor" href="#drain--rebalance"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Taking a data cluster out of rotation is a multi-step process: stop new
writes, move existing bytes off, then remove the cluster from the
configuration. Strata models this as an explicit lifecycle so operators can
follow progress and abort partway through if needed.&lt;/p&gt;
&lt;h2 id="lifecycle"&gt;Lifecycle&lt;a class="anchor" href="#lifecycle"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;pre class="mermaid"&gt;stateDiagram-v2
 [*] --&amp;gt; pending: cluster added to env
 pending --&amp;gt; live: activate
 live --&amp;gt; draining_readonly: drain mode=readonly
 live --&amp;gt; evacuating: drain mode=evacuate
 draining_readonly --&amp;gt; evacuating: upgrade
 draining_readonly --&amp;gt; live: undrain
 evacuating --&amp;gt; live: undrain
 evacuating --&amp;gt; removed: deregister
 removed --&amp;gt; [*]&lt;/pre&gt;&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;pending&lt;/code&gt;&lt;/strong&gt; — a new cluster ID was added to &lt;code&gt;STRATA_RADOS_CLUSTERS&lt;/code&gt; or
&lt;code&gt;STRATA_S3_CLUSTERS&lt;/code&gt; but the gateway has not yet been told to route
default traffic to it. Reads and explicit-policy writes work; default
routing skips it.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;live&lt;/code&gt;&lt;/strong&gt; — normal operating state. The cluster participates in the
default-routing weight wheel.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;draining_readonly&lt;/code&gt;&lt;/strong&gt; — operator-initiated stop-write drain. New PUTs
refuse with &lt;code&gt;503 DrainRefused&lt;/code&gt;. Reads, deletes, and in-flight multipart
sessions keep working. The rebalance worker does not actively move
data — use this for maintenance windows.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;evacuating&lt;/code&gt;&lt;/strong&gt; — operator-initiated decommission drain. Same write
refusal as &lt;code&gt;draining_readonly&lt;/code&gt;, plus the rebalance worker scans the
cluster and migrates chunks to the remaining clusters honoring each
bucket&amp;rsquo;s placement policy. A live progress chip in the operator console
shows bytes moved, estimated time remaining, and per-bucket breakdown.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;&lt;code&gt;removed&lt;/code&gt;&lt;/strong&gt; — cluster is gone. Excluded from every code path.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id="drain-refusal-semantics"&gt;Drain refusal semantics&lt;a class="anchor" href="#drain-refusal-semantics"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;While a cluster is draining, the gateway answers PUTs that would have
landed on it with &lt;code&gt;503 DrainRefused&lt;/code&gt; and a &lt;code&gt;Retry-After: 300&lt;/code&gt; header. This
is &lt;strong&gt;PUT only&lt;/strong&gt; — reads, deletes, HEAD, multipart &lt;code&gt;UploadPart&lt;/code&gt; /
&lt;code&gt;CompleteMultipartUpload&lt;/code&gt; / &lt;code&gt;AbortMultipartUpload&lt;/code&gt;, and &lt;code&gt;ListObjects&lt;/code&gt;
continue working against the draining cluster. The intent is stop-write,
not stop-read.&lt;/p&gt;</description></item><item><title>Parallel chunk PUT + GET (RADOS)</title><link>https://danchupin.github.io/strata/architecture/benchmarks/parallel-chunks/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/parallel-chunks/</guid><description>&lt;h1 id="parallel-chunk-put--get-rados"&gt;Parallel chunk PUT + GET (RADOS)&lt;a class="anchor" href="#parallel-chunk-put--get-rados"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Closes the two open &lt;code&gt;ROADMAP.md&lt;/code&gt; P2 entries:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Parallel chunk upload in &lt;code&gt;PutChunks&lt;/code&gt;&lt;/strong&gt; — sequential &lt;code&gt;for { ReadFull; writeChunk }&lt;/code&gt;
loop replaced with a bounded worker pool that dispatches chunk writes
concurrently. Manifest order + ETag (MD5 of the byte stream) are preserved.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Parallel chunk read / prefetch in &lt;code&gt;GetChunks&lt;/code&gt;&lt;/strong&gt; — sequential per-chunk fetch
replaced with a bounded prefetch reader. The next chunk is fetched while the
current one drains to the wire. Memory-bounded; aborts on client cancel.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;The S3 data backend (&lt;code&gt;internal/data/s3/backend.go&lt;/code&gt;) is unaffected: the AWS SDK
&lt;code&gt;manager.Uploader&lt;/code&gt; already parallelises multipart uploads. Memory backend is
tests-only. Multi-cluster manifests (US-044) automatically benefit since
chunk-write dispatch is cluster-agnostic — the worker pool dials the right
ioctx per chunk via the existing &lt;code&gt;resolveClass&lt;/code&gt; / &lt;code&gt;ioctx&lt;/code&gt; helpers.&lt;/p&gt;</description></item><item><title>Per-bucket signing key rotation</title><link>https://danchupin.github.io/strata/operate/key-rotation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/key-rotation/</guid><description>&lt;h1 id="per-bucket-signing-key-rotation"&gt;Per-bucket signing key rotation&lt;a class="anchor" href="#per-bucket-signing-key-rotation"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata supports KMS-backed per-bucket SigV4 signing keys (US-001 /
US-002 of &lt;code&gt;ralph/auth-dx-trailer-lima&lt;/code&gt;). Each opted-in bucket carries a
32-byte DEK wrapped under a KMS CMK (AWS KMS / Vault Transit /
LocalHSMProvider) plus a creation timestamp. The auth middleware
unwraps on cache miss and uses &lt;code&gt;hex(DEK)&lt;/code&gt; as the SigV4 secret in place
of the IAM access-key secret.&lt;/p&gt;
&lt;p&gt;This page covers the day-2 rotation runbook. The
&lt;a href="https://danchupin.github.io/strata/reference/env-vars/"&gt;KMS provider reference&lt;/a&gt; lists
the per-provider env vars; the
&lt;a href="https://danchupin.github.io/strata/reference/admin-api/"&gt;admin API reference&lt;/a&gt; documents
the three rotation endpoints.&lt;/p&gt;</description></item><item><title>RGW comparison</title><link>https://danchupin.github.io/strata/architecture/benchmarks/rgw-comparison/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/rgw-comparison/</guid><description>&lt;h1 id="benchmarks-vs-ceph-rgw"&gt;Benchmarks vs Ceph RGW&lt;a class="anchor" href="#benchmarks-vs-ceph-rgw"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This page captures the side-by-side numbers backing the README&amp;rsquo;s
&amp;ldquo;drop-in RGW replacement&amp;rdquo; claim. Closes ROADMAP P2 &lt;em&gt;&amp;ldquo;Benchmarks vs RGW&amp;rdquo;&lt;/em&gt;
via cycle &lt;code&gt;ralph/rgw-benchmarks&lt;/code&gt; (US-001..US-012); the post-fix rerun
landed via &lt;code&gt;ralph/p1-fixes&lt;/code&gt; (US-001..US-009) after the per-bucket
&lt;code&gt;bucket_stats&lt;/code&gt; LWT saturation was removed.&lt;/p&gt;
&lt;h2 id="headline-conclusion"&gt;Headline conclusion&lt;a class="anchor" href="#headline-conclusion"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;strong&gt;Bucket-index claim — VERIFIED (provisionally) at the concurrency
RGW can sustain.&lt;/strong&gt; Two independent regimes surface the win:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;1 KiB PUT @ c=8&lt;/strong&gt; (the canonical &amp;ldquo;many small writes&amp;rdquo; hot path):
Strata p99 ≈ &lt;strong&gt;211 ms&lt;/strong&gt; vs RGW p99 &lt;strong&gt;11–22 s&lt;/strong&gt; (runs varied; one
run wedged at 364 s). Strata is &lt;strong&gt;~50× faster at p99&lt;/strong&gt; and stays
flat across the 3-run set; RGW&amp;rsquo;s omap-index serialisation surfaces
immediately past &lt;code&gt;c=1&lt;/code&gt; on the single-OSD lab. Same data tier
(single-OSD &lt;code&gt;ceph-a&lt;/code&gt; memstore), same &lt;code&gt;make up-bench-rgw&lt;/code&gt; shape — the
delta is gateway-side, not OSD.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;ListObjects 100k-key @ c=8&lt;/strong&gt;: Strata sustains
&lt;strong&gt;~208 k ops/s @ p99 ~640 ms&lt;/strong&gt; across 3 runs, zero errors. RGW
could not be measured at this scale on the lima reference dev
box — the per-bucket pools &lt;code&gt;default.rgw.buckets.index&lt;/code&gt; saturate
the memstore OSD before the seed phase completes. The very fact
that RGW cannot complete the workload at the same lab shape
reinforces the bucket-index thesis: Strata&amp;rsquo;s sharded fan-out
(&lt;code&gt;s/B/&amp;lt;bid&amp;gt;/bs/&amp;lt;shard&amp;gt;&lt;/code&gt; × 8 shards on TiKV; sharded &lt;code&gt;objects&lt;/code&gt;
table on Cassandra) absorbs the same concurrent write pressure
RGW&amp;rsquo;s omap index falls over on.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;strong&gt;RGW wins at c=1 small PUT&lt;/strong&gt; as expected: Strata p99 ≈ &lt;strong&gt;12 ms&lt;/strong&gt;
vs RGW p99 ≈ &lt;strong&gt;2 ms&lt;/strong&gt;. The user-space SigV4 + policy-verify hop
Strata adds vs RGW&amp;rsquo;s in-process auth costs ~10 ms on the
single-threaded hot path; the gap closes as soon as concurrency
arrives because the gateway-side per-request cost stops dominating
relative to backend serialisation.&lt;/p&gt;</description></item><item><title>S3 API operations</title><link>https://danchupin.github.io/strata/reference/s3-api/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/reference/s3-api/</guid><description>&lt;!--
Maintainer note: this table is hand-maintained. When adding a new handler
in internal/s3api/, append a row here in the same PR. A lint test
(internal/s3api/docs_reference_test.go) AST-parses the dispatch functions
ServeHTTP / handleBucket / handleObject / handleBucketInventory in
internal/s3api/, collects every *Server method called directly from those
bodies, and asserts each one either:

 1. appears in this markdown — matched by backtick'd function name OR by
 a `Handler file:line` cell of the form `internal/s3api/&lt;file&gt;.go:&lt;line&gt;`
 that points at the func declaration, OR
 2. carries a `// docs:skip` line comment immediately above the func
 declaration (tolerated as intentionally-internal — non-S3 helpers
 like auth gates, quota checks, website routing).

The same test catches orphan rows: any `internal/s3api/&lt;file&gt;.go:&lt;line&gt;`
cell that does NOT resolve to a *Server method body fails the build
(handler renamed or removed without updating the row).

Row sourcing: walk the dispatch arms in `internal/s3api/server.go` +
`inventory.go`. Each route arm becomes one row. `Handler file:line` points
at the route arm in `server.go` OR the leaf handler in
`internal/s3api/&lt;feature&gt;.go`.
--&gt;
&lt;h1 id="s3-api-operations"&gt;S3 API operations&lt;a class="anchor" href="#s3-api-operations"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Operator + SDK-author index of every S3 operation Strata answers. The router
dispatches &lt;code&gt;ServeHTTP&lt;/code&gt; → &lt;code&gt;handleBucket&lt;/code&gt; (bucket-scope) → &lt;code&gt;handleObject&lt;/code&gt;
(object-scope); leaf handlers live alongside each feature in the
&lt;code&gt;s3api&lt;/code&gt; package. The &lt;code&gt;Handler file:line&lt;/code&gt; column is intentionally
line-anchored — a lint test asserts every dispatched method shows up here
(see the maintainer note above this section). AWS divergence column is empty
when the operation is full-compat; one-liner otherwise.&lt;/p&gt;</description></item><item><title>S3-over-S3 data backend</title><link>https://danchupin.github.io/strata/architecture/backends/s3/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/backends/s3/</guid><description>&lt;h1 id="s3-data-backend"&gt;S3 Data Backend&lt;a class="anchor" href="#s3-data-backend"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The S3-over-S3 data backend stores object bytes in any S3-compatible bucket
(AWS S3, MinIO, Ceph RGW, Garage). One Strata object = one backend S3 object
under the key &lt;code&gt;&amp;lt;bucket-uuid&amp;gt;/&amp;lt;object-uuid&amp;gt;&lt;/code&gt;. RADOS remains the default and
recommended data backend; this page covers the S3 alternative.&lt;/p&gt;
&lt;h2 id="when-to-choose-s3-over-rados"&gt;When to choose S3 over RADOS&lt;a class="anchor" href="#when-to-choose-s3-over-rados"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Question&lt;/th&gt;
 &lt;th&gt;Pick &lt;strong&gt;RADOS&lt;/strong&gt; if&lt;/th&gt;
 &lt;th&gt;Pick &lt;strong&gt;S3&lt;/strong&gt; if&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;Existing infra investment&lt;/td&gt;
 &lt;td&gt;Operator already runs Ceph&lt;/td&gt;
 &lt;td&gt;Operator already runs MinIO / has AWS account / wants Garage&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Tail latency budget&lt;/td&gt;
 &lt;td&gt;&amp;lt; 5 ms p99 GET on hot data is critical&lt;/td&gt;
 &lt;td&gt;50–200 ms p99 GET is acceptable (HTTPS round-trip floor)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Bytes-stored cost model&lt;/td&gt;
 &lt;td&gt;Self-hosted hardware, want bare-metal economics&lt;/td&gt;
 &lt;td&gt;Cloud storage tiers (Standard-IA, Glacier) carry the lifecycle&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Availability boundary&lt;/td&gt;
 &lt;td&gt;Single-DC, hardware-controlled&lt;/td&gt;
 &lt;td&gt;Multi-AZ via the cloud provider&amp;rsquo;s bucket SLA&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Encryption-at-rest mandate&lt;/td&gt;
 &lt;td&gt;Strata-side envelope encryption is enough&lt;/td&gt;
 &lt;td&gt;Want backend SSE / KMS independent of Strata&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;Operational complexity&lt;/td&gt;
 &lt;td&gt;Team owns Ceph operations&lt;/td&gt;
 &lt;td&gt;Team owns S3 operations (or has zero ops via AWS)&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;The two backends are equal-tier alternatives — pick by what your operations
team already runs. Mixing within a single Strata deployment is not supported
(&lt;code&gt;STRATA_DATA_BACKEND&lt;/code&gt; is a single-value flag).&lt;/p&gt;</description></item><item><title>Scaling</title><link>https://danchupin.github.io/strata/operate/scaling/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/scaling/</guid><description>&lt;h1 id="scaling"&gt;Scaling&lt;a class="anchor" href="#scaling"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata replicas are stateless gateways — they own no on-disk state, so
sizing is a function of three signals: peak request rate (RPS), the
working-set bytes the gateway holds in flight (RAM), and the cores the
SigV4 + crypto path consumes (CPU). The metadata + data tiers
(Cassandra / TiKV / RADOS) follow their own upstream sizing guides; this
page covers the gateway tier and points operators at the upstream
references.&lt;/p&gt;</description></item><item><title>Storage status</title><link>https://danchupin.github.io/strata/architecture/storage/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/storage/</guid><description>&lt;h1 id="storage-status--operator-guide"&gt;Storage status — operator guide&lt;a class="anchor" href="#storage-status--operator-guide"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The Storage page (&lt;code&gt;/console/storage&lt;/code&gt;) and the Cluster Overview Storage hero
card surface the live health of the meta + data backends and the
per-storage-class object distribution. This page covers the env vars the UI
reads through, what each warning means, and how to interpret the
RADOS / TiKV / Cassandra-specific signals.&lt;/p&gt;
&lt;p&gt;The page is fed by three small admin endpoints:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Endpoint&lt;/th&gt;
 &lt;th&gt;Returns&lt;/th&gt;
 &lt;th&gt;Polled by&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GET /admin/v1/storage/meta&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;MetaHealthReport&lt;/code&gt; (Cassandra peers / TiKV PD stores / memory)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/storage&lt;/code&gt; Meta tab @ 30 s&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GET /admin/v1/storage/data&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;DataHealthReport&lt;/code&gt; (RADOS pool stats / S3 reachability / memory)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/storage&lt;/code&gt; Data tab @ 30 s&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GET /admin/v1/storage/classes&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;{classes:[…], pools_by_class:{…}}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;/storage&lt;/code&gt; Data tab @ 30 s, Cluster Overview hero @ 60 s&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;GET /admin/v1/storage/health&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;aggregate &lt;code&gt;{ok, warnings, source}&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;&amp;lt;StorageDegradedBanner&amp;gt;&lt;/code&gt; @ 30 s on every authed page&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;p&gt;Schema details live in &lt;code&gt;internal/adminapi/openapi.yaml&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>RADOS ReadOp / WriteOp batching</title><link>https://danchupin.github.io/strata/architecture/benchmarks/rados-ops/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/rados-ops/</guid><description>&lt;h1 id="rados-readop--writeop-batching"&gt;RADOS ReadOp / WriteOp batching&lt;a class="anchor" href="#rados-readop--writeop-batching"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Closes the &lt;code&gt;ROADMAP.md&lt;/code&gt; P3 entry &lt;strong&gt;ReadOp / WriteOp batching in RADOS&lt;/strong&gt;
(US-003 of &lt;code&gt;ralph/storage-correctness&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="what-batching-does"&gt;What batching does&lt;a class="anchor" href="#what-batching-does"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;internal/data/rados/ops.go&lt;/code&gt; (build tag &lt;code&gt;ceph&lt;/code&gt;) defines two helpers that
bundle a chunk Write / Read with N xattr ops into a single librados
operation:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;code&gt;writeChunkBatched(ioctx, oid, body, xattrs)&lt;/code&gt; — builds a &lt;code&gt;goceph.WriteOp&lt;/code&gt;,
appends &lt;code&gt;WriteFull(body)&lt;/code&gt; + &lt;code&gt;SetXattr(k, v)&lt;/code&gt; per entry of &lt;code&gt;xattrs&lt;/code&gt;, then
&lt;code&gt;Operate()&lt;/code&gt;s once. xattrs nil ⇒ byte-identical to the legacy &lt;code&gt;writeChunk&lt;/code&gt;
in &lt;code&gt;backend.go&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;readChunkBatched(ioctx, oid, off, length, wantXattrs)&lt;/code&gt; — builds a
&lt;code&gt;goceph.ReadOp&lt;/code&gt; with &lt;code&gt;Read(off, buf)&lt;/code&gt;, then &lt;code&gt;Operate()&lt;/code&gt;s once. When
&lt;code&gt;wantXattrs&lt;/code&gt; is true a follow-up &lt;code&gt;ioctx.ListXattrs&lt;/code&gt; runs to populate the
xattrs map — go-ceph v0.39 does not expose &lt;code&gt;rados_read_op_getxattrs&lt;/code&gt;, so
xattrs sit on a second client round-trip. Today no caller requests
xattrs.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;code&gt;writeChunk&lt;/code&gt; (legacy) and &lt;code&gt;writeChunkBatched(_, _, _, nil)&lt;/code&gt; produce the
same on-wire librados &lt;code&gt;WriteOp{ WriteFull }&lt;/code&gt;. The win materialises only
when xattrs are added to the PUT hot path — a future cycle. The helpers
exist now so the call sites are wired and the bench shape is established.&lt;/p&gt;</description></item><item><title>Sharding</title><link>https://danchupin.github.io/strata/architecture/sharding/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/sharding/</guid><description>&lt;h1 id="sharding"&gt;Sharding&lt;a class="anchor" href="#sharding"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Sharding is the single biggest divergence from Ceph RGW: every bucket&amp;rsquo;s
metadata is split across &lt;code&gt;N&lt;/code&gt; partitions instead of living in a single
bucket-index object. The split avoids RGW&amp;rsquo;s bucket-index ceiling at
large object counts and lets ListObjects scale linearly with &lt;code&gt;N&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="objects-table-partition-key"&gt;Objects table partition key&lt;a class="anchor" href="#objects-table-partition-key"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The cassandra &lt;code&gt;objects&lt;/code&gt; table is partitioned by &lt;code&gt;(bucket_id, shard)&lt;/code&gt;:&lt;/p&gt;
&lt;pre tabindex="0"&gt;&lt;code&gt;PRIMARY KEY ((bucket_id, shard), key, version_id)
WITH CLUSTERING ORDER BY (key ASC, version_id DESC)&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;Where &lt;code&gt;shard = fnv32a(key) % N&lt;/code&gt; and &lt;code&gt;N&lt;/code&gt; is per-bucket
(&lt;code&gt;STRATA_BUCKET_SHARDS&lt;/code&gt; at bucket creation, default 64). &lt;code&gt;N&lt;/code&gt; must be a
power of two — &lt;code&gt;meta.IsValidShardCount(n)&lt;/code&gt; enforces it. Power-of-two
constraint matters for the &lt;a href="#online-reshard"&gt;reshard worker&lt;/a&gt; below: when &lt;code&gt;N&lt;/code&gt; doubles, every old
shard either stays under the new modulo or splits cleanly into two new
ones, never three.&lt;/p&gt;</description></item><item><title>Tracing</title><link>https://danchupin.github.io/strata/best-practices/tracing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/tracing/</guid><description>&lt;h1 id="tracing"&gt;Tracing&lt;a class="anchor" href="#tracing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata emits OpenTelemetry spans across every meaningful tier of the
request and worker paths. Each span carries the &lt;code&gt;strata.component&lt;/code&gt;
attribute so an operator can filter the entire gateway path or the
entire worker path in one Jaeger query, and each worker iteration
appears as a discrete trace so a slow / failing tick is easy to
correlate with the meta + data ops it triggered.&lt;/p&gt;
&lt;p&gt;The wire-up env vars + tail-sampler + ring buffer behaviour live in
&lt;a href="https://danchupin.github.io/strata/operate/monitoring/#opentelemetry-tracing"&gt;Monitoring&lt;/a&gt;.
This page is the operator-facing reference for &lt;strong&gt;what spans exist,
how they are named, and how to filter them&lt;/strong&gt;.&lt;/p&gt;</description></item><item><title>RADOS conn-pool sizing</title><link>https://danchupin.github.io/strata/architecture/benchmarks/rados-pool/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/rados-pool/</guid><description>&lt;h1 id="rados-conn-pool-sizing"&gt;RADOS conn-pool sizing&lt;a class="anchor" href="#rados-conn-pool-sizing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Closes the &lt;code&gt;ROADMAP.md&lt;/code&gt; P3 entry &lt;strong&gt;Connection pool tuning&lt;/strong&gt;
(US-004 of &lt;code&gt;ralph/storage-correctness&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="what-the-pool-does"&gt;What the pool does&lt;a class="anchor" href="#what-the-pool-does"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;A single librados &lt;code&gt;*Conn&lt;/code&gt; serialises ops through one cephx session and a
per-conn thread pool. Write-heavy workloads that saturate that single
session see contention even though the OSD layer has headroom. The
&lt;code&gt;connPool&lt;/code&gt; in &lt;code&gt;internal/data/rados/pool.go&lt;/code&gt; (build tag &lt;code&gt;ceph&lt;/code&gt;) holds N
pre-connected &lt;code&gt;*goceph.Conn&lt;/code&gt; instances per cluster and serves them
round-robin via an atomic counter; per-conn IOContexts are cached
lazily by &lt;code&gt;(pool, namespace)&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>OTel ring-buffer bytes budget</title><link>https://danchupin.github.io/strata/architecture/benchmarks/otel-ringbuf/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/otel-ringbuf/</guid><description>&lt;h1 id="otel-ring-buffer-bytes-budget"&gt;OTel ring-buffer bytes budget&lt;a class="anchor" href="#otel-ring-buffer-bytes-budget"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Closes the &lt;code&gt;ROADMAP.md&lt;/code&gt; P3 entry &lt;strong&gt;OTel ring-buffer eviction tuning
under burst load&lt;/strong&gt; (US-005 of &lt;code&gt;ralph/storage-correctness&lt;/code&gt;).&lt;/p&gt;
&lt;h2 id="what-the-ring-buffer-does"&gt;What the ring buffer does&lt;a class="anchor" href="#what-the-ring-buffer-does"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;internal/otel/ringbuf.RingBuffer&lt;/code&gt; is an in-process &lt;code&gt;SpanProcessor&lt;/code&gt;
that retains every finished span under an LRU + bytes-budget. The
&lt;code&gt;/admin/v1/diagnostics/trace/{requestID}&lt;/code&gt; admin endpoint reads from
the ring so operators can debug a request without a Jaeger / Tempo
deployment. Tail sampling on the exporter side still drops most spans
on the wire — the ring keeps the full payload locally.&lt;/p&gt;</description></item><item><title>Admin API viewer</title><link>https://danchupin.github.io/strata/reference/admin-api-viewer/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/reference/admin-api-viewer/</guid><description>&lt;h1 id="admin-api-viewer"&gt;Admin API viewer&lt;a class="anchor" href="#admin-api-viewer"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Interactive &lt;a href="https://github.com/Redocly/redoc"&gt;Redoc&lt;/a&gt; render of the canonical
Admin-API OpenAPI contract. The YAML is copied into the Hugo static dir at
build time by &lt;code&gt;make docs-openapi-copy&lt;/code&gt; (a prerequisite of &lt;code&gt;make docs-build&lt;/code&gt;
/ &lt;code&gt;make docs-serve&lt;/code&gt;), so the viewer always reflects the contract at the same
SHA as the rest of the docs site.&lt;/p&gt;
&lt;p&gt;For a flat index of every admin endpoint with method, path, audit verb,
and a one-line summary, see &lt;a href="https://danchupin.github.io/strata/reference/admin-api/"&gt;Admin API surface&lt;/a&gt;.
For the raw contract, fetch &lt;a href="https://danchupin.github.io/strata/openapi.yaml"&gt;/openapi.yaml&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Backup + restore</title><link>https://danchupin.github.io/strata/operate/backup-restore/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/backup-restore/</guid><description>&lt;h1 id="backup--restore"&gt;Backup + restore&lt;a class="anchor" href="#backup--restore"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata splits backup responsibility along the same tiers the gateway
splits state into:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Metadata tier (Cassandra / TiKV):&lt;/strong&gt; the source of truth for
bucket / object / IAM rows. Backed up via the upstream tooling for
each backend.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data tier (RADOS / S3-over-S3):&lt;/strong&gt; chunk bytes. Backed up via Ceph
pool snapshots or the upstream S3 service&amp;rsquo;s native versioning /
replication.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Cross-region replication (replicator worker):&lt;/strong&gt; ships object PUT /
DELETE events to a peer Strata cluster in near-real time.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Inventory worker:&lt;/strong&gt; writes manifest.json + CSV.gz pairs that
document every object in a bucket — useful as an audit ledger for
external backup tooling.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This page is a backup-strategy overview. Implementation details live in
&lt;a href="https://danchupin.github.io/strata/architecture/workers/"&gt;Architecture — Workers&lt;/a&gt; (the
inventory + replicator workers) and the upstream docs for Ceph /
Cassandra / TiKV.&lt;/p&gt;</description></item><item><title>Drain progress physical chunks</title><link>https://danchupin.github.io/strata/architecture/migrations/drain-progress-physical/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/migrations/drain-progress-physical/</guid><description>&lt;h1 id="migrating-to-the-physical-chunk-drain-progress"&gt;Migrating to the physical-chunk drain progress&lt;a class="anchor" href="#migrating-to-the-physical-chunk-drain-progress"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;GET /admin/v1/clusters/&amp;lt;id&amp;gt;/drain-progress&lt;/code&gt; and the operator console
&lt;code&gt;&amp;lt;DrainProgressBar&amp;gt;&lt;/code&gt; used to surface &lt;code&gt;chunks_on_cluster&lt;/code&gt; — the
manifest-derived chunk count — as the sole headline metric. After the
&lt;code&gt;ralph/drain-progress-physical&lt;/code&gt; cycle (US-001..US-003) the response
gains three additive fields and the UI renders a 3-state machine that
distinguishes manifest progress from physical pool state.&lt;/p&gt;
&lt;p&gt;This page documents the wire shape change, the back-compat fallback for
backends without the new probe, and the operator-visible state machine.
There is no schema migration; no data migration; the change is purely
additive on the &lt;code&gt;/drain-progress&lt;/code&gt; JSON shape.&lt;/p&gt;</description></item><item><title>GC + lifecycle tuning</title><link>https://danchupin.github.io/strata/best-practices/gc-lifecycle-tuning/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/gc-lifecycle-tuning/</guid><description>&lt;h1 id="gc--lifecycle-tuning"&gt;GC + lifecycle tuning&lt;a class="anchor" href="#gc--lifecycle-tuning"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The gc and lifecycle workers run inside every gateway replica. The
operator-facing knobs split into two layers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Phase 1 — per-replica fan-out:&lt;/strong&gt; &lt;code&gt;STRATA_GC_CONCURRENCY&lt;/code&gt; /
&lt;code&gt;STRATA_LIFECYCLE_CONCURRENCY&lt;/code&gt; cap the goroutine count inside one
replica&amp;rsquo;s worker.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Phase 2 — multi-replica sharding:&lt;/strong&gt; &lt;code&gt;STRATA_GC_SHARDS&lt;/code&gt; shards the
leader-election space so N replicas process disjoint slices of the
queue in parallel.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Both layers compose: a 3-replica deploy with &lt;code&gt;STRATA_GC_SHARDS=3&lt;/code&gt; and
per-replica &lt;code&gt;STRATA_GC_CONCURRENCY=64&lt;/code&gt; runs three independent fan-outs
against disjoint shards.&lt;/p&gt;</description></item><item><title>Kubernetes</title><link>https://danchupin.github.io/strata/deploy/kubernetes/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/deploy/kubernetes/</guid><description>&lt;h1 id="kubernetes-deployment"&gt;Kubernetes deployment&lt;a class="anchor" href="#kubernetes-deployment"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata on Kubernetes is a stateless gateway tier in front of external
metadata (TiKV or Cassandra) and object-data (RADOS or S3) clusters.
Pods are interchangeable — no PVCs, no per-pod storage, no quorum
among gateways. Scale horizontally with &lt;code&gt;kubectl scale&lt;/code&gt;; durability
lives in the storage tier.&lt;/p&gt;
&lt;p&gt;This page walks through the worked example committed under
&lt;a href="https://github.com/danchupin/strata/tree/main/deploy/k8s"&gt;&lt;code&gt;deploy/k8s/&lt;/code&gt;&lt;/a&gt;
— a 3-replica &lt;code&gt;Deployment&lt;/code&gt; + &lt;code&gt;Service&lt;/code&gt; + &lt;code&gt;ConfigMap&lt;/code&gt; + &lt;code&gt;Secret&lt;/code&gt; +
&lt;code&gt;Ingress&lt;/code&gt; aimed at a production-shaped cluster. A Helm chart ships
alongside at &lt;a href="https://github.com/danchupin/strata/tree/main/deploy/helm/strata"&gt;&lt;code&gt;deploy/helm/strata/&lt;/code&gt;&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Observability</title><link>https://danchupin.github.io/strata/architecture/observability/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/observability/</guid><description>&lt;h1 id="observability"&gt;Observability&lt;a class="anchor" href="#observability"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata ships three correlated observability surfaces: structured logs,
an audit log table for state-changing requests, and OTel tracing with a
ring-buffer trace browser embedded in the operator console. All three
key off the same &lt;code&gt;request_id&lt;/code&gt; so an operator can pivot from one to the
next without rebuilding context.&lt;/p&gt;
&lt;h2 id="structured-logs-slog"&gt;Structured logs (slog)&lt;a class="anchor" href="#structured-logs-slog"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;internal/logging&lt;/code&gt; is the canonical setup. The &lt;code&gt;cmd/strata&lt;/code&gt; binary (both
&lt;code&gt;strata server&lt;/code&gt; and &lt;code&gt;strata admin&lt;/code&gt;) calls &lt;code&gt;logging.Setup()&lt;/code&gt; first thing to install a
JSON-handler &lt;code&gt;*slog.Logger&lt;/code&gt; driven by &lt;code&gt;STRATA_LOG_LEVEL&lt;/code&gt;
(&lt;code&gt;DEBUG&lt;/code&gt;/&lt;code&gt;INFO&lt;/code&gt;/&lt;code&gt;WARN&lt;/code&gt;/&lt;code&gt;ERROR&lt;/code&gt;; default &lt;code&gt;INFO&lt;/code&gt;).&lt;/p&gt;</description></item><item><title>Workers</title><link>https://danchupin.github.io/strata/concepts/workers/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/concepts/workers/</guid><description>&lt;h1 id="workers"&gt;Workers&lt;a class="anchor" href="#workers"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;A Strata deployment runs the same &lt;code&gt;strata&lt;/code&gt; binary in two modes: the gateway
handles S3 traffic, and one or more &lt;strong&gt;workers&lt;/strong&gt; handle the background work
that does not belong on the request path. Workers run inside the same
binary you already deploy — set &lt;code&gt;STRATA_WORKERS=gc,lifecycle,…&lt;/code&gt; on a
gateway replica and it spawns those loops alongside the HTTP listener.&lt;/p&gt;
&lt;p&gt;Every worker is &lt;strong&gt;leader-elected&lt;/strong&gt;: across N gateway replicas, one replica
acquires the lease for a given worker and runs it; the others stand by and
take over if the leader&amp;rsquo;s lease expires. Leases are scoped per-worker, so
different replicas can lead different workers — &lt;code&gt;gc&lt;/code&gt; on replica A,
&lt;code&gt;lifecycle&lt;/code&gt; on replica B. Workers panic-restart on exponential backoff so a
single iteration error never takes a worker offline permanently.&lt;/p&gt;</description></item><item><title>Reconcile + rebuild-index</title><link>https://danchupin.github.io/strata/operate/metadata-data-reconcile/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/metadata-data-reconcile/</guid><description>&lt;h1 id="reconcile--rebuild-index"&gt;Reconcile + rebuild-index&lt;a class="anchor" href="#reconcile--rebuild-index"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata splits the S3-key→chunk map (&lt;strong&gt;meta&lt;/strong&gt; tier: TiKV / Cassandra)
from the chunk bytes (&lt;strong&gt;data&lt;/strong&gt; tier: RADOS / S3-over-S3). That split
is the scale advantage — but it drops the safety net RGW gets by
colocating its bucket index with the data. After you restore one tier
from a backup that is even slightly out of step with the other, the
two tiers diverge silently:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Orphan chunks&lt;/strong&gt; — data has a chunk no manifest references. GC walks
meta→data, so it can never see them; they leak storage forever.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Dangling manifests&lt;/strong&gt; — meta has a manifest pointing at a chunk the
data tier no longer holds. The client discovers it as a &lt;code&gt;5xx&lt;/code&gt; on
&lt;code&gt;GET&lt;/code&gt;, not on the restore.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Lost meta backup&lt;/strong&gt; — the bytes are intact in RADOS but unreadable,
because the key→chunk map is gone.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This page is the post-restore runbook for realigning the two tiers.
It pairs with &lt;a href="https://danchupin.github.io/strata/operate/backup-restore/"&gt;Backup + restore&lt;/a&gt;
(how each tier is snapshotted) — run reconcile &lt;strong&gt;after&lt;/strong&gt; the restore
drill, before you promote traffic.&lt;/p&gt;</description></item><item><title>Bench runner setup (self-hosted)</title><link>https://danchupin.github.io/strata/developers/bench-runner-setup/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/developers/bench-runner-setup/</guid><description>&lt;h1 id="bench-runner-setup-self-hosted"&gt;Bench runner setup (self-hosted)&lt;a class="anchor" href="#bench-runner-setup-self-hosted"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The weekly RGW comparison bench
(&lt;a href="https://danchupin.github.io/strata/architecture/benchmarks/rgw-comparison/"&gt;&lt;code&gt;.github/workflows/bench-rgw.yml&lt;/code&gt;&lt;/a&gt;)
runs on a self-hosted GitHub Actions runner labelled
&lt;code&gt;[self-hosted, strata-bench]&lt;/code&gt;. The runner is operator-provisioned once;
afterwards the cron + manual-dispatch triggers fire on schedule with zero
human input. Auto-PRs land against &lt;code&gt;main&lt;/code&gt; carrying refreshed numbers and
the runner&amp;rsquo;s specs.&lt;/p&gt;
&lt;p&gt;This page covers the one-time provision. Closes ROADMAP P3
&lt;em&gt;&amp;ldquo;bench-rgw lima envelope fix&amp;rdquo;&lt;/em&gt; via US-005 of cycle
&lt;code&gt;ralph/auth-dx-trailer-lima&lt;/code&gt;.&lt;/p&gt;
&lt;h2 id="required-hardware"&gt;Required hardware&lt;a class="anchor" href="#required-hardware"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;The bench cycle exercises an 11-workload sweep with multipart uploads up
to 25 GiB transient and a 100 k-key list seed. The lima dev box does not
have the RAM envelope for the full sweep
(&lt;a href="./../architecture/benchmarks/rgw-comparison/#limitations"&gt;&lt;code&gt;rgw-comparison.md&lt;/code&gt; Limitations&lt;/a&gt;).
A dedicated Linux box clears the blocker.&lt;/p&gt;</description></item><item><title>Capacity planning</title><link>https://danchupin.github.io/strata/operate/capacity-planning/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/capacity-planning/</guid><description>&lt;h1 id="capacity-planning"&gt;Capacity planning&lt;a class="anchor" href="#capacity-planning"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Capacity on a Strata deploy splits across three independently-scaled
tiers:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Gateway tier:&lt;/strong&gt; stateless replicas. Capacity = peak RPS / SigV4 +
routing budget per replica. Scale horizontally; see
&lt;a href="https://danchupin.github.io/strata/operate/scaling/"&gt;Scaling&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Metadata tier:&lt;/strong&gt; Cassandra / TiKV. Capacity = total object-row
count × per-row overhead, plus the compare-and-set or pessimistic-txn
rate during writes.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Data tier:&lt;/strong&gt; RADOS / S3-over-S3. Capacity = total object bytes ×
pool replication factor, plus chunk fan-out overhead.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;This page covers the math; pair with
&lt;a href="https://danchupin.github.io/strata/operate/backup-restore/"&gt;Backup + restore&lt;/a&gt; for the
backup overhead and
&lt;a href="https://danchupin.github.io/strata/best-practices/gc-lifecycle-tuning/"&gt;GC + lifecycle tuning&lt;/a&gt;
for the drain rate.&lt;/p&gt;</description></item><item><title>Quotas + billing</title><link>https://danchupin.github.io/strata/best-practices/quotas-billing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/quotas-billing/</guid><description>&lt;h1 id="quotas--billing"&gt;Quotas + billing&lt;a class="anchor" href="#quotas--billing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata enforces hard per-bucket and per-user storage quotas at PUT-validate
time and emits a nightly per-(bucket, storage class) usage aggregate that an
external invoice generator can consume. The shape mirrors AWS / RGW so a
RADOS Gateway tenant migrating to Strata sees the same &lt;code&gt;QuotaExceeded&lt;/code&gt; 403
on overage.&lt;/p&gt;
&lt;p&gt;The cycle stops at the usage feed. Invoice ledger / payment integration
lives in a separate ops-layer service that consumes &lt;code&gt;usage_aggregates&lt;/code&gt;.&lt;/p&gt;</description></item><item><title>TiKV-default lab</title><link>https://danchupin.github.io/strata/architecture/migrations/tikv-default-lab/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/migrations/tikv-default-lab/</guid><description>&lt;h1 id="migrating-to-the-tikv-default-2-replica-lab"&gt;Migrating to the TiKV-default 2-replica lab&lt;a class="anchor" href="#migrating-to-the-tikv-default-2-replica-lab"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;&lt;code&gt;docker compose up -d&lt;/code&gt; used to bring up a single Cassandra-backed
&lt;code&gt;strata&lt;/code&gt; service on &lt;code&gt;:9999&lt;/code&gt; plus a &lt;code&gt;ceph&lt;/code&gt; + &lt;code&gt;ceph-b&lt;/code&gt; multi-cluster
RADOS pair. TiKV was opt-in via &lt;code&gt;--profile tikv&lt;/code&gt; (&lt;code&gt;strata-tikv&lt;/code&gt;) or
&lt;code&gt;--profile lab-tikv&lt;/code&gt; / &lt;code&gt;--profile lab-tikv-3&lt;/code&gt; for 2- and 3-replica
labs, and a parallel 3-replica Cassandra lab lived under
&lt;code&gt;--profile lab-cassandra-3&lt;/code&gt;. Four distinct profile shapes plus the
always-on bare default — each rarely-used, each one a maintenance
surface to keep green across CI + smoke + bench scripts.&lt;/p&gt;</description></item><item><title>TLS termination + backend mTLS</title><link>https://danchupin.github.io/strata/operate/tls-termination/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/tls-termination/</guid><description>&lt;h1 id="tls-termination--backend-mtls"&gt;TLS termination + backend mTLS&lt;a class="anchor" href="#tls-termination--backend-mtls"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This is the operator playbook for putting Strata on the wire securely.
The gateway can terminate TLS itself (recommended for prod) or sit behind
a load balancer that handles termination upstream. Backend connections
(Cassandra, TiKV, S3-upstream) each take an opt-in mutual-TLS bundle so a
network intruder cannot impersonate the gateway. RADOS is the one
exception: it authenticates via Ceph&amp;rsquo;s native cephx protocol and ignores
the TLS layer entirely (see &lt;a href="#rados-uses-cephx-not-tls"&gt;RADOS uses cephx&lt;/a&gt;
below).&lt;/p&gt;</description></item><item><title>Image verification</title><link>https://danchupin.github.io/strata/operate/image-verification/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/image-verification/</guid><description>&lt;h1 id="image-verification"&gt;Image verification&lt;a class="anchor" href="#image-verification"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Every Strata image published to &lt;code&gt;ghcr.io/danchupin/strata&lt;/code&gt; is signed,
attested, and inventoried by the release pipeline
(&lt;code&gt;.github/workflows/release-image.yml&lt;/code&gt;). This page is the operator
playbook for verifying that supply-chain evidence &lt;strong&gt;before&lt;/strong&gt; the image
reaches a node, plus two Kubernetes admission-control shapes that make
the check mandatory at deploy time.&lt;/p&gt;
&lt;p&gt;The pipeline produces four pieces of evidence, all anchored to the same
image digest:&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;Evidence&lt;/th&gt;
 &lt;th&gt;Producer&lt;/th&gt;
 &lt;th&gt;Story&lt;/th&gt;
 &lt;th&gt;Verified with&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;cosign signature (keyless OIDC)&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;sigstore/cosign&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;US-008&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;cosign verify&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SLSA L3 provenance&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;slsa-github-generator&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;US-006&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;cosign verify-attestation&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;SPDX 2.3 SBOM&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;anchore/sbom-action&lt;/code&gt; (syft)&lt;/td&gt;
 &lt;td&gt;US-007&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;grype&lt;/code&gt; / &lt;code&gt;trivy sbom&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;License inventory&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;go-licenses&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;US-009&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;go-licenses report&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="what-signing-buys-you"&gt;What signing buys you&lt;a class="anchor" href="#what-signing-buys-you"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Keyless signing gives you a &lt;strong&gt;non-repudiable provenance trail from source
to running image&lt;/strong&gt;: the GitHub Actions OIDC identity that built the image
is the signer, Fulcio issues a short-lived certificate bound to that
identity, and the signature lands in the Rekor public transparency log.
Because the signature covers the image &lt;strong&gt;digest&lt;/strong&gt; (not a mutable tag), a
verified image is tamper-proof — repush a different layer and the
signature no longer matches. There is &lt;strong&gt;no maintainer-held private key to
leak or rotate&lt;/strong&gt;; the trust root is GitHub&amp;rsquo;s OIDC issuer plus the public
Sigstore infrastructure. The Rekor entry is append-only and publicly
queryable, so a forensic investigation can prove exactly which workflow
run, at which commit, produced a given digest — even years later.&lt;/p&gt;</description></item><item><title>Pre-prod validation walkthrough</title><link>https://danchupin.github.io/strata/operate/pre-prod-validation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/operate/pre-prod-validation/</guid><description>&lt;h1 id="pre-prod-validation-walkthrough"&gt;Pre-prod validation walkthrough&lt;a class="anchor" href="#pre-prod-validation-walkthrough"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;This is the release-owner runbook: a &lt;strong&gt;single documented pass&lt;/strong&gt; that brings
up both production labs and exercises the whole hardened object path so the
GO/NO-GO decision rests on observed behaviour, not story-by-story claims. It
is the operator-facing companion to the per-feature smokes — it composes them
into one walkthrough and records a pass/fail-per-leg readiness note.&lt;/p&gt;
&lt;p&gt;The composite is &lt;code&gt;scripts/smoke-architecture-hardening.sh&lt;/code&gt;, wired as
&lt;code&gt;make smoke-architecture-hardening&lt;/code&gt;. Each leg is independent: it WARN-skips
when its lab is down and only fails the run on a real assertion failure. Set
&lt;code&gt;REQUIRE_LAB=1&lt;/code&gt; to promote a down lab to a hard fail (use this in CI where the
labs are guaranteed up).&lt;/p&gt;</description></item><item><title>Production hardening</title><link>https://danchupin.github.io/strata/best-practices/production-hardening/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/production-hardening/</guid><description>&lt;h1 id="production-hardening"&gt;Production hardening&lt;a class="anchor" href="#production-hardening"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;12-line checklist to run through before flipping a Strata replica into
prod traffic. Each line links to the runbook section that explains the
knob, the metric, and the failure mode it prevents. Every knob is opt-in
and zero-by-default — running through the list flips a memory / lab
deployment into a prod-ready shape without touching code.&lt;/p&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;#&lt;/th&gt;
 &lt;th&gt;Check&lt;/th&gt;
 &lt;th&gt;Runbook&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;1&lt;/td&gt;
 &lt;td&gt;HTTP server timeouts non-zero (&lt;code&gt;STRATA_HTTP_READ_HEADER_TIMEOUT=10s&lt;/code&gt; / &lt;code&gt;STRATA_HTTP_READ_TIMEOUT=60s&lt;/code&gt; / &lt;code&gt;STRATA_HTTP_WRITE_TIMEOUT=30m&lt;/code&gt; / &lt;code&gt;STRATA_HTTP_IDLE_TIMEOUT=120s&lt;/code&gt; / &lt;code&gt;STRATA_HTTP_MAX_HEADER_BYTES=1048576&lt;/code&gt;). Defaults already match — only verify if you tuned them.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/reference/env-vars/#gateway-core-http"&gt;STRATA_HTTP_*&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;2&lt;/td&gt;
 &lt;td&gt;TLS terminated on the gateway (&lt;code&gt;STRATA_TLS_CERT_FILE&lt;/code&gt; / &lt;code&gt;STRATA_TLS_KEY_FILE&lt;/code&gt;, or &lt;code&gt;STRATA_TLS_CERT_DIR&lt;/code&gt; for SNI multi-tenant) — or behind an ingress with &lt;code&gt;STRATA_TRUSTED_PROXIES&lt;/code&gt; set to the ingress source CIDR.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#deploy-shapes"&gt;TLS termination — shapes B/C&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;3&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;STRATA_TLS_MIN_VERSION=TLS1.2&lt;/code&gt; (default) and &lt;code&gt;STRATA_TLS_CIPHER_PROFILE=mozilla-modern&lt;/code&gt; (default). Bump &lt;code&gt;MIN_VERSION=TLS1.3&lt;/code&gt; if every client supports it.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#deploy-shapes"&gt;TLS shapes&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;4&lt;/td&gt;
 &lt;td&gt;Cert hot-reload enabled (&lt;code&gt;STRATA_TLS_RELOAD_INTERVAL=60s&lt;/code&gt;, default) so cert-manager / Vault PKI rotation is picked up without restart.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#cert-manager-kubernetes"&gt;cert-manager recipe&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;5&lt;/td&gt;
 &lt;td&gt;Admin / console / metrics on a separate listener (&lt;code&gt;STRATA_ADMIN_LISTEN=127.0.0.1:9001&lt;/code&gt; recommended; loopback or RFC1918 only). Optionally pin operator client certs via &lt;code&gt;STRATA_ADMIN_TLS_CLIENT_CA_FILE&lt;/code&gt;.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#shape-c--strata-terminated-split-admin--s3-listeners"&gt;Shape C — split admin listener&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;6&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;STRATA_TRUSTED_PROXIES&lt;/code&gt; set to the ingress / LB source CIDR. Default empty = &lt;code&gt;X-Forwarded-*&lt;/code&gt; ignored. Required for the &lt;code&gt;Secure&lt;/code&gt; cookie flag + audit-log client-IP fidelity behind any proxy.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://github.com/danchupin/strata/blob/main/README.md#breaking-changes"&gt;Trusted proxies — README breaking change&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;7&lt;/td&gt;
 &lt;td&gt;Per-IP + per-key ingress rate limit on (&lt;code&gt;STRATA_RATE_LIMIT_PER_IP=N&lt;/code&gt; and / or &lt;code&gt;STRATA_RATE_LIMIT_PER_KEY=N&lt;/code&gt;). Default 0 = disabled. Refusal returns HTTP 429 + &lt;code&gt;&amp;lt;Code&amp;gt;SlowDown&amp;lt;/Code&amp;gt;&lt;/code&gt;.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/reference/env-vars/#gateway-core-http"&gt;STRATA_RATE_LIMIT_*&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;8&lt;/td&gt;
 &lt;td&gt;Cassandra mTLS (&lt;code&gt;STRATA_CASSANDRA_TLS_CA_FILE&lt;/code&gt; + &lt;code&gt;STRATA_CASSANDRA_TLS_CERT_FILE&lt;/code&gt; + &lt;code&gt;STRATA_CASSANDRA_TLS_KEY_FILE&lt;/code&gt;). &lt;code&gt;SKIP_VERIFY&lt;/code&gt; must be &lt;code&gt;false&lt;/code&gt; (default).&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#cassandra"&gt;Cassandra mTLS&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;9&lt;/td&gt;
 &lt;td&gt;TiKV mTLS (&lt;code&gt;STRATA_TIKV_TLS_CA_FILE&lt;/code&gt; is &lt;strong&gt;required&lt;/strong&gt; when any other TLS knob is set — the upstream silently downgrades on empty CA). PD endpoints accept &lt;code&gt;https://&lt;/code&gt;.&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#tikv"&gt;TiKV mTLS&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;10&lt;/td&gt;
 &lt;td&gt;S3-upstream mTLS (&lt;code&gt;STRATA_S3_TLS_*&lt;/code&gt; global default; per-cluster &lt;code&gt;tls&lt;/code&gt; override on &lt;code&gt;STRATA_S3_CLUSTERS&lt;/code&gt; JSON wins outright per cluster).&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#s3-upstream"&gt;S3-upstream mTLS&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;11&lt;/td&gt;
 &lt;td&gt;RADOS cephx in place — &lt;code&gt;STRATA_RADOS_KEYRING&lt;/code&gt; populated; &lt;code&gt;ms_cluster_mode=secure&lt;/code&gt; set in &lt;code&gt;ceph.conf&lt;/code&gt; if wire-level confidentiality is required. (No Strata-side TLS knob for RADOS.)&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/tls-termination/#rados-uses-cephx-not-tls"&gt;RADOS uses cephx&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;12&lt;/td&gt;
 &lt;td&gt;Prometheus alert on &lt;code&gt;sum(strata_backend_tls_skip_verify) &amp;gt; 0&lt;/code&gt; (any backend with &lt;code&gt;SKIP_VERIFY=true&lt;/code&gt;) AND on &lt;code&gt;rate(strata_ingress_rate_limit_refused_total[5m]) &amp;gt; N&lt;/code&gt; (sustained client floods).&lt;/td&gt;
 &lt;td&gt;&lt;a href="https://danchupin.github.io/strata/operate/monitoring/"&gt;Monitoring — alert recipes&lt;/a&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="what-this-checklist-closes"&gt;What this checklist closes&lt;a class="anchor" href="#what-this-checklist-closes"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;p&gt;Every line above closes a P0 gap from the 2026-05-25 prod-readiness
audit:&lt;/p&gt;</description></item><item><title>S3 multi-cluster routing</title><link>https://danchupin.github.io/strata/best-practices/s3-multi-cluster/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/s3-multi-cluster/</guid><description>&lt;h1 id="s3-multi-cluster-routing"&gt;S3 multi-cluster routing&lt;a class="anchor" href="#s3-multi-cluster-routing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;The S3 data backend supports routing per storage class to a distinct
&lt;code&gt;(cluster, bucket)&lt;/code&gt; pair. Two envs hold the full config — &lt;code&gt;STRATA_S3_CLUSTERS&lt;/code&gt;
(JSON array of bucket-less cluster specs) and &lt;code&gt;STRATA_S3_CLASSES&lt;/code&gt; (JSON object
mapping storage class names to &lt;code&gt;{cluster, bucket}&lt;/code&gt; tuples). Adding or removing
a cluster requires a gateway restart; multi-replica deployments hide
per-instance downtime via rolling restart.&lt;/p&gt;
&lt;p&gt;This page is the operator guide. For the conceptual S3 backend overview see
&lt;a href="https://danchupin.github.io/strata/architecture/backends/s3/"&gt;S3 data backend&lt;/a&gt;.&lt;/p&gt;</description></item><item><title>Compliance</title><link>https://danchupin.github.io/strata/best-practices/compliance/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/compliance/</guid><description>&lt;h1 id="compliance"&gt;Compliance&lt;a class="anchor" href="#compliance"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata implements S3 Object Lock with COMPLIANCE and GOVERNANCE retention
modes plus legal hold. COMPLIANCE rows are immutable until their
&lt;code&gt;RetainUntilDate&lt;/code&gt; elapses — not even the bucket owner can shorten the
retention window or delete the object early. The audit log records every
COMPLIANCE-mode write and every expiry the lifecycle worker performs so
auditors can grep &lt;code&gt;audit_log&lt;/code&gt; for retention-policy events with a single
&lt;code&gt;action LIKE 'objectlock:%'&lt;/code&gt; clause.&lt;/p&gt;
&lt;p&gt;This page is the operator workflow guide. For the audit-log shape see
&lt;a href="https://danchupin.github.io/strata/operate/monitoring/#audit-log"&gt;Monitoring — audit log&lt;/a&gt;; for
the underlying retention semantics see the &lt;a href="https://danchupin.github.io/strata/s3-compatibility/"&gt;S3 Compatibility&lt;/a&gt;
matrix.&lt;/p&gt;</description></item><item><title>Placement + rebalance</title><link>https://danchupin.github.io/strata/best-practices/placement-rebalance/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/placement-rebalance/</guid><description>&lt;h1 id="placement--rebalance"&gt;Placement + rebalance&lt;a class="anchor" href="#placement--rebalance"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Once you run more than one RADOS / S3 cluster behind a single Strata
deployment, you also need to control &lt;strong&gt;which&lt;/strong&gt; cluster a bucket&amp;rsquo;s chunks
land on and how to migrate old chunks when a new cluster joins or an
old cluster is being retired. Strata ships both pieces:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;A per-bucket &lt;strong&gt;placement policy&lt;/strong&gt; (&lt;code&gt;meta.Bucket.Placement&lt;/code&gt; — a
&lt;code&gt;{cluster: weight}&lt;/code&gt; map) that the chunk PUT path consults via a
stable hash-mod router.&lt;/li&gt;
&lt;li&gt;A leader-elected &lt;strong&gt;rebalance worker&lt;/strong&gt; (&lt;code&gt;strata server --workers=rebalance&lt;/code&gt;)
that walks every bucket with a non-nil policy, compares the actual
per-cluster chunk distribution to the policy&amp;rsquo;s target, and copies
chunks A → B until the two match.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;A bucket without a policy (&lt;code&gt;Placement == nil&lt;/code&gt;) behaves exactly as
before — chunks land on the storage class&amp;rsquo;s default cluster. No
migration, no schema bump, no behavior change. The policy + worker are
both opt-in.&lt;/p&gt;</description></item><item><title>Billing</title><link>https://danchupin.github.io/strata/best-practices/billing/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/best-practices/billing/</guid><description>&lt;h1 id="billing"&gt;Billing&lt;a class="anchor" href="#billing"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;Strata does not ship an invoice generator. It ships the &lt;strong&gt;inputs an
external invoice generator can consume&lt;/strong&gt;: a daily per-(bucket, storage
class) row in &lt;code&gt;usage_aggregates&lt;/code&gt; whose &lt;code&gt;byte_seconds&lt;/code&gt; field integrates
the bucket-usage counter across the UTC day with the trapezoid rule.&lt;/p&gt;
&lt;p&gt;This page documents the integration math, the sampling cadence, and the
env knob that tunes accuracy vs. meta-backend cost. For the live quota
counter, the &lt;code&gt;QuotaExceeded&lt;/code&gt; shape, the reconcile worker, and the admin
API surface, see &lt;a href="https://danchupin.github.io/strata/best-practices/quotas-billing/"&gt;Quotas + billing&lt;/a&gt;.&lt;/p&gt;</description></item><item><title/><link>https://danchupin.github.io/strata/architecture/benchmarks/data/gc-lifecycle-phase-2/README/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://danchupin.github.io/strata/architecture/benchmarks/data/gc-lifecycle-phase-2/README/</guid><description>&lt;h1 id="phase-2--gc--lifecycle-multi-leader-bench-artifacts"&gt;Phase 2 — gc + lifecycle multi-leader bench artifacts&lt;a class="anchor" href="#phase-2--gc--lifecycle-multi-leader-bench-artifacts"&gt;#&lt;/a&gt;&lt;/h1&gt;
&lt;p&gt;JSONL captured by the bench harness in &lt;strong&gt;in-process simulation mode&lt;/strong&gt;:
both meta and data backends are pure-memory; the multi-shard / multi-
replica race happens inside one &lt;code&gt;strata admin&lt;/code&gt; process via the &lt;code&gt;--shards&lt;/code&gt;
(gc) / &lt;code&gt;--replicas&lt;/code&gt; (lifecycle) flags wired in US-006.&lt;/p&gt;
&lt;h2 id="files"&gt;Files&lt;a class="anchor" href="#files"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;table&gt;
 &lt;thead&gt;
 &lt;tr&gt;
 &lt;th&gt;File&lt;/th&gt;
 &lt;th&gt;What&lt;/th&gt;
 &lt;th&gt;How to regenerate&lt;/th&gt;
 &lt;/tr&gt;
 &lt;/thead&gt;
 &lt;tbody&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sim-bench-gc-shards1.jsonl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;gc bench, single-leader (Phase 1 shape), N=50000, c∈{1,4,16,64,256}&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;for c in 1 4 16 64 256; do STRATA_META_BACKEND=memory STRATA_DATA_BACKEND=memory ./bin/strata admin bench-gc --entries=50000 --concurrency=$c --shards=1; done | jq -c .&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sim-bench-gc-shards3.jsonl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;gc bench, 3-shard (Phase 2 shape), N=50000&lt;/td&gt;
 &lt;td&gt;…&lt;code&gt;--shards=3&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sim-bench-lifecycle-replicas1.jsonl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;lifecycle bench, single-replica (Phase 1), N=10000, 1 bucket&lt;/td&gt;
 &lt;td&gt;&lt;code&gt;for c in …; do … bench-lifecycle --objects=10000 --concurrency=$c --replicas=1 --buckets=1; done&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;tr&gt;
 &lt;td&gt;&lt;code&gt;sim-bench-lifecycle-replicas3.jsonl&lt;/code&gt;&lt;/td&gt;
 &lt;td&gt;lifecycle bench, 3-replica (Phase 2), N=10000, 9 buckets&lt;/td&gt;
 &lt;td&gt;…&lt;code&gt;--replicas=3 --buckets=9&lt;/code&gt;&lt;/td&gt;
 &lt;/tr&gt;
 &lt;/tbody&gt;
&lt;/table&gt;
&lt;h2 id="caveats"&gt;Caveats&lt;a class="anchor" href="#caveats"&gt;#&lt;/a&gt;&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;Memory meta + memory data put the worker on a fast path with effectively
zero per-op latency. Concurrency speedup that the production lab observes
on TiKV (real pessimistic-txn round-trips) does NOT manifest here — the
in-process Go RWMutex is the bottleneck. Treat these numbers as a noise
floor that pins the &lt;strong&gt;shape of multi-leader coordination overhead&lt;/strong&gt;, not
as a forecast of production throughput.&lt;/li&gt;
&lt;li&gt;For the canonical Phase 2 numbers operators should rerun
&lt;code&gt;make bench-gc-multi&lt;/code&gt; / &lt;code&gt;make bench-lifecycle-multi&lt;/code&gt; against the
3-replica lab brought up via &lt;code&gt;make up-lab-tikv-3&lt;/code&gt; (see
&lt;code&gt;docs/site/content/architecture/benchmarks/gc-lifecycle.md&lt;/code&gt;&amp;rsquo;s &amp;ldquo;Phase 2 — multi-leader&amp;rdquo; section).&lt;/li&gt;
&lt;li&gt;Every JSON object on a line is one bench level (one &lt;code&gt;(--concurrency, --shards|--replicas)&lt;/code&gt; combination). Schema matches &lt;code&gt;cmd/strata/admin/ bench_common.go::benchResult&lt;/code&gt;.&lt;/li&gt;
&lt;/ul&gt;</description></item></channel></rss>