Testing and benchmarks

Copy Markdown View Source

Test layers

The suite deliberately separates failures by ownership:

LayerWhat it proves
UnitBinary markers, auth-provider contracts, configuration, error mapping, deadlines, admission, and bounded metrics
HTTP integrationJSON/MessagePack negotiation, malformed input, declared and streamed body limits, stalled uploads, methods, headers, keep-alive, TLS, and HTTP/2 ALPN
In-repository gateway integrationShared authentication, ACL key scopes, ordered command results, stateless rejection before execution, batch limits, and password rotation
Python SDK integrationUnchanged sync/async command APIs, HTTPS Basic auth, pooled connections, pipelines, binary JSON, compact MessagePack, coalescing, SDK errors, and ACL rejection
BenchmarksIn-memory HTTP ceiling plus Lambda-shaped real-SDK warm/cold traffic

The default HTTP suite includes the real in-process gateway and excludes only external SDK suites:

mix test apps/ferricstore_http/test
FERRICSTORE_PYTHON_SDK_PATH=../ferricstore-python \
  mix test apps/ferricstore_http/test/ferricstore_http/python_sdk_integration_test.exs \
  --include python_sdk_integration

The Python executable must have the SDK development extras installed. Set FERRICSTORE_PYTHON_EXECUTABLE when that executable is not python3.

In-memory HTTP/1.1 benchmark

This benchmark isolates listener, parsing, authentication-boundary, encoding, and keep-alive costs from FerricStore storage work:

mix run --no-start bench/http/keepalive_benchmark.exs \
  --clients 50 \
  --requests-per-client 100 \
  --commands-per-request 1 \
  --warmup 5

Each worker represents one warm function environment with one reused HTTP/1.1 connection. Large simultaneous connection bursts also depend on the host listen-backlog and file-descriptor limits. max_connections and acceptors size the listener but cannot override kernel caps; tune the host and distribute bursts across proxy instances when validating thousands of environments.

Python SDK Lambda benchmark

This benchmark starts a TLS listener backed by the real in-process FerricStore gateways, creates one ACL identity per function group, and runs concurrent function environments. Warm scenarios create one SDK client per environment and reuse it. Cold scenarios create a client and TLS connection for every request.

export FERRICSTORE_PYTHON_SDK_PATH=../ferricstore-python
export FERRICSTORE_PYTHON_EXECUTABLE=/path/to/python-with-sdk-dev-extras
export FERRICSTORE_LAMBDA_GROUPS=8
export FERRICSTORE_LAMBDA_ENVS_PER_GROUP=8
export FERRICSTORE_LAMBDA_WARM_REQUESTS=250
export FERRICSTORE_LAMBDA_COLD_REQUESTS=4
export FERRICSTORE_LAMBDA_BENCH_WORKLOAD=flow_get
export FERRICSTORE_LAMBDA_BENCH_TRANSPORTS=http,http2

mix run --no-start bench/http/python_sdk_lambda_benchmark.exs

Supported controls are:

VariableDefaultMeaning
FERRICSTORE_LAMBDA_GROUPS8Distinct ACL credential groups
FERRICSTORE_LAMBDA_ENVS_PER_GROUP8Concurrent function environments per group
FERRICSTORE_LAMBDA_WARM_REQUESTS250Timed requests per warm environment; keep the total large enough to avoid short-burst bias
FERRICSTORE_LAMBDA_COLD_REQUESTS4Timed requests per cold environment
FERRICSTORE_LAMBDA_BENCH_MODEallwarm, cold, or all
FERRICSTORE_LAMBDA_BENCH_WORKLOADpingKV: ping, seeded read, write, 50/50 mixed, or batch; Flow: seeded flow_get, unique flow_create, structured flow_start, or two-command flow_batch (FLOW.CREATE + FLOW.GET)
FERRICSTORE_LAMBDA_EXECUTION_MODELprocessesprocesses isolates each simulated environment; threads is a conservative single-process/GIL diagnostic
FERRICSTORE_LAMBDA_BENCH_TRANSPORTShttpComma-separated http, http2, and/or native; http_transport, httpclient, and httpx are transport diagnostics
FERRICSTORE_LAMBDA_COMPACTfalseUse MessagePack for HTTP requests
FERRICSTORE_LAMBDA_REQUEST_TIMEOUT10Per-operation SDK timeout in seconds
FERRICSTORE_LAMBDA_MIN_HTTP_NATIVE_RATIO0Optional 0..1 warm HTTP/1-to-native throughput floor; requires both http,native and fails the run below the floor
FERRICSTORE_HTTP_BENCH_BACKENDferricstoreferricstore measures the real gateway; memory isolates the HTTP/SDK ceiling
FERRICSTORE_HTTP_BENCH_PROFILEoffcall_time or call_memory enables OTP profiling; profiling results are not throughput results

