learning
A Rust service for the one CPU-bound job
A Rust service for the one CPU-bound job
Image processing — resizing, generating thumbnail variants — is CPU-intensive, genuinely different in character from most of a backend's workload, which is mostly I/O-bound, waiting on a database or an external API. Running it inside the main Node process risks blocking the single-threaded event loop for everything else in that moment — a burst of thumbnail generation degrading API response times for completely unrelated requests, all competing for the same event loop.
Splitting it into its own service solves the event-loop contention regardless of language. Rust specifically buys real performance for CPU-bound work — no garbage collector pauses, genuine memory safety at compile time without the runtime overhead an interpreted or GC'd language pays for the same guarantees. For a purely computational workload with no need for Node's ecosystem libraries, Rust's raw throughput here is a measurable win.
This extends the same idea as the storage abstraction — decoupling where files live from the rest of the app — to how files get processed. The backend's job becomes orchestration: receive the upload, store the original, hand off a processing request, not do the CPU-heavy work inline.
It doesn't come free
A separate service is real operational cost — another thing to deploy and monitor, another network hop with its own failure modes, genuinely two toolchains for one person. Reserve the split for workloads where the performance or isolation benefit is real and measurable, not as a default instinct because it looks cleaner on paper.
This was one of the later additions in a reactive infrastructure buildout — Docker, then Redis, then pgBouncer, then Caddy, then Cloudflare, then this — added once thumbnail processing at real scale became a concrete, measured need, not designed in from day one. Which worked, but came with real avoidable churn along the way. (It also later became a cautionary tale of its own — the service ran healthy for months while nothing consumed its output, because every URL resolver in the product signed the full-resolution original instead.)