Sitemap

💡 The Rust Rewrite: We Chased Throughput, But Found Predictability (and Saved Our Sanity)

9 min readOct 23, 2026

--

Ever had one of those “aha!” moments when you set out to solve a problem, only to trip over an even bigger issue you didn’t even know existed? Man, that’s exactly what happened to us with a big system rewrite we did. Our team jumped into a project to completely revamp one of our core services using Rust. Our main target? Throughput. We wanted that thing to fly! But guess what? While we were busy chasing those speed numbers, we accidentally stumbled onto fixing something way more sneaky, something our normal metrics just weren’t catching. It was a proper eye-opener, honestly. We went from obsessing over raw megabits per second to realizing that what really mattered was predictability. And Rust? Well, it delivered that in spades.

You know how it is in software, right? We’re all hyper-focused on things like requests per second or how much CPU we’re munching. But sometimes, the real game-changers are these quiet, almost invisible improvements that make a system just feel better to use. This piece is all about how our jump to Rust — which, let’s be real, was mostly about raw speed — actually ironed out some of our most annoying, unquantifiable headaches. It turned a really grumpy, temperamental service into this super chill, rock-solid workhorse. So, come on, let’s dive into this little adventure, using the good old Situation-Task-Action-Result (STAR) framework to tell the tale.

Situation: Our Data Jugglers Were, Like, Really Jittery 😵‍💫

So, picture this. We had this super important data processing service. We called it the “Notification Dispatcher.” It was built, oh geez, probably ages ago, using one of those older, garbage-collected languages. And, you know, on paper, it looked okay. Throughput numbers were fine for average traffic. Our dashboards, bless their hearts, would show pretty decent latency percentiles for the usual P50, even P90. But here’s the kicker: when traffic really cranked up, or even just under a steady, moderate load, some users would just hit these utterly bizarre, super-long delays. I’m not talking about milliseconds here; I mean requests that should’ve zipped through were suddenly taking seconds. Yeah, actual seconds. 🐢

These weren’t, like, total system meltdowns, thankfully. Just these really annoying, totally unpredictable “hiccups” that’d snag a small chunk of users. Imagine you’re juggling, right? Most of the time, the balls are just soaring. Smooth. But every now and then, one just drops. Splat! And you have no clue why. This little problem meant we were always in frantic debug mode, fielding frustrated support calls, and honestly, the dev team just felt this constant hum of anxiety. Our monitors would show the CPU doing its little dance, memory spiking, then dropping when the garbage collector finally decided to show up. But it never, ever told us why specific requests got stuck in purgatory. We were seeing the symptoms, but the actual disease? Totally invisible. This flaky behavior really ate away at user trust, making the service feel unreliable even if, technically, its “average” performance was, well, average. Ugh, right?

Task: Okay, Let’s Unleash the Throughput Beast! 🚀

Alright, so our mission kinda crystalized. We had to kill those random slowdowns and, if we could, seriously juice up the Notification Dispatcher’s overall throughput. Our main goal, you ask? To handle way more notifications, especially during those crazy peak times, without breaking a sweat. We totally believed that if we just optimized the core logic and made it faster, those annoying, random delays would just, poof, vanish. Seemed logical, anyway.

Get Hitesh Gera’s stories in your inbox

Join Medium for free to get updates from this writer.

After a bunch of whiteboard sessions and probably a few too many coffee cups, we decided to make a pretty bold move: a complete rewrite in Rust. “Why Rust?” you might be wondering. Well, we were totally hooked by its promise of raw performance and memory efficiency — kinda like C++, but with all those cool, modern safety features built right in. The whole idea was to use Rust’s tight grip on system resources, those “zero-cost abstractions,” to squeeze every last drop of performance out of our hardware, cut down on resource fighting, and, in theory, get some seriously awesome throughput. The team was super hyped about Rust’s “fearless concurrency” too. We thought it’d help us build a way more solid, parallel system without all the usual headaches of data races and deadlocks. It felt like a fresh start, you know?

Action: The Big Rewrite Odyssey 🛠️

Now, doing a whole rewrite? That’s no small Potatoes, my friend. We went about it super carefully, starting with the really CPU-heavy parts first. We painstakingly translated all the business logic, paying super close attention to how we structured our data and what algorithms we used, all aiming for peak efficiency. Here’s a little peek at some of the key things we actually did:

  1. Ownership and Borrowing to the Rescue, OMG: This was huge. We built our entire data flow around Rust’s ownership and borrowing system. Seriously, it forced us to think about how long our data lived and who could mess with it from the absolute get-go. Instead of just crossing our fingers and hoping a garbage collector would magically clean up after us, Rust’s compiler became our strict-but-super-helpful personal trainer. It caught potential memory snafus, like using memory after it was freed or pointing to nothing, right there at compile time! That felt like moving from, I don’t know, driving a stick shift where you might grind gears, to an automatic that just… works. I mean, in our old language, these things often only blew up as super cryptic crashes during runtime, which was a real pain in the neck.
// Okay, so here's a quick example of Rust's ownership preventing common mistakes.
fn process_data(data: String) {
println!("Processing: {}", data);
// See, 'data' literally MOVES here. It's like handing a physical object over.
// The original owner (the main function, in this case) doesn't have it anymore.
} // Once 'process_data' is done, 'data' is dropped, and its memory is automatically freed. Pretty neat, huh?

