V100 did not start as a Rust platform. Like most startups in the video infrastructure space, we began with Node.js. It was fast to develop in, had a mature ecosystem for WebRTC and real-time communication, and our team knew it well. The early V100 prototype was a Node.js monolith that handled signaling, media routing, transcription, and the REST API in a single process.
That worked for the first few hundred users. Then it stopped working. Not catastrophically — the platform did not crash. It degraded. Garbage collection pauses during peak media processing caused audio dropouts. Memory usage grew unpredictably under sustained load. A single misconfigured route handler could block the event loop and freeze signaling for every active meeting. We were spending more time fighting the runtime than building features.
We made the decision that most engineering teams discuss and reject: we would rewrite the entire platform in Rust. Not gradually extract a few hot paths. Not add a Rust sidecar for performance-sensitive operations. A complete rewrite of every service, every endpoint, every media processing pipeline. From zero lines of Rust to 20 production microservices. This is the story of that migration.
Why Rust: The Four Things That Forced Our Hand
1. Garbage collection pauses during media processing. Node.js uses V8's garbage collector, which periodically stops all JavaScript execution to reclaim memory. In a web application, a 10ms GC pause is invisible. In a media processing pipeline where frames arrive every 33ms (at 30fps), a 10ms GC pause causes audible audio artifacts and visible video stuttering. We tried every V8 tuning flag. We reduced allocations. We pooled buffers. The GC pauses got less frequent but never disappeared. Rust has no garbage collector. Allocation and deallocation are deterministic, handled at compile time through the ownership system. GC pauses dropped from "occasional but damaging" to "zero, forever."
2. Memory usage was unpredictable and excessive. A single Node.js process running our meeting service consumed 400-800MB of RAM depending on load, meeting count, and how recently the GC had run. The variation was as important as the absolute number — provisioning for the peak meant wasting resources at the average. A Rust implementation of the same service uses 40-80MB. The predictability is as valuable as the reduction. We know exactly how much memory a service will use because the allocations are visible in the code.
3. Concurrency was fragile. Node.js achieves concurrency through the event loop, which is single-threaded. CPU-intensive work (media transcoding, image analysis, cryptographic operations) blocks the event loop unless offloaded to worker threads. But worker threads in Node.js have high overhead for inter-thread communication (structured clone of data) and limited tooling for synchronization. Rust's async runtime (Tokio) provides genuine multithreaded concurrency with work-stealing schedulers, and the borrow checker prevents data races at compile time. We stopped writing "does this block the event loop?" comments and started writing correct concurrent code.
4. Binary size and startup time. A Node.js service packaged in Docker (with node_modules) produces a container image of 200-500MB. Startup time is 2-5 seconds as the runtime initializes, loads modules, and JIT-compiles hot paths. A Rust service compiles to a statically linked binary of 2-5MB. Startup time is under 50ms. This matters for autoscaling: when traffic spikes, we need new instances serving traffic in milliseconds, not seconds.
Node.js vs Rust: V100's measured numbers
| Metric | Node.js | Rust | Improvement |
|---|---|---|---|
| Server processing latency | ~5ms | 0.01ms | 500x |
| Requests per second | ~15K | 220K | 15x |
| Memory per service | 400-800MB | 40-80MB | 10x |
| Binary size | 100-500MB | 2-5MB | 50-100x |
| Startup time | 2-5s | <50ms | 40-100x |
| GC pauses | 5-15ms periodic | 0ms (none) | Eliminated |
| Docker RAM limit | 4GB+ | 1GB | 4x |
The 20 Services: A Complete Map
V100's production architecture consists of 20 Rust microservices, each responsible for a single domain. Every service compiles to a standalone binary, runs in its own Docker container with a 1GB memory limit, and communicates with other services via gRPC (internal) or REST (external). Here is every service and what it does.
V100 microservice map
The Migration: How We Did It Without Downtime
Rewriting a running production system is the engineering equivalent of replacing a jet engine mid-flight. The commonly repeated wisdom is "never rewrite" — and for most systems, that advice is correct. We broke the rule because video infrastructure has hard real-time requirements that garbage-collected languages fundamentally cannot guarantee, and no amount of incremental optimization would fix that.
We used a strangler fig pattern. Each new Rust service was deployed alongside its Node.js predecessor behind a reverse proxy. We ran both services simultaneously, with the Node.js version handling production traffic and the Rust version handling shadow traffic (duplicate requests with responses discarded). This let us compare behavior, validate correctness, and measure performance without risking customer impact.
Once a Rust service passed all integration tests, matched the Node.js output for 100% of shadow traffic, and demonstrated stable memory and CPU profiles over a 72-hour burn-in period, we switched production traffic to the Rust service. The Node.js service remained running as a fallback for another week. If the Rust service showed any anomaly, a single configuration change could route traffic back to Node.js.
The order of migration was deliberate. We started with stateless, low-risk services (gateway, billing) and ended with stateful, high-risk services (signaling, v100-turn, meeting). The gateway was first because it processes the highest volume of requests but has the simplest logic — the ideal service to validate our Rust toolchain, deployment pipeline, and monitoring. The media services were last because they have the strictest correctness requirements and the most complex state management.
The entire migration took approximately six months. Zero customer-facing downtime. Zero data loss. Every service was migrated independently, tested independently, and rolled back independently. At no point was the platform in a state where a single failure could have forced us to abandon the migration.
The Rust Stack: Libraries and Frameworks
Every V100 Rust service uses the same core stack. Standardizing the framework, async runtime, and serialization libraries across all 20 services means that any engineer can contribute to any service without learning a new toolkit.
Core technology stack
938 Tests, Zero Failures
V100's test suite consists of 938 tests across all 20 services, with zero failures. The suite includes unit tests for individual functions, integration tests for service interactions, end-to-end tests for complete user flows, property-based tests for codec and protocol compliance, and 17 dedicated post-quantum cryptography tests verified against NIST test vectors.
Rust's type system catches an entire class of bugs at compile time that would be runtime errors in Node.js: null/undefined access, type mismatches, missing error handling, data races, and use-after-free. Our experience is that roughly 40% of the bugs we used to find in Node.js testing simply cannot exist in Rust code. The remaining 60% — logic errors, incorrect algorithms, API contract violations — are caught by the test suite.
Every pull request runs the full 938-test suite. The suite completes in under 90 seconds because Rust's test runner executes tests in parallel and the compiled test binaries start in milliseconds. In Node.js, our equivalent test suite took 8-12 minutes because of Jest's startup overhead and the sequential execution of integration tests.
Docker Resource Limits: 1GB RAM Per Service
Every V100 Rust service runs in a Docker container with a hard memory limit of 1GB. This is not a soft recommendation — it is an enforced cgroup limit. If a service exceeds 1GB, the container is killed and restarted. In over six months of production operation, no service has ever hit this limit.
For comparison, our Node.js services required 4GB+ memory limits to handle production traffic without OOM kills. The V8 heap alone (before any application data) consumes 200-400MB. Node.js services under sustained load would grow memory continuously until the GC reclaimed it in large batches, causing the sawtooth memory pattern that is characteristic of garbage-collected runtimes.
The practical impact is infrastructure cost. Running 20 services at 1GB each requires 20GB of total RAM. Running 20 services at 4GB each requires 80GB. That is a 4x reduction in compute costs for the same functionality — before accounting for the performance improvement that means each Rust service handles 10-15x more traffic than its Node.js predecessor, further reducing the instance count.
What We Learned
The Rust learning curve is real but finite. Engineers coming from Node.js or Python require 4-6 weeks to become productive in Rust. The borrow checker is the main obstacle. Once an engineer internalizes ownership and borrowing, their productivity approaches what they had in their previous language. After 3 months, most engineers report being more productive in Rust than in Node.js because the compiler catches so many bugs that would have been runtime debugging sessions.
Compilation time is the cost you pay. A clean build of all 20 services takes approximately 15 minutes. Incremental builds are 10-30 seconds. This is slower than Node.js (no compilation) but is offset by the time saved not debugging runtime type errors, memory leaks, and GC-related performance issues. We use cargo-nextest for parallel test execution and sccache for shared compilation caching to minimize the impact.
The ecosystem is mature enough. When we started the migration in 2025, the Rust ecosystem for web services was already mature. Axum, Tokio, sqlx, tonic, and serde are production-grade libraries with active maintenance and large user bases. We did not have to build any foundational infrastructure from scratch. The only area where we wrote significant custom code was the media server (v100-turn), where we needed WebRTC capabilities that did not exist in any Rust library at the level of optimization we required.
Build on a platform engineered for performance
V100's 20 Rust microservices deliver 0.01ms server processing, 220K RPS, and zero GC pauses. Every API call you make is handled by compiled Rust code, not interpreted JavaScript. Start a free trial and build video applications on the fastest infrastructure available.