- Create comprehensive smoke test suite for post-deployment validation - Implement 4 test categories: infrastructure, service, authentication, order flow - Add graceful failure handling for unavailable services - Create automated test runner script with multiple modes (fast, verbose, category) - Document known blockers from Agent 96 (Backtesting/ML services) - Add 30+ individual smoke tests covering critical paths - Enable smoke-tests feature in tests/Cargo.toml - Create detailed README with usage and troubleshooting Test Categories: 1. Infrastructure Health: PostgreSQL, Redis, Vault, InfluxDB, Prometheus, Grafana 2. Service Health: Trading Service, API Gateway (+ blocked: Backtesting, ML) 3. Authentication Flow: JWT, sessions, revocation, rate limiting 4. Basic Order Flow: Order CRUD, positions, order history Features: - Configurable timeouts (5-10s per test) - Environment variable configuration - Graceful service unavailability handling - Parallel and sequential execution modes - Detailed pass/fail reporting Usage: ./run_smoke_tests.sh # Run all tests ./run_smoke_tests.sh --fast # Critical tests only ./run_smoke_tests.sh --verbose # Debug logging ./run_smoke_tests.sh --category infrastructure Blocked Tests (marked with #[ignore]): - Backtesting Service (config issues from Agent 96) - ML Training Service (config issues from Agent 96) Wave 125 Phase 3B - Deployment Excellence
1284 lines
36 KiB
Markdown
1284 lines
36 KiB
Markdown
# Load Balancing & Horizontal Scaling Guide
|
||
|
||
**Last Updated**: 2025-10-07
|
||
**Status**: Production Ready
|
||
**Audience**: DevOps, SRE, Platform Engineers
|
||
|
||
---
|
||
|
||
## Table of Contents
|
||
|
||
1. [Architecture Overview](#architecture-overview)
|
||
2. [Load Balancer Architecture](#load-balancer-architecture)
|
||
3. [API Gateway Load Balancing](#api-gateway-load-balancing)
|
||
4. [Backend Service Scaling](#backend-service-scaling)
|
||
5. [Horizontal Pod Autoscaling (HPA)](#horizontal-pod-autoscaling-hpa)
|
||
6. [Database Scaling](#database-scaling)
|
||
7. [Redis Scaling](#redis-scaling)
|
||
8. [Performance Targets](#performance-targets)
|
||
9. [Cost Optimization](#cost-optimization)
|
||
10. [Monitoring & Alerting](#monitoring--alerting)
|
||
|
||
---
|
||
|
||
## Architecture Overview
|
||
|
||
### Scaling Topology
|
||
|
||
```
|
||
Internet
|
||
|
|
||
v
|
||
┌───────────────┐
|
||
│ Load Balancer │ (nginx/HAProxy/AWS ALB)
|
||
│ TLS Termination│
|
||
│ DDoS Protection│
|
||
└───────┬────────┘
|
||
|
|
||
┌─────────────────┼─────────────────┐
|
||
v v v
|
||
┌─────────┐ ┌─────────┐ ┌─────────┐
|
||
│ Gateway │ │ Gateway │ │ Gateway │
|
||
│ Pod 1 │ │ Pod 2 │ │ Pod N │
|
||
└────┬────┘ └────┬────┘ └────┬────┘
|
||
| | |
|
||
└────────────────┼─────────────────┘
|
||
|
|
||
┌────────────────┼────────────────┐
|
||
v v v
|
||
┌─────────┐ ┌──────────┐ ┌──────────┐
|
||
│ Trading │ │Backtesting│ │ ML Train │
|
||
│ Service │ │ Service │ │ Service │
|
||
│ (1-N) │ │ (1-M) │ │ (1-K) │
|
||
└────┬────┘ └─────┬─────┘ └────┬─────┘
|
||
| | |
|
||
└─────────────────┼───────────────┘
|
||
|
|
||
┌─────────────────┼─────────────────┐
|
||
v v v
|
||
┌──────────┐ ┌──────────┐ ┌──────────┐
|
||
│PostgreSQL│ │ Redis │ │InfluxDB │
|
||
│(Primary +│ │ Cluster │ │ Cluster │
|
||
│ Replicas)│ │ │ │ │
|
||
└──────────┘ └──────────┘ └──────────┘
|
||
```
|
||
|
||
### Key Principles
|
||
|
||
1. **Stateless Services**: All application services are stateless for horizontal scaling
|
||
2. **External State**: State stored in PostgreSQL, Redis, or S3
|
||
3. **Health-Based Routing**: Load balancer only routes to healthy instances
|
||
4. **Graceful Degradation**: System continues with reduced capacity during failures
|
||
5. **Auto-Scaling**: Automatic scaling based on metrics (CPU, memory, request rate)
|
||
|
||
---
|
||
|
||
## Load Balancer Architecture
|
||
|
||
### Recommended Options
|
||
|
||
#### 1. **nginx** (Recommended for Kubernetes)
|
||
- **Pros**: Excellent gRPC support, lightweight, Kubernetes Ingress controller
|
||
- **Cons**: More complex configuration for advanced routing
|
||
- **Use Case**: Kubernetes deployments, high-performance edge routing
|
||
|
||
#### 2. **HAProxy** (Recommended for VM/Bare Metal)
|
||
- **Pros**: Battle-tested, advanced health checks, transparent gRPC support
|
||
- **Cons**: Steeper learning curve
|
||
- **Use Case**: VM-based deployments, multi-datacenter setups
|
||
|
||
#### 3. **AWS Application Load Balancer (ALB)**
|
||
- **Pros**: Managed service, auto-scaling, AWS integration
|
||
- **Cons**: Vendor lock-in, limited gRPC support (requires workarounds)
|
||
- **Use Case**: AWS-hosted deployments
|
||
|
||
#### 4. **Envoy Proxy** (Advanced)
|
||
- **Pros**: Native gRPC support, service mesh ready, advanced observability
|
||
- **Cons**: Complex configuration, resource overhead
|
||
- **Use Case**: Microservices mesh, multi-protocol routing
|
||
|
||
### gRPC Load Balancing Requirements
|
||
|
||
**Critical**: gRPC uses HTTP/2 with persistent connections, requiring **Layer 7 (L7) load balancing**.
|
||
|
||
**Why L4 Fails**:
|
||
```
|
||
Client ─── [L4 LB] ───> Backend 1 (gets ALL traffic)
|
||
│ Backend 2 (idle)
|
||
│ Backend 3 (idle)
|
||
└─ Single TCP connection = Single backend
|
||
```
|
||
|
||
**Why L7 Works**:
|
||
```
|
||
Client ─── [L7 LB] ───> Backend 1 (33% requests)
|
||
│ Backend 2 (33% requests)
|
||
│ Backend 3 (34% requests)
|
||
└─ Request-level balancing across HTTP/2 stream
|
||
```
|
||
|
||
### Health Check Configuration
|
||
|
||
**Health Check Requirements**:
|
||
1. **Protocol**: gRPC Health Checking Protocol (standard)
|
||
2. **Interval**: Every 5 seconds
|
||
3. **Timeout**: 2 seconds
|
||
4. **Threshold**: 2 consecutive failures = unhealthy
|
||
5. **Path**: `grpc.health.v1.Health/Check`
|
||
|
||
**Example Health Check**:
|
||
```bash
|
||
# Using grpc_health_probe
|
||
grpc_health_probe -addr=api-gateway:50051
|
||
|
||
# Response
|
||
status: SERVING
|
||
```
|
||
|
||
### Session Affinity (Sticky Sessions)
|
||
|
||
**When to Use**:
|
||
- Stateful operations (rare in our architecture)
|
||
- WebSocket connections (if added later)
|
||
- User-specific caching
|
||
|
||
**When NOT to Use**:
|
||
- Stateless services (Trading, Backtesting, ML Training)
|
||
- High-availability scenarios (sticky sessions reduce failover capability)
|
||
|
||
**Configuration**:
|
||
```nginx
|
||
# nginx session affinity (if needed)
|
||
upstream api_gateway {
|
||
ip_hash; # Session affinity based on client IP
|
||
server gateway-1:50051;
|
||
server gateway-2:50051;
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## API Gateway Load Balancing
|
||
|
||
### Single Entry Point Pattern
|
||
|
||
**Architecture**:
|
||
```
|
||
External Clients (TLI, Mobile, Web)
|
||
|
|
||
v
|
||
Load Balancer (TLS termination)
|
||
|
|
||
v
|
||
API Gateway Cluster (N instances)
|
||
|
|
||
v
|
||
Backend Services (Trading, Backtesting, ML)
|
||
```
|
||
|
||
### TLS Termination at Load Balancer
|
||
|
||
**Benefits**:
|
||
1. **Reduced Backend Load**: API Gateway doesn't handle TLS handshakes
|
||
2. **Certificate Management**: Single point for certificate renewal
|
||
3. **Simplified Backend**: Backend services can use plaintext gRPC
|
||
4. **Performance**: Hardware-accelerated TLS at load balancer
|
||
|
||
**Configuration**:
|
||
```nginx
|
||
# nginx TLS termination
|
||
server {
|
||
listen 443 ssl http2;
|
||
|
||
ssl_certificate /etc/ssl/certs/foxhunt.crt;
|
||
ssl_certificate_key /etc/ssl/private/foxhunt.key;
|
||
|
||
ssl_protocols TLSv1.3 TLSv1.2;
|
||
ssl_ciphers HIGH:!aNULL:!MD5;
|
||
|
||
location / {
|
||
grpc_pass grpc://api_gateway_backend;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Rate Limiting at Edge
|
||
|
||
**Why Edge Rate Limiting**:
|
||
1. **DDoS Protection**: Block attack traffic before it reaches backend
|
||
2. **Resource Efficiency**: Save backend resources for legitimate requests
|
||
3. **Fairness**: Prevent single client from monopolizing resources
|
||
|
||
**Rate Limit Tiers**:
|
||
```nginx
|
||
# nginx rate limiting by tier
|
||
limit_req_zone $jwt_user_tier zone=tier1:10m rate=100r/s; # Free tier
|
||
limit_req_zone $jwt_user_tier zone=tier2:10m rate=500r/s; # Premium
|
||
limit_req_zone $jwt_user_tier zone=tier3:10m rate=2000r/s; # Enterprise
|
||
|
||
location /api {
|
||
limit_req zone=tier1 burst=20 nodelay;
|
||
grpc_pass grpc://api_gateway_backend;
|
||
}
|
||
```
|
||
|
||
**Rate Limit Configuration**:
|
||
| Tier | Rate Limit | Burst | Typical Use Case |
|
||
|------------|------------|-------|------------------|
|
||
| Free | 100 req/s | 20 | Retail traders |
|
||
| Premium | 500 req/s | 100 | Active traders |
|
||
| Enterprise | 2000 req/s | 500 | HFT firms |
|
||
| Internal | Unlimited | N/A | Service-to-service |
|
||
|
||
### DDoS Protection
|
||
|
||
**Layer 3/4 Protection** (Network/Transport):
|
||
```nginx
|
||
# nginx connection limits
|
||
limit_conn_zone $binary_remote_addr zone=addr:10m;
|
||
limit_conn addr 10; # Max 10 concurrent connections per IP
|
||
```
|
||
|
||
**Layer 7 Protection** (Application):
|
||
```nginx
|
||
# Rate limiting (see above)
|
||
# Request size limits
|
||
client_max_body_size 1M;
|
||
|
||
# Timeout limits
|
||
grpc_read_timeout 30s;
|
||
grpc_send_timeout 30s;
|
||
```
|
||
|
||
**Additional Measures**:
|
||
1. **IP Blacklisting**: Block known malicious IPs
|
||
2. **Geo-Blocking**: Restrict access by country (if needed)
|
||
3. **Challenge-Response**: CAPTCHA for suspicious traffic
|
||
4. **Cloud-Based DDoS Protection**: Cloudflare, AWS Shield
|
||
|
||
---
|
||
|
||
## Backend Service Scaling
|
||
|
||
### Trading Service: Stateless Scaling
|
||
|
||
**Characteristics**:
|
||
- **Stateless**: No in-memory state (all state in PostgreSQL/Redis)
|
||
- **Horizontal Scaling**: Add instances linearly for more capacity
|
||
- **Auto-Scaling Trigger**: CPU > 70%, Request Rate > 1000 req/s
|
||
|
||
**Scaling Strategy**:
|
||
```yaml
|
||
# Kubernetes HPA for Trading Service
|
||
apiVersion: autoscaling/v2
|
||
kind: HorizontalPodAutoscaler
|
||
metadata:
|
||
name: trading-service-hpa
|
||
spec:
|
||
scaleTargetRef:
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
name: trading-service
|
||
minReplicas: 3 # Always maintain 3 for HA
|
||
maxReplicas: 20
|
||
metrics:
|
||
- type: Resource
|
||
resource:
|
||
name: cpu
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 70
|
||
- type: Pods
|
||
pods:
|
||
metric:
|
||
name: grpc_requests_per_second
|
||
target:
|
||
type: AverageValue
|
||
averageValue: "1000"
|
||
```
|
||
|
||
**Resource Requirements**:
|
||
```yaml
|
||
resources:
|
||
requests:
|
||
cpu: 500m # 0.5 CPU core
|
||
memory: 1Gi # 1GB RAM
|
||
limits:
|
||
cpu: 2000m # 2 CPU cores
|
||
memory: 4Gi # 4GB RAM
|
||
```
|
||
|
||
**Expected Performance Per Instance**:
|
||
- **Throughput**: 1,000-2,000 requests/second
|
||
- **Latency**: P99 < 5ms (order submission)
|
||
- **Concurrent Connections**: 1,000-5,000
|
||
|
||
### Backtesting Service: Job Queue Pattern
|
||
|
||
**Characteristics**:
|
||
- **Long-Running Jobs**: Backtests can take minutes to hours
|
||
- **CPU-Intensive**: High CPU usage during simulation
|
||
- **Job Queue**: Use Redis queue for job distribution
|
||
|
||
**Scaling Strategy**:
|
||
```yaml
|
||
# Kubernetes HPA for Backtesting Service (worker pool)
|
||
apiVersion: autoscaling/v2
|
||
kind: HorizontalPodAutoscaler
|
||
metadata:
|
||
name: backtesting-service-hpa
|
||
spec:
|
||
scaleTargetRef:
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
name: backtesting-service
|
||
minReplicas: 2 # Minimum workers
|
||
maxReplicas: 10 # Maximum workers
|
||
metrics:
|
||
- type: Pods
|
||
pods:
|
||
metric:
|
||
name: redis_queue_length # Scale on queue depth
|
||
target:
|
||
type: AverageValue
|
||
averageValue: "5" # 5 jobs per worker
|
||
```
|
||
|
||
**Job Queue Design**:
|
||
```
|
||
Client Request → API Gateway → Backtesting Service (Job Scheduler)
|
||
↓
|
||
Redis Job Queue
|
||
↓
|
||
Worker Pool (N instances)
|
||
↓ ↓ ↓
|
||
Worker 1 Worker 2 Worker N
|
||
↓ ↓ ↓
|
||
Results stored in PostgreSQL
|
||
```
|
||
|
||
**Resource Requirements** (per worker):
|
||
```yaml
|
||
resources:
|
||
requests:
|
||
cpu: 2000m # 2 CPU cores (CPU-intensive)
|
||
memory: 4Gi # 4GB RAM (large datasets)
|
||
limits:
|
||
cpu: 4000m # 4 CPU cores
|
||
memory: 8Gi # 8GB RAM
|
||
```
|
||
|
||
### ML Training Service: GPU Resource Allocation
|
||
|
||
**Characteristics**:
|
||
- **GPU-Dependent**: Requires NVIDIA GPU (RTX 3050 Ti or better)
|
||
- **Long-Running Jobs**: Training can take hours to days
|
||
- **Resource-Intensive**: High GPU memory, CPU, and RAM usage
|
||
|
||
**Scaling Strategy** (Kubernetes with NVIDIA GPU Operator):
|
||
```yaml
|
||
# ML Training Service with GPU affinity
|
||
apiVersion: apps/v1
|
||
kind: Deployment
|
||
metadata:
|
||
name: ml-training-service
|
||
spec:
|
||
replicas: 2 # Manual scaling (GPU resources expensive)
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: ml-training-service
|
||
image: foxhunt/ml-training-service:latest
|
||
resources:
|
||
requests:
|
||
cpu: 4000m # 4 CPU cores
|
||
memory: 16Gi # 16GB RAM
|
||
nvidia.com/gpu: 1 # 1 GPU per pod
|
||
limits:
|
||
cpu: 8000m # 8 CPU cores
|
||
memory: 32Gi # 32GB RAM
|
||
nvidia.com/gpu: 1
|
||
env:
|
||
- name: CUDA_VISIBLE_DEVICES
|
||
value: "0"
|
||
```
|
||
|
||
**GPU Scheduling Considerations**:
|
||
1. **Node Affinity**: Schedule only on GPU-enabled nodes
|
||
2. **GPU Sharing**: Use NVIDIA MPS for multi-tenant GPU sharing (if needed)
|
||
3. **Preemption**: Low-priority training jobs can be preempted for inference
|
||
4. **Cost**: GPU instances are 5-10x more expensive than CPU
|
||
|
||
**Auto-Scaling NOT Recommended**:
|
||
- GPU instances take 2-5 minutes to provision
|
||
- Training jobs are scheduled, not real-time
|
||
- Use job queue pattern (like Backtesting Service)
|
||
|
||
### Database Connection Pooling
|
||
|
||
**Why Connection Pooling**:
|
||
- PostgreSQL connection limit: ~100-500 connections
|
||
- Each service instance needs 5-20 connections
|
||
- 20 service instances × 20 connections = 400 connections (nearing limit)
|
||
|
||
**Solution: PgBouncer**:
|
||
```
|
||
Service Instances (100+) → PgBouncer (pools to 50 connections) → PostgreSQL
|
||
```
|
||
|
||
**PgBouncer Configuration**:
|
||
```ini
|
||
# pgbouncer.ini
|
||
[databases]
|
||
foxhunt = host=postgres port=5432 dbname=foxhunt
|
||
|
||
[pgbouncer]
|
||
listen_addr = 0.0.0.0
|
||
listen_port = 6432
|
||
auth_type = scram-sha-256
|
||
auth_file = /etc/pgbouncer/userlist.txt
|
||
|
||
# Connection pooling
|
||
pool_mode = transaction # Transaction-level pooling
|
||
max_client_conn = 1000 # Max connections from services
|
||
default_pool_size = 25 # Pool size per database
|
||
reserve_pool_size = 10 # Emergency pool
|
||
```
|
||
|
||
**Service Configuration** (use PgBouncer instead of direct PostgreSQL):
|
||
```bash
|
||
# Before (direct PostgreSQL)
|
||
DATABASE_URL=postgresql://foxhunt:password@postgres:5432/foxhunt
|
||
|
||
# After (via PgBouncer)
|
||
DATABASE_URL=postgresql://foxhunt:password@pgbouncer:6432/foxhunt
|
||
```
|
||
|
||
---
|
||
|
||
## Horizontal Pod Autoscaling (HPA)
|
||
|
||
### Metrics for Scaling Decisions
|
||
|
||
**CPU-Based Scaling** (Most Common):
|
||
```yaml
|
||
metrics:
|
||
- type: Resource
|
||
resource:
|
||
name: cpu
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 70 # Scale when CPU > 70%
|
||
```
|
||
|
||
**Memory-Based Scaling**:
|
||
```yaml
|
||
metrics:
|
||
- type: Resource
|
||
resource:
|
||
name: memory
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 80 # Scale when memory > 80%
|
||
```
|
||
|
||
**Custom Metrics** (Request Rate):
|
||
```yaml
|
||
metrics:
|
||
- type: Pods
|
||
pods:
|
||
metric:
|
||
name: grpc_requests_per_second
|
||
target:
|
||
type: AverageValue
|
||
averageValue: "1000" # Scale when > 1000 req/s per pod
|
||
```
|
||
|
||
**Custom Metrics** (Queue Depth):
|
||
```yaml
|
||
metrics:
|
||
- type: Pods
|
||
pods:
|
||
metric:
|
||
name: redis_queue_length
|
||
target:
|
||
type: AverageValue
|
||
averageValue: "5" # Scale when > 5 jobs per worker
|
||
```
|
||
|
||
### Scale-Up Thresholds
|
||
|
||
**Aggressive Scale-Up** (Trading Service - latency-sensitive):
|
||
```yaml
|
||
behavior:
|
||
scaleUp:
|
||
stabilizationWindowSeconds: 30 # Wait 30s before scaling up
|
||
policies:
|
||
- type: Percent
|
||
value: 100 # Double replicas
|
||
periodSeconds: 15
|
||
- type: Pods
|
||
value: 4 # Or add 4 pods
|
||
periodSeconds: 15
|
||
selectPolicy: Max # Use whichever scales faster
|
||
```
|
||
|
||
**Conservative Scale-Up** (Backtesting Service - cost-sensitive):
|
||
```yaml
|
||
behavior:
|
||
scaleUp:
|
||
stabilizationWindowSeconds: 60 # Wait 60s before scaling up
|
||
policies:
|
||
- type: Percent
|
||
value: 50 # Add 50% more replicas
|
||
periodSeconds: 60
|
||
- type: Pods
|
||
value: 2 # Or add 2 pods
|
||
periodSeconds: 60
|
||
selectPolicy: Min # Use whichever scales slower
|
||
```
|
||
|
||
### Scale-Down Thresholds
|
||
|
||
**Slow Scale-Down** (Prevent flapping):
|
||
```yaml
|
||
behavior:
|
||
scaleDown:
|
||
stabilizationWindowSeconds: 300 # Wait 5 minutes before scaling down
|
||
policies:
|
||
- type: Percent
|
||
value: 25 # Remove 25% of replicas
|
||
periodSeconds: 60
|
||
- type: Pods
|
||
value: 1 # Or remove 1 pod
|
||
periodSeconds: 60
|
||
selectPolicy: Min # Use whichever scales slower
|
||
```
|
||
|
||
**Why Slow Scale-Down**:
|
||
1. **Prevent Flapping**: Avoid rapid up/down cycles (costs CPU, disrupts connections)
|
||
2. **Traffic Spikes**: Allow system to handle sudden load increases
|
||
3. **Cost**: Over-provisioning by 10-20% is cheaper than SLA violations
|
||
|
||
### Min/Max Replica Counts
|
||
|
||
**Recommended Configurations**:
|
||
|
||
| Service | Min Replicas | Max Replicas | Reasoning |
|
||
|-------------------|--------------|--------------|-----------|
|
||
| API Gateway | 3 | 20 | HA (3) + burst capacity |
|
||
| Trading Service | 3 | 20 | HA (3) + 50K orders/sec |
|
||
| Backtesting | 2 | 10 | Workers for job queue |
|
||
| ML Training | 1 | 3 | GPU resources expensive |
|
||
|
||
**Min Replicas Rationale**:
|
||
- **3 for HA**: Survive 1 node failure + 1 rolling update
|
||
- **2 for Workers**: Minimum for job queue parallelism
|
||
- **1 for GPU**: GPU resources too expensive for idle HA
|
||
|
||
**Max Replicas Rationale**:
|
||
- **20 for Trading**: Based on performance testing (50K orders/sec ÷ 2.5K orders/sec per pod)
|
||
- **10 for Backtesting**: Rarely need more than 10 concurrent backtests
|
||
- **3 for ML Training**: GPU cluster size limit
|
||
|
||
---
|
||
|
||
## Database Scaling
|
||
|
||
### Read Replicas for Read-Heavy Workloads
|
||
|
||
**When to Use Read Replicas**:
|
||
- **Read:Write Ratio**: > 3:1 (75%+ reads)
|
||
- **Query Patterns**: Analytics, reporting, backtesting
|
||
- **Write Latency**: Not affected by read load
|
||
|
||
**PostgreSQL Streaming Replication**:
|
||
```
|
||
Primary (Write) ──────> Replica 1 (Read)
|
||
│ Replica 2 (Read)
|
||
│ Replica 3 (Read)
|
||
└─ Async replication (< 100ms lag)
|
||
```
|
||
|
||
**Configuration**:
|
||
```yaml
|
||
# Kubernetes StatefulSet with read replicas
|
||
apiVersion: apps/v1
|
||
kind: StatefulSet
|
||
metadata:
|
||
name: postgres-replica
|
||
spec:
|
||
replicas: 2 # 2 read replicas
|
||
serviceName: postgres-replica
|
||
template:
|
||
spec:
|
||
containers:
|
||
- name: postgres
|
||
image: timescale/timescaledb:latest-pg15
|
||
env:
|
||
- name: POSTGRES_REPLICA_MODE
|
||
value: "replica"
|
||
- name: POSTGRES_MASTER_HOST
|
||
value: "postgres-primary"
|
||
```
|
||
|
||
**Application-Level Read/Write Splitting**:
|
||
```rust
|
||
// trading_engine/src/persistence/postgres.rs
|
||
pub struct PostgresPool {
|
||
write_pool: sqlx::PgPool, // Primary
|
||
read_pool: sqlx::PgPool, // Read replica(s)
|
||
}
|
||
|
||
impl PostgresPool {
|
||
pub async fn execute_write(&self, query: Query) -> Result<()> {
|
||
// All writes go to primary
|
||
self.write_pool.execute(query).await
|
||
}
|
||
|
||
pub async fn execute_read(&self, query: Query) -> Result<Rows> {
|
||
// Reads go to replica (or primary if replica unavailable)
|
||
self.read_pool.fetch_all(query).await
|
||
}
|
||
}
|
||
```
|
||
|
||
### Connection Pooling (PgBouncer)
|
||
|
||
**See "Backend Service Scaling" section for PgBouncer configuration.**
|
||
|
||
**Benefits**:
|
||
1. **Reduced Connection Overhead**: Reuse connections instead of creating new ones
|
||
2. **Connection Limit Management**: Protect PostgreSQL from connection exhaustion
|
||
3. **Transaction Pooling**: Release connections immediately after transaction
|
||
|
||
**Pooling Modes**:
|
||
```ini
|
||
# Transaction pooling (recommended for stateless services)
|
||
pool_mode = transaction # Connection released after transaction
|
||
|
||
# Session pooling (for stateful operations)
|
||
pool_mode = session # Connection held for entire session
|
||
|
||
# Statement pooling (most aggressive, use with caution)
|
||
pool_mode = statement # Connection released after each statement
|
||
```
|
||
|
||
### Sharding Strategy (Future - If Needed)
|
||
|
||
**When to Shard**:
|
||
- Database size > 1TB
|
||
- Write throughput > 10K writes/sec
|
||
- Read replicas insufficient
|
||
|
||
**Sharding Strategies**:
|
||
|
||
1. **By Asset Class** (Natural partition):
|
||
```sql
|
||
-- Shard 1: Equities
|
||
-- Shard 2: Options
|
||
-- Shard 3: Futures
|
||
-- Shard 4: Crypto
|
||
```
|
||
|
||
2. **By Time** (TimescaleDB hypertables - automatic):
|
||
```sql
|
||
-- TimescaleDB automatically partitions by time
|
||
CREATE TABLE market_data (
|
||
time TIMESTAMPTZ NOT NULL,
|
||
symbol TEXT NOT NULL,
|
||
price DOUBLE PRECISION
|
||
);
|
||
|
||
SELECT create_hypertable('market_data', 'time');
|
||
-- Automatic partitioning by month/week
|
||
```
|
||
|
||
3. **By User ID** (Hash-based):
|
||
```sql
|
||
-- Shard based on user_id hash
|
||
Shard = user_id % num_shards
|
||
```
|
||
|
||
**Recommendation**: Start with TimescaleDB hypertables (time-based partitioning). Only implement custom sharding if necessary.
|
||
|
||
### TimescaleDB Hypertables
|
||
|
||
**Already Configured** (from migrations):
|
||
```sql
|
||
-- Migration 002: TimescaleDB extension
|
||
CREATE EXTENSION IF NOT EXISTS timescaledb;
|
||
|
||
-- Migration 003: Market data hypertable
|
||
SELECT create_hypertable('market_data', 'timestamp',
|
||
chunk_time_interval => INTERVAL '1 day');
|
||
|
||
-- Automatic compression for old data
|
||
ALTER TABLE market_data SET (
|
||
timescaledb.compress,
|
||
timescaledb.compress_orderby = 'timestamp DESC',
|
||
timescaledb.compress_segmentby = 'symbol'
|
||
);
|
||
```
|
||
|
||
**Benefits**:
|
||
- **Automatic Partitioning**: By time (day/week/month)
|
||
- **Compression**: Old data automatically compressed (10x space savings)
|
||
- **Parallel Queries**: TimescaleDB parallelizes across chunks
|
||
- **Retention Policies**: Automatic data deletion after N days
|
||
|
||
---
|
||
|
||
## Redis Scaling
|
||
|
||
### Redis Cluster for Horizontal Scaling
|
||
|
||
**When to Use Redis Cluster**:
|
||
- Dataset size > 10GB (single node RAM limit)
|
||
- Throughput > 100K ops/sec
|
||
- High availability required
|
||
|
||
**Redis Cluster Architecture**:
|
||
```
|
||
Client ──> Redis Cluster Proxy
|
||
│
|
||
├──> Shard 1 (Master + Replica)
|
||
├──> Shard 2 (Master + Replica)
|
||
└──> Shard 3 (Master + Replica)
|
||
```
|
||
|
||
**Configuration** (docker-compose.yml):
|
||
```yaml
|
||
services:
|
||
redis-cluster:
|
||
image: redis:7-alpine
|
||
command: redis-server --cluster-enabled yes --cluster-config-file nodes.conf
|
||
ports:
|
||
- "7000-7005:7000-7005"
|
||
volumes:
|
||
- redis-cluster-data:/data
|
||
|
||
# Create cluster (run once)
|
||
redis-cluster-init:
|
||
image: redis:7-alpine
|
||
command: |
|
||
redis-cli --cluster create \
|
||
redis-1:7000 redis-2:7001 redis-3:7002 \
|
||
redis-4:7003 redis-5:7004 redis-6:7005 \
|
||
--cluster-replicas 1
|
||
depends_on:
|
||
- redis-cluster
|
||
```
|
||
|
||
**Client Configuration**:
|
||
```rust
|
||
// Use redis-cluster-async crate
|
||
use redis_cluster_async::{Client, ClusterClient};
|
||
|
||
let client = ClusterClient::new(vec![
|
||
"redis://redis-1:7000",
|
||
"redis://redis-2:7001",
|
||
"redis://redis-3:7002",
|
||
])?;
|
||
|
||
// Client automatically routes to correct shard
|
||
let mut conn = client.get_connection().await?;
|
||
conn.set("key", "value").await?;
|
||
```
|
||
|
||
### Redis Sentinel for High Availability
|
||
|
||
**When to Use Sentinel** (Alternative to Cluster):
|
||
- Single master + replicas (no sharding)
|
||
- Automatic failover required
|
||
- Dataset fits in single node RAM
|
||
|
||
**Sentinel Architecture**:
|
||
```
|
||
Client ──> Sentinel (monitors health)
|
||
│
|
||
v
|
||
Primary ──> Replica 1
|
||
└─> Replica 2
|
||
|
||
(If Primary fails, Sentinel promotes Replica 1)
|
||
```
|
||
|
||
**Configuration**:
|
||
```yaml
|
||
# docker-compose.yml
|
||
services:
|
||
redis-primary:
|
||
image: redis:7-alpine
|
||
ports:
|
||
- "6379:6379"
|
||
|
||
redis-replica-1:
|
||
image: redis:7-alpine
|
||
command: redis-server --replicaof redis-primary 6379
|
||
|
||
redis-sentinel:
|
||
image: redis:7-alpine
|
||
command: redis-sentinel /etc/redis/sentinel.conf
|
||
volumes:
|
||
- ./redis-sentinel.conf:/etc/redis/sentinel.conf
|
||
```
|
||
|
||
```conf
|
||
# redis-sentinel.conf
|
||
sentinel monitor mymaster redis-primary 6379 2
|
||
sentinel down-after-milliseconds mymaster 5000
|
||
sentinel parallel-syncs mymaster 1
|
||
sentinel failover-timeout mymaster 10000
|
||
```
|
||
|
||
### Cache Warming Strategies
|
||
|
||
**Cold Cache Problem**:
|
||
- Cache miss rate spikes after restart
|
||
- Database overload from cache misses
|
||
- Slow response times until cache warms
|
||
|
||
**Solution 1: Lazy Loading** (Default):
|
||
```rust
|
||
// Check cache first, load from DB on miss
|
||
async fn get_user_config(user_id: &str, cache: &RedisPool, db: &PgPool) -> Result<UserConfig> {
|
||
// Try cache first
|
||
if let Some(config) = cache.get(user_id).await? {
|
||
return Ok(config);
|
||
}
|
||
|
||
// Cache miss: Load from database
|
||
let config = db.fetch_one("SELECT * FROM user_config WHERE user_id = $1", user_id).await?;
|
||
|
||
// Warm cache for next request
|
||
cache.set(user_id, &config, Duration::from_secs(3600)).await?;
|
||
|
||
Ok(config)
|
||
}
|
||
```
|
||
|
||
**Solution 2: Proactive Warming**:
|
||
```rust
|
||
// Warm cache on startup with most-used data
|
||
async fn warm_cache(cache: &RedisPool, db: &PgPool) -> Result<()> {
|
||
// Load top 1000 most active users
|
||
let active_users = db.fetch_all(
|
||
"SELECT user_id, config FROM user_config ORDER BY last_active DESC LIMIT 1000"
|
||
).await?;
|
||
|
||
for user in active_users {
|
||
cache.set(&user.user_id, &user.config, Duration::from_secs(3600)).await?;
|
||
}
|
||
|
||
Ok(())
|
||
}
|
||
```
|
||
|
||
**Solution 3: Cache Priming** (Scheduled):
|
||
```rust
|
||
// Periodically refresh cache for hot data
|
||
async fn prime_cache_job(cache: &RedisPool, db: &PgPool) -> Result<()> {
|
||
loop {
|
||
// Refresh market data cache every minute
|
||
let latest_quotes = db.fetch_all("SELECT * FROM latest_quotes").await?;
|
||
for quote in latest_quotes {
|
||
cache.set("e.symbol, "e, Duration::from_secs(60)).await?;
|
||
}
|
||
|
||
tokio::time::sleep(Duration::from_secs(60)).await;
|
||
}
|
||
}
|
||
```
|
||
|
||
### Eviction Policies
|
||
|
||
**Redis Eviction Policies**:
|
||
```conf
|
||
# redis.conf
|
||
maxmemory 2gb
|
||
maxmemory-policy allkeys-lru # Evict least recently used keys
|
||
```
|
||
|
||
**Policy Options**:
|
||
| Policy | Description | Use Case |
|
||
|-------------------|-------------|----------|
|
||
| `noeviction` | Return error when memory full | Never (avoid data loss) |
|
||
| `allkeys-lru` | Evict least recently used keys | **Recommended for cache** |
|
||
| `allkeys-lfu` | Evict least frequently used keys | Hot key workloads |
|
||
| `volatile-lru` | Evict LRU keys with TTL set | Mixed cache + persistent data |
|
||
| `volatile-ttl` | Evict keys with shortest TTL | Time-sensitive data |
|
||
|
||
**Recommendation**: Use `allkeys-lru` for cache workloads.
|
||
|
||
---
|
||
|
||
## Performance Targets
|
||
|
||
### Latency Targets (from PERFORMANCE_BENCHMARKS.md)
|
||
|
||
| Operation | P50 | P99 | P99.9 |
|
||
|-----------|-----|-----|-------|
|
||
| Order Submission | < 1ms | < 5ms | < 10ms |
|
||
| Market Data Ingestion | < 500μs | < 2ms | < 5ms |
|
||
| Position Query | < 2ms | < 10ms | < 20ms |
|
||
| Risk Check | < 1ms | < 5ms | < 10ms |
|
||
|
||
### Throughput Targets
|
||
|
||
| Service | Target Throughput | Peak Capacity |
|
||
|---------|------------------|---------------|
|
||
| API Gateway | 10K req/s | 50K req/s |
|
||
| Trading Service | 5K orders/s | 20K orders/s |
|
||
| Market Data Ingestion | 100K events/s | 500K events/s |
|
||
| Backtesting Service | 10 concurrent backtests | 50 concurrent backtests |
|
||
|
||
**Scaling Math**:
|
||
```
|
||
# Trading Service Example
|
||
Target: 20K orders/sec
|
||
Per-instance capacity: 2K orders/sec
|
||
Required instances: 20K ÷ 2K = 10 instances
|
||
Safety margin (20%): 10 × 1.2 = 12 instances
|
||
```
|
||
|
||
### Concurrent User Limits
|
||
|
||
| User Tier | Concurrent Connections | Rate Limit |
|
||
|-----------|----------------------|------------|
|
||
| Free | 1 | 100 req/s |
|
||
| Premium | 5 | 500 req/s |
|
||
| Enterprise | 50 | 2000 req/s |
|
||
|
||
### Resource Utilization Targets
|
||
|
||
**Target Utilization** (for auto-scaling):
|
||
- **CPU**: 60-70% (allows headroom for bursts)
|
||
- **Memory**: 70-80% (allows headroom for spikes)
|
||
- **Disk**: < 80% (alerts at 80%, critical at 90%)
|
||
- **Network**: < 50% (network saturation kills performance)
|
||
|
||
**Why NOT 90% Utilization**:
|
||
1. **No Headroom**: No capacity for traffic spikes
|
||
2. **Slow Auto-Scaling**: Takes 30-60s to add instances
|
||
3. **Latency Degradation**: High CPU = increased latency
|
||
4. **OOM Kills**: High memory usage risks out-of-memory crashes
|
||
|
||
---
|
||
|
||
## Cost Optimization
|
||
|
||
### Right-Sizing Instances
|
||
|
||
**CPU/Memory Ratios by Service**:
|
||
|
||
| Service | CPU | Memory | Ratio | Instance Type (AWS) |
|
||
|---------|-----|--------|-------|---------------------|
|
||
| API Gateway | 1 core | 2GB | 1:2 | t3.small |
|
||
| Trading Service | 2 cores | 4GB | 1:2 | t3.medium |
|
||
| Backtesting Service | 4 cores | 8GB | 1:2 | c6i.xlarge (compute-optimized) |
|
||
| ML Training Service | 8 cores | 32GB + GPU | 1:4 | g4dn.2xlarge (GPU) |
|
||
|
||
**How to Right-Size**:
|
||
1. **Monitor Actual Usage**: Use Prometheus metrics for 2 weeks
|
||
2. **Identify Bottlenecks**: CPU-bound vs memory-bound vs I/O-bound
|
||
3. **Adjust Instance Types**: Match instance type to workload
|
||
4. **Iterate**: Re-evaluate every quarter
|
||
|
||
### Spot Instances for Non-Critical Workloads
|
||
|
||
**What are Spot Instances**:
|
||
- Spare cloud capacity sold at 50-90% discount
|
||
- Can be terminated with 2-minute notice
|
||
- **Only use for fault-tolerant workloads**
|
||
|
||
**Recommended Use Cases**:
|
||
```yaml
|
||
# Kubernetes node groups
|
||
nodeGroups:
|
||
# Production workloads (on-demand)
|
||
- name: production-on-demand
|
||
instanceType: t3.medium
|
||
desiredCapacity: 10
|
||
labels:
|
||
workload: production
|
||
|
||
# Backtesting workers (spot instances)
|
||
- name: backtesting-spot
|
||
instanceType: c6i.xlarge
|
||
desiredCapacity: 5
|
||
instancesDistribution:
|
||
onDemandBaseCapacity: 1 # Always keep 1 on-demand
|
||
onDemandPercentageAboveBaseCapacity: 0 # Rest are spot
|
||
spotAllocationStrategy: capacity-optimized
|
||
labels:
|
||
workload: backtesting
|
||
```
|
||
|
||
**Never Use Spot For**:
|
||
- Trading Service (real-time, SLA-critical)
|
||
- API Gateway (user-facing)
|
||
- Databases (stateful)
|
||
|
||
**Good Use Cases**:
|
||
- Backtesting workers (jobs can retry)
|
||
- ML training (checkpointing allows resume)
|
||
- Batch processing
|
||
|
||
### Reserved Instances for Base Load
|
||
|
||
**What are Reserved Instances**:
|
||
- 1-year or 3-year commitment
|
||
- 30-60% discount vs on-demand
|
||
- Best for predictable, steady workload
|
||
|
||
**Recommended Strategy**:
|
||
```
|
||
Total Capacity Needed: 20 instances
|
||
├── Base Load (always needed): 10 instances → Reserved (50% savings)
|
||
├── Typical Load: +5 instances → On-Demand
|
||
└── Peak Load: +5 instances → Auto-Scaling (On-Demand or Spot)
|
||
```
|
||
|
||
**Cost Comparison** (AWS t3.medium, us-east-1):
|
||
| Pricing Model | Cost/month | Savings |
|
||
|---------------|------------|---------|
|
||
| On-Demand | $30.37 | 0% |
|
||
| Reserved (1yr, no upfront) | $18.25 | 40% |
|
||
| Reserved (3yr, all upfront) | $11.68 | 62% |
|
||
| Spot (average) | $9.11 | 70% |
|
||
|
||
### Auto-Scaling for Burst Capacity
|
||
|
||
**Cost-Optimized Auto-Scaling**:
|
||
```yaml
|
||
# HPA with cost awareness
|
||
apiVersion: autoscaling/v2
|
||
kind: HorizontalPodAutoscaler
|
||
metadata:
|
||
name: trading-service-hpa
|
||
spec:
|
||
minReplicas: 10 # Reserved instances (base load)
|
||
maxReplicas: 20 # Auto-scale to 20 (on-demand/spot)
|
||
metrics:
|
||
- type: Resource
|
||
resource:
|
||
name: cpu
|
||
target:
|
||
type: Utilization
|
||
averageUtilization: 70
|
||
|
||
# Slow scale-down to avoid flapping
|
||
behavior:
|
||
scaleDown:
|
||
stabilizationWindowSeconds: 600 # 10 minutes
|
||
policies:
|
||
- type: Pods
|
||
value: 1
|
||
periodSeconds: 120 # Remove 1 pod every 2 minutes
|
||
```
|
||
|
||
**Cost Math**:
|
||
```
|
||
Scenario 1: No Auto-Scaling (always 20 instances)
|
||
Cost: 20 × $30.37 = $607.40/month
|
||
|
||
Scenario 2: Auto-Scaling (10 reserved, 10 auto-scale)
|
||
Base: 10 × $18.25 = $182.50 (reserved)
|
||
Burst: 10 × $30.37 × 30% = $91.11 (on-demand, 30% of time)
|
||
Total: $273.61/month
|
||
Savings: $333.79/month (55%)
|
||
```
|
||
|
||
---
|
||
|
||
## Monitoring & Alerting
|
||
|
||
### Key Metrics to Monitor
|
||
|
||
**Service Health**:
|
||
```promql
|
||
# Service availability (should be > 99.9%)
|
||
up{job="api-gateway"} == 1
|
||
|
||
# Request rate
|
||
rate(grpc_requests_total[5m])
|
||
|
||
# Error rate (should be < 0.1%)
|
||
rate(grpc_requests_total{status="error"}[5m]) / rate(grpc_requests_total[5m])
|
||
|
||
# Latency (P99 should be < 5ms)
|
||
histogram_quantile(0.99, rate(grpc_request_duration_seconds_bucket[5m]))
|
||
```
|
||
|
||
**Auto-Scaling Metrics**:
|
||
```promql
|
||
# Current replica count
|
||
kube_deployment_status_replicas{deployment="trading-service"}
|
||
|
||
# Desired replica count (from HPA)
|
||
kube_horizontalpodautoscaler_status_desired_replicas{hpa="trading-service-hpa"}
|
||
|
||
# CPU utilization
|
||
avg(rate(container_cpu_usage_seconds_total{pod=~"trading-service.*"}[5m])) * 100
|
||
|
||
# Memory utilization
|
||
avg(container_memory_working_set_bytes{pod=~"trading-service.*"}) /
|
||
avg(container_spec_memory_limit_bytes{pod=~"trading-service.*"}) * 100
|
||
```
|
||
|
||
**Database Metrics**:
|
||
```promql
|
||
# Connection pool usage (should be < 80%)
|
||
pg_stat_database_numbackends / pg_settings_max_connections * 100
|
||
|
||
# Replication lag (should be < 100ms)
|
||
pg_stat_replication_replay_lag_bytes
|
||
|
||
# Query latency
|
||
rate(pg_stat_database_blks_read[5m])
|
||
```
|
||
|
||
### Alerts to Configure
|
||
|
||
**Critical Alerts** (PagerDuty):
|
||
```yaml
|
||
# Service down
|
||
- alert: ServiceDown
|
||
expr: up{job=~".*service"} == 0
|
||
for: 1m
|
||
labels:
|
||
severity: critical
|
||
annotations:
|
||
summary: "Service {{ $labels.job }} is down"
|
||
|
||
# High error rate
|
||
- alert: HighErrorRate
|
||
expr: rate(grpc_requests_total{status="error"}[5m]) / rate(grpc_requests_total[5m]) > 0.05
|
||
for: 5m
|
||
labels:
|
||
severity: critical
|
||
annotations:
|
||
summary: "High error rate on {{ $labels.service }}: {{ $value | humanizePercentage }}"
|
||
|
||
# High latency
|
||
- alert: HighLatency
|
||
expr: histogram_quantile(0.99, rate(grpc_request_duration_seconds_bucket[5m])) > 0.1
|
||
for: 5m
|
||
labels:
|
||
severity: critical
|
||
annotations:
|
||
summary: "High P99 latency on {{ $labels.service }}: {{ $value }}s"
|
||
```
|
||
|
||
**Warning Alerts** (Slack):
|
||
```yaml
|
||
# High CPU usage (approaching auto-scale threshold)
|
||
- alert: HighCPU
|
||
expr: avg(rate(container_cpu_usage_seconds_total[5m])) by (pod) > 0.8
|
||
for: 10m
|
||
labels:
|
||
severity: warning
|
||
annotations:
|
||
summary: "High CPU on {{ $labels.pod }}: {{ $value | humanizePercentage }}"
|
||
|
||
# Database connection pool approaching limit
|
||
- alert: HighDatabaseConnections
|
||
expr: pg_stat_database_numbackends / pg_settings_max_connections > 0.8
|
||
for: 5m
|
||
labels:
|
||
severity: warning
|
||
annotations:
|
||
summary: "Database connection pool at {{ $value | humanizePercentage }}"
|
||
```
|
||
|
||
### Dashboards
|
||
|
||
**Recommended Grafana Dashboards**:
|
||
1. **Service Overview**: Request rate, error rate, latency (RED metrics)
|
||
2. **Auto-Scaling**: Current/desired replicas, CPU/memory utilization
|
||
3. **Database**: Connection pool, query latency, replication lag
|
||
4. **Load Balancer**: Active connections, requests per backend
|
||
5. **Cost**: Instance count, spot vs on-demand usage
|
||
|
||
---
|
||
|
||
## Quick Reference
|
||
|
||
### Scaling Decision Matrix
|
||
|
||
| Symptom | Root Cause | Solution |
|
||
|---------|-----------|----------|
|
||
| High CPU (> 80%) | Not enough compute | Scale up replicas |
|
||
| High memory (> 90%) | Memory leak or under-provisioned | Increase memory limits or fix leak |
|
||
| High latency + low CPU | Database bottleneck | Add read replicas or optimize queries |
|
||
| High error rate | Service overload | Scale up replicas or rate limit clients |
|
||
| Database connection exhaustion | Too many service instances | Add PgBouncer connection pooling |
|
||
| Redis memory full | Cache size exceeds capacity | Add Redis Cluster or increase eviction |
|
||
|
||
### Scaling Commands
|
||
|
||
**Kubernetes**:
|
||
```bash
|
||
# Manual scaling
|
||
kubectl scale deployment trading-service --replicas=10
|
||
|
||
# Check HPA status
|
||
kubectl get hpa
|
||
|
||
# Describe HPA (shows scaling events)
|
||
kubectl describe hpa trading-service-hpa
|
||
|
||
# View pod resource usage
|
||
kubectl top pods
|
||
|
||
# View node resource usage
|
||
kubectl top nodes
|
||
```
|
||
|
||
**Docker Compose** (development):
|
||
```bash
|
||
# Scale service
|
||
docker-compose up -d --scale trading_service=5
|
||
|
||
# View resource usage
|
||
docker stats
|
||
```
|
||
|
||
### Load Balancer Health Checks
|
||
|
||
**nginx**:
|
||
```bash
|
||
# Check nginx status
|
||
curl http://localhost/nginx_status
|
||
|
||
# Test gRPC endpoint
|
||
grpc_health_probe -addr=localhost:443 -tls
|
||
```
|
||
|
||
**HAProxy**:
|
||
```bash
|
||
# Check HAProxy stats
|
||
curl http://localhost:8404/stats
|
||
|
||
# Test backend health
|
||
echo "show servers state" | socat /var/run/haproxy.sock stdio
|
||
```
|
||
|
||
---
|
||
|
||
## Additional Resources
|
||
|
||
- **Kubernetes HPA**: https://kubernetes.io/docs/tasks/run-application/horizontal-pod-autoscale/
|
||
- **nginx gRPC Load Balancing**: https://www.nginx.com/blog/nginx-1-13-10-grpc/
|
||
- **PostgreSQL Connection Pooling**: https://www.pgbouncer.org/
|
||
- **Redis Cluster**: https://redis.io/docs/manual/scaling/
|
||
- **Performance Benchmarks**: See `PERFORMANCE_BENCHMARKS.md`
|
||
- **Scaling Playbook**: See `SCALING_PLAYBOOK.md`
|
||
|
||
---
|
||
|
||
**Last Updated**: 2025-10-07
|
||
**Version**: 1.0
|
||
**Owner**: Platform Engineering Team
|