Skip to content

Repository files navigation

fastcached

Two things ship from this repository:

  • fastcached — a fast in-memory cache daemon that speaks the memcached text, memcached binary, memcached meta, and Redis RESP2 protocols on a single port, auto-detecting which one each client is using from its first bytes. On the in-memory GET workload a compile cache cares about, it benchmarks faster than native redis and memcached (see Benchmarks).
  • fastcache-cc — a drop-in compiler launcher in the style of ccache and sccache, backed by fastcached. Unlike those, its cache entries are portable across checkout paths, so CI runners and developer machines share one cache even when their source trees live at different depths.

Use them together for a shared compile cache, or run fastcached on its own as a memcached/Redis-compatible cache — including as a plain sccache backend.

It is not a general-purpose replacement for memcached or Redis: it implements only the slice of each protocol a cache backend needs. It is in production use as a shared compile cache for a large C++ codebase, backing both CI runners and developer machines.

Full documentation: https://lastrada-software.github.io/fastcached/

Install

Released packages — .deb, .rpm, macOS .pkg/.dmg, Windows .msi — install both executables and register the daemon to start automatically. On macOS the installer offers the launchd job as a per-user agent (default) or a system-wide daemon, and ships fastcached-uninstall to remove it.

Or build it yourself; a build installs both executables into bin/:

cmake --preset clang-release
cmake --build --preset clang-release
cmake --install out/build/clang-release --prefix /usr/local

Or run the daemon in a container, built from the included Dockerfile:

docker build -t fastcached .
docker run --rm -p 11211:11211 fastcached

Requires CMake 3.28+, a C++23 compiler, and Ninja. Full details — presets, dependencies, and platform notes — in Install.

Quick start: the cache daemon

fastcached --port=11211

Then talk to it with any memcached or Redis client. These transcripts are copied from the unit tests, so they reflect exactly what the server emits:

> set foo 0 0 5\r\nhello\r\n
< STORED\r\n
> get foo\r\n
< VALUE foo 0 5\r\nhello\r\nEND\r\n
> *3\r\n$3\r\nSET\r\n$1\r\nk\r\n$5\r\nhello\r\n
< +OK\r\n
> *2\r\n$3\r\nGET\r\n$1\r\nk\r\n
< $5\r\nhello\r\n

Protocol coverage is intentionally a subset — the commands a cache client actually uses. See the coverage matrix for the exact list.

Quick start: caching your compiles

fastcache-cc fronts each compile: on a hit it reproduces the object file and replays the compiler's output; on a miss it runs the real compiler and stores the result. If anything goes wrong — daemon down, network gone, cache corrupt — it silently runs the real compiler. A broken cache can slow your build down, never break it.

1. Start a daemon

Raise the value cap: real object files routinely exceed the 16 MiB default (a large C++ codebase was measured at ~122 MB for its biggest object).

fastcached --port=11211 \
           --storage=$HOME/.cache/fastcached/cache.cow \
           --storage-max-value=256M

Point every machine that should share the cache at that one daemon.

2. Point the launcher at it

Three variables must all be set, or every compile runs uncached:

export FASTCACHE_ADDR=127.0.0.1:11211    # the daemon
export FASTCACHE_SRCROOT=$PWD            # your checkout root
export FASTCACHE_BUILDTREE=$PWD/build    # your build directory
export FASTCACHE_COHORT=myproject-main   # optional: prefetch grouping

SRCROOT and BUILDTREE are what make entries portable: paths under them are rewritten to tokens before hashing, so the same source at /home/alice/proj and /ci/runner/w/1/s/proj produces the same cache key.

3. Wire it into the build

cmake -S . -B build -G Ninja \
  -DCMAKE_C_COMPILER_LAUNCHER=fastcache-cc \
  -DCMAKE_CXX_COMPILER_LAUNCHER=fastcache-cc

