Skip to content

Benchmarks

Results

  • PutFS on ZFS (HDD+SSD) is 3–4x faster than MinIO on XFS/SSD-only for concurrent reads
  • PutFS writes are 2–6x faster than MinIO regardless of MinIO's filesystem
  • PutFS directory listings are much faster than MinIO
  • PutFS uses 30x less memory
  • Large sequential reads are equal across all configurations
  • MinIO wins on bulk parallel uploads (mc mirror vs putfs sync)

Small file random reads (10K files, 1K–100K, wrk, 30s)

Concurrency PutFS (ZFS) MinIO (XFS/SSD) Ratio
1 11,448 req/s 2,771 req/s 4.1x
10 52,676 req/s 17,319 req/s 3.0x
100 82,891 req/s 22,945 req/s 3.6x
1000 86,141 req/s 24,493 req/s 3.5x

PutFS is 3–4x faster on concurrent small file reads. nginx serves files via sendfile directly from ZFS ARC – Python is never involved.

Single-file write latency

File size PutFS (ZFS) MinIO (ZFS) MinIO (XFS/SSD)
1 KB 14.1ms 45.8ms 25.4ms
10 KB 14.3ms 29.2ms 25.8ms
100 KB 11.3ms 27.0ms 27.9ms
1 MB 10.6ms 45.1ms 55.2ms
10 MB 18.1ms 100.1ms 116.5ms
100 MB 103ms 620ms 620ms
1 GB 1.0s 5.1s 6.3s
10 GB 46.6s 71.9s 68.1s

PutFS is 2–6x faster than both MinIO configurations across all file sizes.

Bulk upload (10K files, 1K–100K)

System Method files/s
MinIO (ZFS) mc mirror 2,434
MinIO (XFS/SSD) mc mirror 1,730
PutFS (ZFS) putfs sync 1,349

MinIO's mc mirror wins on bulk parallel uploads – Go's native concurrency outperforms Python's threaded putfs sync. The gap has narrowed significantly since switching from sequential curl to threaded sync.

Large file sequential read (1 GB)

System Time Throughput
PutFS (ZFS) 413ms 2,478 MB/s
MinIO (ZFS) 445ms 2,301 MB/s
MinIO (XFS/SSD) 433ms 2,365 MB/s

Nearly equal – all systems are memory/cache-bound on large sequential reads.

Directory listing

Dataset size PutFS (ZFS) MinIO (ZFS) MinIO (XFS/SSD)
1K objects 9ms 69ms 72ms
10K objects 10ms 670ms 483ms
100K objects 11ms 4,294ms 4,290ms

PutFS is 8–390x faster on listing. The near-constant ~10ms regardless of dataset size comes from streaming os.scandir results directly to the client. MinIO's listing performance degrades linearly – its internal metadata overhead dominates.

Memory footprint (idle)

System Total
PutFS (ZFS) 186 MB (API: 159 MB + nginx: 27 MB)
MinIO (XFS/SSD) 5,582 MB

PutFS uses 30x less memory.

Summary

Metric PutFS (ZFS) MinIO (XFS/SSD) Ratio
Reads (c=1000) 86,141 req/s 24,493 req/s 3.5x
Write 1 KB 14.1ms 25.4ms 1.8x
Write 1 GB 1.0s 6.3s 6.3x
Large read 1 GB 2,478 MB/s 2,365 MB/s ~equal
List 100K 11ms 4,290ms 390x
Bulk upload 1,349 files/s 1,730 files/s 0.8x
Memory idle 186 MB 5,582 MB 30x

Tuning

Write latency has been reduced ~15–20% by using a Unix socket between nginx and granian. See Granian tuning and Nginx tuning.

access_log

access_log off is critical for nginx performance. In our benchmarks, access_log /dev/stdout reduced throughput from 104K req/s to 3.8K req/s – a 27x penalty. See Nginx tuning for details.


Test setup

