Archipelago logo

Rust · zero dependencies · deterministic simulation

Lose a node,
keep every byte.

Archipelago is a distributed file system written from scratch in Rust with zero dependencies: content-addressed chunking, rendezvous placement, quorum writes, and self-healing re-replication. The unusual part is that the whole cluster, including the network, runs inside one seeded deterministic simulation. Write a file, crash a node, partition the network, and watch quorum and SHA-256 content-hash checks decide every read. A read returns the exact bytes or fails loudly, never silent corruption.

Open the simulation View on GitHub
How to use this playground
  1. Write. Type a path and a size in chunks, or pick a sample file, then press Write. Watch the file split into chunks. Each chunk gets a content hash and lands on R distinct nodes chosen by hashing. Replicas of the same chunk share one color across nodes. Watch the quorum meter fill as replicas acknowledge.
  2. Read. Type a path already written, then press Read. The client gathers each chunk from a live replica, verifies its content hash, and reassembles the file in order. The console names which node served each chunk. Watch served nodes light green and the hash check pass on every chunk.
  3. Fail node. Click any node to crash it. It goes dark and dashed. Its chunks are now missing a replica, so those tiles are highlighted under-replicated on the nodes that still hold them. Watch the file status turn amber in the table.
  4. Heal. Click a downed node again, or press Heal all, to bring nodes back up. A recovered node rejoins and becomes eligible to hold replicas again.
  5. Partition network. Split the cluster into two groups, A and B. The client sits in group A and can only reach group A nodes. Chunks whose only live replicas are stranded in group B become unreachable for reads until the partition heals. Press again to heal it.
  6. Re-replicate. Run the repair pass. For every under-replicated chunk it copies a fresh replica onto a healthy, reachable node until the chunk is back to R copies. Watch under-replicated tiles lose their amber outline as R is restored.
  7. Replication R. Choose R equals 2 or 3. This sets how many replicas each new chunk gets and how many losses a chunk can survive. It applies to writes made after you change it.
  8. Random workload. Writes a few random files at once so you can see many chunks spread across the cluster quickly.
  9. Reset. Clears all files and brings every node back to a clean, healthy, unpartitioned state.

Guided tour

  1. Write /photos/sunset.raw at size 4 with R equals 3. See four chunks, each on three nodes.
  2. Read it back. Every chunk is served from a live replica and its hash verifies. Status stays green.
  3. Click a node that holds one of those chunks. That chunk shows under-replicated. The file goes amber.
  4. Press Re-replicate. A new copy is placed on a healthy node and R is restored. The amber clears.
  5. Read again. Still green, still verified. That is the durability guarantee in action.
  6. Now fail R nodes that all hold the same chunk. That chunk has zero live replicas. Read the file and see it reported unavailable, not corrupt.
Last write quorum
idle
Chunk color = chunk identity (replicas share it)
a1b2c3... content hash (SHA-256)
node healthy
node down
under-replicated
chunk lost

How it really works

The concepts the simulation is built on, in plain terms, each one a small module you can read in an afternoon.

Architecture

A metadata service maps every file path to its ordered list of chunk hashes and tracks the directory tree. A set of storage nodes hold the chunks themselves. A client talks to both. It asks metadata where a file's chunks live, then reads or writes those chunks on the storage nodes directly.

Nothing here needs a second machine. The metadata service, the nodes, and the message scheduler all run inside one process.

Chunking and content addressing

A file is split into fixed-size chunks. Each chunk is named by the SHA-256 hash of its own bytes, so identical content is stored once and every reference to a chunk carries a built-in integrity check.

The metadata for a file is just the ordered list of those hashes. To rebuild the file you fetch each chunk by hash and concatenate them back in order.

Replication and quorum

Every chunk is copied onto R storage nodes chosen by rendezvous hashing. Each node scores the chunk, and the top R scorers hold it. Losing one node moves only that node's chunks, not the whole placement.

A write is durable once a quorum of replicas acknowledge. Reads assemble the file from any live replica of each chunk, so a single slow or dead node never blocks a read.

