Skip to content
All posts

Engineering

How Varc holds a Redis command mid-flight

A breakpoint for Redis means pausing a live command without breaking the client that sent it. Here's how the proxy holds, releases, and drops commands — and stays on the hot path while doing it.

Mira KesslerDan Mahon

Mira Kessler & Dan Mahon

· Updated 4 min read

TRAFFIC CONTROL / WORKFLOW DIAGRAM01Application02Breakpoint03Redis / ValkeyDEL session:42Hold a matching command before it reaches the database.

A debugger for code pauses a thread. A debugger for Redis has a harder job: it has to pause one command on a live connection while the client on the other end keeps waiting — patiently, and without noticing anything is wrong. Get it subtly wrong and you either execute a command twice or hang a production client.

Here’s how Varc’s proxy does it.

The mental model: a queue you can stop

The proxy is a transparent man-in-the-middle. Your client connects to it; it connects to Redis. With no breakpoints armed, it’s a pipe. Arm one, and the matching command is held — parked in a queue — while everything about that connection freezes in place.

Play the session below. Reads flow straight through; every write is held at the breakpoint until you release or drop it.

varc · debuggerbreakpoint: writes
AUTH ****OK
GET session:42"{\"uid\":42}"
■ Paused · write held
SET session:42 {…} EX 3600
3 writes · 3 reads

Parse the header, forward the bytes

The temptation is to fully decode every command into a tidy struct. On the hot path that’s death by allocation. The proxy instead reads just enough of each RESP frame to know the command name and key — enough to match a breakpoint — and forwards the original bytes untouched when nothing matches.

Naïve — decode everything

// Allocates a Vec<Bytes> for every command,
// even when no breakpoint is armed.
let cmd = RespValue::parse_owned(&mut buf)?;
if breakpoints.matches(&cmd) {
    hold(cmd).await;
} else {
    upstream.write_all(&cmd.encode()).await?; // re-encode!
}

Varc — peek, then forward

// Borrow a header view; keep the original frame.
let frame = RespFrame::peek(&buf)?;        // no alloc
if breakpoints.matches(frame.name(), frame.key()) {
    hold(buf.split_to(frame.len())).await; // own only when held
} else {
    upstream.write_all(&buf[..frame.len()]).await?; // pass through
}

Only when a command is actually held does the proxy take ownership of its bytes and lift it into a fully parsed, inspectable command. The common case — no breakpoint hit — never allocates.

Held
14:22:07.118  GET  session:42        → "{uid:42}"
14:22:07.119  SET  session:42 … EX 3600
            ■ held · waiting on you
            client blocked, 0 commands lost

Drag the handle: the client’s view never diverges from reality. A held command either runs exactly once when you release it, or — if you drop it — is answered with a synthesised reply so the client never hangs. If the client disconnects while a command is held, the command is discarded rather than executed against a socket that’s gone.

Staying honest on a cluster

A single Redis is easy. A cluster moves keys between nodes and answers with MOVED/ASK redirects. The proxy presents your client with one endpoint while routing each held-and-released command to the node that owns its slot — so stepping through a session feels identical whether you’re on one node or sixteen.

What it costs

Because the idle path is a straight byte forward, an armed-but-not-matching proxy adds only the cost of a header peek per command:

Per-command overhead by proxy state
Forward (idle)peek only
6µs
Match, releaseown + parse
22µs
Match, hold+ round-trip to UI
30µs

Local loopback, pipelined GET/SET · lower is better

The debugger you’re not using should be free. The one you are using should feel instant. That’s the whole design goal — and it’s why the proxy stays a pipe until the moment you ask it not to be.

Mira Kessler

Mira Kessler · Engineer

Works on the Varc proxy and debugger engine. Enjoys RESP frames a little too much.

Dan Mahon

Dan Mahon · Founder

Building Varc — a Redis and Valkey client with a debugger built in. Writes about keyspaces, proxies, and the parts of Redis that bite in production.

varc.dev

Keep reading