Performance is deliberate, not accidental.
Yella uses a single-process runtime with a local SQLite database because that is the shape the engine is built for: durable execution, low operational overhead, predictable recovery, and no network hop between your code and your state.
This page is not a SQLite-versus-Postgres argument. It is the record of a deliberate design. The benchmark suite exists to show how that design behaves under realistic automation workloads, memory-heavy tasks, bursty traffic, degraded downstreams, and restart drills.
What we publish
Every benchmark claim should be backed by three artifacts:
- The harness. The repeatable benchmark suite lives in examples/benchmarks.
- The raw logs. Full text reports and JSON summaries live in examples/reports/benchmarks.
- The operating story. This page explains what the numbers mean, where the architecture is strong, and where you should choose something else.
Headline claims
0 SQLite errors
Across the completed benchmark, burst, soak, and restart runs published here, the harness recorded zero SQLite lock or busy errors.
Concurrency scales predictably
The API mixed workload rises from 10 runs/s at fleet concurrency 20 to 96 runs/s at fleet concurrency 200 without SQLite errors.
Memory-heavy tasks need caps
The scraper runs show the real tradeoff: raising the scraper cap improves admissions, but it raises RSS quickly.
Trust needs long runs
Burst recovery, degraded downstreams, overnight soak tests, and restart drills are part of the evidence, not optional extras.
Throughput By Fleet Concurrency
Same API-style workload, different fleet concurrency settings.
Browser Cap Versus Memory
Browser-style scraper work gets expensive quickly. Cap it separately from the rest of the fleet.
Queue Pressure Under Stress
Queue growth is acceptable. The important question is whether it stays bounded and recovers.
Database Footprint After The Soak
The database grew with retained history, while the WAL stayed small. That is the storage shape we want to see.
Published benchmark scenarios
These are the site-facing benchmark runs. Each row links directly to the raw `.txt` report and the structured `.json` summary.
API Mixed Workload
| Scenario | Throughput | P95 | Peak RSS | Dropped | Retries | Dead | SQLite | Reports |
|---|---|---|---|---|---|---|---|---|
| API mixed, fleet c20 Conservative concurrency on a small box | 10 runs/s | 1000ms | 106MB | 0 | 3 | 0 | 0 | txt / json |
| API mixed, fleet c50 Higher I/O-bound concurrency without changing the workload shape | 31 runs/s | 1000ms | 126MB | 0 | 32 | 0 | 0 | txt / json |
| API mixed, fleet c200 A practical upper sweep on one local process | 96 runs/s | 1000ms | 149MB | 0 | 105 | 0 | 0 | txt / json |
Bounded Scraper Workload
| Scenario | Throughput | P95 | Peak RSS | Dropped | Retries | Dead | SQLite | Reports |
|---|---|---|---|---|---|---|---|---|
| Scraper bounded, cap 1 Protect a tiny box with a hard browser cap | 26 runs/s | 500ms | 207MB | 367 | 17 | 0 | 0 | txt / json |
| Scraper bounded, cap 5 A practical middle ground for browser work | 30 runs/s | 2500ms | 358MB | 210 | 17 | 1 | 0 | txt / json |
The c10 scraper run is intentionally left out until its final report is available. The c1 and c5 rows already show the memory-versus-admission tradeoff clearly.
Trust checks
The public suite is only half the story. These runs are how we validate burst recovery, degraded downstream behavior, long-run stability, and restart behavior against the same SQLite store.
| Scenario | Key result | Why it matters | Reports |
|---|---|---|---|
| Burst recovery, API mixed | 79 runs/s avg, peak queue 47, 0 SQLite errors | Shows the queue can absorb a burst and recover cleanly when load falls. | txt / json |
| Degraded downstream, API mixed | 49 runs/s avg, p95 2500ms, dead letters 1 | Shows how retries, queueing, and latency behave when downstream systems slow down. | txt / json |
| Overnight soak, API mixed | 985,742 runs completed over 6 hours, 0 SQLite errors | This is the long-run stability proof for the normal automation workload. | txt / json |
| Overnight soak, bounded scraper | Peak RSS 531MB, scraper drops 2,749, 0 SQLite errors | Shows what a browser-heavy workload really costs and why per-process caps matter. | txt / json |
| Restart drill, API mixed | 1 deliberate restart, 24,773 runs completed, 0 SQLite errors | Shows restart and resume behavior against the same SQLite store under live load. | txt / json |
| Restart drill, scraper load | 1 deliberate restart, peak RSS 615MB, 0 SQLite errors | Shows that recovery still behaves under expensive browser-like work. | txt / json |
Why SQLite here
SQLite is a good choice for Yella because the engine is intentionally single-process and local-disk-first. WAL mode gives concurrent readers and writers without introducing a remote database hop or a separate operational stack. The point is not theoretical maximum throughput. The point is a small, solid system with clear failure modes.
On Yella hosting, Litestream continuously ships WAL changes off-box. That gives you a low-friction recovery story on top of the local runtime: local SQLite for live execution, WAL for durability, and continuous replication for restore.
Best practices
Tune global concurrency by workload latency. Slow API-bound handlers benefit from more in-flight work. Fast, write-heavy handlers should stay more conservative. The engine concurrency docs explain the engine-side control.
Use per-process caps for expensive tasks. Browser automation, large payload transforms, and other memory-heavy work should have their own limit so they cannot dominate a small machine. We use the browser connector as a scraper-style workload in the benchmark suite, and the bounded scraper results show exactly why those caps matter.
Expect bursts to trade throughput for queueing. The right question is not whether the queue ever grows. The right question is whether it drains cleanly when the burst passes.
Measure degraded downstreams separately. When Stripe, Slack, Chromium, or an LLM slows down, that becomes your bottleneck long before SQLite does.
Use the trust suite, not just the benchmark suite. A five-minute benchmark tells you about shape. An overnight soak and a restart drill tell you whether the system is boring under stress.
When should you probably not choose Yella
If your requirement is sustained thousands of events per second with extremely tight catastrophe-loss windows, you are in distributed-log territory, not embedded-runtime territory. That comes with a very different price and operational model.
Yella is for reliable automation on small infrastructure. That boundary is deliberate.