Engineering·October 20, 2026

Rewriting Blue's Backend in Rust, One Query at a Time

I am replacing Blue's Node backend with Rust while 19,000+ companies keep using it. Why now, why Rust, and the first results from production.


Rewriting Blue's Backend in Rust, One Query at a Time

Yesterday morning, Blue’s backend was 100% Node.js. Today, twenty of its capabilities run on Rust. About 360,000 requests went through the new code in the last twelve hours, and nobody noticed anything except that things got faster.

That is the whole point. I am replacing Blue’s backend piece by piece, while 19,000+ companies keep working in it.

Why a rewrite makes sense now

Blue’s backend has been in production since 2018. It has seven years of accumulated decisions, and it has hit the ceiling of what Node can do. Building a large workspace’s records takes seconds, and the memory bill grows with every record.

The standard advice is “never rewrite”. It was right when a rewrite cost a full team several years. It is wrong now. Agents write the code, so typing is no longer the cost. What is left is proving that the new code behaves exactly like the old code, and proof is something I can build.

I have done a big bang before

When I rebuilt the data model for databases, I did it as one cutover, because the old model and the new one could not run side by side. Over the following week I fixed more than two hundred issues.

I got through it, and I will not do it again. A big bang puts every risk into one weekend. For a backend that 19,000+ companies depend on, I want many small changes, each one proven and each one reversible.

The strangler fig

The approach has a name. Microsoft’s architecture guide on the strangler fig pattern describes it: put a front door in front of the old system, move one capability at a time to the new system, and when nothing depends on the old one, switch it off. It is named after the fig that grows around a tree until the tree is gone.

Three stages of the strangler fig pattern: Node handles every capability, Rust takes capabilities one by one behind the same Node front door, and Node is removed.

For Blue, Node is the front door. The app, the API and every integration keep talking to the same place. When a capability moves, Node asks the Rust service for the answer and passes it on. Both backends run in production at the same time, on the same database.

Three rules keep it safe:

  • Merging is deploying. There are no flags and no percentage rollouts. One code path, for everyone.
  • Rollback is one git revert.
  • If Rust fails, Node answers. In the last twelve hours, fewer than one call in 500 fell back to Node, and no customer saw an error.

Why Rust and not Go

This was a real decision. Blue’s CLI and Cobalt are both written in Go. I know Go, I like it, and it compiles faster. I ran a pilot to find out: the same feature, the same AI model, two separate agents, one in each language. Both passed every test.

Rust won on two things I care about for the next ten years.

Correctness. The Go version made a mistake that Rust refuses to compile. It read a column that can be empty as if it could never be empty, and every create stopped delivering webhooks. In Rust, a whole family of these mistakes is impossible to write. Since I do not read the code line by line, I want the compiler to be the strictest reviewer in the room.

Performance. Both languages are far faster than Node. At the largest scale I tested, a 50,000-record workspace, Node needed 131 seconds. Go needed 9.2 seconds and Rust needed 5.7 seconds. Rust used 1.9 GB of memory, against 3.8 GB for Node and 4.5 GB for Go.

I accepted a slightly slower build loop in exchange for both.

Two reviewers that never get tired

Agents write the Rust, so review has to come from somewhere else.

The first reviewer is the compiler. Whatever it rejects, no person ever needs to find.

The second is a black-box test harness, a separate program that knows nothing about either backend. It only talks to Blue the way a customer’s browser or integration would, and it runs the same scenarios against Node, against Rust and against both together. When they disagree, I do not bend the test. I prove who is right, and the test stays as it is.

Every migrated query passes both before it ships.

The results

In isolated benchmarks with byte-identical output:

WorkloadNodeRust
Build 5,000 records × 50 fields1,399 ms135 ms
Build 50,000 records × 50 fields131 s5.7 s
Evaluate 10,000 formulas68 ms14 ms

In production, the work behind each migrated query now takes a few milliseconds:

QueryNode, p95Rust, p95
userChanges159 ms3 ms
organizationChanges170 ms5 ms
workspaceChanges179 ms5 ms
hasUnreadInboxThreads156 ms5 ms
recordDatabaseMenuOrder162 ms5 ms
githubRepoLink127 ms4 ms

The heavier queries gain even more in absolute terms. automationList went from a 668 ms median to 55 ms. savedViews dropped from 262 ms to 116 ms at p95.

Twenty capabilities moved in about a day, each with its own tests, its own review and its own rollback.

What happens next

Every part of the backend will move to Rust, one piece at a time, until Rust covers 100% of it. Then the Node code is deleted and the fig has finished its job.

The only thing you will notice is that Blue gets faster and more consistent.