The durability guarantee

There is no data loss with fewer than R node failures. Lose one or two nodes with R at three and every chunk still has a live copy. Under-replicated chunks re-replicate automatically once healthy capacity is available, restoring the full replica count.

Only when all R replicas of a specific chunk are gone is that file reported unavailable. It is never silently returned as corrupt bytes.

Deterministic simulation

The cluster runs on a seeded message scheduler that can delay, reorder, drop, and partition messages. The same seed produces the same run every time.

That is what makes failures testable. A partition or a dropped acknowledgement is not a flaky accident here. It is a reproducible event you can replay and reason about.

Erasure coding, optional

Instead of replicating each chunk R times, each chunk can be Reed Solomon encoded over GF(2^8) into k data plus m parity shards spread over distinct nodes. A read needs any k of the k+m shards, so up to m holders can be lost and the file still reads.

A fetched shard is verified against its content address, so a corrupt shard just counts as missing, and lost shard positions are re-encoded onto live nodes as repair.

How it compares

Archipelago is not a production replacement for a real cluster file system. It is the readable, deterministic version of the same ideas, built so the failure modes are reproducible instead of flaky.

Ceph, HDFS, GlusterFS

Production distributed file systems, battle-tested across many machines, with POSIX access, rebalancing, and huge real clusters. They need real hardware and real networks to run, and their most interesting bugs live in timing that is slow and flaky to reproduce on that hardware.

Archipelago

The same core ideas, chunking, rendezvous placement, quorum, replication, self-healing, and Reed Solomon erasure coding, in code small enough to read in an afternoon with zero external dependencies. The whole cluster and network run inside one process as a seeded deterministic simulation, so a partition or a dropped acknowledgement is a reproducible event you can replay from its seed and check against an in-memory oracle.

Honest non-goals: a single process rather than real machines, a simulated scheduler rather than real RPC, no POSIX mount, and no tuning for production scale. The point is provable correctness you can read, not throughput. This is the FoundationDB and TigerBeetle style of deterministic simulation testing, applied to a file system.

The tests are the point

Archipelago turns the cluster into a pure function of a seed and an operation script, then holds it to hard properties. Each gate proves one, and they all run under cargo test.

# the whole suite is pure and dependency-free; one command runs every gate
$ cargo test
test result: ok. 51 passed; 0 failed   # src/lib.rs unit tests
test result: ok.  1 passed; 0 failed   # differential: random ops vs an in-memory oracle
test result: ok.  5 passed; 0 failed   # faults: durability under crash and partition
test result: ok.  6 passed; 0 failed   # erasure: Reed Solomon over GF(2^8)
test result: ok.  2 passed; 0 failed   # determinism: same seed, identical run
test result: ok.  7 passed; 0 failed   # boundaries: empty files, renames, edge sizes
test result: ok.  1 passed; 0 failed   # fault_differential: faults interleaved with the oracle
test result: ok.  2 passed; 0 failed   # roundtrip: chunking and content addressing
test result: ok.  3 passed; 0 failed   # stress: samples the Options space under faults

# turn the fuzz knobs up for a longer adversarial run
$ ARCH_FUZZ_OPS=5000 cargo test --test differential
test result: ok. 1 passed; 0 failed; finished in 55.40s
$ ARCH_FAULT_FILES=400 cargo test --test faults
test result: ok. 5 passed; 0 failed; finished in 17.95s

A read returns exactly the oracle bytes, or the file is reported unavailable. A committed file that vanishes or comes back wrong is a hard failure. That is the FoundationDB and TigerBeetle style of deterministic simulation testing, applied to a file system small enough to hold in your head.

Use it

# build and test the whole system (Rust, zero dependencies)
cargo build
cargo test

# the demo: write files, spread chunks over five nodes, kill one,
# re-replicate onto the survivors, then read every file back verified
cargo run --bin arch -- demo

# the interactive session
cargo run --bin arch
arch> mkdir /docs
arch> put /docs/hello hello archipelago
arch> fail 0
arch> get /docs/hello
arch> stabilize
arch> status