The Rewrite Temptation
Every programmer has a “rewrite monster” living inside them.
Looking at that tangled mess of old system code, there’s always a voice whispering: “Why don’t we scrap it all and rewrite it in Rust? Memory safety, performance explosion, p99 latency skyrocketing.”
Honestly, I’ve had this thought too. Especially when you’re staring at the monitoring dashboard, watching memory curves go up and down like a roller coaster, with GC pauses occasionally dropping 50ms “surprises” on you, you really want to flip the table.
But do you really need to rewrite?
Rewriting is a Gamble
Rewriting sounds easy, but it’s hard in practice. You think you’re renovating a house, but you’re actually demolishing and rebuilding it.
Three-month estimate becomes eighteen months of work. Requirements change, team members come and go, halfway through you realize those “incomprehensible codes” in the original system actually made sense.
Even worse, the old system must keep running while the new one isn’t live yet. You’re maintaining both sides. It’s like changing tires while driving—exciting, but easy to crash.
So today I want to talk about another path: don’t rewrite, but secretly let Rust help you do the work.
Sidecar Pattern: Finding a Helper for Dirty Work
The word sidecar sounds a bit weird. But think of that little compartment attached to the side of a motorcycle—someone sits in it, following the main vehicle but remaining relatively independent.
In software architecture, that’s exactly what sidecar means: your main service stays the same, but a small service runs alongside it, specifically handling certain tasks.

