smart-chatbot-api / docs /scaling_reference.md
GitHub Actions
Deploy from GitHub Actions (2026-05-31 09:04 UTC)
55c0d78
|
Raw
History Blame Contribute Delete
4.75 kB
# Scaling Reference
**Purpose:** What to know and what to change when traffic grows. Written at V1 (sync, low-traffic SME demo). Read this before making any scaling decision.
---
## Current Architecture (V1 Baseline)
| Layer | Technology | Limit |
| ------------- | ---------------------------- | ----------------------------------------------------- |
| Web framework | FastAPI (sync, thread pool) | ~200–500 concurrent requests before thread exhaustion |
| DB driver | psycopg2 (sync) | Fine up to ~100 concurrent DB queries |
| DB | PostgreSQL (single instance) | Fine up to ~500–1000 connections with PgBouncer |
| LLM | Groq (sync HTTP call, 1–3s) | **Real bottleneck β€” async won't help here** |
| Vector DB | ChromaDB (in-process) | Fine for low-volume; becomes a bottleneck at scale |
| Deployment | Single Docker container | No horizontal scaling |
---
## The Real Bottleneck: LLM Latency
Every `/chat` request waits 1–3 seconds for the LLM response. At V1 traffic (5–50 concurrent users), this is fine. At scale:
- Async DB saves ~5ms per request
- LLM call costs 1,000–3,000ms per request
**Conclusion:** Optimizing the DB layer before solving LLM latency is premature. Address LLM first.
---
## Scale Thresholds and What to Do
### < 100 concurrent users β€” Do nothing
Current sync architecture handles this comfortably. Focus on product.
### 100–500 concurrent users
1. **Add PgBouncer** β€” connection pooling in front of PostgreSQL. Prevents DB connection exhaustion. Config change only, no code change.
2. **Add Redis caching** β€” cache system prompts and KB entries per tenant. Eliminates redundant DB reads on every `/chat` call.
3. **Horizontal scale** β€” run 2–3 replicas behind a load balancer (Nginx/Traefik). Stateless FastAPI app supports this with zero code changes.
### 500–5,000 concurrent users
4. **LLM request queue** β€” move LLM calls to a background worker (Celery + Redis). `/chat` returns a job ID immediately; client polls for the result. Eliminates thread exhaustion from long-running LLM calls.
5. **ChromaDB β†’ managed vector DB** β€” replace in-process ChromaDB with Qdrant or Weaviate (dedicated service). In-process ChromaDB doesn't scale horizontally.
6. **Read replicas** β€” add PostgreSQL read replicas for conversation history and KB reads.
### > 5,000 concurrent users
7. **Async migration** β€” switch to `asyncpg` driver + async SQLAlchemy + async FastAPI. Requires rewriting all `get_db` dependencies, session management, and test fixtures. High cost β€” only justified at this scale.
8. **Kubernetes** β€” horizontal pod autoscaling, per-tenant rate limiting at ingress, proper resource limits.
9. **LLM provider strategy** β€” provider failover, per-model rate limits, tenant-level concurrency caps.
---
## Async Migration Path (when justified)
When async is finally needed, the migration order is:
1. Change `DATABASE_URL` scheme: `postgresql://` β†’ `postgresql+asyncpg://`
2. Replace `SessionLocal` with `AsyncSession` in `database.py`
3. Replace `get_db` dependency with async generator
4. Change all router functions from `def` to `async def`
5. Replace all `db.scalars(...)` with `await db.scalars(...)`
6. Rewrite all test fixtures in `conftest.py` (async session, async client)
7. Run full test suite β€” expect failures in every file that touches the DB
**Cost:** ~2–3 days of pure refactoring with no new features. Only do this when load data proves it's needed.
---
## One-Way Doors to Avoid Before Scale
These decisions become very expensive to undo at scale β€” make them correctly from the start:
| Decision | Risk if wrong |
| ------------------------------- | ----------------------------------------------------------------------- |
| No `credit_events` table | Can't reconstruct billing history β€” dispute with no evidence |
| No tenant isolation in ChromaDB | Data leakage between tenants β€” legal liability |
| No API versioning (`/api/v1/`) | Can never change request/response contract without breaking all clients |
| No `llm_models` versioned rates | Can't prove which rate applied to a historical charge |
---
## Summary
> Sync is fine for V1 and beyond. The LLM call is the bottleneck, not the DB driver.
> Add PgBouncer + Redis + horizontal replicas before ever touching async.
> Migrate to async only when data shows you need it β€” and budget 2–3 days for the rewrite.