Running fastcached as a compile cache¶
Serving fastcache-cc needs no special mode: the
compile-cache protocol is detected from the
first byte, so any listener serves it.
Check the value cap¶
The per-value cap defaults to 256 MiB, chosen for exactly this workload, so most deployments need no flag here. It matters because a value above the cap is rejected outright — that translation unit then never caches, which looks like a poor hit rate rather than an error.
The rejection is at least diagnosable: the daemon answers with a typed
payload-too-large error naming the declared size and the cap, so it appears in
fastcache-cc --show-stats under fall-back reasons and in the daemon log, rather
than as a dropped connection.
Object files in a large C++ codebase routinely exceed 16 MiB; one measured codebase peaked at ~122 MB for a single object. If your largest object goes past 256 MiB, raise it:
--storage-max-value also raises the wire frame-payload cap, so a single flag
covers both limits — for the compile-cache and Redis protocols. The two
memcached framings keep a fixed 16 MiB ceiling that this flag does not move.
To find your largest object:
Persist the cache¶
Without --storage the cache is memory-only and evaporates on restart, which
for a compile cache usually means throwing away hours of compiles.
--max-memory sizes the in-memory L1 tier; the on-disk L2 grows as the
workload needs. Reads consult L1 first and fall through to disk on a miss, so a
warm working set is served from RAM while the long tail stays durable.
Cap the disk footprint with --storage-max-disk; the tree then evicts its LRU
tail to fit rather than growing without bound.
Scale it¶
For a shared cache serving many builders concurrently:
fastcached --bind=0.0.0.0 \
--storage=/var/lib/fastcached/cache \
--storage-shards=16 \
--threads=16 \
--requirepass=<secret> \
--metrics --metrics-bind=0.0.0.0
--storage-shards=N(N>1) makes--storagea directory ofshard-NN.cowfiles, so writes to different shards never block each other.--threads=Nruns N pinned single-threaded reactors.- Binding
0.0.0.0exposes the cache to the network — always pair it with--requirepass. See Deployment.
Compression¶
The on-disk tier compresses values by default (--compression=zstd). Object
files compress well, so this is usually worth keeping; reads always return
plaintext because each record decodes by its own tag. Lower
--compression-level (default 3) if CPU is the constraint rather than disk.
The in-memory L1 tier has a codec of its own, off by default:
--memory-compression, --memory-compression-level and
--memory-compression-min-bytes. Turning it on makes --max-memory hold more
rather than less — the budget counts the bytes a value actually occupies — at a
decompress on every read.
lz4 and zstd are only present when the build was configured with
FASTCACHED_ENABLE_COMPRESSION. A codec you name on a build without them is
refused at startup; the disk half's zstd default is not, since nobody typed
it — that tier stores plaintext instead, and a warning says so.
Both codecs are on the startup banner, and both name what the store will actually do rather than what was asked for:
... storage=/var/lib/fastcached/store.cow durability=batched compression=zstd memory-compression=none max-value=256M ...
Without --storage there is no on-disk tier for a codec to describe, so that
field reads <no disk tier> — distinct from none, which is the name of a codec.
Prefetch groups¶
Prefetching is automatic and needs no configuration. When a FETCH hits a key that belongs to a group, the rest of that group is warmed into memory in the background, so the compiles that follow in the same build are served from RAM.
Clients control the grouping with FASTCACHE_PREFETCH_GROUP. Since it is not
part of the cache key, regrouping never invalidates anything — set it per
project and branch (myproject-main) for the tightest prefetch locality.
Monitoring¶
--metrics serves Prometheus metrics on /metrics and a liveness probe on
/healthz on a dedicated port (default 9259). For a compile cache the useful
signals are the hit ratio and the eviction rate: sustained evictions with a
falling hit ratio mean the working set no longer fits and --max-memory (or
--storage-max-disk) needs raising.
The client side reports independently — fastcache-cc --show-stats gives the hit
rate as each builder sees it, which is what actually determines build times.
fastcache-cli live-stats pointed at the 0xFC port draws the same reading live: the
daemon streams its cache subject, the counters and snapshot /metrics renders, pushed
on a fixed tick rather than scraped
(#1399). The node and fleet
subjects belong to a compile node, and the daemon refuses them by name. A stream is held to
the connection's credential while it runs, not only when it opens: a connection that never
authenticated loses its stream on the tick --requirepass takes effect, and one that did
keeps it when the secret rotates. The live counters are the node's rows, described under
Live stats.
If you run a compile fleet rather than a lone cache, the leader can show you
the whole of it on one page: see
Looking at the whole fleet.
That is a view of who is a member, how big each machine is and what its cache
holds — the thing /metrics cannot give you without a Prometheus in front of it,
because a scrape is one process reporting itself. /metrics stays the source of
truth for anything you alert on; the page is for the question you ask once, while
something is wrong.
A note on client configuration¶
If hit rates are low, check the client before tuning the server. Cache keys
incorporate the compiler identity and the relativized command line, so mismatched
compiler versions or differing flags between CI and developer machines produce
different keys by design. fastcache-cc --show-stats distinguishes a genuine miss
from a cache that was never reached.