Jump to content

Rust in Production: Lessons from Prime Video’s Global Engineering

From JOHNWICK

When you press a button on your TV remote to browse Prime Video, you expect an instant response. Behind that simple interaction lies a fascinating engineering challenge: delivering a smooth experience across more than 8,000 different device types, from high-end smart TVs to budget streaming sticks. Prime Video’s engineering team found their solution in an unexpected place Rust and WebAssembly.

The Performance Problem

Prime Video serves millions of customers globally on devices with vastly different hardware capabilities. Their original architecture split responsibilities between a high-performance C++ engine stored on-device and JavaScript code downloaded at runtime. While this approach enabled rapid updates without slow native releases, it created a fundamental tension: JavaScript offered updatability but struggled with performance on lower-end devices.

Frame times on mid-range TVs averaged 28 milliseconds, with worst-case scenarios hitting 40 milliseconds. For a smooth 60 frames per second experience, you need consistent 16.67-millisecond frame times. They were falling short.

Why Rust and WebAssembly

The team evaluated WebAssembly as a compilation target in August 2020. Their prototypes revealed impressive results , code written in Rust and compiled to WebAssembly ran 10 to 25 times faster than equivalent JavaScript for the low-level systems Prime Video relied on.

Here’s a simplified example of how Rust’s performance characteristics shine in real-time rendering:

use std::time::Instant;

struct FrameMetrics {

   frame_count: u64,
   total_time: u128,

}

impl FrameMetrics {

   fn new() -> Self {
       FrameMetrics {
           frame_count: 0,
           total_time: 0,
       }
   }
   
   fn record_frame(&mut self, duration: u128) {
       self.frame_count += 1;
       self.total_time += duration;
   }
   
   fn average_frame_time(&self) -> f64 {
       if self.frame_count == 0 {
           return 0.0;
       }
       (self.total_time as f64) / (self.frame_count as f64)
   }

}

fn main() {

   let mut metrics = FrameMetrics::new();
   
   for _ in 0..1000 {
       let start = Instant::now();
       // Simulate frame rendering work
       perform_frame_operations();
       let duration = start.elapsed().as_micros();
       metrics.record_frame(duration);
   }
   
   println!("Average frame time: {:.2}µs", metrics.average_frame_time());

}

fn perform_frame_operations() {

   // Zero-cost abstractions and memory safety
   let data: Vec<u32> = (0..1000).collect();
   let _sum: u32 = data.iter().sum();

}

The Architecture Evolution

Rather than rewriting their entire application, Prime Video’s team made a strategic decision: migrate only the low-level systems to WebAssembly while keeping application logic in JavaScript. This hybrid approach maintained backward compatibility with legacy devices lacking WebAssembly support. The new architecture looks like this:

┌─────────────────────────────────────────────────────────┐
│                    Device Layer (C++)                   │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │   Input      │  │Media Pipeline│  │Image Decoding│   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
└───────────────────────┬─────────────────────────────────┘
                        │
        ┌───────────────┴───────────────┐
        │                               │
┌───────▼──────────┐         ┌─────────▼─────────┐
│  JavaScript VM   │         │   WebAssembly VM  │
│                  │         │    (Rust code)    │
│  ┌────────────┐  │◄────────►  ┌────────────┐   │
│  │Application │  │  Messages  │ Low-level  │   │
│  │   Logic    │  │            │  Systems   │   │
│  └────────────┘  │            └────────────┘   │
│                  │            │  Scene Mgmt│   │
│                  │            │  Animation │   │
└──────────────────┘            │  Rendering │   │
                                └────────────┘   │
                                └────────────────┘

The JavaScript VM and WebAssembly VM run in separate threads, communicating through a message-passing system. This ensures the rendering thread never gets blocked by application logic. Message Passing Implementation The communication layer between VMs had to be lightweight yet robust:

use serde::{Deserialize, Serialize};

  1. [derive(Serialize, Deserialize)]

enum Command {

   CreateNode { id: u32, node_type: String },
   UpdateNode { id: u32, properties: Vec<(String, String)> },
   DeleteNode { id: u32 },

}

  1. [derive(Serialize, Deserialize)]

struct NodeUpdate {

   timestamp: u64,
   commands: Vec<Command>,

}

// Lightweight host nodes in JavaScript send commands // Real host nodes in Wasm execute them fn handle_command_batch(updates: NodeUpdate) {

   for cmd in updates.commands {
       match cmd {
           Command::CreateNode { id, node_type } => {
               // Create node in Wasm scene graph
           }
           Command::UpdateNode { id, properties } => {
               // Update existing node properties
           }
           Command::DeleteNode { id } => {
               // Remove node from scene
           }
       }
   }

}

The Results That Matter After deploying 37,000 lines of Rust code, the improvements were measurable and significant: Performance Gains:

  • Average frame time reduced from 28ms to 18ms (36% improvement)
  • Worst-case frame time dropped from 40ms to 25ms (37.5% improvement)
  • 10x better input latency on living room devices

Resource Efficiency:

  • WebAssembly VM consumes only 7.5MB total memory
  • Saved 30MB of JavaScript heap memory
  • Binary size: just 150KB compressed (750KB uncompressed)

Development Velocity:

  • Zero startup time impact despite adding WebAssembly
  • Rust’s compiler catches memory safety issues automatically
  • Access to high-quality Rust ecosystem libraries

Real-World Tradeoffs

The migration wasn’t without challenges. The team had to replicate certain JavaScript quirks that the application relied on even the incorrect behaviors that had become de facto features. Thread safety required careful attention to ensure JavaScript never called functions on the wrong thread.

One interesting debugging tool emerged from Rust’s ecosystem. Using the egui library, they built an overlay debugger that provides real-time insights into the engine’s internals, something that took only hours to integrate.

Memory Safety Without Overhead

Rust’s ownership model provides memory safety without garbage collection overhead:

struct SceneNode {

   id: u32,
   children: Vec<Box<SceneNode>>,
   properties: HashMap<String, String>,

}

impl SceneNode {

   fn update(&mut self, key: String, value: String) {
       // Compiler ensures no data races
       self.properties.insert(key, value);
   }
   
   fn traverse<F>(&self, mut callback: F)
   where
       F: FnMut(&SceneNode),
   {
       callback(self);
       for child in &self.children {
           child.traverse(&mut callback);
       }
   }

}

The borrow checker ensures thread safety at compile time, eliminating entire classes of bugs that would require extensive runtime testing in other languages.

Looking Forward

Prime Video continues migrating systems to WebAssembly, targeting focus management and layout systems next. The goal remains consistent: reliable 60fps frame generation across all devices.

The lesson here extends beyond Prime Video. When performance matters and you need to ship code to diverse hardware, Rust and WebAssembly offer a compelling path forward. The combination provides the performance of native code with the portability of web technologies.

For teams considering similar migrations, start small. Prime Video didn’t rewrite everything , they identified performance-critical components and migrated those first. The hybrid approach let them prove value while maintaining stability. The future of cross-platform development increasingly points toward WebAssembly as the compilation target of choice, and Rust as the language that unlocks its full potential.


References:

  • How Prime Video updates its app for more than 8,000 device types
  • Rebuilding Prime Video UI with Rust and WebAssembly
  • QCon San Francisco 2024 Rebuilding Prime Video
  • Prime Video with Alexandru Ene Rust in Production

All code examples are simplified for educational purposes .

Read the full article here: https://medium.com/@neerupujari5/rust-in-production-lessons-from-prime-videos-global-engineering-6873111eb0a7