Labour-Connect / SCALING.md
Abhisingh-18's picture
Mirror of github.com/Abhisingh18/Labour-Connect
b24b684 verified
|
Raw
History Blame Contribute Delete
5.15 kB
# Labour Connect — Scaling Guide (20k users)
You're right: **20k registered / 2k DAU / 200–500 peak concurrent / 500–1000
bookings’ day is comfortably a single-server workload.** This codebase is already
shaped for it. This doc maps your target architecture to what exists and what to
add, in order of impact.
## Target topology
```
Flutter App + React Admin
│ HTTPS (TLS 1.3)
Nginx (LB + TLS + rate-limit + static)
Gunicorn + Uvicorn workers (FastAPI) ← stateless, scale horizontally
│ │ │
▼ ▼ ▼
Redis PostgreSQL Object Storage (S3)
(cache + (primary + (KYC docs, images)
rate-limit read replica
+ Celery at scale)
broker)
Background workers (Celery/RQ) ← SMS/OTP, FCM push, image processing
```
## Capacity math (sanity check)
- 2k DAU, peak 500 concurrent. Even at 5 req/user-action, that's a few hundred
req/s **burst**, not sustained. A single 4-vCPU box running Gunicorn handles this.
- 500–1000 bookings/day = **< 1 write/sec average**. The DB is essentially idle;
reads (worker search, listings) dominate → cache those.
## 1. App server — run it like production
Replace the single Uvicorn dev process with Gunicorn managing Uvicorn workers:
```bash
gunicorn app.main:app \
-k uvicorn.workers.UvicornWorker \
-w 4 --threads 2 \
-b 0.0.0.0:8000 \
--timeout 60 --max-requests 1000 --max-requests-jitter 100
```
Rule of thumb: `workers = 2 × vCPU + 1`. Stateless app → add boxes behind Nginx
when needed. **One caveat:** our in-memory rate limiter (`core/rate_limit.py`) is
per-process — with multiple workers/instances, **move it to Redis** (same interface,
swap the dict for Redis INCR + EXPIRE). Until then run `-w 1` or accept per-worker limits.
## 2. PostgreSQL — already indexed
Move off SQLite to Postgres (just unset `DATABASE_URL`, set `POSTGRES_*`). The
schema already has the indexes that matter at this scale:
- `bookings`: indexed on `customer_id`, `worker_id`, `status`
- `worker_profiles`: indexed on `category_id`; search filters on verified/available
- `users`: unique indexes on `phone`, `email`
At 20k users no sharding/partitioning is needed. Add a **read replica** only when
read latency climbs; route `GET` traffic to it. Connection pooling via `pool_pre_ping`
is on; add **PgBouncer** if you run many app instances.
## 3. Redis — the highest-leverage addition
Use it for three things (one dependency, big wins):
1. **Cache** hot reads: category list, worker search results (short TTL 30–60s).
Wrap `crud.category.list_all` and `crud.worker.search` with a cache-aside helper.
2. **Distributed rate-limiting / OTP lockout** (replaces in-memory store).
3. **Celery broker** (below).
## 4. Background workers (Celery/RQ)
Anything slow or external must leave the request path:
- **OTP SMS send** (when `OTP_MOCK=false`) — fire-and-forget task.
- **FCM push notifications** (new booking → worker, status change → customer).
- **Image processing / AV scan** for KYC uploads.
- **Rating recompute** could be async on review create.
```
celery -A app.worker worker --concurrency=4
```
(Add `app/worker.py` with a Celery app bound to the Redis broker.)
## 5. Object storage (S3)
KYC docs + profile images go to **private S3** (not the app disk). The upload
endpoint already centralises this in `services/uploads.py` — swap the local write
for `boto3.put_object` with server-side encryption, return a key, serve via signed URLs.
## 6. Nginx (LB + edge)
- TLS 1.3 termination, HTTP→HTTPS redirect (HSTS header already emitted by the app).
- `limit_req` zone for a global per-IP throttle (defence beyond app rate-limits).
- Serve the built React admin (`admin/dist`) as static; proxy `/api` to Gunicorn.
- gzip/brotli; cache static assets.
## Single-box vs scale-out
| Stage | Setup | Good for |
|-------|-------|----------|
| **MVP / launch** | 1 box: Nginx + Gunicorn(4w) + Postgres + Redis; S3 for files | up to ~20–50k users |
| **Growth** | Separate DB (managed RDS) + Redis (ElastiCache); 2–3 app boxes behind ALB; Celery workers | 100k+ |
| **Scale** | Read replica + PgBouncer, autoscaling app tier, CDN for assets | 500k+ |
## What to add to this repo next (priority order)
1. `gunicorn` to `requirements.txt` + a `gunicorn.conf.py` (prod run).
2. Redis-backed `rate_limit` + cache-aside on category/worker reads.
3. Celery app + tasks for SMS + FCM push.
4. S3 storage backend in `services/uploads.py`.
5. `docker-compose.prod.yml`: nginx + api(gunicorn) + postgres + redis + worker.
> Bottom line: the code is already “single-server for 20k” ready. The only
> correctness change required before horizontal scale-out is **moving the rate
> limiter to Redis**; everything else (replica, Celery, S3, CDN) is additive.