Orbitra Load Test: 480 req/s, Zero Errors, p99 182 ms
A tracker lives under load: every click is a write, a uniqueness check and a redirect. So before tagging v1.6.3 we rebuilt the uniqueness pipeline on a production-scale database and hammered it with stepped k6 load on our test bench. Here are the numbers.
Why we ran the test
v1.6.3 made the duplicate filter honest toward mobile NAT: the uniqueness key became IP + user agent instead of IP alone. Behind one carrier NAT IP, two different people used to collide β the second visitor got a subid with no click row in the database, and his conversion had nothing to attach to.
The change required a new ua_hash column and a composite index on a database already carrying real volume. That is the scariest kind of migration β so before tagging the release, we replayed the exact path a user install takes, on production-scale data.
The test bench
- Cloud container: 2 vCPU / 7 GB RAM, Ubuntu 24.04. nginx 1.24 + PHP-FPM 8.3 in the stock configuration exactly as
install.shleaves it β no manual tuning in the default runs. - SQLite in WAL mode. k6 runs on the same box (~0.3 core of overhead), so the numbers are, if anything, understated.
- Stepped constant-arrival-rate load: 60 s per step, 5 s timeout, redirects not followed.
- Campaign: one regular stream β offer redirect, uniqueness window 24 h over IP + User-Agent. Unique requests use a random public IP and a unique UA; repeat visitors use 50 fixed IP/UA pairs.
- Three databases: ~50k clicks (sanity), 3.2M clicks (main), and a 3.9M-click pre-upgrade snapshot for the migration rehearsal (~1M of them in the last 24 hours, ~8,500 clicks per NAT IP).
- Paired runs: v1.6.2 served the same database next to the new build; after any collapsed run we waited out the FPM backlog before measuring again.
Results
| Run (3.2M-click DB unless noted) | rps | Errors | p99 | CPU |
|---|---|---|---|---|
| Unique visitors, stock FPM (~50k DB) | 480 | 0 | 46 ms | 76% |
| Unique visitors | 480 | 0 | 75 ms | 77% |
| Repeat visitors (50 NAT pairs) | 480 | 0, ~1% via spool | 14 ms | 61% |
| Unique + panel open, reports refresh every 2 s | 320 | 0 | 587 ms | 81% |
| Unique, tuned FPM/nginx, geo DB on | 560 | 0 | 187 ms | 94% |
| Unique, deliberately overloaded | 800 | 27% timeouts | β | 97% |
And the migration rehearsal on the 3.9M-click snapshot: the first request after the βupgradeβ ran migration 55 in 0.04 s, one cron pass built the index in 4.8 s and backfilled 1,008,085 UA hashes in 29.6 s β and NAT traffic at 480 req/s went through with zero errors and a 182 ms p99, every subid in the database. The previous version lost 27,775 subids in the same scenario; the new one lost zero.
Two details worth calling out. Even at 800 req/s β past the breaking point β every single served redirect was still written to the database: overload degrades latency, not durability. And hash correctness got its own check: a 2,000-row sample verified against PHP’s crc32 β zero mismatches.
The economics: one click costs about 2.5 ms of CPU, which puts the ceiling of a 2 vCPU box at ~550β600 req/s, CPU-bound. On 4 vCPU the same math extrapolates to ~1,000 req/s β measured on paper only, and labeled as such.
What the stress test caught before release
Half the value of the run is the bugs it flushed out β all fixed before the tag:
- Unclosed cursors on the click path caused instant SQLITE_BUSY_SNAPSHOT locks β returning visitors’ clicks went to the spool instead of the database.
- The debounce was keyed by IP alone, so NAT neighbours lost clicks and received a subid that did not exist.
- Report date filters used
date(created_at, tz)and missed the index β reports crawled on big databases. - A pixel INSERT had 23 columns and 22 values, so pixel clicks were silently dropped.
- Legacy rows with a NULL hash kept the NAT collapse alive for one uniqueness window after the upgrade.
Why it holds up
- SQLite in WAL mode, one file, zero extra services. No ClickHouse, Redis or MySQL eating RAM β the bottleneck is disk I/O, and it scales with your VPS.
- A write spool queue: if SQLite locks up for a moment, the click lands in a file queue that a per-minute cron flushes β the visitor’s redirect never waits, and the click is never lost.
- Uniqueness probes go by index: each click carries a compact crc32 UA hash, and the four uniqueness checks per click search the (ip, ua_hash, created_at) index instead of scanning all of an IP’s clicks.
Honest caveats
- Tested on the v1.6.3 release candidate. The storage architecture β SQLite, spool queue, indexes β is unchanged in current releases.
- Our bench is not your VPS: absolute numbers move with hardware. The ratios and the failure mode are what transfer.
- The 4 vCPU figure is an extrapolation, not a measurement β we say so rather than round it up.
- This was a synthetic stress run plus a production-scale migration rehearsal β not a real-traffic A/B against other trackers.
What this means for you
A modest VPS β the class most guides recommend for a start β already has headroom for serious daily volumes, and no per-event billing waits above it. If you are on Keitaro because “SQLite won’t survive”, this is the dataset to look at.
Orbitra installs on a clean Ubuntu server in about 15 minutes β one command, and the live demo is open if you want to poke around the panel first.
FAQ
What hardware was the test run on?
A cloud container with 2 vCPU and 7 GB RAM on Ubuntu 24.04, nginx and PHP-FPM in the stock configuration install.sh leaves behind, and k6 on the same box β so the numbers are, if anything, understated. Absolute numbers always depend on your VPS: you scale the server, not the tariff.
Do these results apply to the current version?
The test ran on the v1.6.3 release candidate. The storage architecture (SQLite + write spool + uniqueness indexes) is the same in current releases; changes since then are features and UI.
Why SQLite instead of ClickHouse?
One file, WAL mode, no separate database server. At low-to-mid volumes the bottleneck is disk I/O, not RAM, so a cheap VPS goes a long way. For sustained millions of clicks per day, a columnar architecture is stronger β we say so openly.
Did the upgrade lose any data?
In the repeat-visitor scenario the previous version lost 27,775 subids; the new one lost zero. Reports over 3.2M clicks matched the previous version row for row.
Related reading
Voluum Alternative: Free Self-Hosted Tracker
Voluum starts at ~$149/mo. Orbitra is a free self-hosted tracker with built-in cloaking and 50+ CPA integrations. Full comparison and migration guide.
Read article βKeitaro Alternative: Free Self-Hosted Tracker
The closest self-hosted competitor β but the license starts at $79/mo. Feature comparison plus one-click campaign import.
Read article β