At hour 6, the system looked flawless: p95 latency sitting at ~180ms, error rate under 0.05%, dashboards green across the board. Any reasonable person would have signed off on the release. By hour 30, the same system was on the edge of collapse – heap at 90%, response times sailing past 400ms, and a support queue starting to fill. Nothing changed in the code or the traffic. The only variable was time. That’s the exact failure mode a soak test is built to expose, and it’s the kind of slow-onset problem that a 20-minute load run or a 10-minute stress test will never catch.

This guide hunts three specific culprits – the silent killers that hide from short tests and ambush you in production: memory leaks that accumulate below the garbage collector’s reclaim threshold, database connection pool exhaustion that slowly drains available connections until requests hang, and gradual performance degradation where latency creeps upward with no single obvious cause. You’ll get the root-cause science behind each, a reproducible detection and trending methodology, defensible pass/fail thresholds, and a blueprint for automating all of it in CI/CD. If you already run performance tests but keep getting blindsided by outages your green dashboards never predicted, this is for you. For a broader map of where these tests fit, see our overview of the four types of load testing and when each should be used.
- What Is Soak Testing? Definition, Purpose, and How It Differs From Load, Stress & Endurance Testing
- When to Run Soak Testing: A Quick Decision Checklist
- The Silent Killers: The Defect Classes Only Long-Duration Testing Reveals
- Designing an Effective Soak Test: Duration, Load Profile & Success Criteria
- Soak Testing Infrastructure & Tool Setup: Load Generation, Instrumentation, and WebLOAD for Multi-Day Runs
- Analyzing Soak Test Results: Resource Trending, Slope Interpretation & Root-Cause Analysis
- Operationalizing Soak Testing: CI/CD Automation, Progressive Runs & Cost Optimization
- Real-World Case Study: A 24-to-30-Hour Degradation Timeline (Healthy at Hour 6, Near-Collapse by Hour 30)
- Frequently Asked Questions About Soak Testing
- What’s the difference between soak testing and endurance testing?
- How long should a soak test run, and can it catch every leak?
- Should soak tests run in CI/CD pipelines – won’t they block everything?
- Is 100% soak coverage across every service worth the investment?
- What tools handle multi-day soak runs, and what should I evaluate them on?
- References
What Is Soak Testing? Definition, Purpose, and How It Differs From Load, Stress & Endurance Testing
Soak testing applies a sustained, moderate load – typically 50 – 80% of the level at which your system starts failing under stress – over a long window, and it measures stability rather than peak throughput. The question a soak test answers isn’t “how much can we handle?” It’s “do we degrade over time?” That distinction changes everything about how you design, run, and read the test. Where a load test validates that you meet SLAs at expected concurrency, and a stress test finds the breaking point, a soak test watches for the slow drift that only accumulates with runtime.
The standardized performance-testing taxonomy from the ISTQB treats endurance and soak testing as closely related test types focused on behavior under load over an extended period [1]. Getting the terminology governance right up front matters because these terms get used loosely across teams.
The Comparison Matrix: Soak vs. Load vs. Stress vs. Spike vs. Endurance

