Releases: LASTRADA-Software/fastcached
Release list
fastcached v0.3.0
What's Changed
⚠️ Breaking changes
- Every Raft peer connection proves the cluster key by @christianparpart in #1429
Features
- feat(cli): fastcache-cli, an operator's client for the cache by @christianparpart in #1198
- feat(cli): the memcached-text verbs, and the wire column that makes room for them by @christianparpart in #1232
- Stack six batches onto one branch: 17 tickets, one CI cycle by @christianparpart in #1249
- Name the toolchain on a dispatched compile, and measure the /fleet render instead of caching it by @christianparpart in #1263
- feat(node): a reset peer and one that said goodbye are counted apart by @christianparpart in #1262
- The upstream row is not replicated, the stream decoder reserves within a cap, and a setting must say what reads it by @christianparpart in #1273
- feat(cli): reach a pure-0xFC node, and let it say what it is by @christianparpart in #1275
- The cluster verbs in fastcache-cli, and one predicate for following a NotLeader by @christianparpart in #1312
- A worker that is never chosen says so by @christianparpart in #1316
- NodeStatus says what a node is DOING, so a default node is observable by @christianparpart in #1315
- feat(node): the worker's cache tiers take the daemon's six compression settings by @christianparpart in #1324
- The fleet tables answer a terminal, not only a browser by @christianparpart in #1326
- cluster admit echoes the endpoint it recorded, so a typo is visible by @christianparpart in #1336
- Group COMMANDS by wire, derive the fleet-column coverage, and ship the KPI strip computed by @christianparpart in #1341
- A runtime enrollment window, and the --raft-join trap it makes sharper by @christianparpart in #1333
- The live-stats dashboard is a fold over one event stream (#134 stage 3) by @christianparpart in #1386
- fastcache-cli live-stats: a live terminal view of a cache, a compile node or the fleet by @christianparpart in #1392
- A Counter becomes an index in one place, and a scan says so by @christianparpart in #1409
- CacheDrop: an operator removes one key from the cache tier that answers by @christianparpart in #1411
- compile-node: a worker can be cordoned, drains, and says when stopping it abandons nothing by @christianparpart in #1412
- compile-node: --scheduler is a list, and a dead first entry falls back in the same round by @christianparpart in #1419
- A unit compiled against a different Counter enum fails to link by @christianparpart in #1414
- compile-node: NodeMetrics answers the reading /metrics renders, so a node's cache tier shows without an admin surface by @christianparpart in #1435
- cli: fleet reads the leader's tables over 0xFC, follows a follower to them, and prints the history as a table by @christianparpart in #1436
- compile-node: print the consensus address peers dial, apart from the one the node binds by @christianparpart in #1425
- compile-node: --slots=0 runs no worker, and the worker is a tier by @christianparpart in #1459
- live-stats: a consensus node's panel says where its peers dial it by @christianparpart in #1463
- One duration grammar for every length of time the tools read and print by @christianparpart in #1458
- build: every shipping configure runs the instruction-set check, in its job and before its build by @christianparpart in #1493
- fleet: an admission names every route that admitted it, through one door by @christianparpart in #1498
- fleet: a node says how many client tombstones it has APPLIED, and absent is not zero by @christianparpart in #1502
- node: a machine that holds the cluster key is admitted on 0xFC by proving it, not by its address by @christianparpart in #1513
- node: a live subscription is gated on the cluster-key proof at its door and on every tick, not on an address by @christianparpart in #1516
- node: a machine with no worker announces itself, so the fleet can see it by @christianparpart in #1517
- node: an operator can ask WHICH routes admit or refuse a host, not only whether by @christianparpart in #1522
- node: the fleet surfaces report WHICH clients the cluster has forgotten, not how many by @christianparpart in #1523
- board 3: thirteen changes integrated on one branch -- learners, identity keys, conditions, arm64 and the CI guards by @christianparpart in #1551
- board 3, final: per-node identity replaces the cluster key (#178 PRs 3-6), a follower refuses what it cannot read, and a refusal survives its close by @christianparpart in #1558
Bug fixes
- fix(test): FileTrust's 0640 cases assert for the privilege mode they are actually in by @christianparpart in #1131
- The MSVC bindir is chosen by the machine, not by the architecture this build targets by @christianparpart in #1133
- fix(async): give every Submit site the ownership a detached chain needs by @christianparpart in #1132
- fix(ci): sweep apt sources by an allowlist of hosts, not a list of vendors by @christianparpart in #1164
- fix(net): retire an IOCP read synchronously, and stop reporting every IOCP error as SystemError by @christianparpart in #1136
- The caveat self-test's quiet case no longer bets on a fast host by @christianparpart in #1154
- fix(test): utf8-argv takes its verdict in bytes, not through the console's code page by @christianparpart in #1147
- fix(tests): take the per-file process out of two hygiene scans by @christianparpart in #1151
- fix(ci): the gate reports what its tests actually did — skips by name (#1130), crashes named (#1158), states reconciled (#1159) by @christianparpart in #1153
- fix: four tickets — derived wire-enum bounds (#197), an unclassifiable compiler's own refusal (#264), fleet-open admission (#1112), windows.h include category (#1135) by @christianparpart in #1186
- The gate's target set, and one staged tree for the check-signals selftest by @christianparpart in #1199
- Stop a pipe SIGPIPE reddening master, and make three checks report what they found by @christianparpart in #1200
- fix(launcher,server): the compile budget, the MSVC fingerprint premise, and a compile-time guard on what a connection holds by @christianparpart in #1203
- The macOS package fixture gets a port of its own, and hygiene budgets get a headroom report by @christianparpart in #1201
- fix(ci): a notarization stall names Apple, and the toolchain survey is bounded on silence by @christianparpart in #1205
- fix(hygiene): four defects in the script-check family's readers and verdict arms by @christianparpart in #1222
- fix(test): a Catch2 case name may not begin with a dash by @christianparpart in #1240
- Three reactor and socket teardown tickets, and a fourth that did not reproduce by @christianparpart in #1250
- fix(ci): a green TSan run says WHICH suppressions matched, not just how many by @christianparpart in #1264
- fix(cli):
helpanswers, and a settled action still reads how to render it by @christianparpart in https://github.com/LASTRADA-Sof...
fastcached v0.2.0
What's Changed
⚠️ Breaking changes
- node: the cache tier serves this machine, and no member list changes that by @christianparpart in #369
Features
- Say why a dependency path did not reach the compile-cache key by @christianparpart in #113
- Grow a cluster's Raft quorum at runtime, without restarting its members by @christianparpart in #110
- fastcache-compile-node: the leader learns what each member's cache holds by @christianparpart in #137
- fastcache-compile-node: a fleet dashboard, served by the leader by @christianparpart in #138
- Discover the machine's toolchains at compile-node startup by @christianparpart in #151
- Show what a toolchain is, beside the fingerprint that identifies it by @christianparpart in #219
- The fleet dashboard can now list the leases it counts, not just count them by @christianparpart in #222
- Dashboard history: eight windows to twelve months, surviving restarts and elections by @christianparpart in #227
- node: every port it opens is a row, and an opener takes the surface by @christianparpart in #359
- node: the worker's settings are a file, read through the one option table by @christianparpart in #399
- node: the scheduler verbs take an inbound credential, checked before the payload by @christianparpart in #409
- Verify one cache hit in N against a fresh compile by @christianparpart in #424
- A node serves its cache tier while it identifies its toolchains by @christianparpart in #428
- Six refusals answered correctly and counted nothing by @christianparpart in #430
- Compiles arrive on the merged 0xFC surface too by @christianparpart in #446
- A reactor listener can adopt an inherited descriptor by @christianparpart in #464
- One 0xFC listener: the node retires its dedicated compile port by @christianparpart in #467
- feat(net): notice a worker whose host went away, and say which happened by @christianparpart in #520
- A node reloads what it can, and names everything it cannot by @christianparpart in #533
- feat(node): a swept connection says which window it outran, and is counted once by @christianparpart in #525
- node: a toolchain change is reloadable, and re-registers with the fleet by @christianparpart in #590
- A worker learns the scheduler term from authentic grants, and refuses a deposed one by @christianparpart in #616
- A worker notices a client that left, and stops writing an object to nobody by @christianparpart in #669
- A dispatched compile pulses Status::Progress while it runs, so an idle bound of seconds can sit under a total that stays minutes by @christianparpart in #730
- A worker says once when it is refusing every grant on the scheduler term by @christianparpart in #768
- feat(testing): a flake-qualifying idiom that keeps a tally by @christianparpart in #936
- feat(node): say something about every client exchange, at a level chosen per verb by @christianparpart in #992
- feat(node): the compile-argument allowlist extends from configuration, and reloads by @christianparpart in #1020
- feat(node): the credential and the member list both reach a running worker by @christianparpart in #1028
- A node mints its own identity, and consensus is decided by --listen-raft by @christianparpart in #1043
- A worker can retire a toolchain it no longer serves by @christianparpart in #1091
- feat(launcher): say which kind of decline, so an operator knows what to fix by @christianparpart in #1094
- Let a site say its translation units are long by @christianparpart in #1120
Bug fixes
- Fix needless re-elections when a follower loses contact with the leader by @christianparpart in #107
- cc,testclient: two review findings the launcher and the test client were carrying by @christianparpart in #114
- Refuse to cache a compile whose paths are neither keyed nor guarded by @christianparpart in #108
- Log the consensus term and what deposed a leader by @christianparpart in #123
- net: a listening socket keeps its address to itself on Windows too by @christianparpart in #127
- Name the path and the reason when a translation unit gets no direct-mode manifest by @christianparpart in #121
- macOS/Windows: let the compile worker install itself as a service by @christianparpart in #120
- Retire the cached manifests that can still direct-hit a drive-relative compile by @christianparpart in #150
- cluster: a discovery node answers from an address of its own by @christianparpart in #156
- Fix a healthy cluster re-electing while a follower still had a leader by @christianparpart in #152
- distributed: a peer that cannot name itself in UTF-8 cannot break /fleet.json for the fleet by @christianparpart in #160
- Emit MutationKind::Expire and ::Evict, and make NotifyingStorage's header true by @christianparpart in #163
- Convert an old on-disk store instead of refusing it as corrupt by @christianparpart in #165
- Refuse an unusable registration at install time, not at every boot by @christianparpart in #169
- Claim a CoW store file exclusively, so two processes cannot corrupt it in silence by @christianparpart in #161
- Fix clang-cl fingerprinting differently under a service than in a developer prompt by @christianparpart in #164
- build: say so when the compiler-cache fallback can produce a wrong build by @christianparpart in #171
- docs,fastcached: say what a shared sccache backend costs, where it is offered by @christianparpart in #182
- Fix a compile node reserving memory for a cache tier it never built by @christianparpart in #185
- Reclaim and report a key whose TTL lapses and which nothing touches again by @christianparpart in #184
- Fix nine defects verified from a mis-scoped master review by @christianparpart in #189
- node: the consensus tier's command-line refusals belong in the policy tables by @christianparpart in #190
- Fix a node frame endpoint answering only one request per connection by @christianparpart in #205
- Fix cache hits and remote compiles that ignored the compiler's target by @christianparpart in #192
- cc: every MSVC toolset keyed as the string "cl", so one served another's objects by @christianparpart in #204
- Fix non-ASCII arguments being mangled on Windows, which the fleet then refuses by @christianparpart in #193
- Fix a Windows service that could neither report a failure nor restart from one by @christianparpart in #211
- Report the RAM a cache tier's key index costs by @christianparpart in #218
- distributed: 0 compiling forever, and the operator's own build rendered as somebody else's by @christianparpart in #217
- node: a listen flag's grammar belongs in the policy table, not in the tier that binds it by @christianparpart in https://github....
fastcached v0.1.1
What's Changed
- Use fastcache-cc for our own build when a daemon is running by @christianparpart in #46
- Color the fastcache-cc stats report and add an HTML dashboard by @Yaraslaut in #50
- Fix stale direct-mode cache hits when a source file's own body changes by @Yaraslaut in #52
- Add opt-in auto-install of fastcache-cc when no compiler cache is present by @christianparpart in #54
- Streamline compile-cache naming: CMake-style dirs and prefetch groups by @christianparpart in #55
- Fix a cache hit replaying a depfile that names a moved header's old path by @christianparpart in #61
- Key a compile on the headers it resolved, and make MSVC keys carry the source by @christianparpart in #62
- Fix the compile-cache key having 32 bits of strength, not 128 by @christianparpart in #67
- fastcache-cc: relativize the fused MSVC object-output flag, off one path-flag table by @christianparpart in #71
- Classify a path's anchor in three states so a Windows drive-relative path is not called absolute by @christianparpart in #75
- Fix fastcache-cc dying of SIGPIPE when an object is too large to cache by @christianparpart in #73
- fastcache-cc: classify a dependency path's anchor before asking if it is toolchain by @christianparpart in #72
- Fix lost cache sharing with relative include paths by @christianparpart in #74
- Reconcile root spellings so a moved header can re-key on any host by @christianparpart in #76
- Require a credential on the compile-cache protocol by @christianparpart in #77
- Add distributed compilation: dispatch cache misses to worker nodes by @christianparpart in #78
- Add a Raft consensus library with a deterministic cluster simulation by @christianparpart in #82
- consensus: Raft over the network — the peer wire, transport, pre-vote, membership and snapshots by @christianparpart in #83
- node: worker metrics, an endpoint to serve them, and the facts a scheduler weighs by @christianparpart in #86
- node: a daemon shell for the compile worker by @christianparpart in #88
- cluster: find peers on a LAN, and prove who holds the key before admitting them by @christianparpart in #91
- distributed: the scheduler moves to the node, and --listen-dispatch is deleted by @christianparpart in #93
- node: a cache of its own, so a local rebuild never reaches the wire by @christianparpart in #94
- cmake: FASTCACHE_AUTO_START stages and starts a daemon by @Yaraslaut in #95
- Resource-aware scheduling, and a node closed to who it is not by @christianparpart in #96
- Runtime cluster configuration, and a cluster that actually forms by @christianparpart in #98
- One TCP client, in Net/, coroutine-aware — and a test client that builds on POSIX by @christianparpart in #99
- ci: a documentation check queues per ref, not behind every Pages deploy by @christianparpart in #101
Full Changelog: v0.1.0...v0.1.1
fastcached v0.1.0
This is the first release of fastcached, so rather than a list of changes, here is what the project is and what it can do today.
What it is
Two things ship from this repository:
fastcached — an in-memory cache daemon that speaks the memcached text, memcached binary, memcached meta and Redis RESP2 protocols on a single port. It works out which one a client is using from the first bytes of the connection, so the port selects no protocol; every client reaches the same daemon on the same one.
fastcache-cc — a compiler launcher in the style of ccache and sccache, backed by that daemon. Its distinguishing property is that cache entries are portable across checkout paths: paths under your source root and build tree are rewritten to tokens before hashing, so the same source compiled at /home/alice/proj and at /ci/runner/w/1/s/proj produces the same cache key. CI runners and developer machines can therefore share one cache even when their trees live at different depths — which is the reason the launcher exists, since sccache keys on absolute paths and cannot.
They are useful together as a shared compile cache, and fastcached is useful on its own as a memcached/Redis-compatible cache, including as a plain sccache backend.
What is in this release
The daemon
- Four wire protocols on one port, detected per connection — memcached text, binary and meta (
mg/ms/md/ma/me/mn), and Redis RESP2. The default port is 6674, unassigned by IANA, unprivileged, and below the ephemeral range. Additional listeners are repeatable via--listen, so a client you cannot re-point keeps working on its own port, and that port speaks every protocol too. - Optional persistence with
--storage: a copy-on-write B+tree where every commit is crash-consistent. The file always matches either the previous transaction or the new one, so akill -9at any instant leaves no half-written state, and a restart picks the cache back up with no warm-up. Storage is composed as an in-memory LRU over the on-disk tree, sharded by key hash so writes to different shards do not block each other. --threads=Nruns N single-threaded reactors (epoll, kqueue or IOCP), each pinned to a core, with every connection pinned to one reactor for its lifetime. Concurrent clients are bounded by memory rather than by a worker count.- Authentication with
--requirepass(RedisAUTH, memcached SASL PLAIN), TLS on an OpenSSL build, and Prometheus/metricsplus/healthzon a separate admin port with--metrics. - YAML configuration with CLI flags taking precedence, re-read on
SIGHUPor the Windows service manager'sPARAMCHANGE. Started without--config, the daemon finds its own file from a per-platform list of locations — and which locations apply depends on whether the process could actually be the machine-wide service. - Packaged for Linux (
.deb,.rpm), macOS (.pkg,.dmg) and Windows (.msi), each installing both executables and registering the daemon to start automatically, plus aDockerfilefor containers.
The launcher
- Drop-in via
CMAKE_<LANG>_COMPILER_LAUNCHER, or as a plain prefix for a single compile. Supportsgcc/g++,clang/clang++including versioned names, and MSVCclandclang-cl. - If anything goes wrong — daemon unreachable, network gone, cache corrupt — it silently runs the real compiler. A broken cache can slow a build down; it should not be able to break one.
--show-statsreports hit rate, latency distributions and, importantly, a separateunavailablecount, so "the cache did not help" and "the cache was never reached" do not look alike.
Performance
On in-memory GET throughput, measured on one AMD Ryzen 9 9950X3D against redis 8.10.0 and memcached 1.6.40 built and run as native binaries on the same host:
| Concurrency | fastcached | vs redis | vs memcached |
|---|---|---|---|
| 1 | ~125k ops/s | ~1.05× (tie) | ~1.0× (tie) |
| 16 | ~1.06M ops/s | 3.0× | ~1.07× (tie) |
| 64 | ~1.45M ops/s | 4.5× | 1.6× |
| 256 | ~1.32M ops/s | 4.4× | 1.6× |
These are honest but narrow numbers, and worth reading with their limits attached: one fast desktop CPU, a single machine, and a Python load generator sharing that CPU with the server. At one connection there is no parallelism to exploit and all three tie. The redis baseline runs at its default io-threads 1, so a redis tuned for several I/O threads would narrow the network gap — what stands is the multi-core architecture, not a claim about redis at its best. The suite is in bench/ and reproduces with python bench/fastcached_bench.py --vs redis,memcached.
Scope, honestly
fastcached is not a general-purpose replacement for memcached or Redis. It implements only the slice of each protocol a cache backend actually uses, and the coverage matrix in the documentation says exactly which commands that is. If you need the rest of Redis, you need Redis.
It is, however, in production use as a shared compile cache for a large C++ codebase, backing both CI runners and developer machines.
Getting started
fastcached --storage=$HOME/.cache/fastcached/cache.cowThen point clients at it, or wire the launcher into a build — the README walks through both, and the full documentation lives at https://lastrada-software.github.io/fastcached/.
This being a first release, there will be rough edges we have not found yet. Issue reports and feedback are genuinely welcome.
Licensed under the Apache License, Version 2.0. Memcached and Redis are registered trademarks of their respective owners.