Deployment Tiers
Choose a tier based on your traffic volume, latency requirements, and budget.Monthly cost, decisions/sec, and P99 latency in this table are planning estimates to
size a tier — not benchmarked guarantees. Actual figures depend on cloud pricing,
pipeline complexity, candidate-offer count, and enrichment/model configuration.
Benchmark on your own workload before committing to a tier.
Background Jobs (BullMQ)
Several features run asynchronously through BullMQ queues backed by Redis: DSAR exports, model retraining, journey wait-step advancement, drift detection, batch decisioning, and sample-data seeding. Each job needs two halves to work:- A producer — the API enqueues jobs into Redis. ✅ Always on, regardless of tier.
- A consumer — a Node.js process running BullMQ
Workerinstances that read jobs back out and execute the handlers. Required, otherwise queued jobs silently pile up forever in Redis.
Hobby tier guarantee: the $6-8/mo Hobby figure assumes
WORKER_INPROCESS=1. The single App Runner service hosts both producer and consumer. No second container, no second bill.Redis backend portability
Any Redis-protocol-compatible service works. The codebase has zero Upstash-specific dependencies — it uses plainioredis. To switch providers, change REDIS_URL and restart:
For BullMQ Cluster or Sentinel HA setups, a small wrapper around
new Redis(url) is needed — see Self-Hosted Deployment for the Helm value.
Optional Services
ML Worker (Python)
The ML Worker is a separate Python/FastAPI service required for traininggradient_boosted models and for higher-accuracy scikit-learn analysis on datasets > 5K rows. Scoring trained models works without it — the tree ensemble runs in-process in Node.js, so the /recommend hot path never calls Python.
Without the ML Worker:
- ✅
/recommendworks for all model types (scoring is in-process) - ✅ Scorecard, Bayesian, linear, and logistic models train in-process
- ❌
gradient_boostedmodel training returns a 503 (ML_WORKER_URL is not configured) - ⚠️ Auto-Segmentation, Policy Recommender, and Content Intelligence fall back to LLM-based analysis (still functional, lower accuracy on > 5K rows)
Response Time Breakdown
What happens during a/api/v1/recommend call:
These per-stage durations are approximate estimates for a typical pipeline with
50-100 candidate offers — not benchmarked figures. Latency scales roughly linearly
with candidate count and varies with enrichment and model configuration.
Scaling Levers
Horizontal scaling
Horizontal scaling
Add more App Runner or ECS instances. Each instance handles approximately 100-500 req/s
depending on pipeline complexity. App Runner auto-scales based on concurrency — tune the
auto-scaling configuration’s max-concurrency-per-instance value to control when new
instances spin up.
Vertical scaling
Vertical scaling
A larger database instance reduces query time. This is most impactful when qualification
rules or enrichment queries are complex. Moving from
t3.micro to r6g.large can cut
DB-bound latency by 60-70%.Redis caching
Redis caching
Enrichment caching reduces database load by 80%+. A cache hit resolves in ~1-2ms versus
~10-30ms for a cache miss. Set the
REDIS_URL environment variable to enable caching in
production. This is the single highest-impact optimization.Read replicas
Read replicas
Use read replicas for dashboard and analytics queries. Keep the primary instance dedicated
to decision writes and real-time reads. RDS and Aurora both support up to 15 read replicas.
Connection pooling
Connection pooling
PgBouncer reduces PostgreSQL connection overhead and is essential at more than 50 concurrent
connections. The Helm chart deploys PgBouncer by default (
pgbouncer.enabled: true, transaction
pooling, pool size 25). For App Runner, use Supabase’s built-in connection pooler or deploy
PgBouncer separately.Batch mode
Batch mode
For non-real-time use cases, use the
/api/v1/recommend/batch endpoint with batch customer lists.
Batch mode amortizes connection and parsing overhead across many decisions, achieving
higher throughput at the cost of individual response latency.Cost Optimization Tips
Start small
Begin with the Hobby tier for evaluation. Scale up only when you have real traffic
that demands it.
Free database tiers
Supabase and Neon free tiers provide sufficient PostgreSQL capacity for development
and small production workloads.
Enable Redis early
Redis caching is the single biggest performance win. Enable it before scaling compute.
Pay-per-request pricing
App Runner charges per request-second, making it cost-effective for unpredictable
or bursty traffic patterns.
Graduate to containers
Move to ECS or EKS only when you need sustained throughput above 100 req/s.
Container orchestration adds operational overhead.
Spot instances for workers
Use Spot instances for worker pods handling non-latency-sensitive batch processing.
Spot pricing can reduce compute costs by 60-90%.