I’ve previously shared how I approach a backend system in my post How I Optimize a Backend System. Today, I’ll walk you through another story how I applied those principles in a real-world scenario.
One fine day, a client reached out to me in a panic: Their backend system was constantly overloaded, leading to high latency and a seriously degraded user experience. After digging in, I discovered a small change that delivered jaw-dropping results, boosting throughput by 30% without upgrading hardware or scaling the database. Let’s explore it together!
Pinpointing the Cause of the Bottleneck
The client’s system included a management dashboard using Role-Based Access Control (RBAC):
- Admin: Full system management rights.
- Editor: Can edit content but not manage users.
- Viewer: Read-only access to data.
Most requests required user authentication to verify access rights. After some analysis, I identified the main bottleneck in the database layer. On average, each request triggered three queries, and I wanted to cut that number down. One of those queries came from the authMiddleware
to fetch user info:
This query took about 0.01 seconds per call:
What’s the Problem? If the system handles 1000 requests per second, just authenticating users generates 1000 queries per second to the database! Meanwhile, the database can only manage around 1000 queries per second total. I asked myself: Could I eliminate this query entirely?
Solution: Embedding Roles and Permissions in JWT
In reality, most requests only need userId
, role
, and permissions
for authorization. So why not store this info directly in the JWT payload? This way, we can eliminate the unnecessary database query.
The new JWT would look like this:
Update authMiddleware
to use data from the JWT instead of querying the database:
Thanks to this, each request saves one database query, reducing the average number of queries from 3 to 2.
Handling Changes in Permissions
But hold on! If a user’s role or permissions change, or if the user is deleted, the old token remains valid. This could lead to a security vulnerability.
My solution is to use Redis to manage token validity:
- When a role or permissions change, or a user is deleted, set a flag in Redis:
tokenPayloadExpired::<userId>
.
Update authMiddleware
:
Results Achieved
✅ Average number of queries per request dropped from 3 to 2.
✅ Average response time reduced by 80-90ms per request.
✅ System throughput increased by about 30%.
✅ Database load decreased significantly, making the system more stable.
Summary
If you’re facing a similar situation, give this approach a shot! 🚀
How have you optimized authentication in your projects? Feel free to share your thoughts—hehe!