DEV Community

Cover image for Performance and Failure Simulation using Service Virtualization
beefed.ai
beefed.ai

Posted on • Originally published at beefed.ai

Performance and Failure Simulation using Service Virtualization

  • Simulating Latency, Throttling, and Errors with Precision
  • Scenario Templates: Timeouts, Partial Responses, and Rate Limits
  • Measuring Impact: Metrics, Instrumentation, and Analysis
  • Best Practices for Production-like Performance Simulations
  • Practical Application: Checklists and Runbooks

Real systems fail in patterns, not mysteries: high latency, transient throttles, malformed responses, and abrupt connection resets are the failure modes that break releases and erode user trust. Using virtual services to reproduce those modes — with controlled latency simulation, error injection, and network-level manipulations — turns unknowns into repeatable experiments you can measure and learn from.

Real symptoms you’re already seeing: intermittent end-to-end test failures, long and brittle CI pipelines, unexpected production slowdowns that only appear under load, and post-release firefighting because retries and backoffs weren’t exercised. Those symptoms point to a test environment that treats external dependencies as either "always available" or "completely mocked" instead of a first-class participant in resilience testing.

Simulating Latency, Throttling, and Errors with Precision

Service virtualization gives you two axes of control: behavior at the protocol level (HTTP status, body shape, truncated responses) and network/system characteristics (latency, jitter, bandwidth limits, TCP resets). Choose the right axis for the failure you want to reproduce.

  • Use HTTP-level virtualization to reproduce realistic response shapes, status codes, and streaming behaviors with tools like WireMock and Mountebank. WireMock supports fixed delays, chunked streaming dribble, and built-in fault types such as connection resets or malformed chunks.
  • Use TCP/network proxies to inject latency, jitter, bandwidth caps, and timeouts that a real network would create; Toxiproxy is designed for this and exposes latency, bandwidth, and timeout toxics you can add/remove at runtime.
  • Record-and-replay proxies (e.g., Mountebank in proxy mode) let you capture real production latency and replay it as a behavior for deterministic tests. Mountebank can capture actual response times and save them as wait behaviors for later replay.

Practical configuration examples:

  • Fixed HTTP delay (WireMock JSON mapping):
{
  "request": { "method": "GET", "url": "/api/payments" },
  "response": {
    "status": 200,
    "body": "{\"status\":\"ok\"}",
    "fixedDelayMilliseconds": 1500
  }
}
Enter fullscreen mode Exit fullscreen mode
  • Chunked / throttled response (WireMock chunkedDribbleDelay):
{
  "response": {
    "status": 200,
    "body": "large payload",
    "chunkedDribbleDelay": { "numberOfChunks": 5, "totalDuration": 2000 }
  }
}
Enter fullscreen mode Exit fullscreen mode
  • TCP latency via Toxiproxy (HTTP API):
curl -s -X POST http://localhost:8474/proxies -d '{
  "name": "db",
  "listen": "127.0.0.1:3307",
  "upstream": "127.0.0.1:3306"
}'
curl -s -X POST http://localhost:8474/proxies/db/toxics -d '{
  "name": "latency_down",
  "type": "latency",
  "stream": "downstream",
  "attributes": { "latency": 1000, "jitter": 100 }
}'
Enter fullscreen mode Exit fullscreen mode
  • Mountebank response with wait behavior (add latency to a stub):
{
  "port": 4545,
  "protocol": "http",
  "stubs": [
    {
      "responses": [
        {
          "is": { "statusCode": 200, "body": "ok" },
          "behaviors": [{ "wait": 500 }]
        }
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Important: Calibrate delays and rates to observed production percentiles (p50/p95/p99). Start with realistic values, then escalate to stress points. Google SRE guidance on SLOs and percentile thinking is the right mental model here.

Scenario Templates: Timeouts, Partial Responses, and Rate Limits

Below are compact, reusable scenarios you can encode as virtual-service templates in your test catalog.

Scenario Tools Minimal config snippet What to assert When to run
Slow backend Toxiproxy or WireMock Add 100–500ms jitter to downstream calls Client p95 increases but p50 remains stable; no queue saturation Early integration and performance tests
Throttle simulation (RPS cap) Toxiproxy (bandwidth) or API gateway rate-limit return 429 bandwidth toxic or return 429 Retry-After Client receives 429, retry/backoff honored Load tests and resilience runs
Partial/streamed responses WireMock chunkedDribbleDelay or Mountebank inject truncated JSON Stream body in 4 chunks over 2s Client streaming code handles incomplete chunks or fails gracefully Streaming and mobile tests
Connection reset / abrupt close WireMock fault or Toxiproxy down fault: "CONNECTION_RESET_BY_PEER" or disable proxy Confirm retry logic and circuit breakers engage Chaos trials and game days
Rate limit + degraded payload Virtual service returns 200 with smaller payload + X-RateLimit headers is response with trimmed JSON Client degrades feature set (graceful fallback) Feature-flagged progressive rollouts

How to configure a timeout scenario (practical tip): set the virtual service delay to slightly above the client timeout for one run (e.g., client timeout = 1s, virtual delay = 1.2s) to validate retry and fallback paths without producing huge queue pressure. Use progressively longer delays to exercise backoff windows.

Practical examples — returning partial JSON (Mountebank decorate):

{
  "is": { "statusCode": 200, "body": "{\"items\":" },
  "behaviors": [{ "wait": 500 }]
}
Enter fullscreen mode Exit fullscreen mode

Then follow with a second response chunk; combine decorate or streaming stubs to test parser resilience and recovery logic.

Measuring Impact: Metrics, Instrumentation, and Analysis

Design your experiments around measurable hypotheses and SLIs/SLOs — not guesses. Use percentiles, error budgets, and traces as your primary evidence.

  • Collect distributional latency: capture p50, p95, and p99 for both client-observed and service-side latencies. The SRE approach to using percentiles for SLI/SLO work is essential: percentiles reveal long-tail behavior that averages hide.
  • Instrument with histograms and use server-side aggregation (histogram + histogram_quantile() in Prometheus) when you must aggregate across instances. Prometheus recommends histograms for aggregate quantiles and explains when summaries vs histograms are appropriate.
  • Track these additional signals: error rate (4xx/5xx), retry counts, circuit-breaker trips, queue lengths, DB connection pool usage, CPU and memory, and request traces (Jaeger/Zipkin) for root-cause correlation.

Sample PromQL to record p95 and error rate (recording rules):

groups:
- name: service.rules
  rules:
  - record: http:p95_latency:1m
    expr: histogram_quantile(0.95, sum(rate(http_request_duration_seconds_bucket[5m])) by (le))
  - record: http:error_rate:1m
    expr: sum(rate(http_requests_total{status=~"5.."}[1m])) / sum(rate(http_requests_total[1m]))
Enter fullscreen mode Exit fullscreen mode

How to analyze results (practical sequence):

  1. Baseline collection: capture normal traffic metrics and traces for your test window.
  2. Inject the scenario and collect the same metrics with identical load patterns.
  3. Compare deltas on p95/p99, error budget burn, retries, and downstream saturation metrics.
  4. Use traces to confirm whether latency is added at the dependency boundary or accumulates across the call chain.
  5. Ask whether observed failure modes match the hypothesis; refine scenarios (more jitter, packet loss, or partial responses) if not.

Data point: Recording percentiles and using aggregated histograms gives you both fleet-level p95 and node-level detail — use both views to avoid mistaken conclusions.

Best Practices for Production-like Performance Simulations

The closer your virtual service matches production semantics, the more valuable the test. The following practices come from running these experiments across multi-team pipelines.

  • Version and catalog your virtual services: store OpenAPI-derived contracts or recorded imposters in a service library with semver-aware tags and automated deploy scripts. Treat virtual assets like code.
  • Use real request patterns: replay sampled production traffic (sanitized) to your virtual services so you exercise real paths and header combinations. Mountebank proxy+record modes help capture realistic latency and request shapes.
  • Progressive escalation: begin with mild perturbations (100ms latency), verify metrics, then escalate to severe conditions (1s–5s, packet loss). Chaos engineering advises starting small and scaling experiments after confidence increases.
  • Run experiments in purpose-built staging environments that mirror production topology (same number of instances, same autoscaling rules) to detect architectural queuing behaviors and cascading failures.
  • Keep data realistic but safe: generate production-like datasets and mask PII before injecting them into test environments.
  • Make experiments reproducible: record the virtual service config, the exact toxics applied, the test payloads, and the metric snapshots so you can reproduce incidents in postmortems.
  • Integrate with CI/CD: spin up virtual services as ephemeral containers in the pipeline, run the scenario suite, and tear down. This makes resilience testing part of the delivery pipeline instead of a separate activity.

Common pitfalls to avoid:

  • Over-simplified stubs that never return error codes (gives a false sense of robustness).
  • Excessive reliance on synthetic traffic that does not match distribution of real workloads.
  • Running fault-injection experiments without a pre-declared rollback plan and observability hooks — always automate rollback and alerting.

Practical Application: Checklists and Runbooks

Below is a compact runbook and checklist you can drop into a CI job or an SRE playbook.

Runbook: Latency Ramp Test (example)

  1. Preconditions: baseline metrics collected in the last 24 hours; virtual-service images built and tagged; observability (Prometheus/Grafana + tracing) enabled.
  2. Setup: deploy virtual services and Toxiproxy proxies using docker-compose or Kubernetes manifests. Ensure traffic routes through proxies.
  3. Baseline run: execute test workload (duration 5–10 minutes) and snapshot http:p95, http:p99, error rate, retries, and resource utilization.
  4. Apply perturbation: add latency toxic at 100ms then 500ms then 1000ms in incremental steps (5-minute holds). Capture metrics and traces at each step.
  5. Observe thresholds: stop or rollback if CPU > 85% cluster-wide, error budget burn > X% in 10 minutes, or SLA-critical user journeys fail.
  6. Post-run analysis: record differences, update SLO impact table, and file remediation tickets with evidence (traces, logs, Prometheus snapshots).

Checklist for CI job integration:

  • [ ] Start Toxiproxy and populate proxies via /populate.
  • [ ] Start WireMock or Mountebank containers with stored mappings/imposters.
  • [ ] Run baseline smoke tests and capture traces.
  • [ ] Apply scenario (scripted via API) and run full test suite.
  • [ ] Collect metrics and compare against recording rules (http:p95_latency, http:error_rate).
  • [ ] Save artifacts: mappings, toxics config, Prometheus snapshots, trace IDs.
  • [ ] Tear down services and mark run with metadata (commit, branch, timestamp).

Example docker-compose fragment to spin Toxiproxy + WireMock (CI-friendly):

version: "3.8"
services:
  toxiproxy:
    image: ghcr.io/shopify/toxiproxy
    ports:
      - "8474:8474"    # admin
    healthcheck:
      test: ["CMD", "toxiproxy-cli", "list"]
      interval: 5s
  wiremock:
    image: wiremock/wiremock:latest
    ports:
      - "8080:8080"
    volumes:
      - ./wiremock/mappings:/home/wiremock/mappings
Enter fullscreen mode Exit fullscreen mode

Quick troubleshooting tips:

  • When client p95 jumps but upstream latency is low, inspect retry storms and connection pooling.
  • When downstream errors increase only at scale, reproduce traffic shape (use JMeter or k6) rather than constant RPS.

Sources

WireMock — Simulating Faults - Documentation for fixedDelayMilliseconds, chunkedDribbleDelay, and simulated fault types used for HTTP-level latency and malformed/abrupt connection behavior.

Mountebank — Behaviors & Proxies - Details on wait behaviors, decorate, and proxy-record-and-replay features to capture and replay real response latencies.

Shopify Toxiproxy (GitHub) - Reference on latency, bandwidth, timeout toxics, CLI/API examples, and recommended usage patterns for network fault simulation.

SmartBear — What is Service Virtualization? - Rationale and business/engineering benefits of using service virtualization to remove dependency bottlenecks and enable earlier integration and performance testing.

Google SRE Book — Service Level Objectives (SLOs) - Guidance on SLIs/SLOs, using percentiles for latency indicators, and the error-budget control loop that should drive resilience experiments.

Prometheus — Histograms and Summaries (Best Practices) - Practical guidance on collecting latency distributions, choosing histograms vs. summaries, and using histogram_quantile() for percentile calculation.

Top comments (0)