Benchmarks
Speed and a small footprint are why this server exists. Every change to a hot path is benchmarked against the previous image before it merges.
Results
Section titled “Results”SQLite and filesystem storage on a Ryzen 9 5950X (32 threads), 64 GB RAM, btrfs on NVMe. node is the last TypeScript release (9.9.0), rust the Rust rewrite.
| Metric | node 9.9.0 | rust |
|---|---|---|
| Image size | 260.1 MiB | 28.9 MiB |
| Startup to healthy | 3178.6 ms | 507.8 ms |
| Idle memory | 332.9 MiB | 3.0 MiB |
| Peak memory | 436.0 MiB | 88.4 MiB |
| Total CPU time | 58.56 s | 4.66 s |
| Upload (8 × 128 MiB, parallel) | 356 MiB/s | 2371 MiB/s |
| First download (merge on read) | 368 MiB/s | 8060 MiB/s |
| Later download (merged) | 829 MiB/s | 2312 MiB/s |
| Lookups per second (64 concurrent) | 1953 | 33998 |
| Lookup p50 | 27.7 ms | 1.7 ms |
| Lookup p99 | 87.7 ms | 7.0 ms |
| Small save+restore per second (256 KiB, 32 concurrent) | 4 | 1143 |
On S3 compatible storage (RustFS on the same host): 1172 MiB/s uploads, 970 MiB/s first downloads, 29,000 lookups per second and 500 small saves per second.
Method
Section titled “Method”- Each image runs as a container on a fresh Docker volume. Cleanup jobs are off, and token validation is skipped, so the numbers measure the server and not GitHub’s key endpoint.
- A protocol client sends the same requests as
@actions/cache: Twirp calls for lookups, reservations and commits, and 32 MiB blocks for uploads. - Upload: 8 entries of 128 MiB in parallel, like a build matrix.
- First and later download: all 8 entries, before and after the merge.
- Lookups: 5,000 lookups at 64 concurrent.
- Small save+restore: 300 entries of 256 KiB at 32 concurrent.
- CPU time comes from
cpu.statand memory frommemory.stat anon(page cache excluded) in each container’s cgroup, sampled every 10 ms.
The full write-up, including what each optimization changed, is in PR #288.
Run it yourself
Section titled “Run it yourself”The benchmark ships with the repository. It needs Docker with cgroup v2 and the systemd cgroup driver.
git clone https://github.com/falcondev-oss/github-actions-cache-servercd github-actions-cache-servercargo run --release --example benchmark -- \ latest=ghcr.io/falcondev-oss/github-actions-cache-server:latest \ node=ghcr.io/falcondev-oss/github-actions-cache-server:9.9.0Pass any number of label=image pairs. Each image is measured in turn and the report prints one column per label.
Compared to GitHub’s hosted cache
Section titled “Compared to GitHub’s hosted cache”GitHub’s hosted cache is a remote service. From a self-hosted runner, every restore crosses the internet to GitHub’s storage, so it is bounded by your link and your distance to GitHub. Reported numbers:
| Setup | Reported speed | 1 GiB restore |
|---|---|---|
| GitHub cache, self-hosted runner | ~20 MB/s (actuated, 2023), 5.4 MB/s from Tokyo (google/wireit#239, 2022) | ~54 s |
| GitHub cache, GitHub-hosted runner | 231 MB/s (google/wireit#239, 2022) | ~4.6 s |
| This server, over 10 GbE | the link’s line rate, 1.16 GiB/s | ~0.86 s |
| This server, same host | 7.9 GiB/s (measured above) | ~0.13 s |
The GitHub figures are user reports, not a controlled benchmark, and the 10 GbE row is the link’s limit, not a measurement. To measure your own case, time the same actions/cache restore against both backends: the step log prints the size and the speed.