cmake --build build      # first run: misses, populates the cache
rm -rf build/CMakeFiles/*.dir
cmake --build build      # second run: hits

It also works as a plain prefix for a single compile, which is the quickest way to check your setup:

FASTCACHE_VERBOSE=1 fastcache-cc g++ -c src/a.cpp -o build/a.o
# fastcache-cc: MISS key=05b1b5ef9ef5135e119560a2aa140aef
# fastcache-cc: STORED key=05b1b5ef9ef5135e119560a2aa140aef bytes=1427

With PowerShell and MSVC:

$env:FASTCACHE_ADDR      = '127.0.0.1:11211'
$env:FASTCACHE_SRCROOT   = $PWD
$env:FASTCACHE_BUILDTREE = "$PWD\build"

cmake -S . -B build -G Ninja `
  -DCMAKE_CXX_COMPILER_LAUNCHER=fastcache-cc.exe

4. See how it did

fastcache-cc --show-stats                    # totals, hit rate, latency distributions
fastcache-cc --show-stats --cohort ci-main   # one cohort only
fastcache-cc --zero-stats                    # discard the log
compiles     : 4
hits         : 2  (66.7% of 3 cacheable)
misses       : 1
unavailable  : 1  (25.0% of all compiles -- CACHE NOT REACHED)
fall-back reasons
  1x  connect failed

A non-zero unavailable count means the cache was never reached — check FASTCACHE_ADDR and the daemon before concluding caching does not help.

Supported compilers: gcc/g++, clang/clang++ (including versioned names like g++-14), and MSVC cl / clang-cl. The full reference — every environment variable, exit codes, how the key is computed, and the known limitations — is in the fastcache-cc docs.

Using fastcached as an sccache backend

If you already use sccache, you can keep it and just point it at fastcached:

fastcached --port=11211 &
export SCCACHE_MEMCACHED=tcp://127.0.0.1:11211

sccache g++ -std=c++23 -c hello.cpp -o hello.o   # miss
sccache g++ -std=c++23 -c hello.cpp -o hello.o   # hit
sccache --show-stats

sccache up to 0.7.x talks to memcached over the text protocol; sccache ≥ 0.8 talks binary. Both work, because the listener detects the wire format from the first bytes the client sends. SCCACHE_REDIS works the same way. CI exercises all three protocols on every build.

Note that sccache keys on absolute paths, so its entries are not portable between checkouts at different paths — that is the reason fastcache-cc exists.

Production deployment

Run it authenticated, encrypted, and monitored:

# Container: cache on 11211, Prometheus /metrics + /healthz on 9259.
docker run --rm -p 11211:11211 -p 9259:9259 fastcached \
    --bind=0.0.0.0 --metrics --metrics-bind=0.0.0.0 --requirepass=secret

# With TLS (needs an OpenSSL build):
fastcached --requirepass=secret --tls --tls-cert=server.crt --tls-key=server.key \
           --metrics
redis-cli --tls --insecure -a secret ping     # -> PONG
curl http://127.0.0.1:9259/healthz            # -> 200 OK

Binding 0.0.0.0 without --requirepass exposes the cache to the network — pair them. The image's HEALTHCHECK calls fastcached --healthcheck, a self-contained probe needing no curl in the image. Full guide, including a Kubernetes manifest with liveness/readiness probes, in the deployment docs.

Configuration

Run fastcached --help for the complete, always-current flag list. The ones that matter most:

Flag Purpose
--bind=<addr> / --port=<num> Where to listen (default 127.0.0.1:11211).
--max-memory=<size> In-memory budget; k/m/g suffixes, or N% of host RAM (default 64 MiB).
--storage=<path> Persist to a crash-consistent copy-on-write B+tree; without it the cache is memory-only.
--storage-max-value=<size> Per-value cap, and the wire payload cap with it (default 16 MiB). Raise it for compile caches.
--storage-durability=<mode> fsync / batched (default) / none.
--threads=<N> Independent pinned reactors; the server's across-core parallelism.
--requirepass=<secret> Require authentication (Redis AUTH, memcached SASL PLAIN).
--metrics Serve Prometheus /metrics and /healthz.

Every flag can also come from a YAML file via --config=<path>, with CLI flags taking precedence; SIGHUP (POSIX) or the service manager's PARAMCHANGE (Windows) re-reads it. On Windows, --install-service registers the daemon with the Service Control Manager. See the configuration docs.

Persistence and scaling

With --storage, every commit is crash-consistent: the file always matches either the previous or the new transaction, and a kill -9 at any instant leaves no half-written state. Restarting with the same flags picks the cache back up with no warm-up.

Storage is composed as an in-memory LRU (L1) over the on-disk B+tree (L2), and sharded by key hash when --storage-shards>1 so writes to different shards never block each other. --threads=N runs N single-threaded reactors, each pinned to its own core, with every connection pinned to one reactor for its lifetime — so the server scales across cores without cross-thread coroutine migration. Details in the architecture docs.

Benchmarks

The repo ships a reproducible suite (bench/) that drives fastcached and native redis-server / memcached through the same scenarios.

Throughput: fastcached vs redis/memcached

In-memory GET throughput on an AMD Ryzen 9 9950X3D (16C/32T, 96 GB), median of 3 reps, both competitors run as native binaries:

Concurrency fastcached vs native redis vs native memcached
1 ~120k ops/s ~1.0× (tie) ~1.0× (tie)
16 ~900k ops/s 2.5× ~1.0× (tie)
64 ~1.4M ops/s 3.7× 1.6×
256 ~1.2M ops/s 4.7× 1.5×

Geomean across the small-value in-memory scenarios is ~2.7× redis and ~1.35× memcached, with 0 errors and 0 timeouts across the sweep. At a single connection there is no parallelism to exploit and all three tie; fastcached's per-core reactors pull ahead of single-threaded redis from 16 connections up and overtake native memcached at 64+. The persistent backend sustains ~11k durable SET ops/s at one connection and ~67k at 16, p99 under 0.5 ms.

These are honest but narrow numbers: one fast desktop CPU, single machine. The redis baseline is the native single-threaded build, so a modern io-threads redis would narrow the network gap — the multi-core architecture advantage is what stands. Reproduce with python bench/fastcached_bench.py --vs redis,memcached.

Contributing

See AGENT.md for the architecture, error taxonomy, coding guidelines, and how to build and test the project.

License

Licensed under the Apache License, Version 2.0. See LICENSE. Memcached and Redis are registered trademarks of their respective owners.

About

Memcached / Redis compatible cache backend, suitable for sccache and other clients.

Resources

Stars

1 star

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages