A transparent query cache that attaches to a running program.
Barnacle aims to provide a turnkey query cache allowing system administrators to quickly accelerate slow DB queries without explicitly configuring a cache or maintaining more infrastructure. It attaches to processes that are already running and know nothing about it, and detaches again without restarting them.
The name is the shape of the thing: it fastens onto something already moving,
travels with it, and comes off without leaving a mark. bncl is the internal
prefix -- macros, C symbols, namespaces and the components other than the
command line itself, which is just barnacle.
The only configuration required is:
- Location of the cache
- How to identify outbound DB connections
- Which queries to optimize
There are two cache tiers: Valkey, which every host shares, and a per-host
copy in a BPF arena that each attached process has mapped, so a hit there
costs no round trip. local_cache = off gives the local tier up and sends
every lookup to Valkey, which is slower but makes the shared copy the only
authority.
Strictly speaking, however, all except #1 can be inferred through heuristics
There is ONE daemon that does need to run, although it is entirely self-contained, not requiring any ports or permissions. It simply serves to coordinate and schedule cache notification [...]
One dependency is frida-gum, which for the time being does the heavy lifting of binary injection and patching at a few points in the program. This is needed in order to intercept I/O of applications using OpenSSL without having to run a full- blown man-in-the-middle proxy. Instead, we intercept calls to the SSL functions themselves, and determine whether or not they involve caching or not.
| what it is | language | |
|---|---|---|
barnacle |
entry point. Owns the daemon's lifecycle, finds target processes via /proc, decides whether each can be hooked, injects the agent, and reports status. |
Python |
bncl-daemon |
the userspace daemon. Owns the dpipe pool, the arena and the cache; answers lookups and stores; carries a control socket. | C++ |
libbnclagent.so |
the agent injected into each client process. Hooks the TLS library's read/write entry points. | C++ |
bncl-inject |
injects the above into a running process, via frida-gum. | C++ |
kclient.bpf.o |
the only eBPF: two sk_msg programs that splice a client socket to the daemon. |
C |
barnacle start-server start the daemon
barnacle stop-server stop it
barnacle attach-client attach the agent to the configured programs
barnacle detach-client undo every attachment
barnacle reload-config re-read the config; tell the daemon and the agents
barnacle status what is running, and what it has done
barnacle list which processes match, and their verdicts
barnacle analyze as above, with the reasoning for each verdict
The daemon and the agents are reached differently because they are different kinds of thing. The daemon is one long-lived process with a Unix control socket, so start, stop, reload and status are a request and an answer. The agents are code running inside somebody else's process with no thread of their own, so they are reached by injection -- to hook, or to put every entry point back -- and by two values in a BPF map that they poll, which is how a detach or a new statement list reaches every attached process at once.
Nothing in barnacle needs libbpf or bpftool: every effect on a BPF map goes through the daemon, which already has them. That is what lets it run on a host with no toolchain.
A docker-compose stack in demo/: MySQL with five genuinely slow queries,
Valkey, a sealed client application in its own container, and a barnacle host
with supervisor access to the client and nothing else.
docker compose -f demo/docker-compose.yml up -d --build
docker compose -f demo/docker-compose.yml exec ctl make
demo/terminals.shThat opens four windows -- what reaches MySQL, what reaches Valkey, the client's own log, and a shell on the barnacle host. In the last one:
./build/barnacle -c demo/config/barnacle.conf start-server
./build/barnacle -c demo/config/barnacle.conf attach-clientThe client's latency histogram collapses into its fastest bucket, its "over 500ms" count goes to zero, and the MySQL window stops moving -- without the client being restarted or reconfigured, or knowing anything happened.
See demo/README.md for what each window shows, how the
latency is arranged, what reload-config and detach-client do, and the two
client settings (TLS and autocommit) that will otherwise stop it working
silently.