stability-sim
v0.2.0
Published
A dependency-free discrete-event simulator for distributed system stability experiments
Maintainers
Readme
stability-sim
A typed, dependency-free discrete-event simulation library for distributed-system stability experiments. The simulator was extracted from stability-sim into a standalone library with a CDK-like API.
Each distributed system component is a class that can be instantiated with a set of properties. Components can be connected to other components, and failures can be added to the scenario to simulate different failure scenarios. Once a scenario is created, it can be run synchronously.
Install
Node.js 22.18 or newer is required.
npm install stability-simUsage
import { Client, Distribution, LoadBalancer, LoadBalancerStrategy, Queue, Retry, Scenario, Server, ServerCrash, Traffic } from 'stability-sim';
const scenario = new Scenario({
name: 'Metastable Retry Storm',
endTime: 60,
metricsWindowSize: 1,
seed: 42,
});
const client = scenario.add(new Client('client-1', {
trafficPattern: Traffic.openLoop({ meanArrivalRate: 70 }),
retryStrategy: Retry.fixedN({ maxRetries: 3 }),
timeout: 1,
}));
const loadBalancer = scenario.add(new LoadBalancer('lb-1', {
strategy: LoadBalancerStrategy.ROUND_ROBIN,
}));
const queueA = scenario.add(new Queue('queue-a', {
maxCapacity: 10_000,
maxConcurrency: 5,
}));
const queueB = scenario.add(new Queue('queue-b', {
maxCapacity: 10_000,
maxConcurrency: 5,
}));
const serverA = scenario.add(new Server('srv-1', {
serviceTimeDistribution: Distribution.exponential({ mean: 0.1 }),
concurrencyLimit: 5,
}));
const serverB = scenario.add(new Server('srv-2', {
serviceTimeDistribution: Distribution.exponential({ mean: 0.1 }),
concurrencyLimit: 5,
}));
client.sendsTo(loadBalancer);
loadBalancer.routesTo(queueA);
loadBalancer.routesTo(queueB);
queueA.sendsTo(serverA);
queueB.sendsTo(serverB);
scenario.addFailure(new ServerCrash(serverA, {
triggerTime: 5,
recoveryTime: 10,
}));
const result = scenario.run({ snapshotInterval: 1 });
console.log(result.finalSnapshot);In this distributed system simulation, a client connects to a load balancer, which routes requests to servers, whose buffers are represented by queues. A failure scenario, namely a short single server outage, is injected from T=5 to T=10, causing an amplified retry storm from clients that keeps both branches overloaded after the server recovers.
The result object contains information about the simulation which can be probed further:
const allSnapshots = result.snapshots; // an array of MetricSnapshot objects
const finalSnapshot = result.finalSnapshot; // the final MetricSnapshot object, for convenienceIf we inspect the final snapshot, we can see metrics including the number of client requests successfully fulfilled, the number that were retried, the final size of the queues, as well as latency percentiles for the client:
console.log(finalSnapshot);
/*
{
simTime: 59.99985197301004,
componentMetrics: {
'client-1': {
completedCount: 662,
failedCount: 13948,
retriedCount: 10631,
timedOutCount: 13474,
inFlightCount: 281,
tokenBucketTokens: 0
},
'lb-1': {
tpsForwarded: 14891,
totalFailed: 0,
failedDownstreamCount: 0,
errorRate: 0
},
'queue-a': {
queueDepth: 4245,
totalEnqueued: 7446,
totalDequeued: 3201,
totalRejected: 0
},
'queue-b': {
queueDepth: 4437,
totalEnqueued: 7445,
totalDequeued: 3008,
totalRejected: 0
},
'srv-1': {
activeCount: 5,
utilization: 1,
tpsProcessed: 2722,
totalRejected: 474,
crashed: 0,
latencySpikeMultiplier: 1,
cpuReductionPercent: 0,
errorRate: 0
},
'srv-2': {
activeCount: 5,
utilization: 1,
tpsProcessed: 3003,
totalRejected: 0,
crashed: 0,
latencySpikeMultiplier: 1,
cpuReductionPercent: 0,
errorRate: 0
}
},
latencyPercentiles: {
p50: 29.621504949269067,
p95: 33.26896852844208,
p99: 34.40729320856889,
p999: 34.9146943670209
},
completedCount: 662,
failedCount: 13948
}
*/The docs below provide further information about the available APIs and how to use the simulator. For more examples, see the examples directory.
Low-level JSON API
A simulation can also be created from a raw JSON object by calling Scenario.fromJSON().
import type { ScenarioJSON } from 'stability-sim';
const document = {
schemaVersion: 1,
name: 'Low-level example',
seed: 42,
endTime: 5,
metricsWindowSize: 1,
components: [
{
id: 'client',
type: 'client',
config: {
trafficPattern: { type: 'burst', count: 10, atTime: 0 },
retryStrategy: { type: 'none' },
targetComponentId: 'server',
timeout: 1,
},
},
{
id: 'server',
type: 'server',
config: {
serviceTimeDistribution: {
type: 'uniform',
min: 0.02,
max: 0.04,
},
concurrencyLimit: 10,
},
},
],
connections: [
{ id: 'client-to-server', sourceId: 'client', targetId: 'server' },
],
failureScenarios: [],
};
const scenario = Scenario.fromJSON(document); // also accepts a JSON string
const result = scenario.run();To serialize a builder scenario:
const document = scenario.toJSON();
const json = scenario.toJSONString(2);Validation
There are some ways that a simulation can be created incorrectly, especially if it's created directly from JSON. The API provides two validation levels to help catch these issues:
- TypeScript rejects incompatible relationships, failure targets, and malformed component props at development time.
- Runtime validation checks numerical ranges, required connections, unique IDs, references, connection cardinality, and JSON discriminants.
Validation happens automatically before synthesis or execution. It is also directly accessible:
const issues = scenario.validate(); // readonly ValidationIssue[]
scenario.assertValid(); // throws ScenarioValidationErrorEvery runtime issue includes a stable code, JSON-style path, and message:
{
code: ValidationIssueCode.INVALID_TOPOLOGY,
path: '$.components[0]',
message: 'client requires exactly one downstream connection',
}Simulation control
scenario.run() will run a full simulation.
For stepping or metric access, create a simulation session:
const simulation = scenario.createSimulation();
simulation.step();
simulation.currentTime;
simulation.queueSize;
simulation.snapshot();
const result = simulation.run({
snapshotInterval: 0.5,
latencyWindow: 10,
maxEvents: 1_000_000,
onSnapshot(snapshot) {
console.log(snapshot.simTime, snapshot.completedCount);
},
});
const points = simulation.getTimeSeries('api-a', 'utilization'); // TimeSeriesPoint[]
simulation.reset();API reference
Public values
| API | Description |
| --- | --- |
| Cache | Pass-through cache component with probabilistic or keyed TTL behavior. |
| Client | Workload source component configured with traffic and retry policies. |
| Component | Abstract base class shared by builder component objects. |
| Database | Terminal database processor with read/write latency and a connection pool. |
| LoadBalancer | Pass-through router supporting round-robin, random, and least-connections selection. |
| Queue | Pass-through buffer with capacity, concurrency, shedding, and ordering controls. |
| Server | Terminal request processor with service-time and concurrency controls. |
| Throttle | Pass-through component that limits concurrency or request rate. |
| Connection | Typed directed relationship returned by component relationship methods. |
| Traffic | Immutable factories for open-loop, closed-loop, ramping, and burst traffic patterns. |
| Retry | Immutable factories for none, fixed-count, token-bucket, and circuit-breaker retries. |
| Distribution | Immutable distribution factories and the union type they produce. |
| LoadDependentLatency | Immutable load-scaling factories and the union type they produce. |
| ThrottleMode | Immutable throttle-mode factories and the union type they produce. |
| CacheFlush | Failure object that clears a cache at a configured simulation time. |
| CpuReduction | Failure object that temporarily reduces a server's available concurrency. |
| Failure | Abstract base class shared by typed failure objects. |
| LatencySpike | Failure object that temporarily multiplies server or database latency. |
| NetworkPartition | Failure object that temporarily disables a Connection. |
| RandomError | Failure object that temporarily injects errors into a server or load balancer. |
| ServerCrash | Failure object that crashes and later recovers a server or database. |
| Scenario | Builder, validator, serializer, and entry point for running a scenario. |
| ScenarioValidationError | Error containing every structured issue found during scenario validation. |
| Simulation | Stateful synchronous simulation session supporting run, step, metrics, and reset. |
Enums
| Enum | Description |
| --- | --- |
| CacheEvictionPolicy | FIFO or LRU cache eviction policy. |
| ComponentType | Stable serialized type of a simulation component. |
| DistributionType | Uniform, exponential, or log-normal distribution discriminant. |
| EventKind | Arrival, departure, failure, recovery, or timeout event kind. |
| FailureType | Stable serialized type of an injectable failure. |
| LoadBalancerStrategy | Round-robin, random, or least-connections routing strategy. |
| LoadDependentLatencyMode | Linear, polynomial, or exponential load-scaling mode. |
| QueueOrdering | FIFO or LIFO queue ordering. |
| RetryStrategyType | Stable discriminant for a retry strategy. |
| SimulationStatus | Idle, running, paused, or completed execution status. |
| ThrottleModeType | Disabled, concurrency, or RPS throttle-mode discriminant. |
| TrafficPatternType | Stable discriminant for a traffic pattern. |
| ValidationIssueCode | Stable category assigned to a validation diagnostic. |
Component and connection types
| Type | Description |
| --- | --- |
| AnyComponent | Union of every high-level component object. |
| CacheProps | Constructor properties for Cache, excluding its object-referenced downstream target. |
| ClientProps | Constructor properties for Client, excluding its object-referenced target. |
| ConnectableComponent | Union of component objects allowed as connection sources. |
| DatabaseProps | Constructor properties for Database. |
| LoadBalancerProps | Constructor properties for LoadBalancer. |
| QueueProps | Constructor properties for Queue. |
| ServerProps | Constructor properties for Server. |
| TargetComponent | Union of component objects allowed as connection targets. |
| TerminalComponent | Union of terminal Server and Database objects. |
| ThrottleProps | Constructor properties for Throttle. |
| ConnectionOptions | Optional relationship settings, currently the serialized connection ID. |
Factory result and property types
| Type | Description |
| --- | --- |
| OpenLoopTraffic | Traffic value produced by Traffic.openLoop(). |
| OpenLoopTrafficProps | Properties accepted by Traffic.openLoop(). |
| ClosedLoopTraffic | Traffic value produced by Traffic.closedLoop(). |
| ClosedLoopTrafficProps | Properties accepted by Traffic.closedLoop(). |
| RampingTraffic | Traffic value produced by Traffic.ramping(). |
| RampingTrafficProps | Properties accepted by Traffic.ramping(). |
| BurstTraffic | Traffic value produced by Traffic.burst(). |
| BurstTrafficProps | Properties accepted by Traffic.burst(). |
| NoRetry | Retry value produced by Retry.none(). |
| FixedNRetry | Retry value produced by Retry.fixedN(). |
| FixedNRetryProps | Properties accepted by Retry.fixedN(). |
| TokenBucketRetry | Retry value produced by Retry.tokenBucket(). |
| TokenBucketRetryProps | Properties accepted by Retry.tokenBucket(). |
| CircuitBreakerRetry | Retry value produced by Retry.circuitBreaker(). |
| CircuitBreakerRetryProps | Properties accepted by Retry.circuitBreaker(). |
| UniformDistribution | Distribution value produced by Distribution.uniform(). |
| UniformDistributionProps | Properties accepted by Distribution.uniform(). |
| ExponentialDistribution | Distribution value produced by Distribution.exponential(). |
| ExponentialDistributionProps | Properties accepted by Distribution.exponential(). |
| LogNormalDistribution | Distribution value produced by Distribution.logNormal(). |
| LogNormalDistributionProps | Properties accepted by Distribution.logNormal(). |
| LinearLoadDependentLatency | Scaling value produced by LoadDependentLatency.linear(). |
| LinearLoadDependentLatencyProps | Properties accepted by LoadDependentLatency.linear(). |
| PolynomialLoadDependentLatency | Scaling value produced by LoadDependentLatency.polynomial(). |
| PolynomialLoadDependentLatencyProps | Properties accepted by LoadDependentLatency.polynomial(). |
| ExponentialLoadDependentLatency | Scaling value produced by LoadDependentLatency.exponential(). |
| ExponentialLoadDependentLatencyProps | Properties accepted by LoadDependentLatency.exponential(). |
| DisabledThrottleMode | Throttle value produced by ThrottleMode.disabled(). |
| ConcurrencyThrottleMode | Throttle value produced by ThrottleMode.concurrency(). |
| ConcurrencyThrottleModeProps | Properties accepted by ThrottleMode.concurrency(). |
| RpsThrottleMode | Throttle value produced by ThrottleMode.rps(). |
| RpsThrottleModeProps | Properties accepted by ThrottleMode.rps(). |
Failure types
| Type | Description |
| --- | --- |
| AnyFailure | Union of every high-level failure object. |
| CacheFlushProps | Timing properties accepted by CacheFlush. |
| CpuReductionProps | Timing and reduction properties accepted by CpuReduction. |
| LatencySpikeProps | Timing and multiplier properties accepted by LatencySpike. |
| NetworkPartitionProps | Timing properties accepted by NetworkPartition. |
| RandomErrorProps | Timing and error-rate properties accepted by RandomError. |
| ServerCrashProps | Crash and recovery timing properties accepted by ServerCrash. |
| FailureScenario | Low-level discriminated union used to serialize failure scenarios by ID. |
Scenario, simulation, and validation types
| Type | Description |
| --- | --- |
| ScenarioOptions | Name, seed, duration, and metrics-window settings for Scenario. |
| ValidationIssue | Structured validation diagnostic containing a code, path, and message. |
| RunOptions | Snapshot, latency-window, event-limit, and callback options for a run. |
| SimulationResult | Final status, counts, timing, snapshots, and metrics returned by a run. |
| SnapshotOptions | Options controlling an on-demand metrics snapshot. |
Configuration, JSON, and metric types
| Type | Description |
| --- | --- |
| CacheConfig | Canonical low-level cache configuration including its downstream component ID. |
| ClientConfig | Canonical low-level client configuration including its target component ID. |
| DatabaseConfig | Canonical database latency, pool, and load-scaling configuration. |
| Distribution | Discriminated union of uniform, exponential, and log-normal distributions. |
| LoadBalancerConfig | Canonical load-balancer strategy configuration. |
| LoadDependentLatency | Discriminated union of linear, polynomial, and exponential load scaling. |
| QueueConfig | Canonical queue capacity, concurrency, shedding, and ordering configuration. |
| RetryStrategy | Discriminated union of supported retry strategies. |
| ServerConfig | Canonical server service-time, concurrency, and load-scaling configuration. |
| ThrottleConfig | Canonical throttle configuration containing a ThrottleMode. |
| ThrottleMode | Discriminated union of disabled, concurrency, and RPS throttle modes. |
| TrafficPattern | Discriminated union of supported client traffic patterns. |
| ComponentJSON | Low-level union of ID-based serialized component entries. |
| SavedScenarioJSON | Existing application save-document shape accepted by Scenario.fromJSON(). |
| ScenarioJSON | Canonical ID-based scenario document emitted by Scenario.toJSON(). |
| LatencyPercentiles | P50, P95, P99, and P99.9 latency values. |
| MetricSnapshot | Aggregate and per-component metrics captured at one simulation time. |
| TimeSeriesPoint | One time/value pair returned from a metric series. |
Runnable TypeScript examples
Build the library once, then run any example directly with Node.js:
npm run build
node examples/metastable-retry.ts
node examples/gc-death-spiral.ts
node examples/connection-pool-exhaustion.ts
node examples/cache-stampede.ts
node examples/cache-flush.ts
node examples/timeout-cascade.ts
node examples/lb-sinkholing.ts
node examples/goodput-collapse.ts
node examples/from-json.tsexamples/metastable-retry.ts— Metastable Failure (Retry Storm).examples/gc-death-spiral.ts— GC Pressure Death Spiral.examples/connection-pool-exhaustion.ts— Connection Pool Exhaustion.examples/cache-stampede.ts— Cache Stampede.examples/cache-flush.ts— Cache Flush Metastability.examples/timeout-cascade.ts— Timeout Cascade at High Utilization.examples/lb-sinkholing.ts— Load Balancer Sinkholing.examples/goodput-collapse.ts— Goodput Collapse (Stale Queue).
Development
The library has no dependencies. TypeScript, tsx, and Node types are development-only.
npm run typecheck
npm test
npm pack --dry-runThe build emits ESM JavaScript, source maps, and .d.ts declarations under dist/. Advanced low-level engine classes are available from stability-sim/engine.
