Sugatraj Sarwade
Command Palette

Search for a command to run...

Architecting a High-Performance Multi-Tenant SaaS with FastAPI, Redis, and React

4 min read

Architecting a High-Performance Multi-Tenant SaaS with FastAPI, Redis, and React

Building scalable multi-tenant SaaS applications requires careful architectural choices around data isolation, latency reduction, and release management. In this post, I break down key lessons learned from building and deploying MenuMitra—a production restaurant management platform handling 500+ daily orders across 20+ live outlets.


Key Challenges in Multi-Tenant Architectures

When handling live point-of-sale (POS) operations and customer ordering across multiple outlets simultaneously, two critical issues arise:

  1. Database Contention & Query Latency: Multiple outlets querying shared database tables causes lock contention and slow response times during peak hours.
  2. Real-time Synchronization: Updating menus, pricing, and POS software across multiple physical terminals must occur seamlessly without forcing app restarts or dropping live orders.

1. Database Indexing & Multi-Tenant Schema Strategy

Rather than deploying isolated database instances per tenant (which inflates infrastructure costs), we adopted a shared-database, tenant-isolated schema strategy using indexed tenant_id / outlet_id foreign keys in MySQL.

Optimization Highlights:

  • Composite Indexing: Added composite indexes on (outlet_id, is_active, created_at) for high-frequency queries like menu fetching and order history.
  • Connection Pooling: Configured SQLAlchemy async engine pooling with pool_size=20 and max_overflow=10 to avoid connection exhaustion under burst traffic.
# FastAPI Async Dependency for Tenant Context
from fastapi import Header, HTTPException, Depends
from sqlalchemy.ext.asyncio import AsyncSession

async def get_current_tenant_id(x_outlet_id: str = Header(...)) -> str:
    if not x_outlet_id:
        raise HTTPException(status_code=400, detail="Missing X-Outlet-ID header")
    return x_outlet_id

2. Low-Latency Caching with Redis & TanStack Query

To achieve sub-50ms data access latency for menu items and pricing tables:

  • Redis Cache Layer: Read-heavy endpoints (such as active customer menus) are cached in Redis with a 5-minute TTL.
  • Cache Invalidation on Mutation: Any admin menu update triggers an immediate invalidation of the specific outlet's Redis cache key (outlet:{id}:menu).
  • Frontend Optimistic State: The React.js POS frontend utilizes TanStack Query (useQuery and useMutation) with optimistic updates, allowing cashier UI actions to render instantly while background synchronization executes.

3. Zero-Downtime Releases with PM2 & Nginx

Deploying updates to a live production platform processing orders requires zero downtime:

  • PM2 Process Management: Running FastAPI under uvicorn processes managed by PM2 in cluster mode across available CPU cores.
  • Nginx Reverse Proxy & SSL Termination: Nginx manages load balancing, rate limiting, and gzip compression for static assets and API payloads.
  • Staged Rollouts: Release automation scripts ensure new backend container builds pass health checks before Nginx routes live traffic to the updated process.

Conclusion & Key Takeaways

  1. Index Early: Proper composite indexes on tenant IDs cut query times by over 70%.
  2. Cache Read-Heavy Assets: Caching customer-facing menus in Redis keeps backend database load predictable.
  3. Automate Deployments: PM2 + Nginx zero-downtime reloads allow shipping features continuously during operating hours.

Stay tuned for more deep dives into Python backend engineering, React Native mobile apps, and stateful AI agent workflows!

Command Palette

Search for a command to run...