Scaling Guide
When to Scale
| Signal | Action |
|---|---|
| API p95 > 500ms consistently | Add backend workers or optimize queries |
| Database CPU > 70% | Add read replicas or optimize queries |
| QR scan volume > 10k/day | Consider CDN for SDK, caching for scan resolution |
Database Read Replicas
When the primary database becomes a bottleneck for read-heavy widget queries:
Setup
# config/settings.py
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql',
# ... primary connection
},
'replica': {
'ENGINE': 'django.db.backends.postgresql',
# ... replica connection
}
}
DATABASE_ROUTERS = ['config.db_router.ReadReplicaRouter']
Router
# config/db_router.py
class ReadReplicaRouter:
def db_for_read(self, model, **hints):
# Route widget reads to replica
if model._meta.app_label == 'inventory':
return 'replica'
return 'default'
def db_for_write(self, model, **hints):
return 'default'
def allow_relation(self, obj1, obj2, **hints):
return True
def allow_migrate(self, db, app_label, model_name=None, **hints):
return db == 'default'
Considerations
- Replica lag: stock data may be 1-2 seconds stale — acceptable for display, not for transactions
- Always write to primary, read from replica only for non-critical queries
- Use
using('default')explicitly for reads that must be fresh (e.g., pre-transaction stock checks)
CDN for SDK Distribution
When widget deployments scale globally:
Setup
- Build SDK:
cd sdk && npm run build - Upload
dist/to CDN (CloudFront, Cloudflare R2, etc.) - Update SDK docs with CDN URL:
Cache Headers
Version the SDK path (/sdk/v1/, /sdk/v2/) so cache-busting is unnecessary.
Horizontal Backend Scaling
The default stack runs a single backend service. To run more than one backend
instance, place a load balancer (e.g. nginx, Caddy, or a cloud LB) in front of
several backend containers and round-robin /api/ requests across them.
Important: With multiple instances, ensure:
- Session/auth is stateless (JWT is, by default) or backed by shared storage
- File uploads go to shared storage (S3/NFS), not local disk
- Migrations run on only one instance before deployment