In software architecture, sidecar means exactly that: your main service stays as is, but a small service runs alongside it, specifically handling certain tasks.
[Your Legacy System]
|
| Local Call
v
[Rust Sidecar: Compression/Validation/Formatting]
This small service can be written in Rust. It’s deployed together with the main service, communicating through local HTTP or Unix Socket. Latency is negligible, but the benefit is that you’ve offloaded all those CPU-intensive dirty jobs.
Real Results: Memory Cut by 40%
Let’s look at some real data.
One team moved their hottest path—data formatting, validation, compression—from the main service into a Rust sidecar. Just this one operation showed immediate results:
| Metric | Before | After |
|---|---|---|
| Main Service Memory | 1.9 GB | 1.1 GB |
| Sidecar Memory | - | 180 MB |
| Total Memory | 1.9 GB | 1.28 GB (32% reduction) |
| GC Pause p95 | 42 ms | 18 ms |
| API Latency p95 | 212 ms | 158 ms |
Look at these numbers—the main service memory dropped from 1.9GB to 1.1GB. Even with an additional 180MB for the sidecar, we’re still saving 600-700MB overall.
What’s more critical is the GC pause time. Dropping from 42ms to 18ms is a tangible improvement in user experience. Without those occasional hiccups, users won’t feel your system is “a bit slow.”
Which Tasks Are Suitable for Sidecar?
Not every task is suitable for offloading.
Think about it—sidecars excel at “head-down work” tasks. Like data compression and decompression, gzip, brotli—CPU crunches and it’s done. JSON validation, Protobuf parsing, signature verification, hash calculations for encryption. Image resizing, thumbnail generation, extracting data from logs with regex, simple scoring calculations.
These tasks share common characteristics: given input, it crunches away and spits out output without asking questions back and forth. It’s like hiring a temp worker to help move bricks—bricks here, bricks there, no need to know your overall renovation plan.
But some tasks aren’t suitable for offloading.
For example, those frequently accessing databases, or spending most time waiting for network responses—their bottleneck isn’t CPU, so offloading is useless. Those maintaining complex session states, or processing huge file streams—these don’t naturally fit the sidecar’s “stateless single-call” model. Forcing them in just adds network overhead, not worth the trouble.
Let’s Look at the Code
Writing a simple Rust sidecar service actually requires very little code. Here’s a service handling text formatting and compression:
use axum::{routing::post, Router, Json};
use serde::{Deserialize, Serialize};
use flate2::{write::GzEncoder, Compression};
use std::io::Write;
#[derive(Deserialize)]
struct Input {
text: String
}
#[derive(Serialize)]
struct Output {
ok: bool,
bytes: usize,
compressed: Vec<u8>
}
async fn normalize(Json(input): Json<Input>) -> Json<Output> {
// Simple formatting: trim and lowercase
let clean = input.text.trim().to_lowercase();
if clean.is_empty() {
return Json(Output { ok: false, bytes: 0, compressed: vec![] })
}
// Compression
let mut encoder = GzEncoder::new(Vec::new(), Compression::fast());
encoder.write_all(clean.as_bytes()).ok();
let body = encoder.finish().unwrap_or_default();
Json(Output { ok: true, bytes: clean.len(), compressed: body })
}
#[tokio::main]
async fn main() {
let app = Router::new().route("/process", post(normalize));
let listener = tokio::net::TcpListener::bind("127.0.0.1:8081").await.unwrap();
axum::serve(listener, app).await.unwrap();
}
# Cargo.toml
[dependencies]
axum = "0.7"
tokio = { version = "1", features = ["full"] }
serde = { version = "1", features = ["derive"] }
flate2 = "1"
Your main service just needs to send a POST request to 127.0.0.1:8081/process. Data makes a local round trip—latency is basically negligible.
Why Does This Work?
You might ask: isn’t it just adding another service? How does it save so much memory?
The answer lies in heap isolation.
Languages with garbage collectors like Java, Python, Node allocate lots of temporary objects on the heap when processing requests. These objects are used once and discarded, but GC needs time to clean them up. When data volume is large, GC pressure increases.
Moving this work to a Rust sidecar means these temporary allocations aren’t happening on the main service’s heap. Rust manages memory itself—released immediately when done, no waiting for GC sweeps.
It’s like your trash bin at home is always full. Instead of getting a bigger bin (adding memory) or hiring someone to empty it more frequently (tuning GC parameters), you outsource the trash-generating work to someone else at their place. Naturally, your trash becomes much less.
Practical Implementation Tips
If you’re serious about implementing a sidecar, pay attention to these details:
For communication, local HTTP is usually sufficient. If you’re pursuing极致performance, Unix Socket can save the TCP handshake overhead, but honestly, HTTP is enough for most scenarios—don’t prematurely optimize.
You must set timeouts. Cap each call at 20ms. If the sidecar hangs or crashes, don’t let it drag down the main service. Rate limiting and circuit breakers are also essential—if the sidecar is overloaded, let it return 429, and the main service should degrade gracefully.
The rollback mechanism is critical—it must exist. Use feature flags to control traffic flow. If problems occur, change a config to switch back instantly without redeployment. This “always-can-rollback” design is the confidence needed for production innovation.
Don’t slack on logging and monitoring. At minimum, log trace_id, input/output sizes, duration, and status codes for each request. This data saves lives when things go wrong.
Benefits You Can Actually Get
The sidecar approach brings quite a few benefits:
First is heap isolation—the main service’s GC no longer needs to track those large temporary objects, cleanup pressure drops significantly. Latency stabilizes too—Rust releases memory as soon as it’s done, no “wait for GC mood” situations, so p95 naturally smooths out.
With saved memory, the same machines can handle more traffic—that’s easy math. The main service is lighter after slimming down, cold starts are faster too.
There’s a particularly practical benefit: rollback is super easy. Change a config to switch traffic back to main service processing, no redeployment needed. Being able to “flee anytime” is the prerequisite for daring to try new things.
Monitoring looks better too—one service one heap, problems are obvious on graphs, no more guessing among mixed metrics.
And don’t laugh at this one—it’s easier to push this approach in companies. Telling your boss “we need to rewrite in Rust” might make him green in the face. But saying “we’re adding a small tool to help process” is completely different.
When to Use Sidecar
Is this task CPU-intensive?
|
Yes -> Is I/O size controllable?
|
Yes -> Can it be a stateless single call?
|
Yes -> Try sidecar
No -> Keep in main service
No -> Keep in main service
No -> Keep in main service
Simply put, consider a sidecar if three conditions are met: CPU-intensive, controllable data volume, stateless.
Sidecar is Not a Silver Bullet
Of course, sidecars aren’t a cure-all.
Data gets passed between services, serialization overhead is unavoidable. Local calls are fast, but it’s still a layer more than function calls. You have an extra process to operate—though simple, it’s still one more thing to monitor, deploy, and debug. Someone on the team needs to understand Rust code—you can’t just stare at the sidecar when problems occur.
Also, don’t get carried away. Sidecars should handle “physical labor,” core business logic stays in the main service. I’ve seen people getting a taste and wanting to stuff everything into sidecars, ending up with a system that’s a collection of small services—that’s just another form of “rewrite.” Might as well have rewritten properly from the start.
FAQ
Q: What’s the difference between Rust Sidecar and microservices?
A: Sidecars are deployed alongside the main service in the same pod/container, communicating locally. Microservices are separate services deployed independently, often with network communication overhead.
Q: How much latency overhead does sidecar pattern add?
A: Local HTTP calls typically add 0.5-2ms latency. Unix Socket can reduce this to under 1ms. For CPU-intensive tasks saving hundreds of milliseconds, this overhead is negligible.
Q: Which main service languages pair well with Rust sidecars?
A: Java/Python/Node services benefit most due to their GC pressure. Go services with good memory management might see less dramatic improvements.
Final Thoughts
Next time you’re staring at system monitoring, watching memory curves climb, and GC doing occasional big sweeps, don’t rush to shout “rewrite.”
First ask yourself: Is there a CPU-intensive hot path that can be extracted and handed to Rust?
No major surgery needed, no convincing the whole company to switch tech stacks. Just a small service running alongside, helping you handle the heaviest work.
Save 40% memory, cut latency by half, rollback in minutes if something goes wrong.
This deal, no matter how you calculate it, is a win.
If you’re interested in other Rust performance optimization techniques, check out these articles:
- From 800ms to 90ms: How Rayon Library Saved My Multithreading Nightmare - Parallel computing optimization
- Rust mmap Memory-Mapped IO - File reading performance optimization
- io_uring Introduction: Building High-Performance Servers with Rust - Async IO optimization
If this article helps you, please like, share, and bookmark. Follow me for more system optimization and Rust practical experience.
If you have questions, leave them in the comments section—let’s discuss together.