| Test Type | Load Shape | Typical Duration | Question It Answers | Defects It Surfaces |
|---|---|---|---|---|
| Load | Steady at expected concurrency | 30 min – 2 hrs | Do we meet SLAs at normal traffic? | Baseline latency/throughput regressions |
| Stress | Ramping past capacity | 30 min – 1 hr | Where’s our breaking point? | Failure mode, recovery behavior |
| Spike | Sudden surge then drop | Seconds to minutes | Can we survive and recover from a surge? | Autoscaling gaps, queue overflow |
| Soak | Constant moderate (50 – 80% of stress ceiling) | 8 – 72 hrs | Do we degrade over time? | Memory leaks, pool/FD exhaustion, latency creep |
| Endurance | Constant moderate over extended runtime | 8 – 72+ hrs | Can we sustain load without drift? | Same slow-burn class as soak |
The pattern here: soak and endurance sit apart from the others because duration is their defining variable, not load magnitude. For the full taxonomy context, our guide to the different types of performance testing breaks down where each method earns its place.
Soak vs. Endurance vs. Longevity: Getting the Terminology Right
Soak and endurance testing are functionally near-synonyms – both apply sustained load to detect drift – and the ISTQB glossary treats them within the same behavioral category [1]. The subtle usage distinction is contextual rather than technical: “endurance testing” tends to show up in enterprise QA and script-driven contexts, while “soak testing” is the term you’ll hear more often in DevOps and SRE circles. “Longevity testing” occasionally appears as a third label for the same idea. Pick one term, define it once for your team, and move on. (And to be clear for anyone who wandered in from a search: this is software endurance testing, not the fitness kind.) For a dedicated deep dive, see our endurance testing in software guide.
Why Duration Is the Whole Point: The Defect-Emergence Timeline
The reason soak testing exists is that certain defects are invisible until enough time passes. A leak that adds 150MB/hour is trivial in the first 20 minutes – well within normal noise – but it becomes catastrophic across a 30-hour window. This is why our opening scenario matters: healthy at hour 6 (~180ms p95, <0.05% error), near-collapse at hour 30 (90% heap, >400ms latency). The defect was present the entire time; only the timeline revealed it. The Google SRE book frames this precisely, noting that latency increases are often a leading indicator of saturation and that many systems degrade in performance before they ever reach 100% utilization [2]. That leading-indicator principle is the theoretical spine of everything that follows.
When to Run Soak Testing: A Quick Decision Checklist
You don’t need a 72-hour run before every commit. Soak testing is a targeted investment, best triggered when the risk of slow-onset failure is real. Microsoft’s performance-testing guidance places endurance validation squarely in the pre-release phase of the SDLC, after functional and load validation but before production sign-off. Here’s the decision checklist:
- If you’re shipping a major release that changes runtime behavior, then run a full soak on your most session-heavy workflow.
- If you’ve upgraded a framework, runtime, or ORM library, then soak-test to catch new resource-lifecycle regressions.
- If your app maintains user sessions exceeding ~4 hours (SaaS dashboards, multiplayer game servers), then the session state itself is a leak surface worth soaking.
- If you run a 24/7 system with no maintenance window (payments, trading, telemetry ingestion), then treat soak testing as mandatory outage insurance.
- If you’ve observed unexplained memory growth or periodic restarts in production, then reproduce it under a controlled soak before guessing at fixes.
Where soak fits in a complete testing program is covered in our performance testing types overview.
High-Stakes Triggers: Releases, 24/7 Systems, and Framework Upgrades
The costliest place to discover a slow-burn defect is production, and the highest-stakes triggers share a common trait: no natural reset. A payments API refactoring its ORM layer, for example, can introduce an unclosed-connection pattern that stays invisible until the pool drains twelve hours later. A multiplayer game server maintaining persistent player sessions accumulates per-session state that a stateless load test would never exercise. For these systems, a soak test is the only test that runs long enough to let the defect surface.
When You Can Safely Skip a Full Soak (and What to Run Instead)
Realistically, not every service needs a multi-day run. A stateless microservice that spins up fresh per request, holds no session state, and closes resources deterministically is a poor candidate for a 72-hour soak – you’re mostly testing your load generator. For that profile, a 30-minute memory-profiling run under moderate load will surface anything a full soak would, at a fraction of the cost. Progressive soaking (start at 4 hours, extend only if trends warrant) is often the pragmatic middle path. And remember: automation narrows the search, but a human still confirms the root cause. No soak-testing setup is “self-driving.”
The Silent Killers: The Defect Classes Only Long-Duration Testing Reveals
This is where the science lives. Each of these three defect classes shares one property: they accumulate below the threshold that short tests can detect, then cross into failure only after sustained runtime. Understanding why is what turns a soak test from an overnight guess into a diagnostic instrument.
| Symptom | Root Cause | Detection Signal (over a soak run) |
|---|---|---|
| Climbing heap, eventual OOM | Retained references GC can’t reclaim | Rising post-GC heap floor (positive slope) |
| Requests hang then time out | Connection leak / undersized pool | Waiting-connection count trending up, idle trending to zero |
| Latency creeps up, throughput flat | Cache bloat, fragmentation, thread starvation | p99 slope rising while RPS holds constant |
| Process crash after N hours | File descriptor / socket leak | lsof open-FD count climbing linearly |
For structured bottleneck analysis once you’ve spotted a signal, see our guide to identifying bottlenecks in performance testing.
Memory Leaks: Why the Heap Climbs Below the GC’s Radar
Oracle’s garbage collection documentation describes the collector’s job cleanly: it “determines which parts of that memory is still in use by the application” and “reclaims the unused memory for reuse,” using generational scavenging to concentrate on regions likely to hold reclaimable objects [3]. A leak defeats this by keeping references alive – an object that’s still reachable is, by definition, still “in use,” so the collector correctly declines to reclaim it. The heap climbs not because the GC is broken, but because your code is holding on.
The critical skill is distinguishing a leak from high-but-stable usage. Watch the post-GC heap floor: after each major collection, healthy memory returns to a stable baseline (the classic sawtooth), while a leak leaves the floor itself creeping upward run over run. Do the math on the slope. A leak adding 150MB/hour against a 4GB heap with ~1GB of headroom crosses into OOM territory in roughly 27 hours (1024 ÷ ~38MB/hour of net floor growth, adjusted for reclaim) – which is exactly why an 8-hour run misses it and a 30-hour run catches it. Common culprits: unbounded static collections that never evict, event listeners registered but never removed, and unclosed streams accumulating buffers. The mechanics differ across runtimes – JVM heap versus .NET managed heap versus Node’s V8 versus Go’s runtime – but the slope-based detection logic is identical everywhere. For the underlying reclamation mechanics, the official Java garbage collection tuning guide is the authoritative reference.
Database Connection Pool Exhaustion: The Slow Drain That Hangs Requests
Connection pool exhaustion is the classic silent killer because it fails so quietly. Every request that borrows a connection and forgets to return it – an unclosed connection in an error path, a transaction that never commits – permanently removes one connection from circulation. Over hours, available connections drain toward zero, at which point new requests queue, then time out, then cascade into a full outage. Short tests never run long enough to drain a properly sized pool.
The HikariCP pool-sizing guidance gives you defensible math. Its deadlock-avoidance formula is pool size = Tn x (Cm - 1) + 1, where Tn is the maximum thread count and Cm is the maximum simultaneous connections a single thread holds [4]. Counterintuitively, the guidance favors “a small pool, saturated with threads waiting for connections” over a large one, and cites the PostgreSQL starting-point formula connections = ((core_count * 2) + effective_spindle_count) [4]. During a soak run, watch active, idle, and waiting connection counts as three separate trends – idle drifting to zero while waiting climbs is your exhaustion signature. The remediation is almost always ensuring deterministic release. In Java:
// Leaky — connection escapes on exception
Connection c = dataSource.getConnection();
doWork(c);
c.close(); // never runs if doWork throws
// Fixed — try-with-resources guarantees release
try (Connection c = dataSource.getConnection()) {
doWork(c);
} // closed automatically, even on exception
Creeping Latency & Resource Exhaustion: Cache Bloat, Thread/FD Leaks, and Disk Growth
“Gradual performance degradation” gets defined in industrial-maintenance terms far too often; for software, it’s a specific, diagnosable set of causes. The Google SRE framing is the right lens: latency is a leading indicator of saturation, and systems degrade before hitting 100% utilization [2]. Map each cause to its detectable signal:
- Cache saturation / eviction thrashing – an unbounded or misconfigured cache grows until eviction churn dominates; detect via rising cache-size metric alongside climbing p99.
- Thread pool exhaustion / deadlock – blocked threads accumulate; detect via active-thread count plateauing at max while queue depth climbs.
- File descriptor & socket leaks – unclosed sockets or files accumulate; detect via
lsofopen-FD count rising linearly across the run. - Unbounded log / disk growth – verbose logging fills disk; detect via free-disk trending down, with a time-to-exhaustion projection.
These map directly onto the SRE four golden signals – latency, traffic, errors, and saturation – which give you a principled instrumentation checklist. Understanding the performance metrics that matter in performance engineering helps you decide which of these signals to prioritize. See Google’s SRE guidance on monitoring the four golden signals.
Designing an Effective Soak Test: Duration, Load Profile & Success Criteria
A soak test has three phases: a short ramp-up (typically 5 minutes to reach target concurrency), a long plateau (the 8 – 72 hour steady state where the real detection happens), and a brief ramp-down (5 minutes). Hold the plateau at 50 – 80% of your stress ceiling – high enough to exercise resource paths, low enough that you’re isolating time-based drift rather than capacity limits. Select user scenarios that represent genuinely long workflows: authenticated sessions, multi-step transactions, and anything that holds server-side state. The SRE prediction framing – “it looks like your database will fill its hard drive in 4 hours” – captures exactly the kind of time-to-exhaustion projection your design should enable [2]. For turning results into launch decisions, the SRE workbook on implementing SLOs is the reference.
The Duration-Selection Framework: How Many Hours Is Enough?
Two principles drive duration. First, the slowest-cycle principle: your run must span the longest natural cycle in production. If a batch job runs weekly, an 8-hour test can’t observe its resource impact – you need a run that covers that cycle. Second, the leak-rate projection: estimate the net resource growth per hour from a shorter diagnostic run, then compute time-to-exhaustion. Available headroom ÷ growth rate = required hours, plus margin. If a pilot run shows ~40MB/hour net heap-floor growth against 1GB of headroom, exhaustion lands near 25 hours – so a 30-hour soak is the minimum that proves the point.
Defining a ‘Stability Pass’: Thresholds That Hold Up in Review
Set pass/fail criteria on both absolute values and, more importantly, trend slopes. Two defensible thresholds to start from:
- Fail if post-GC heap-floor slope exceeds baseline + 20MB/hour – this isolates genuine leaks from healthy sawtooth.
- Fail if p99 latency drifts more than 15% from the run’s first hour to its last hour – sustained p99 drift under constant load is a direct SLA-breach risk and warrants a no-go.
Tie each threshold to a business consequence so the gate survives review: “p99 drift >15% = projected SLA breach within a weekend = no-go” is a decision a release manager can defend. The SRE workbook’s SLO-based framing gives this structure [5]. (Note: these figures are illustrative starting points – always calibrate against your own baselines before enforcing gates.)
What to Monitor and How Often: A Resource-Monitoring Strategy
Capture, at minimum: post-GC heap floor, RSS, GC frequency and pause time, thread count, active/idle/waiting connection counts, open file descriptors and sockets, p95/p99 latency, and error rate. Anchor this set to the four golden signals so nothing structural slips through [2]. For sampling cadence on multi-day runs, 30-second scrapes for latency/error and a captured post-GC heap floor every 30 minutes strike a balance between resolution and storage. For correlating these with real user impact, our guide to application monitoring covers APM integration.
Soak Testing Infrastructure & Tool Setup: Load Generation, Instrumentation, and WebLOAD for Multi-Day Runs
Your soak environment must be isolated and production-like – shared or under-provisioned environments introduce noise that masquerades as drift. But the most overlooked requirement is the load generator itself: it has to survive the full run without degrading.
| Duration-Specific Criterion | Why It Matters | Target |
|---|---|---|
| Load-generator memory stability over 72h | A leaking generator produces false positives | RSS growth <2% over 24h |
| Distributed / parallel generation | Single-node generators cap sustainable load | Multi-agent coordination |
| Multi-day result retention | A hour-30 finding must trace back to hour 1 | Full-run time-series storage |
| Integrated trend / APM correlation | Manual metric stitching doesn’t scale to days | Load-side + system-side in one view |
Most “best load testing tool” listicles evaluate peak throughput and ignore every one of these; our practitioner comparison of the best load testing tools weighs the criteria that actually matter for sustained runs. For enterprise multi-day runs, WebLOAD is engineered for exactly this profile – stable distributed generation across 24 – 72 hour windows with integrated monitoring that correlates load-side and server-side trends, plus support for complex enterprise protocols including sustained-connection scenarios. For metric capture methodology, Prometheus instrumentation best practices is the authoritative reference.
Can Your Load Generator Survive 72 Hours? Sustained-Duration Evaluation Criteria
Here’s the trap: if your load generator leaks, you’ll spend a day chasing a “server memory leak” that’s actually in the tool. Vet any candidate on generator-side resource stability under a self-directed 24-hour run before you trust its output – target RSS growth under 2%. RadView’s platform is built around resource-efficient generation precisely so the tool never becomes the variable you’re debugging, with distributed agents that sustain load without per-agent drift.
Instrumentation & Telemetry: Wiring Up Memory and Connection Metrics
The metrics that make soak testing meaningful come from instrumentation, not the load tool. Expose process memory (process_resident_memory_bytes), GC counters, connection-pool gauges, and thread counts as scrapeable metrics. Prometheus guidance recommends using the right metric type – gauges for point-in-time values like heap and connection counts, counters for cumulative events – so rate() and slope functions behave correctly [6]. An integrated monitoring layer that pulls both load-side timing and these system-side gauges into a single correlated view is what lets you connect a latency inflection to a heap inflection at the same timestamp.
Automated Alerting & Data Retention for Multi-Day Runs
Alert on slopes, not just absolutes. A static “heap > 90%” alert fires at hour 30 – too late. A trend rule like alert if post-GC heap floor rises >20MB/hour over any 2-hour window fires at hour 8, giving you 22 hours of runway. Retain the full-run time series so a defect surfaced late can be traced to its origin. AI-assisted correlation can flag the anomalous slope and narrow the suspect window automatically – but it narrows the search; a human still confirms the root cause. Treat that as a firm guardrail, not a limitation to apologize for.
Analyzing Soak Test Results: Resource Trending, Slope Interpretation & Root-Cause Analysis
Resource trending analysis is the technique that makes soak testing worth running. Raw metrics tell you a number; the slope of those metrics over hours tells you the diagnosis. The SRE book’s emphasis on analyzing long-term trends – and its observation that long-horizon experiments tolerate the occasional missed sample because they won’t hide a running trend – validates the whole approach [2].
Reading the Slopes: Flat, Positive, and Sawtooth Explained
Three patterns, three verdicts:

- Flat – the resource holds a stable baseline across the run. Verdict: healthy, stable.
- Sawtooth – memory climbs, drops sharply at each GC, and returns to a stable floor. Verdict: healthy GC behavior, not a leak. The Oracle GC docs explain exactly why: the collector reclaims unused memory back to that floor each cycle [3].
- Positive slope – the post-GC floor itself trends upward run over run, never returning to baseline. Verdict: genuine leak.
The single diagnostic that separates a leak from healthy GC is whether the post-GC heap floor returns to a stable baseline (healthy) or climbs (leak). Trust the floor’s slope over the peak’s height every time.
From Trend to Root Cause: Correlation and Heap-Dump Diffing
Once a trend is suspicious, confirm the cause. For a heap leak, capture two heap dumps – one early, one late – and diff retained-object counts; a growing collection class (a HashMap gaining entries indefinitely, say) is your smoking gun. For connection exhaustion, inspect pool state and correlate the waiting-connection climb with a specific endpoint’s request timestamps. The diagnostics differ by runtime – HPROF snapshots and jstack on the JVM, dotnet-counters on .NET, pprof on Go, --inspect heap snapshots on Node – but the workflow is identical: trend points you at when, correlation points you at where, and the dump confirms what. Our bottleneck identification guide extends this into full root-cause workflows.
Operationalizing Soak Testing: CI/CD Automation, Progressive Runs & Cost Optimization
Soak testing earns its keep when it’s continuous rather than an occasional heroic overnight watch. The practical challenge is that a multi-hour test can’t block a fast build pipeline. The solution is decoupling: run progressive soaks on a schedule, not per-commit. A workable cadence is a nightly 8-hour soak on the main branch and a weekly 24 – 72 hour full soak off-peak, each publishing results to a historical baseline store. For the gating philosophy, the SRE workbook on implementing SLOs frames how to turn trends into pass/fail decisions [5].

Scheduling Long Runs Without Blocking Fast Pipelines
Decouple the soak from the build entirely. A cron-triggered nightly job – 0 22 * * * to launch a soak at 10pm against the latest deployed build – runs asynchronously and posts results the next morning. Per-commit pipelines stay fast with a short smoke test; the nightly and weekly soaks run as separate scheduled workflows. This is why soak runs go nightly/weekly and never per-commit: blocking a 30-minute merge on an 8-hour test would grind delivery to a halt.
Baseline-Drift Gating: Failing a Build on a Trend, Not Just a Threshold
Static thresholds catch cliffs; baseline-drift gating catches erosion. Store each run’s key slopes and build a rolling baseline – say, a 4-week window – then gate on regression against it: fail the run if the current heap-floor slope exceeds the rolling 4-week baseline + 20MB/hour, or fail if week-over-week p99 baseline drifts >10%. This catches the slow degradation that a fixed threshold would only trip long after a problem became expensive. The SLO framing gives you the objective structure to justify these gates in review [5].
Cost Optimization for Cloud-Based Soak Testing
Multi-day cloud runs cost real money, mostly in generator compute. Three levers: right-size your load generators to the actual concurrency you need (over-provisioning burns budget for the entire plateau); schedule the long plateau phase on off-peak spot instances where your provider offers them, which can materially reduce compute cost – validate against your own cloud pricing; and choose a resource-efficient generator so you need fewer instances to produce the same load. RadView’s platform is designed for that efficiency, keeping the generator footprint lean across sustained runs. No unbounded “10x cheaper” claims here – the savings depend on your workload and pricing, so measure them.
Real-World Case Study: A 24-to-30-Hour Degradation Timeline (Healthy at Hour 6, Near-Collapse by Hour 30)
A team ran a 500 RPS constant-rate soak against a service ahead of a major release. Here’s what the run showed, hour by hour, drawn from a documented practitioner case [7]:
- Hour 6: p95 latency ~180ms, error rate <0.05%, heap comfortably mid-range. Every dashboard green. A short test would have signed off here.
- Hours 6 – 24: heap floor creeping upward on a shallow but unmistakable positive slope; p95 edging up a few milliseconds per hour – invisible to anyone watching absolute numbers, obvious to anyone watching the slope.
- Hour 30: heap at 90%, response times exceeding 400ms, error rate climbing as GC pauses lengthened and requests backed up.
The business translation: a p99 blowing past 400ms with a climbing error rate is a direct SLA breach, and the projected trajectory pointed at a full outage – precisely the kind of weekend incident that generates a war room. The remediation was an LRU cache-sizing fix: an unbounded cache had been retaining entries indefinitely, and capping it with proper eviction flattened the heap-floor slope on the re-run. For turning these metrics into a defensible go/no-go, the SRE implementing SLOs approach is the reference [5]. See also our application monitoring guide for catching these signals in production.
The Timeline Broken Down: What Each Metric Was Telling Us
Here’s the punchline for anyone still relying on static thresholds. A slope-based alert – heap floor rising >20MB/hour over a 2-hour window – would have fired around hour 8, when the positive slope was already unmistakable but impact was still zero. The static “heap > 90%” alert didn’t trip until hour 30, when the system was already failing. That 22-hour gap is the entire value proposition of resource trending analysis and the leading-indicator theory the SRE book articulates [2]: the trend was screaming for 22 hours before the absolute number bothered to notice.
Supporting Mini-Cases & Lessons Learned Across Industries
- E-commerce: a memory leak in a session-caching layer caused weekend outages every ~40 hours of uptime; a Friday-to-Monday soak reproduced it, and a bounded-cache fix ended the pattern.
- Banking (WebLOAD-executed): a 24/7 payments service exhausted its connection pool after ~12 hours of sustained load due to an unclosed connection on a rare error path. A distributed multi-day run surfaced the waiting-connection climb; a try-with-resources fix and pool resizing per the HikariCP formula [4] resolved it.
- SaaS: cache saturation degraded response times over several days as an unbounded cache thrashed on eviction; a 72-hour soak caught the p99 creep, and LRU eviction restored stability.
The consolidated lesson across all four: the defect was always present, the short tests always passed, and only duration plus slope-based trending turned an invisible risk into a fixed bug before customers felt it.
Frequently Asked Questions About Soak Testing
What’s the difference between soak testing and endurance testing?
They’re near-synonyms – both apply sustained load over an extended window to detect drift – and the ISTQB taxonomy treats them in the same behavioral category [1]. The one practical distinction is contextual: “endurance testing” is more common in enterprise QA and script-driven contexts, while “soak testing” dominates in DevOps and SRE conversations. Pick one term for your team. More detail lives in our endurance testing guide.
How long should a soak test run, and can it catch every leak?
Plan for 8 – 72 hours, driven by the slowest-cycle principle






