Scaling
Scale HelloJohn horizontally, configure Redis for distributed state, and tune for high-traffic workloads.
Scaling
HelloJohn is designed to scale horizontally. Multiple instances can run behind a load balancer with no shared in-memory state — all state lives in PostgreSQL and Redis.
Horizontal Scaling
Run multiple HelloJohn instances behind any load balancer:
┌─────────────────────────┐
│ Load Balancer │
│ (nginx / ALB / Caddy) │
└──────┬──────────┬───────┘
│ │
┌───────▼──┐ ┌────▼──────┐
│ HJ Pod 1 │ │ HJ Pod 2 │
└───────┬──┘ └────┬──────┘
│ │
┌──────▼──────────▼──────┐
│ PostgreSQL │
└──────────┬─────────────┘
│
┌──────────▼─────────────┐
│ Redis (optional) │
└────────────────────────┘No sticky sessions required. HelloJohn is fully stateless — any instance can handle any request.
Redis Requirement for Multi-Instance
When running multiple instances, Redis is required for:
| Feature | Without Redis | With Redis |
|---|---|---|
| Rate limiting | Per-instance (inconsistent) | Distributed (consistent) |
| Session cache | None | Shared cache across instances |
| Webhook event dedup | Not supported | Supported |
HELLOJOHN_REDIS_URL=redis://user:password@redis-host:6379/0For Redis Cluster:
HELLOJOHN_REDIS_URL=redis+cluster://node1:6379,node2:6379,node3:6379/0Database Connection Pooling
At scale, many HelloJohn instances each maintaining their own PostgreSQL connection pool can exhaust database connections. Use PgBouncer in transaction mode as a connection pooler:
PgBouncer (Docker)
# docker-compose addition
pgbouncer:
image: pgbouncer/pgbouncer
environment:
DATABASES_HOST: postgres
DATABASES_PORT: 5432
DATABASES_USER: hellojohn
DATABASES_PASSWORD: your_password
DATABASES_DBNAME: hellojohn
POOL_MODE: transaction
MAX_CLIENT_CONN: 1000
DEFAULT_POOL_SIZE: 25
ports:
- "6432:5432"Update HelloJohn to connect through PgBouncer:
HELLOJOHN_DATABASE_URL=postgresql://hellojohn:password@pgbouncer:6432/hellojohn?sslmode=requireManaged Connection Pooling
Most managed PostgreSQL providers offer built-in pooling:
- Neon: Connection pooling enabled by default (Supavisor)
- Supabase: Use the pooler connection string
- RDS Proxy: AWS-native connection pooler
Load Balancer Configuration
nginx Upstream
upstream hellojohn {
least_conn;
server hj-instance-1:8080;
server hj-instance-2:8080;
server hj-instance-3:8080;
keepalive 32;
}
server {
listen 443 ssl;
server_name auth.example.com;
location / {
proxy_pass http://hellojohn;
proxy_http_version 1.1;
proxy_set_header Connection "";
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
}AWS Application Load Balancer
- Target type: IP (for ECS) or Instance (for EC2)
- Stickiness: Disabled (HelloJohn is stateless)
- Health check path:
GET /health - Deregistration delay: 30s (matches HelloJohn's request timeout)
Cloudflare
If using Cloudflare as a reverse proxy:
- Enable HTTP/2 and HTTP/3
- Set Cache Everything to OFF for the HelloJohn domain
- Use Argo Smart Routing for multi-region performance
Autoscaling
Docker Swarm
services:
hellojohn:
image: ghcr.io/hellojohn/hellojohn:latest
deploy:
replicas: 2
update_config:
parallelism: 1
delay: 10s
resources:
limits:
cpus: "0.5"
memory: 512M
restart_policy:
condition: on-failureKubernetes HPA
See Kubernetes Deployment for the full HPA manifest.
spec:
minReplicas: 2
maxReplicas: 20
metrics:
- type: Resource
resource:
name: cpu
target:
type: Utilization
averageUtilization: 70
- type: Resource
resource:
name: memory
target:
type: Utilization
averageUtilization: 80Performance Tuning
HelloJohn Configuration
# Worker goroutines (default: 4x CPU count)
HELLOJOHN_WORKERS=16
# Database connection pool per instance
HELLOJOHN_DB_MAX_OPEN_CONNS=25
HELLOJOHN_DB_MAX_IDLE_CONNS=10
HELLOJOHN_DB_CONN_MAX_LIFETIME=5m
# HTTP timeouts
HELLOJOHN_READ_TIMEOUT=10s
HELLOJOHN_WRITE_TIMEOUT=30s
HELLOJOHN_IDLE_TIMEOUT=120sPostgreSQL Tuning
For a dedicated PostgreSQL server handling HelloJohn at scale:
# postgresql.conf
max_connections = 200
shared_buffers = 256MB # 25% of RAM
effective_cache_size = 768MB # 75% of RAM
work_mem = 4MB
maintenance_work_mem = 64MB
checkpoint_completion_target = 0.9
wal_buffers = 16MB
default_statistics_target = 100
random_page_cost = 1.1 # For SSD storageCapacity Planning
Approximate resource usage per HelloJohn instance:
| Metric | Light Load | Medium Load | Heavy Load |
|---|---|---|---|
| CPU | < 50m | 100-200m | 300-500m |
| Memory | 64 MB | 128 MB | 256 MB |
| DB connections | 5-10 | 15-20 | 20-25 |
| Requests/sec | < 50 | 50-500 | 500-2000 |
Rule of thumb: 1 HelloJohn instance handles ~500 sign-in requests per second on modern hardware. Scale horizontally beyond that.
MAU to Instance Count
| MAU | Typical Instances | Notes |
|---|---|---|
| < 10K | 1-2 | Single server fine |
| 10K–100K | 2-4 | Add Redis |
| 100K–1M | 4-10 | Managed DB recommended |
| 1M+ | 10+ | Multi-region, global DB |
Monitoring
Set up monitoring to detect performance issues:
Key Metrics to Track
| Metric | Alert Threshold |
|---|---|
| API p99 latency | > 500ms |
| Error rate | > 1% |
| Database connection pool saturation | > 80% |
| Redis memory usage | > 80% |
| CPU utilization | > 80% sustained |
Prometheus Metrics
HelloJohn exposes Prometheus metrics at GET /metrics:
# Scrape config for prometheus.yml
scrape_configs:
- job_name: hellojohn
static_configs:
- targets:
- hj-instance-1:8080
- hj-instance-2:8080
metrics_path: /metricsKey metrics exposed:
hellojohn_http_requests_total{method, path, status}
hellojohn_http_request_duration_seconds{method, path, quantile}
hellojohn_db_pool_connections{state}
hellojohn_auth_sign_ins_total{method, status}
hellojohn_webhook_deliveries_total{status}Zero-Downtime Deploys
HelloJohn supports zero-downtime rolling updates:
- Migrations must be backward-compatible (always additive, never drop columns used by old version)
- Run migrations before updating instances:
hj migrate - Rolling restart: replace instances one at a time
- Kubernetes handles this automatically with the rolling update strategy
# Manual rolling restart (Kubernetes)
kubectl rollout restart deployment/hellojohn -n hellojohn
# Watch rollout status
kubectl rollout status deployment/hellojohn -n hellojohn