It's 2:47 AM. Your phone buzzes. Then again. Then a third time, and you're already awake because you know exactly what's happening: the video processing queue has collapsed. A product launch, a viral post, a partner integration that just went live—something sent a wave of uploads through your pipeline, and your video API is now returning 502s to every single one of them.
You're not alone in this. It's one of the most predictable, most avoidable, and most painfully repetitive failure modes in modern software. Video traffic doesn't scale like typical web traffic because video jobs are heavier, they last longer, and they fail in ways that cascade. A single bad encode can eat 400% of a CPU core for four minutes and drag every other request on the machine down with it.
This article is about why that happens, what most teams get wrong when they build video pipelines, and how to architect around traffic spikes instead of just hoping you'll have enough headroom when they arrive. Along the way I'll show where a serverless FFmpeg API like FFmpeGo fits into the picture, because the honest answer to "how do I stop my video API from failing under load" is usually stop running the load on your own infrastructure.
But first, let's talk about what a spike actually looks like from the inside.
The Anatomy of a Traffic Spike (What Actually Happens)
Traffic spikes aren't mysterious. They're physical events with a shape. And the shape matters, because different parts of your stack fail at different points along the curve.
The Thundering Herd Problem
Imagine a scheduled newsletter goes out at 9:00 AM. Half your users click through within 90 seconds. If even 2% of them are uploading a video, you've just taken a trickle of 5 uploads per minute and turned it into 400 uploads per minute, all arriving inside an 8-minute window.
That's the thundering herd. It's not uniform. It's a spike, a sharp vertical wall of demand, and then a rapid drop-off. Your autoscaling system, no matter how tuned, will always lag behind a spike like this. It takes 60–180 seconds to spin up new containers, pull images, warm up the runtime, and start accepting work. By the time those instances are healthy, the spike is half over. And then—cruelly—you're paying for overprovisioned capacity while your queue drains.
Video Workloads Are Uniquely Brutal
Compare a video job to a typical web request:
- A JSON API request might take 40ms and 15MB of RAM.
- A single H.264 transcode of a 3-minute 1080p clip can take 45 seconds and pin a core to 100%.
- A
filter_complexjob with multiple overlays, scaling steps, and audio mixing can take minutes and consume several hundred megabytes of memory on top of the CPU cost.
So when a spike hits, you're not absorbing "more small requests." You're absorbing "more of the heaviest class of work your system can do." That's a fundamentally different scaling problem, and treating it like regular API traffic is the first mistake many teams make.
What "Success" Looks Like at 3 AM
If everything is going well during a spike, the following things are true:
- Every submitted job completes eventually, even if it takes longer than usual.
- No customer sees a 5xx response they weren't expecting.
- Your infrastructure bill for that hour doesn't require a phone call to your CFO.
- The failures you do see are isolated, retryable, and won't take down the next job in line.
That's a low bar, honestly. But most home-grown video pipelines fail at least two of those conditions whenever traffic doubles. The rest of this article is about why.
Why Traditional Video Infrastructure Breaks Under Load
If you've ever run FFmpeg on your own servers, you already know the pain. Let's get specific about where the failure modes live.
CPU and Memory Contention
FFmpeg is a brilliant piece of software that will happily use every core you give it. That's great when you have a machine dedicated to encoding. It's terrible when that machine is also serving web requests, handling database queries, or running Redis.
When you co-locate FFmpeg with anything else, you get contention. Your web server's p99 latency spikes even though the request path has nothing to do with video. Memory pressure builds, the OOM killer shows up, and now you're restarting containers instead of processing media. I've seen teams spend weeks debugging a "mystery" latency regression that turned out to be a single unconstrained FFmpeg process on a shared worker.
The Queue Death Spiral
Your queue is supposed to be a shock absorber. Instead, it becomes a magnifying glass.
Here's the death spiral, step by step:
- Traffic spikes. Jobs pile into the queue.
- Workers start processing jobs. Each job takes longer than usual because CPU is oversubscribed.
- The queue grows faster than workers can drain it.
- Users get impatient and re-submit. Now your queue has duplicates.
- Queue depth becomes so high that stale jobs get processed long after the user has moved on. You're doing work nobody wants anymore.
- If any job crashes the worker, it restarts and tries again—sometimes re-running the whole thing because no idempotency key was set.
- Eventually the queue itself becomes a bottleneck. Redis falls over. Everything dies.
Sound familiar? It's the same story in every team that has built video processing infrastructure from scratch. Queues don't protect you when the workload is heavy enough and downstream capacity is fixed.
Horizontal Scaling Doesn't Save You (By Itself)
"Just add more machines" sounds good. It fails for three reasons:
Cold start cost. A container that's ready to accept FFmpeg jobs takes time to boot. That time is dead weight during a spike.
Fixed capacity ceilings. You probably have a max worker count. That max is there for a reason—cost. When you hit it, you can't scale further without manual intervention. And if you can scale infinitely, you're now exposed to a runaway bill when someone decides to upload a 4-hour 4K video.
Coordination overhead. Redis-based queues, distributed locks, retry coordination, exactly-once semantics—every one of these becomes a liability at scale. Concurrency bugs that never appeared at 5 jobs/minute suddenly show up at 500 jobs/minute.
Cold Starts and Container Churn
Sharp spikes cause churn. Instances that were idle an hour ago are now fully loaded. Instances that were fully loaded are now idle because the spike passed. Autoscaling heuristics fight themselves. And every fresh container starts cold: no cached binaries, no JIT-warmed runtime, no preloaded fonts, no warmed codec tables.
If your job is already going to take 30 seconds of CPU, paying another 45 seconds for a cold container to become useful is a 150% tax on every request that happens to land on a new instance. During a spike, that's most of them.
The Real Cost of a Failed Video Job
It's tempting to treat a failed encode as a small thing. It's not. Let's break it down.
User-Facing Damage
Users don't see your queue. They see a spinner, then a "Something went wrong" message. If you're an app where videos are the core value—a social platform, a marketplace, a course product—that failure is the product. Trust erodes fast when uploads break.
And the failure tends to cluster. When your pipeline is stressed, the users affected are the newest arrivals, which means the people with the least patience and the least context for why the product is "slow today."
Downstream Consequences
Failed jobs don't just vanish. They set off a cascade:
- Webhook listeners retry on failure and flood the same broken endpoint.
- Support tickets pile up, each one requiring a human to triage whether the encode actually succeeded somewhere despite the error.
- Analytics and moderation pipelines that depend on the encoded output break silently.
- Your on-call engineer burns the evening manually requeueing work.
The original 30-second failure turns into 30 hours of cleanup.
Infrastructure Bill Shock
Then there's the bill. If you're on a cloud provider with autoscaling, a spike means you pay for the peak—including all the wasteful cold-start time, the over-provisioned ceiling that stays up for 20 minutes after the spike, and the retries that re-run jobs that were going to fail anyway. A single bad night can add hundreds or thousands of dollars to your monthly invoice.
Common Architectural Mistakes That Cause Spikes to Break Things
Before we talk about solutions, let's be direct about the mistakes that cause most of these failures. I've seen every one of these in production.
Treating Video Like a CRUD Request
Video processing is not CRUD. It's not even in the same family. If you architect your video endpoint the same way you'd architect a database write endpoint, you will fail. Video jobs need:
- Explicit queueing with backpressure
- Bounded concurrency per instance
- Job-level timeouts
- Retry logic that's idempotent
A CRUD endpoint that exceeds capacity just gets slower. A video endpoint that exceeds capacity gets unstable.
Unbounded Concurrency
This is the number-one killer. If your worker pool doesn't cap how many FFmpeg processes run simultaneously per machine, a spike will happily launch 40 at once on a 4-core box. Every process starves. Every job takes five times as long. Throughput collapses.
The rule of thumb: at most number_of_cores - 1 concurrent encoders per machine, sometimes fewer if jobs use multiple threads. Most teams forget this until they're debugging why nothing finishes.
No Backpressure
A queue without backpressure is a queue without limits. If your API accepts every job immediately regardless of queue depth, you've built a system that can only be overwhelmed, never defended. Good systems reject (or defer) work when they're saturated. Bad systems keep accepting work and die.
Synchronous Processing on the Request Path
If your API endpoint waits for FFmpeg to finish before responding, you've combined the worst traits of every system: high latency, high resource cost, and no ability to retry without confusing the client. Video jobs should be submitted and tracked, not blocked on.
Ignoring Filter Complexity
Most teams test with simple transcodes and assume filter_complex jobs are "the same but a bit slower." They aren't. A multi-input job with several overlays, audio replacing, scaling, and watermarking can run 10–20x as long as a basic conversion. If your capacity planning assumes uniform job cost, a spike in complex jobs will break you regardless of how low the overall request rate is.
Missing Idempotency
If the same logical job can be submitted twice and run twice, retries become expensive and duplicative. Idempotency keys are cheap to add and save you enormous amounts of wasted compute.
How Serverless FFmpeg Changes the Math
Okay, so the traditional model is rough. What's the alternative?
The idea behind a serverless FFmpeg API is simple: you don't run the encoder. You send a job description to a service, it runs the job somewhere you don't have to think about, and it returns the result. The infrastructure—the machines, the queues, the autoscaling, the codec setup—belongs to someone else.
That's what FFmpeGo is. You hit a single endpoint (/v2/run) with a JSON payload. The payload contains input URLs and the exact FFmpeg arguments you want to execute. The platform runs the command and returns the output. No servers to provision, no autoscaling groups to tune, no queue to babysit.
Let me explain why this matters specifically for spikes.
The Request-Response Model
Here's the thing that makes serverless FFmpeg different from your home-built queue: the capacity you're using isn't capacity you own. When a spike hits, you're not competing with yourself for CPU. You're competing with a pool managed by the platform, and the platform handles the boxing.
Your application code becomes:
- Accept the user's upload or video URL.
- Construct the FFmpeg arguments.
- POST to
/v2/run. - Handle the result.
There's no worker pool to size. No queue depth to monitor. No "should I spin up more instances" decision at 2 AM. The scaling problem moves from your on-call rotation to the platform's infrastructure team.
Pay for Compute Seconds, Not Idle Capacity
Traditional hosting has a nasty property: you pay for peak capacity all month, even during quiet hours. Serverless FFmpeg flips this. FFmpeGo bills by "compute seconds"—the actual wall-clock time your FFmpeg process ran. If you had no jobs yesterday, you paid nothing yesterday.
For teams with spiky workloads—and honestly, that's most teams—this is where the math gets good. You stop paying for the idle headroom you were holding just in case.
Only Pay for Successful Encodes
This is a detail that surprises people: FFmpeGo only bills for jobs that return 2xx responses. If a job fails because of a bad input URL, a codec error, or an invalid filter, you don't pay. That means you can test freely, retry aggressively, and iterate on your filter graph without watching a meter.
For spike scenarios, this matters. During a traffic surge, some jobs are going to fail. Bad inputs, malformed payloads, files your users uploaded that turn out to be corrupt. With most infrastructure, you pay for those attempts anyway. With FFmpeGo, you don't.
Arbitrary Commands, Not Presets
A lot of "video APIs" out there are really just a few preset operations wrapped in a REST interface. "Convert to MP4." "Resize." "Extract a thumbnail." That's fine for the simplest use cases, but the moment you need to overlay a watermark, mix two audio tracks, apply a time-based filter, or chain five operations together, you're stuck.
FFmpeGo's selling point is that it runs arbitrary FFmpeg commands. If you can run it locally in a terminal, you can run it through the API. That's the difference between a preset service and a real programmable cloud FFmpeg.
A Practical Walkthrough: Sending a Spike to FFmpeGo
Let's make this concrete. Suppose you run a small marketplace where sellers upload product videos. During a flash sale, uploads triple. You want to normalize every upload: scale to 1080p, add a subtle watermark, strip audio, and set a compression target.
The JSON Payload
Here's roughly what a job looks like:
{
"inputs": [
"https://your-bucket.s3.amazonaws.com/uploads/abc123.mp4"
],
"args": [
"-i", "input0",
"-i", "https://your-cdn.example.com/watermark.png",
"-filter_complex", "[0:v]scale=1920:1080[v0];[1:v]scale=200:-1[w];[v0][w]overlay=W-w-20:H-h-20[vout]",
"-map", "[vout]",
"-an",
"-c:v", "libx264",
"-preset", "medium",
"-crf", "23",
"-movflags", "+faststart",
"output.mp4"
]
}
A few things worth noting:
- You reference inputs as
input0,input1, and so on inside the args array. - You can supply remote URLs for both the main video and the overlay image. No need to upload the watermark yourself.
- The
filter_complexchain does the scaling and overlay in one pass. That's efficient—one decode, one encode.
Understanding the Response
The API returns a job object with a status and, when finished, an output URL. If your pipeline is async (which it should be), you'll typically poll a status endpoint or wait on a webhook. Either way, the shape is predictable, and success is indicated by a 2xx status.
Handling Retries
If you get a network error talking to FFmpeGo, retry. The job likely didn't run because the failure was on the wire. Include an idempotency key on your side (a hash of the inputs and args is fine) so you don't accrue duplicate work.
If you get a 4xx response, don't blindly retry—that usually means your args were malformed or an input URL was inaccessible. Log the response, surface it to the user, and move on.
If the job itself fails during execution (bad input, decoder error), the response will tell you. That's a code issue on your side, not a transient network problem.
A Practical Advice Corner
If you're migrating from a self-hosted pipeline to a serverless FFmpeg API, don't try to port everything at once. Start with your simplest job—probably "convert this uploaded file to MP4"—and get it running end-to-end. Then port your more complex jobs. You'll find the API surface is tiny: one endpoint, one payload shape. The complexity was always in the FFmpeg arguments, not in the transport.
Comparing Approaches: Self-Hosted vs Preset APIs vs FFmpeGo
Because "just use a serverless API" isn't always the right answer, here's an honest comparison of the three main options teams consider.
| Approach | Setup cost | Scale ceiling | Flexibility | Cost profile | Failure mode under spike |
|---|---|---|---|---|---|
| Self-hosted FFmpeg on VMs | High (days to weeks) | Medium (limited by provisioning) | Total | Fixed monthly, pay for idle | Queue collapse, autoscaling lag, OOM kill |
| Preset video APIs | Low | High | Very low (only preset options) | Per-request | Usually holds up; feature limits bite instead |
| Serverless FFmpeg (FFmpeGo) | Low | High | Total (arbitrary args) | Compute-second based | Absorbed by platform capacity |
| Self-hosted Kubernetes | Very high | High but fragile | Total | Fixed + ops time | Config drift, resource contention, node crashes |
The interesting column here is the last one. Under a spike, preset APIs and serverless FFmpeg APIs both absorb the load because the platform handles capacity. Only one of those lets you write the filter graph you actually want.
That's the real reason serverless FFmpeg is interesting: it gives you the flexibility of self-hosting without the operational cost, and it gives you the reliability of a managed service without the preset-API ceiling.
Designing Your App to Survive Spikes (Besides the Backend)
Backend capacity is half the story. The other half is how your application behaves when the backend is slow. Here's what to get right on your side.
Client-Side Throttling
Your users can absolutely overwhelm your API if you let them. Put a per-user or per-IP rate limit on video submissions. Even something simple—five concurrent jobs per user—prevents one account from monopolizing the whole system.
If your app is a web front-end, disable the upload button while a job is in flight. Sounds trivial. Prevents a surprising number of duplicate submissions.
Webhooks and Polling
Don't make the client wait on a video encode. Submit, get a job ID, and let the client poll or receive a webhook. This decouples user latency from encode latency and lets you absorb the reality that video jobs take real time.
For webhooks, make your endpoint idempotent. You will receive duplicate deliveries eventually, and if your handler isn't prepared, you'll process the same job twice.
Idempotency Keys
Every job submission should carry a key that identifies "this logical job." If you're ever uncertain whether a submission succeeded, you can resubmit with the same key and expect the same result. On your side, this is a hash of the input URL plus the intended args. It's cheap. It saves hours.
Graceful Degradation
When the queue is deep, tell the user. "Your video is processing, it may take a few minutes" is infinitely better than a spinner that eventually times out. Honesty beats silence.
Metric and Alert Hygiene
Measure the right things. P50 latency is almost useless for video. Track:
- Job acceptance rate vs rejection rate
- Time to first byte for job submission
- Time to job completion (p50 and p95)
- Job failure rate by error class
- Cost per successful job
Alert on the failure rate and cost per job, not just on raw throughput. Throughput is fine to watch, but failure rate is where the fire shows first.
Edge Cases and What to Watch For
Even with a solid design, edge cases will get you. These are the ones worth thinking about ahead of time.
Huge Input Files
A user uploads a 4-hour conference recording. That's not a spike in the usual sense—it's a single heavy job that occupies a slot for a long time. Combined with a spike, it can pin capacity longer than a burst of small jobs would.
Mitigate by setting your own job budget. Most APIs, including FFmpeGo, have ways to control execution limits. But the cheapest fix is to validate input length client-side: if you don't need to process a 4-hour file, don't submit one.
filter_complex Graphs That Explode
A filter graph that looks innocent on paper can run 40x as long as a basic transcode. Two common culprits:
- Multiple scaling steps in a single chain.
fpsfilters applied after overlays (they force re-timing work).
If your job class includes a complex graph, categorize it separately in your metrics. The p95 for "simple transcode" and "complex filter_complex" should be tracked independently, because they scale differently.
Multi-Input Jobs
Multi-input jobs are more sensitive to input reliability. If one of three inputs fails to fetch, the entire job fails. That's real money and real user impact. Where possible, cache inputs you reuse—watermarks, bumpers, brand overlays—so you're not paying to fetch them for every job.
Long-Running Jobs vs Short Spikes
A spike of short jobs is a very different problem from a spike of long jobs. Short jobs drain quickly, so autoscaling has time to react. Long jobs occupy slots, so during a spike your effective throughput is much lower than your capacity numbers suggest.
If you're processing long jobs, either submit them to a priority lane or split them into chunks you can parallelize. FFmpeg supports segment-based processing, and cutting a 30-minute clip into six 5-minute segments can be dramatically faster in aggregate.
Common Mistakes Developers Make (And How to Avoid Them)
A quick list, drawn from watching teams fight these fires over the years:
- Running FFmpeg on the same node as your web server. Don't. Just don't. Put a wall between media processing and request handling.
- Setting concurrency to "unlimited." See above. You will regret it.
- Estimating capacity from a single benchmark. A single FFmpeg run measures one file on one machine. Real-world capacity is 30–50% lower because of contention, cold starts, and mixed job cost.
- Using a synchronous endpoint when async will do. If the user can wait for a webhook, don't block on an HTTP response.
- Not logging the actual FFmpeg command. When something fails, you need the exact args. Log them.
- Assuming a job won't fail. It will. Have a retry policy with a cap, and surface failures clearly.
- Forgetting to cap monthly spend. If you're on any metered service without a hard cap, you're one misconfigured retry away from a headache. Set the cap first.
- Mixing production and test traffic on the same quota. Test in a separate environment, always.
- Not testing spiky scenarios. You can simulate a spike. Do it. Load-test your own API with realistic job mixes before your users do it for you.
- Forgetting to shut off the spigot. When a spike passes, make sure your app stops re-submitting failed jobs. A stuck retry loop is the most expensive kind of bug.
How FFmpeGo Handles the Spike Use Case Specifically
Now let's bring this back to how a serverless FFmpeg API actually fits the spike scenario.
When you submit a job and your pipeline hits its own traffic limit, you're still bounded by the API's ability to accept submissions—but that's a much higher ceiling than "one queue in front of N workers that you can't scale fast enough." Under normal usage, the platform absorbs the concurrency; you don't need to plan for peak provisioning weeks in advance.
There are two things that matter most here, in my experience.
One: the pricing model aligns with the spike. Compute-second billing means you don't pay for peak headroom during quiet periods. If your traffic is seasonal—say, holiday e-commerce with product video uploads—this is a very real difference on the invoice.
Two: you only pay for successful encodes. During a spike, some jobs will fail. Input errors, timeouts, codec problems. The platform doesn't charge for the ones that didn't produce output. That's a rare property, and it makes the spike scenario economically recoverable instead of financially punitive.
And because FFmpeGo runs arbitrary FFmpeg arguments, you don't lose the flexibility you'd have with a self-hosted setup. You can still write the filter_complex graphs, use whatever codecs you want, and chain as many filters as you need. You're not trading power for reliability—those are both on the table.
For teams building their first video pipeline, there's a generous free tier and hard monthly caps to prevent surprise overages. For teams migrating off self-hosted infrastructure, the single /v2/run endpoint means the porting surface is small: you swap your subprocess call for an HTTPS request, and you delete the queue, the worker pool, and the autoscaling group.
A Quick Sanity Checklist Before You Ship
Before you push your next release to production, run through this list.
- Are video jobs submitted asynchronously, not on the request path?
- Is there backpressure in front of your job submissions?
- Is FFmpeg running on infrastructure that isn't shared with user-facing web servers?
- Do you have a hard monthly cap on media processing cost?
- Are you tracking p95 (not just p50) job duration, split by job class?
- Do you have idempotency keys on job submissions?
- Are you logging the full FFmpeg args for every failed job?
- Do you have a runbook for "queue depth exceeded" that isn't "spin up more workers"?
- Have you load-tested with a realistic mix of simple and complex jobs?
- Are you only paying for successful encodes?
If you can't check every box, the place to start is usually the last one—because a surprising amount of the pain in a spike scenario is financial, and it goes away entirely when your provider only bills on success.
Frequently Asked Questions
What actually causes a video API to collapse under a spike?
Resource contention. FFmpeg jobs are CPU-heavy and long-running, so a burst of them oversubscribes the machine, drives up latency for everyone, and eventually causes timeouts and restarts. Autoscaling lags behind spikes by a minute or more, so the extra capacity arrives after the peak has passed. The net effect is a queue that grows faster than it drains, followed by cascading failures.
Can't I just autoscale harder?
You can, up to a point. Autoscaling helps with sustained load, but it's poor at short, sharp spikes because instance boot time is longer than the spike itself in many cases. It also doesn't solve the cost problem—you're still paying for peak capacity, including the over-provisioned tail after a spike. And there's usually a hard max worker count, so "just autoscale" has a ceiling.
Is a serverless FFmpeg API actually cheaper than self-hosting?
It depends on your utilization. If you're running FFmpeg close to 24/7 on dedicated hardware, self-hosting can be cheaper per hour. If your traffic is bursty—which most teams' traffic is—compute-second billing usually wins because you're not paying for idle capacity. The break-even point tends to be somewhere around 30–40% average utilization.
What happens if my FFmpeg job fails on FFmpeGo?
The failed job doesn't bill. You only pay for successful encodes (2xx responses). That means you can iterate heavily on your args, test new filter graphs, and retry problematic inputs without watching the meter rise for work that didn't produce anything.
Does FFmpeGo limit me to certain FFmpeg options?
No—it runs arbitrary FFmpeg commands with whatever arguments you supply. That includes filter_complex graphs, multi-input jobs, and any codec or filter that FFmpeg supports. This is the main thing that separates it from the preset-style video APIs that only offer a fixed set of operations like "convert to MP4."
How do I handle spikes in my own application, not just the backend?
Client-side throttling (per-user concurrency limits), async job submission, idempotency keys, and honest user messaging. When things are slow, "processing, expect a few minutes" beats a silent spinner. And avoid retry storms: cap your retries and use exponential backoff.
What's the biggest mistake teams make when building video pipelines?
Treating video jobs like ordinary API requests. Video isn't CRUD. It needs queueing, backpressure, capped concurrency, and a cost model that reflects that most jobs run for 30+ seconds. When people skip these, they get the 2 AM wake-up call.
Final Thoughts: Stop Building Capacity, Start Buying It
The core problem with video processing is that it doesn't scale the way most software scales. A spike in video work isn't 10% more requests—it's a burst of the heaviest possible class of compute, and it will find every architectural weakness in your pipeline.
You have essentially two honest choices. Either you build and maintain sophisticated infrastructure to absorb the load yourself—queues, workers, autoscaling, careful capacity planning, buffer machine pools—or you hand the problem off to a platform whose entire job is absorbing that load for you.
For a lot of teams, the second option is the right one. FFmpeGo exists to make that trade. You get a single API call, arbitrary FFmpeg arguments, and compute-second pricing that only bills for the jobs that actually succeed. No servers to provision, no queues to watch, no 3 AM pages because a sale went viral.
If you're curious what the spike scenario looks like on the other side of that architecture, the FFmpeGo docs and free tier are where to start. Send a simple conversion job. Watch what happens when you send a hundred. It's a very different experience than the one you might be having right now at 2:47 AM.
Stop building capacity you'll only need occasionally. Start buying only the compute you actually use.