Every result is emitted as one JSON object containing the workload, concurrency, request, command, and error counts, duration, requests and commands per second, and p50/p95/p99/max request latency. The batch and flow_batch workloads count one HTTP request and two FerricStore commands per operation. The Flow workloads use the real FerricStore gateway; they measure storage and workflow execution as well as the HTTP transport. flow_start specifically covers the SDK's structured native-opcode bridge. Diagnostic transport implementations (http_transport, httpclient, and httpx) intentionally support only PING; use http, http2, or native for data workloads. Any request error makes the benchmark exit unsuccessfully. Compare runs on the same host and toolchain; these numbers are regression and capacity-planning evidence, not a universal production limit. The isolated benchmark raises the shared authentication limiter only to the configured concurrency plus headroom, preventing a burst of successful simultaneous authentications from obscuring transport measurements. The normal HTTP integration suite still exercises the production rate-limit response.

Local optimization evidence

On the same 16-scheduler development host, with 64 process-isolated warm environments, TLS, Basic authentication, the real FerricStore gateway, and 250 timed PING requests per environment:

MeasurementRequests/secondp95 latencyErrors
In-memory backend control15,697.737.27 ms0
Real gateway before mailbox-free scoped leases6,809.2130.22 ms0
Real gateway after mailbox-free scoped leases, run 110,756.9916.75 ms0
Real gateway after mailbox-free scoped leases, run 210,877.1216.62 ms0
Current release candidate on Python 3.14, run 118,076.485.87 ms0
Current release candidate on Python 3.14, run 217,008.156.53 ms0

The two matching post-change runs are about 59% faster than the sustained pre-change run. Later samples on this workstation were rejected because unrelated long-running benchmark processes and a virtual machine consumed multiple cores. Always inspect host contention, repeat the same scenario, and retain controls; do not select the best sample from a noisy machine. The release-candidate rows validate the complete TLS and Python SDK path on the newer toolchain; they are not a causal comparison with the older scoped-lease measurements.

Homogeneous command-gateway batch evidence

The Java SDK's real HTTP KV benchmark also covers the shared gateway and durable storage rather than an in-memory backend. The following controlled A/B used the unchanged 0.11.11 worktree and the candidate worktree on the same host, plain HTTP/1.1, 64 in-flight batches, 100 hot keys, and 16-byte values. GET batches contained 1,000 commands and SET batches contained 500 commands. Each value is the median of three zero-error five-second samples after the binary correctness probe. SET candidate samples used a fresh server process so sustained writes from an earlier sample could not trigger storage-batcher admission in a later sample. The benchmark-only authentication attempt allowance was 1,000 on both A/B servers, isolating the gateway and storage path from connection-startup auth bursts.

GatewayRuntimeWorkloadCommands/secondp95 batch latencyErrors
Unchanged 0.11.11Java 21GET76,4951,965.9 ms0
Homogeneous KV batchJava 21GET762,626130.6 ms0
Unchanged 0.11.11Java 21SET7,6574,194.8 ms0
Homogeneous KV batchJava 21SET647,85855.1 ms0
Homogeneous KV batchJava 17GET768,617138.0 ms0
Homogeneous KV batchJava 17SET677,25551.4 ms0

The same candidate server then completed the 10,000-flow, three-step Java workflow benchmark with zero execution or verification errors: 2,576 workflow completions/s on Java 21 and 2,608/s on Java

  1. The workflow commands deliberately remain on their existing structured paths, so these runs are a regression guard rather than evidence that the KV fast path accelerates Flow. These are local regression measurements, not universal capacity claims; compare candidates on the same host and retain the zero-error requirement.

With the production default of 10 authentication attempts per window, a fresh Java HTTP/1.1 client opening 64 connections concurrently also intermittently received 429 rate_limited for valid Basic credentials. The retained throughput samples exclude that separate startup-admission effect; it needs its own auth single-flight or queued-verification correction rather than a higher production rate limit.