MatCraft is designed to scale horizontally. Here is how to handle increasing numbers of users, campaigns, and evaluations.
The FastAPI backend is stateless — all session state is stored in the database and Redis. To handle more concurrent users:
# Scale API instances behind a load balancer
docker compose up -d --scale api=4Each API instance can handle approximately 500 concurrent connections (including WebSocket). For 100 concurrent dashboard users, 2-3 instances are sufficient.
Key considerations:
Workers are the primary bottleneck for campaign throughput. Each worker process handles one task at a time:
# Scale horizontally (more worker containers)
docker compose up -d --scale worker=8
# Scale vertically (more processes per worker)
celery -A materia.tasks worker --concurrency=8Rules of thumb:
For mixed workloads, use priority queues to ensure interactive requests are not blocked by batch jobs:
# In task definitions
@app.task(queue="high_priority")
def train_surrogate(campaign_id: str):
...
@app.task(queue="low_priority")
def export_results(campaign_id: str):
...# Dedicated workers per queue
celery -A materia.tasks worker --queues=high_priority --concurrency=4
celery -A materia.tasks worker --queues=low_priority --concurrency=2PostgreSQL is typically the last bottleneck. Strategies for scaling:
candidates table by campaign ID:CREATE TABLE candidates (
id UUID PRIMARY KEY,
campaign_id UUID NOT NULL,
...
) PARTITION BY HASH (campaign_id);On Kubernetes or ECS, configure horizontal pod auto-scaling based on:
# Kubernetes HPA example
apiVersion: autoscaling/v2
kind: HorizontalPodAutoscaler
metadata:
name: matcraft-worker
spec:
scaleTargetRef:
apiVersion: apps/v1
kind: Deployment
name: matcraft-worker
minReplicas: 2
maxReplicas: 20
metrics:
- type: External
external:
metric:
name: redis_queue_depth
target:
type: AverageValue
averageValue: "10"