fn main() {
let message = String::from("Hello, Rust!");
process_data(message);
// This next line? TOTAL compile-time ERROR!
// println!("Original message: {}", message); // ❌ Error: value moved, can't use it again!
}
  1. Having that compile-time safety? It was like building our whole house on a rock-solid concrete slab instead of, I don’t know, quicksand.
  2. Concurrency with Confidence (No More Guessing Games!): Our Notification Dispatcher had to handle, like, a zillion requests all at once. Rust’s really strong concurrency primitives and its ownership model meant we could write seriously concurrent code without constant anxiety attacks. We used things like Arc<Mutex<T>> for carefully protected shared data and channels for sending messages between different parts of our program. This drastically cut down the chances of data races and just made our concurrent logic so much clearer to reason about.
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::Duration;

fn main() {
// Here, we're making a counter that can be shared across multiple threads.
// The `Arc` lets us share it, and the `Mutex` makes sure only one thread
// can change the number at a time. It's like a VIP pass for editing.
let counter = Arc::new(Mutex::new(0));

let mut handles = vec![];

for i in 0..5 {
// We clone the `Arc` for each new thread. It's not cloning the actual counter,
// just the pointer to it, so everyone knows where the real counter lives.
let counter_clone = Arc::clone(&counter);
let handle = thread::spawn(move || {
// Okay, get this: to actually change the counter, you gotta get the lock.
// If someone else has it, you wait. It's like a bathroom stall lock!
let mut num = counter_clone.lock().unwrap();
*num += i; // Now we can actually modify the number inside.
println!("Thread {} updated counter to: {}", i, *num);
thread::sleep(Duration::from_millis(50)); // Just pretending to do some work
});
handles.push(handle);
}

for handle in handles {
handle.join().unwrap(); // Wait for all our little worker threads to finish up.
}

println!("Final counter value: {}", *counter.lock().unwrap()); // Grab the lock one last time to see the grand total.
}
  1. This whole “fearless concurrency” thing was such a massive win. It let us build super fast parallel operations without constantly worrying about those sneaky bugs that only pop up when traffic is just right — or rather, wrong.
  2. Minimal Runtime Overhead (Less Faffing About): By ditching the garbage collector and getting super precise control over our memory, we just, well, drastically reduced the runtime overhead. What did that mean for us? More predictable resource usage, way less “pause amplification” (you know, when a tiny hiccup snowballs into a massive delay), and ultimately, a service that just ran smoother than a fresh jar of peanut butter.

Result: The Unexpected, Blissful Calm 🧘‍♀️

Okay, so initially, yeah, the throughput benchmarks did look better. The Rust service could totally handle more notifications, sometimes double the traffic of the old one, with lower latency and way less CPU and memory usage. That was awesome, and we definitely had a little celebration! 🥳

But then, something much, much bigger started to happen. Those frantic, late-night debugging sessions about random, unexplainable latency spikes? They just stopped. Like, completely disappeared. 🤯 The pager alerts that used to scream at us whenever the service felt a bit overwhelmed? Silence. Utter, beautiful silence. What we noticed wasn’t just raw speed; it was this profound, fundamental shift in how the system behaved.

The Rust rewrite actually fixed a few crucial things we weren’t even measuring:

  • Unpredictability? See ya!: The system, my friends, became boring. And I mean that in the best possible way! Those random five-second pauses? Gone. The wild, erratic spikes in our p99 latency? Totally smoothed out. It wasn’t that every single request suddenly became a gazillion times faster; it was that no requests suddenly became arbitrarily, infuriatingly slow. The Notification Dispatcher went from being that twitchy, unpredictable juggler to this calm, zen master machine.
  • Less Resource Churn (and Hello, Savings!): Rust’s memory management meant way, way less memory churn. In the old system, all those dynamic allocations and deallocations, plus the garbage collector doing its thing, created these tiny micro-pauses and fights for resources that were almost impossible to track down. Rust’s strict-at-compile-time rules and efficient resource handling meant super stable memory usage and way less pressure on the system at runtime. Oh, and bonus? Our cloud bills went down! Like, significantly. Which, let’s be honest, is always a win.
  • Forced Us to Be Better Engineers: This might sound weird, but the Rust compiler, with its super strict rules, kinda forced us to confront every weird edge case and potential error state right upfront. It wasn’t always a walk in the park, trust me. But it led to much more thoughtful code, clearer talks about how we’d handle concurrency, and robust error handling. It was a subtle, but real, cultural shift in how we approached building reliable software. We caught bugs while writing code, not when a customer was screaming.
  • Way Better Observability: While we were laser-focused on throughput at first, the new system’s sheer stability made it so much easier to observe. With barely any weird, unexplained stuff happening, the signals from our monitoring tools became crystal clear and actually actionable. We could finally trust what our dashboards were telling us again, which was honestly a huge relief.

So yeah, our main goal was throughput. But the most valuable thing we got was finally getting rid of that unmeasured variance. Users didn’t necessarily go, “Wow, this is 15% faster!” But they absolutely noticed when those frustrating, random delays just vanished into thin air. That’s the kind of improvement that really builds trust and confidence in a system, even if it doesn’t always show up as a bigger number on a pretty graph.

Looking back, if I had to do it all over again in October 2026, I’d probably tell myself to spend less time staring at average throughput numbers and way, way more time measuring the variance and the waiting times inside our systems. Because, as we painfully learned, systems rarely glitch out where work is actually happening; they usually break where work is just waiting. And Rust, in its own quiet, principled way, totally fixed that waiting game for us. It’s kinda funny how things turn out, isn’t it?

What about you? Have you ever had a project where you aimed for one thing and ended up fixing something totally different, or even better, something you weren’t even looking for? I’d love to hear your stories in the comments below! 👇 Let’s chat!

--

--

Hitesh Gera
Hitesh Gera

Written by Hitesh Gera

⚡ Programmer | Debugger | Lifelong Learner