head2head · jimmyhoughjr.net

vault: Node vs Swift / Hummingbird 2

The same ~small auth-and-storage service, implemented twice and swapped live behind the same Dokku app on an 8-core ARM64 pi (16 GB). Identical API, session format, and storage files — the frontend never noticed. Measured July 7, 2026.

Memory (RSS)
−54%
Swift · 24.7 vs 54 MB
Avg latency
≈ tie
10.5 vs 11.6 ms
Worst latency
−29%
Swift · 14.4 vs 20.3 ms
Deploy time
20×
Node · 15 s vs ~5 min

The numbers

Node 22 Swift 6.1 / Hummingbird 2
Process RSS
54 MB
24.7 MB
/health avg (LAN, n=20)
10.5 ms
11.6 ms
/health max (LAN, n=20)
20.3 ms
14.4 ms
Deploy (code change)
~15 s
299 s

Everything measured

MetricNode 22Swift / HB2Edge
Process RSS after boot54 MB24.7 MBSwift
/health latency, LAN avg (n=20)10.5 ms11.6 mstie
/health latency, LAN max (n=20)20.3 ms14.4 msSwift
Deploy, wall clock~15 s299 s (450 s cold)Node
Attempts to first good deploy13Node
App payload in image18 KB source112 MB static binaryNode
Runtime base imagenode:22-alpineubuntu:noble
Lines of code (incl. shared HTML)367489Node
Dependencies02 (Hummingbird, swift-crypto)Node
Mac dev-loop rebuildnone needed3–6 s incrementalNode

What was identical

Both implementations expose the same routes, mint the same HMAC-SHA256 session cookies, and write the same one-JSON-file-per-user storage layout. That was proven, not assumed: a session token minted by the Node code validated against the live Swift server, and an authed write/read round-trip ran through Cloudflare against production. The watts app needed zero changes; Dokku's config, domain, and storage mount carried over untouched.

What the deploy numbers hide

Node deployed right on the first push. Swift took three: the code compiled cleanly on the Mac's Swift 6.3 toolchain, then failed twice on the pi's Swift 6.1 Linux image — once because Linux swift-crypto's SymmetricKey isn't Sendable under strict concurrency, once because Linux Foundation lacks URLResponse(). Each round-trip to find that out cost ~8 minutes of pi build time. That's the real Swift tax on this setup: not runtime performance, but the length and platform-fidelity of the feedback loop when Dokku builds on the device itself.

Two mitigations worth noting: failed builds never touched the running service (Dokku keeps the old container), and the Dockerfile could cache dependency compilation in its own layer to cut code-change rebuilds further.

Verdict

For a service this small, both are overkill and both are fine. Swift genuinely halves the memory footprint and trims tail latency — on a 16 GB pi hosting a growing pile of containers, that's the number that compounds. Node wins everything about the iteration loop: 20× faster deploys, first-try correctness, no cross-platform surprises, a fifth the image payload.

The Swift version is what's running now. It earned the slot on memory, and reverting is one git push from the Node repo if the build friction ever outweighs it.

Methodology: latency is 20 sequential LAN curls per implementation via the pi's nginx (Cloudflare excluded); RSS from ps inside each container after boot; deploy times are wall-clock git push to "application deployed." Single-run wall clocks — directional, not benchmarks. Built by Jimmy Hough Jr & Claude · Donations appreciated · Powered by Roost.