Load test · October 2025
Published · 4 min read
Why I ran a ticketing backend on two Raspberry Pis
Tixin, the pilot that became tunyt, ran on a Kubernetes cluster I built by hand on two Raspberry Pi 5s, and held about 16K requests a second in a load test. The decisions behind it, what CQRS really costs, and why the number I'm proudest of is one I refuse to quote as capacity.
It sounds like a joke: Tixin's backend ran on two Raspberry Pis.
It also held about 16,000 requests a second in a load test. This is how, and more importantly why I made each choice.
Decision 1: hardware that's too small on purpose
Tixin was the ticketing pilot that later grew into tunyt. I built its Kubernetes cluster by hand, on two Raspberry Pi 5s.
On a big cloud instance, a slow query costs you nothing you can see. You have headroom, so you shrug, and the slow query becomes a permanent resident. On two small boards there's no headroom to hide anything in. Every slow query, every chatty service and every lazy default shows up immediately, and the only way past it is to fix it. You can't buy your way out.
I wanted that pressure, and I'd do it again.
The problem ticketing actually has
Ticketing traffic isn't a curve. It's a cliff.
For most of the day almost nothing happens. Then sales open and a lot of people try to do things at the same moment. Most of them are reading: looking at the event, checking prices, refreshing to see what's left. A smaller number are writing, which means actually booking. If reads and writes share one path and one database, they slow each other down at exactly the moment it matters.
Decision 2: split reads from writes
So Tixin split them. The pattern is called CQRS, command query responsibility segregation, which is a long name for a simple idea: the code that changes things and the code that reads things don't have to share a path.
Commands, like "book this ticket", go onto Kafka. Queries, like "show me this event", never touch that path and are served from a Redis cache. A person refreshing an event page and a person booking a seat are now doing completely different work on completely different infrastructure.
Decision 3: Kafka on the write path
Kafka is an append-only log, and appending is about the cheapest thing a computer can do. A burst of bookings becomes a burst of appends. The log absorbs the spike, and the work behind it catches up at its own pace instead of falling over at the peak. At peak, the pipeline sustained more than 50K events a second.
What CQRS costs you
CQRS gets sold as free. It isn't.
When reads come from a cache and writes go somewhere else, there's a moment where the two disagree. A booking has happened and the read side hasn't caught up yet. Usually the window is tiny, but in ticketing it forces real questions. What does someone see in that window? Where do you make sure a seat can't be sold twice? On which side does the truth live?
The pattern doesn't answer those questions. It makes you answer them on purpose instead of by accident, which is most of what good architecture does.
Decision 4: tune the boring parts
Splitting the paths was most of the win. The rest was unglamorous. After tuning PostgreSQL and MongoDB on the booking read path, query latency dropped by 70%. Rebuilding the CI/CD automation cut deploy time by 60%.
None of that is clever, and all of it was forced by the hardware. On a big instance I'd have shrugged. On a Pi it was the difference between holding and falling over.
Decision 5: refuse to quote the number
This is the decision people find strangest. I don't quote 16K as Tixin's capacity.
A load test isn't production. I chose the workload, so the result tells you where the limits are under conditions I picked. It says nothing about real people on flaky phones, retries, double-taps on the pay button and everything else real users do.
So 16K is evidence of one specific thing: the architecture wasn't the bottleneck. That's useful to know. It isn't a promise, and the fastest way to lose a senior engineer's trust is to dress one up as the other.
What I took into tunyt
Tixin is retired now, but two things from it run through everything I build.
One is the habit of measuring first and fixing what the measurements show, and not what you suspect. The other is the pattern itself. I later pulled the read and write split out of Tixin into its own repository, cqrs, so it could be read without a product wrapped around it.
The Pis didn't make Tixin fast. They made it impossible to pretend it was.