I’m building a Go backend for a museum membership and visitor management system, and I’m looking for some advice on designing a fast and reliable QR check-in service.
Access is granted or denied in just a few milliseconds.
The challenge is that some museums can have hundreds of visitors arriving within a very short period, so the validation endpoint needs to remain responsive even during peak traffic.
Right now, I’m thinking about using:
Go for the backend API
PostgreSQL for membership data
Redis to cache active memberships
Docker for deployment
I’m also wondering whether the QR code should contain only a member ID or a signed token (such as a JWT) to reduce unnecessary database lookups while keeping the system secure.
For developers who have built similar systems or high-traffic APIs:
How would you structure the validation process?
Is Redis the right choice for this type of workload?
What strategies have helped you reduce API response times under heavy traffic?
Are there any security or scalability considerations that are easy to overlook?
I’d really appreciate hearing about your experiences and any lessons you’ve learned from building production Go services.
Go ist extremely fast in handling API calls and PostgreSQL is extremely fast in handling lookups. If we are talking about less than 10 Million total memberships and less than 1000 requests per second in peak times? Then I would venture you don’t need Redis or any tricks.
My path to a solution would look like this:
Build the simplest service - single Go Backend, single PostgreSQL table.
Load testing with comparable numbers (you can use a simple Go program to fill the database with lots of rows and then generate a lot of concurrent traffic)
If this works with good performance you are done, happy and have saved yourself a lot of time and resources otherwise wasted in premature optimization
If the performance is to slow optimize your database in this order A:indexes B:partitioning
If performance is still too slow you can think about a simple cache with a big HashMap in Go (check Memory requirements and use a callback in PostgreSQL to update the HashMap on changes, if they even can happen live throughout the day)
If the HashMap is too big then you can introduce an extra caching layer like Redis
If the Go backend is too slow you can scale up with multiple instances
In my experience most systems like this are over-engineered in the wrong places and lacking in the problematic ones. Keeping your system simple will bring a lot of upsides in maintenance and performance, because you spend less time in switching between systems.
JWT is a very enticing prospect, since it brings some amazing benefits, but also some downsides:
With JWT you don’t need access to any server or database, your client can verify claims and signature offline - so no load on your server at all and museums with bad internet connection don’t have any problems, which is perfect resilience
With JWT people need a new QR code, if they want to extend their membership beyond the original time (since the validity time is encoded in the QR code)
With JWT your QR Code needs to include a lot more data and thus becomes more complex and harder to read. So if the lighting is bad, or the scanner is dirty, or the card is old - it will be harder to scan the QR code reliably without errors.
Wit JWT if you want a way to revoke a lost membership card, you have to introduce a revoke list, which your app needs to download periodically to check for revocations.
So you will have to weigh these pros and cons for the decision.
Obviously you know your problem better than me, but my gut feeling here is you’re overengineering.
The challenge is that some museums can have hundreds of visitors arriving within a very short period, so the validation endpoint needs to remain responsive even during peak traffic.
Consider the Louvre, which Google claims is the busiest museum in the world. It gets 30k visitors per day (peak), and is open for ~8 hours (according to a quick Google search). Assuming those visitors all arrive during their peak hours of 11am-3pm, and are spread evenly across that time, that’s a little over 2 (two) visitors per second. This is obviously all back-of-a-napkin and roughly averaged, but it’s reasonable to assume it’s the right order of magnitude.
If so: you don’t need:
Access is granted or denied in just a few milliseconds
A response in even 100-200ms will seem perfectly responsive to an actual human who has scanned a QR code (after standing in a queue for a while).
As Falco says above, Go and Postgres will be Just Fine for your use case for a long time (and/or a lot of museums). I strongly doubt you’ll ever need caching per se (how often does any one person visit a museum? how long do you plan to cache records for?), when a better solution might be “ensure your whole Postgres dataset fits in RAM”.