Both PutFS and MinIO ran in docker on the same server. PutFS with the best possible ZFS setup for this hardware (HDD mirror, SSD special vdev, SSD SLOG, SSD L2ARC). MinIO was tested on its recommended setup (single disk XFS on SSD, noatime,nodiratime). PutFS small files land on the ZFS special vdev (SSD), making the SSD-vs-SSD comparison fair.

Tuning

  • PutFS:
  • MinIO:
    • MINIO_API_REQUESTS_MAX: "0"
    • MINIO_SCANNER_SPEED: "fastest"
    • MINIO_BROWSER: "off"
    • GOMAXPROCS: "0"

Hardware

Component Spec
CPU Intel Core i7-8700 @ 3.20 GHz (6 cores, 12 threads)
RAM 64 GB DDR4
Storage ZFS mirror: 2x 8TB HGST HDD
Special vdev ~900 GB SSD (metadata + small files)
SLOG 32 GB SSD (write intent log)
L2ARC 128 GB SSD (read cache)
MinIO XFS/SSD single SSD partition, XFS
OS Debian 13 (trixie), kernel 6.12

Note: Special vdev was not mirrored. SLOG and L2ARC on SATA SSD, not NVMe.

Running

All benchmark scripts live in benchmark/. See the benchmark README for details.

cd benchmark
make putfs     # PutFS benchmark
make minio     # MinIO comparison

Extract results as CSV:

bash benchmark/analyze.sh benchmark/results/putfs

Implementation comparison (Python / Go / Rust)

The repository includes Go and Rust ports of the app server alongside the reference Python/Starlette build – the HTTP API is identical, only the language differs. This benchmark isolates the app-server hot path (PUT / DELETE / LIST; in production GET/HEAD are served by nginx sendfile, never the app) by running each server bare on localhost (no nginx, no auth) and driving it with wrk.

Different setup from the MinIO comparison above

These runs are on a developer laptop against the page cache (4 KB objects, no fsync), so they measure per-request handling overhead, not disk throughput – they are not comparable to the ZFS-rig MinIO numbers. Listing here uses wrk over a kept-alive connection, i.e. the server's real scandir + serialise cost; that is why Python listing grows with object count here rather than showing the near-constant ~10 ms of the curl-per-request measurement used in the MinIO section above.

Parallel PUT throughput (4 KB objects, wrk)

Concurrency Python (granian, 16 workers) Go Rust
1 1,435 req/s 8,350 req/s 8,506 req/s
10 3,945 req/s 31,255 req/s 51,061 req/s
100 6,479 req/s 67,857 req/s 35,656 req/s

Directory listing (wrk, keep-alive)

Objects Python Go Rust
1K 4.2 ms · 240k keys/s 1.3 ms · 755k keys/s 0.83 ms · 1.26M keys/s
10K 37 ms · 268k keys/s 9.6 ms · 1.04M keys/s 5.4 ms · 1.84M keys/s
100K 362 ms · 265k keys/s 73 ms · 1.36M keys/s 37 ms · 2.71M keys/s
  • Listing – Rust wins decisively: 1.26M–2.71M keys/s, ~1.7–2.0× Go and ~5–10× Python, widening as the directory grows.
  • Writes – Rust wins at mid concurrency, Go at high, c1 a wash. Rust leads c10 PUT (~1.6× Go); Go takes c100 (~1.9× Rust); at c1 the two are tied (~8.5k req/s). Rust offloads each PUT's temp-file open and rename syscalls to a thread pool (mirroring Python's anyio.to_thread), which costs it only at high fan-out.
  • Both ports are ~5–13× Python on writes (depending on concurrency). Python is stable but its per-line async listing tops out ~240–270k keys/s regardless of dataset size.

Measured on an Intel Core i7-1360P (16 threads), 30 GB RAM, Debian forky/sid (kernel 7.0.10); bare servers on localhost, page cache, mean of three wrk runs. Different machine from the ZFS rig used for the MinIO comparison above.

make build-rust build-go     # build the native binaries (repo-root Makefile)
cd benchmark && make impls   # benchmark python + go + rust
make analyze-impls           # side-by-side CSV