ARCHITECTURE.md
Last updated: 2026-08-12 · Revision: 3
This document describes the high-level architecture of RustFS. If you want to familiarize yourself with the code base, you are in the right place!
See also CONTRIBUTING.md for development workflow. See also docs/architecture for active architecture migration guardrails.
RustFS is a high-performance, S3-compatible distributed object storage system written in Rust. It uses erasure coding for data durability, supports multi-tenancy through IAM/STS, and provides a web-based admin console.
A running RustFS node exposes:
/minio/ prefix) — cluster management, IAM, metricsThe core data flow for a PUT request looks like:
HTTP request
→ server (TLS, auth, routing, compression)
→ app/object_usecase (validation, policy, lifecycle)
→ storage/ecfs (erasure coding, encryption, checksums)
→ ecstore (disk pool selection, data distribution)
→ rio (reader pipeline: encrypt → compress → hash → write)
→ io-core (zero-copy I/O, buffer pool, direct I/O)
→ local disk / remote disk via RPC
The repository is a Cargo workspace with a flat crates/ layout:
rustfs/ # Workspace root (virtual manifest)
├── rustfs/ # Main binary + library crate
│ └── src/
│ ├── main.rs # Entry point, startup sequence
│ ├── lib.rs # Module tree root
│ ├── server/ # HTTP server, TLS, routing, middleware
│ ├── admin/ # Admin API handlers and console
│ ├── app/ # Use-case layer (object, bucket, multipart)
│ ├── storage/ # Storage engine interface and implementation
│ ├── auth.rs # S3 request authentication
│ ├── config/ # CLI args, config parsing, workload profiles
│ └── ...
├── crates/ # library crates (authoritative list: Cargo.toml [workspace].members)
│ ├── ecstore/ # Erasure-coded storage engine
│ ├── rio/ # Reader I/O pipeline (encrypt, compress, hash)
│ ├── io-core/ # Zero-copy I/O, scheduling, buffer pool
│ ├── io-metrics/ # I/O metrics collection
│ ├── common/ # Shared runtime state, globals, data usage types
│ ├── config/ # Configuration types and parsing
│ ├── utils/ # Pure utility functions
│ ├── ... # (see "Crate Reference" below)
│ └── e2e_test/ # End-to-end integration tests
└── docs/ # Design documents and analysis
rustfs/src/)The main crate is organized in layers, top to bottom:
| Layer | Directory | Responsibility |
|---|---|---|
| Server | server/ | HTTP listener, TLS, CORS, compression, middleware, graceful shutdown |
| Admin | admin/ | Admin API routing, 30+ handler modules, web console |
| App | app/ | Use-case orchestration: object_usecase, bucket_usecase, multipart_usecase |
| Storage | storage/ | S3 API translation, erasure-coded FS, SSE encryption, RPC, concurrency |
| Auth | auth.rs | S3 signature verification, credential validation |
| Config | config/ | CLI parsing, config struct, workload profiles |
A request flows downward through the layers. No layer should reach upward (e.g., storage must not import from admin).
Cargo.toml is the authoritative workspace membership and cargo tree is the
authoritative dependency graph. This overview deliberately avoids line-count
and dependency-depth snapshots because both quickly become stale during
refactors.
| Domain | Current workspace crates | Responsibility |
|---|---|---|
| Foundation | checksums, common, config, data-usage, utils | Shared configuration, data-usage models, utilities, and checksums. |
| I/O and storage | concurrency, ecstore, filemeta, heal, io-core, io-metrics, lifecycle, lock, object-capacity, object-data-cache, replication, rio, rio-v2, scanner, storage-api | Erasure-coded object storage, metadata, recovery, lifecycle, replication, locking, cache, and I/O pipelines. |
| Security and identity | credentials, crypto, iam, keystone, kms, policy, security-governance, signer, tls-runtime, trusted-proxies | Credentials, authentication, authorization, encryption, key management, TLS, and security contracts. |
| Protocols and contracts | extension-schema, madmin, protos, protocols, s3-ops, s3-types, s3select-api, s3select-query | Admin, inter-node, S3, S3 Select, and optional protocol contracts. |
| Operations and integration | audit, notify, obs, targets, zip | Auditing, observability, event delivery, notification targets, and archive support. |
| Test support | e2e_test, test-utils | End-to-end validation and shared test bootstrap utilities. |
The rustfs binary crate composes these libraries into the running server.
ecstore remains the storage engine at the architectural center; its internal
module split is tracked under docs/architecture/. rio-v2 is the
feature-gated MinIO on-disk format compatibility I/O layer; it ships in no
default build (lifecycle:
docs/architecture/minio-file-format-compat.md).
These are rules that the codebase should follow. Some are currently violated (marked with ⚠️). Documenting them here makes the violations explicit and trackable.
Layers flow downward. Server → Admin/App → Storage → ecstore → rio/io-core. No upward imports.
Leaf crates have zero internal dependencies. config, credentials, crypto,
io-metrics, and madmin should depend only on external crates.
utils → config and common → filemeta/madmin
edges were removed; do not reintroduce them (see Known Structural Issues).Each type has exactly one definition. Types shared across crates must be defined in one crate and re-exported or imported by others.
ReplicationStats names three unrelated types
(crates/data-usage/src/data_usage.rs,
crates/obs/src/metrics/collectors/replication.rs,
crates/ecstore/src/bucket/replication/replication_state.rs) — a naming
collision, not copies; renaming is tracked in rustfs/backlog#1847.LastMinuteLatency has two deliberately different implementations: the
per-second bucketed accumulator in crates/common/src/last_minute.rs and
the in-memory endpoint-health sample tracker in
crates/ecstore/src/bucket/bucket_target_sys.rs (its doc comment explains
why it stays local).BackpressureConfig and DataUsageInfo each have exactly one
definition (crates/io-core/src/backpressure.rs,
crates/data-usage/src/data_usage.rs). The zero-consumer
BackpressureSettings copy that lingered in io-metrics was removed
(rustfs/backlog#1833).ecstore does not know about HTTP or S3 protocol details. It operates on storage-level abstractions (objects, buckets, disks, pools).
crates/ecstore/src reference s3s
(rg -l 's3s' crates/ecstore/src | wc -l), crates/ecstore/src/client/
is a ~9.4K-line embedded S3 HTTP client, and crates/ecstore/Cargo.toml
depends on s3s, http, hyper/hyper-util/hyper-rustls, and
reqwest. Target state: the engine's need to act as an S3 client
(tiering, replication targets) is served by an extracted client crate,
and ecstore holds no wire or DTO types.The rustfs binary crate is the only place that wires everything together.
Individual crates should be testable in isolation.
Error types use thiserror with descriptive names (e.g., StorageError,
not bare Error).
snafu is gone from source
(rg -l snafu crates/ rustfs/ is empty) and library code no longer uses
anyhow (remaining hits are test code and the e2e_test crate; heal
uses thiserror).pub enum Error:
crypto, filemeta, heal, iam, policy, and replication
(src/resync.rs) — all thiserror-derived.This section documents known problems in the current architecture. It exists so the team can track and address them deliberately.
scanner/data-usage duplicate .usage-cache.bin serialization types. The
original finding ("common/scanner code duplication, ~3K lines") is resolved:
scanner imports the shared data-usage types from rustfs-data-usage (see
the pub use rustfs_data_usage::… re-exports at the top of
crates/scanner/src/data_usage_define.rs). What remains: scanner and
data-usage each hold their own serialization types for the scanner cache
file (DataUsageCacheInfo/DataUsageEntryInfo in
crates/scanner/src/data_usage_define.rs vs
DataUsageCacheInfo/DataUsageEntry in
crates/data-usage/src/data_usage.rs); convergence is tracked in
rustfs/backlog#1828.
ecstore is a monolith (265 files, ~288K lines — roughly half is inline
#[cfg(test)] code). Measured with
find crates/ecstore/src -name '*.rs' | xargs wc -l. It contains disk
management, bucket management, erasure coding, replication, lifecycle, RPC,
and configuration — all in one crate. It should be decomposed along its
existing subdirectories; the split plan lives in
docs/architecture/ecstore-module-split-plan.md.
Dependency inversions. Historical utils → config and
common → filemeta/madmin edges must stay removed so leaf/helper crates do
not regain upward dependencies.
Three-layer backpressure/deadlock policy bridging across io-core,
concurrency, and rustfs/src/storage. The config types are no longer
duplicated (BackpressureConfig and DeadlockDetectorConfig are each
defined once, in io-core). Storage policies expose and consume explicit
projections into the concurrency/io-core policy shapes, and workload
admission snapshots are composed through provider registries; later work
should use those bridges before deleting compatibility wrappers.
Bare Error naming. Error-handling strategy has converged on thiserror
(no snafu, no anyhow in library code); the remaining inconsistency is the
bare pub enum Error naming in the 6 crates listed under Invariant 6.
common is mostly parked domain code, not shared utilities. Of its
6,724 lines, ~83% is scanner/heal domain code stranded there to break
dependency cycles (metrics.rs, ~4,810 lines of scanner-domain metrics;
heal_channel.rs, ~776 lines of heal-domain channel types). The
"common vs utils" naming ambiguity is secondary to moving that code to its
domain owners.
The project convention is thiserror for typed errors with descriptive names.
See AGENTS.md: "Prefer thiserror for library-facing error types."
// GOOD
#[derive(Debug, thiserror::Error)]
pub enum StorageError {
#[error("disk not found: {0}")]
DiskNotFound(String),
}
// AVOID
pub enum Error { ... } // too generic
anyhow::Result<T> // in library code (OK in tests/CLI)
tracing crate (info!, warn!, error!, debug!, trace!)tracing::info!(bucket = %name, "created bucket")rustfs-obs runtime and schemarustfs-io-metricsrustfs-obs#[cfg(test)] mod tests in the same filetests/)crates/e2e_test/ — tests against a running servermake test or cargo nextest runThe binary (main.rs) boots in this order:
MINIO_* → RUSTFS_*)FullReady ┌─────────┐
│ rustfs │ (binary + lib)
│ main │
└────┬────┘
│
┌───────────────┼───────────────┐
│ │ │
┌────▼────┐ ┌────▼────┐ ┌──────▼─────┐
│ server │ │ admin │ │ app │
│ (HTTP) │ │(console)│ │(use-cases) │
└────┬────┘ └────┬────┘ └──────┬─────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────▼──────┐
│ storage │
│ (ecfs, SSE, │
│ RPC, ACL) │
└──────┬──────┘
│
┌──────────────────┼──────────────────┐
│ │ │
┌─────▼──────┐ ┌──────▼──────┐ ┌──────▼──────┐
│ ecstore │ │ rio │ │ io-core │
│ (core) │ │ (readers) │ │ (zero-copy) │
└─────┬──────┘ └─────────────┘ └─────────────┘
│
┌─────┬──┼──┬─────┬──────┐
│ │ │ │ │ │
common utils config policy filemeta ...
"Where does S3 PutObject go?"
server/ routes → app/object_usecase validates → storage/ecfs encodes →
ecstore distributes → rio encrypts/compresses → io-core writes
"Where are bucket policies enforced?"
app/bucket_usecase calls into crates/policy/
"Where is replication configured?"
admin/handlers/replication.rs and admin/handlers/site_replication.rs for API,
ecstore/src/bucket/replication/ for engine
"Where do I add a new admin endpoint?"
Add handler in admin/handlers/, register in admin/router.rs
"Where do I add a new metric?"
Define descriptor/collector in crates/obs/src/metrics/, expose via /minio/v2/metrics
Inspired by matklad's ARCHITECTURE.md and rust-analyzer's architecture.md.