Attic, a Nix binary cache server, exposes no Prometheus metrics endpoint and logs nothing by default. The author discovered all useful data lives in its SQLite database and built a lightweight exporter using sqlite3 in read-only mode, a shell script, and node_exporter's textfile collector. Key findings after three weeks: 6,153 store paths, 2.45x total savings (1.17x dedup, 2.10x compression), and 87.5% of paths with null last_accessed_at. The post explains Attic's GC logic, why coalesce(last_accessed_at, created_at) is the right field to track, and which two alerts are actually worth writing.
Table of contents
What the schema gives youWhat garbage collection will takeThe exporterDo I need alerting?Questions this post answers
How do I monitor an Attic binary cache if it has no metrics endpoint?
Attic exposes no /metrics endpoint and logs nothing by default with RUST_LOG unset. All useful data lives in its SQLite database. A read-only sqlite3 connection (sqlite3 -readonly) is safe to use while atticd is running because Attic uses WAL mode, so reads take no write lock. Queries can emit Prometheus text format lines, collected by node_exporter's textfile collector via a shell script and a systemd timer. Engineers self-hosting Attic track operational patterns like these on daily.dev.
What deduplication and compression savings does Attic actually provide for a Nix binary cache?
After three weeks of one developer's builds, Attic achieved 1.17x deduplication and 2.10x compression (zstd), combining to 2.45x total savings. Logical size was 18.4 GiB, after dedup 15.7 GiB, and on-disk 7.5 GiB across 6,153 store paths and 234,026 chunks. Compression did most of the work; dedup was lower than expected, likely due to limited build overlap. Nix cache operators comparing storage efficiency strategies find real-world numbers like these on daily.dev.
How does Attic's garbage collection decide which objects to delete?
Attic's GC in server/src/gc.rs deletes an object when two conditions both hold: the object has never been accessed or its last access is older than the retention period, and it was created before the cutoff. The effective field is coalesce(last_accessed_at, created_at) — a single fetch refreshes last_accessed_at and extends the object's life, making retention a sliding window for actively used paths. Developers tuning Attic retention policies find operational deep-dives like this on daily.dev.
3.2K Impressions1 Comment