Repository navigation
A multithreaded, self-clustering cache for Linux, with its own encrypted protocols and a Redis-compatible door. New here? Start with the README.
This page covers 0.6.3 to 0.6.6: 0.6.3, 0.6.4 and 0.6.5 were not published and have no pages of their own. Coming from 0.6.2, read all of it. The ChangeLog has the detail.
The first release published only after it was proven. 0.6.6 is the commit v0.6.6-rc3 (7d28c44), green on both CI systems and run for 72 hours on a three-node test fleet under staging traffic before this tag was put on it. Nothing was rebuilt for this page: the signed tarball below is the build that ran there. On that fleet it rolled in one node at a time over 0.6.4, which had rolled in over 0.6.2.
The dashboard leaves the daemon
perfcached-dashboardserves the status page from its own process: a client of the fleet's native door through libperfd, one encrypted connection per member, so a dashboard under attack stalls no daemon. The page is the daemon's, byte for byte, under the same script-hash CSP.- Its HTTP layer is libmicrohttpd. TLS and HTTP/2 belong to a reverse proxy in front;
contrib/carries HAProxy and nginx front ends for several dashboards, andtrusted_proxynames the proxies whoseX-Forwarded-Foris believed. - A POST needs a custom request header, a JSON body under
body_maxand the session token; each action asks for the fleet's enable secret once per browser page; wrong secrets lock the address out. Several dashboards sharing asession_keyhonour each other's session tokens. - Its own package, with its own user and a disabled unit. The daemon's own page stays, deprecated, and goes in 0.7.0.
- Wrong enable secrets are counted per source address on the native door: after
enable_lockout_afterof them withinenable_lockout_s(5 in 60 s by default) that address's enable is refused for the window.
Signed builds and the node's half of an upgrade (0.6.5)
- A release tarball comes with a detached ed25519 signature.
perfcached -K <tarball>checks it against the release key built into the binary, or[upgrade] trusted_keysfor builds you sign yourself. upgrade_stagereceives a signed build over a privileged connection (perfcli upgrade-stage <tarball> <version>), verifies it, and passes it to every member that can take one;upgrade_statuslists what is staged.- An optional root helper,
perfcached-upgrade, with its systemd units installed but not enabled, switches a node to a staged build and switches it back if the new one does not report ready. The Debian packages do not carry it. - A node refuses to start on state a newer build wrote - a newer snapshot format, a WAL record of an unknown type, a newer cluster-id file - and says so, instead of reading it as damage.
Settings a fleet must agree on, compared (0.6.5)
- Each member's keepalive carries the settings the fleet must share:
[wal] fsync, whether an HTTP token is set, the[alerts]thresholds, the tombstone limits, and each read-through collection's write settings. A member that differs from the most common value is named on every node as aconfig.<setting>alert. - Every member reports its build; the status page shows each node's version and the header says "mixed versions" during a roll, where the cards of the members still on the older build blink amber.
- The tombstone table's limits are alerts:
cluster.tombstoneswhen deletes are dropped beforetombstone_retain_sis up,cluster.peer_absentwhen a member returns after an absence longer than deletes are kept.
The restart reconcile has a fast path (0.6.4)
- A node back from a restart asks a peer that was up throughout for every delete and expiry made since its snapshot began; when the peer can vouch for that window, the pass ends in seconds instead of asking about every replayed key. Otherwise it asks key by key as before, and
stats.cluster.reconcile_fast_whysays why. - An eagerly replicated key that expires is remembered with the retained deletes, so an older, longer-lived copy cannot come back. Size
tombstone_retain_maxfor deletes plus expiries. - Asking key by key is paced to the free pending-request slots, between 64 and
reconcile_rate_maxa second; stats,/metricsand the page show an open pass's progress.
Write-through to Redis, hardened (0.6.3 - 0.6.5)
- Under
upstream_writes = throughevery node follows every change Redis makes under the collection's prefixes (broadcast tracking): a write another Redis client makes to a key written through here drops the node's copy, and the next read fetches Redis's value. - A node's restart no longer empties the fleet's write-through cache, and a node no longer answers nil for a key Redis holds when an invalidation lands between a record and its no-fall mark.
- The write queue is bounded (
upstream_write_queue, 10,000): past it a write is refused at once, and one with under a quarter of its timeout left is answered "not applied" without being sent. - The writer has its own credential (
upstream_write_user,upstream_write_password), sends plain writes pipelined, and the native door forwards add, sub, hash writes and mset under through. upfillfills the keys a collection owned before the switch to through, compares each with what Redis already holds, and says what Redis's values replaced - never a value, only lengths and expiries. Sweeps are paced by bytes as well as keys (upstream_drain_mb_rate), and the writer thread's CPU share is on the page.
libperfd 0.6.6
- The admin verbs, typed:
perfd_stats,perfd_membersandperfd_clientsanswer the trees the status page reads,perfd_enableraises privilege and raises it again by itself after a re-dial, andperfd_upfill/perfd_updrainrun, check or stop a sweep.
Files
The signed tarball: perfcached-0.6.6.tar.gz with perfcached-0.6.6.tar.gz.sig - the exact build that soaked on the test fleet, x86_64, built on Debian 13 (glibc 2.41) with libcurl mail. It unpacks to one prefix directory (bin/, lib/, include/, share/) for /opt, and it is what upgrade_stage and the perfcached-upgrade helper take. Check it with the binary you already run (0.6.5 or later):
perfcached -K perfcached-0.6.6.tar.gz # GOOD, the release key 5646e8e4 (built in)
Debian 12 and 13, Ubuntu 20.04 and 24.04, amd64:
perfcached: the clustered daemon, every cluster mode.perfcached-standalone: the single-node edition, built from the same tree without the cluster code. A[cluster]section in its config is refused rather than ignored.perfcached-dashboard(new): the status page's own process, unit installed disabled. Not for Ubuntu 20.04: its libmicrohttpd (0.9.66) predates the API (0.9.71) the dashboard is written for. Run the dashboard on another machine for a 20.04 fleet - it reaches the members over the native door.libperfd0: the client library,libperfd.so.0.libperfd-dev: its header, static archive,libperfd.solink andlibperfd.pc.
The two daemon packages install the same files and replace each other. They do not need libperfd0: install it, or libperfd-dev, for your own programs.
sudo apt install ./perfcached_0.6.6-1+deb13_amd64.deb
sudo apt install ./perfcached-dashboard_0.6.6-1+deb13_amd64.deb # on the machine that shows the page
sudo apt install ./libperfd0_0.6.6-1+deb13_amd64.deb ./libperfd-dev_0.6.6-1+deb13_amd64.deb # to build against libperfd
Each distribution's packages were built for it: Debian 13 natively (gcc 14.2, glibc 2.41), Debian 12 (gcc 12.2), Ubuntu 24.04 (gcc 13.3) and Ubuntu 20.04 (gcc 9.4, debhelper from focal-backports) each in a container of that distribution. Every build ran noisetest, the selftest and a deliberately broken-lock build that must fail. Each set was then checked in a clean container of its distribution:
- the version and revision
perfcached -Vreports (0.6.6 (7d28c44)), and the systemd unit; - the daemon starting and answering ping, set and get over its encrypted native door from
perfcli(on epoll: the containers' default seccomp profile refuses io_uring); - a program built against
libperfd-devthrough pkg-config, linkinglibperfd.so.0and reporting 0.6.6; - the shipped config passing
perfcached -C, a[cluster]section refused by the standalone edition, and the two editions replacing each other; - the dashboard package: its binary's libraries, its unit file and the example config;
- an upgrade over the published 0.6.0 packages.
Alpine 3.24 (musl), x86_64, both editions, as tarballs of the tree's make install layout: binaries in usr/bin (the dashboard among them), libperfd (shared and static) with perfd.h and libperfd.pc, the example config, and the docs. No systemd unit or OpenRC service is shipped; run the daemon under your own supervisor.
apk add libsodium libcurl libmicrohttpd
tar -xzf perfcached-0.6.6-alpine3.24-x86_64.tar.gz
cp -a perfcached-0.6.6-alpine3.24-x86_64/usr perfcached-0.6.6-alpine3.24-x86_64/etc /
Built with gcc 15.2 in a clean Alpine container behind the same three gates, then checked in a second clean container: -V and the musl interpreter, the dashboard's libraries, the round trip, the libperfd program, the shipped config and the [cluster] refusal.
libperfd for Alpine: libperfd and libperfd-dev as .apk, signed with the perfcached release key. Trust the key once (its public half is attached here and in contrib/alpine/keys), then install:
cp yury@kfi.net.au-6aca2bb6.rsa.pub /etc/apk/keys/
apk add ./libperfd-0.6.6-r0.apk ./libperfd-dev-0.6.6-r0.apk
Checked in a clean container that trusts only that key: both install, pkg-config reports 0.6.6, a program links the shared or the static library and pings a 0.6.6 daemon, and without the key apk refuses them. The tree's contrib/alpine/APKBUILD at this tag still says pkgver=0.6.0; these packages were built with pkgver=0.6.6, the library's version.
SHA256SUMS covers every file on this page.
Known
- In a container under Docker's or podman's default seccomp profile, io_uring is refused and the daemon runs on epoll.
perfcached -Cand the log say which. - No lists, sets, sorted sets or streams yet. Keys hold strings, counters, JSON documents or hashes.
discovery = unicast(clouds, Kubernetes) is tested in lab network namespaces, not yet in a real cloud deployment.spreadmode has not yet carried production traffic.- In
eagermode a delete made on one side of a network partition can be undone when the partition heals. - The RESP and HTTP doors are plaintext: keep them on a trusted segment, behind their allow-lists.