Skip to main content
Audience: SREs, platform operators, DevOps engineers Last updated: 2026-02-23 Infrastructure: EKS (Kubernetes), RDS PostgreSQL, ElastiCache Redis, PgBouncer

Table of Contents

  1. Scaling the API Layer
  2. Scaling Workers
  3. Scaling the Database
  4. Scaling Redis
  5. Scaling PgBouncer
  6. Capacity Planning

1. Scaling the API Layer

Current Architecture

Horizontal Pod Autoscaler (HPA)

Default HPA configuration (from Helm chart):
The default Helm chart provides CPU-based HPA only. For production environments with high throughput, add memory and custom metric scaling via a Helm values override:

HPA Tuning

Adjusting target CPU utilization:
Adjusting replica bounds:
Adjusting scale-up/down behavior:

Manual Scaling

For planned events or emergencies:

Pod Resource Tuning

Adjusting pod resources:

Pre-scaling for Planned Events

Scheduler Behaviour When Running Multiple API Replicas

Each API pod starts two in-process schedulers on boot:
  • The Flow scheduler (fires due pipelines and file_arrival triggers) takes a PostgreSQL advisory lock, so it is safe to run many API replicas — only the lock holder fires each tick. Disable with FLOW_INTERNAL_SCHEDULER_ENABLED=false.
  • The maintenance scheduler (self-invokes the /api/**/cron/* jobs when CRON_SECRET is set) assumes a single replica — it is not distributed-locked, so N replicas run N duplicate passes. The cron jobs are idempotent, so duplicates are harmless, but for large fleets disable it on all replicas (MAINTENANCE_SCHEDULER_ENABLED=false) and drive the cron routes from one external scheduler (EventBridge or a K8s CronJob) instead.

2. Scaling Workers

Current Architecture

KEDA Autoscaler

The Helm chart ships a KEDA ScaledObject for the worker when worker.keda.enabled: true. It scales the kaireon-worker deployment on the depth of the BullMQ queue lists. Chart defaults: minReplicas: 1, maxReplicas: 10, queueThreshold: "5" (see helm/values.yaml). Rendered ScaledObject (from helm/templates/worker-deployment.yaml):
The five worker queues are batch-jobs, dsar-jobs, journey-jobs, retrain-jobs, and seed-jobs. Add a redis trigger per queue you want to scale on, pointing listName at the matching bull:<queue>:wait key.

KEDA Tuning

Adjusting trigger thresholds:
Recommended settings by scenario: Only minReplicas, maxReplicas, and queueThreshold (→ listLength) are exposed through helm/values.yaml; the other columns require editing the ScaledObject template or a custom overlay.

Manual Worker Scaling

Worker Resource Tuning

Workers are CPU-intensive during scoring and memory-intensive during pipeline execution.

Queue-Specific Worker Pools

For isolating workloads, deploy separate worker pools per queue:

3. Scaling the Database

Vertical Scaling (RDS)

Instance type progression: Scaling up:
Important: Scaling up causes a brief outage (typically 1-3 minutes). Schedule during maintenance windows if possible. With Multi-AZ, failover minimizes downtime.

Read Replicas

Use read replicas to offload read-heavy queries (dashboards, analytics, reporting). Creating a read replica:
Application configuration for read replicas: The application connects through a single DATABASE_URL and does not expose a read/write-split env var (there is no DATABASE_READ_URL). To serve reads from a replica, either point read-heavy tooling (dashboards, ad-hoc analytics, BI) directly at the replica endpoint, or front reads with a proxy/PgBouncer that routes to the replica. Routing the application’s own reads to a replica requires application-level changes, not a config toggle. Monitoring replica lag:
Promoting a read replica (for failover or splitting):

Storage Scaling

Connection Limit Scaling

When scaling the database vertically, adjust max_connections accordingly:

4. Scaling Redis

Vertical Scaling (ElastiCache)

Instance type progression: Scaling up (single node, non-clustered):

Cluster Mode

For datasets that exceed single-node memory or require higher throughput. Enabling cluster mode:
Resharding (adding shards):
Application configuration for cluster mode:

Redis Memory Management

Scaling Redis for Specific Use Cases

Decision caching (read-heavy):
  • Use read replicas for read distribution.
  • Set short TTLs (30-60s) to limit memory growth.
  • Use volatile-lru eviction.
Queue processing (write-heavy):
  • Scale vertically for more throughput.
  • Monitor instantaneous_ops_per_sec.
  • Consider cluster mode if >100K ops/sec.
Session storage:
  • Separate from cache Redis.
  • Use noeviction policy (sessions must not be evicted).
  • Size for peak concurrent users.

5. Scaling PgBouncer

Pool Size Calculations

Scaling PgBouncer Horizontally

Deploy multiple PgBouncer instances behind a Kubernetes Service:
Adjusting pool size when scaling API/workers: Important: Total max_db_connections across all PgBouncer instances must not exceed PostgreSQL max_connections.

Applying Pool Changes


6. Capacity Planning

Metrics to Track

Monthly Capacity Review Checklist

  1. Review 30-day trends for all metrics above.
  2. Project growth for the next 90 days based on customer onboarding pipeline.
  3. Identify any component within 30 days of hitting a threshold.
  4. Plan scaling actions with cost estimates.
  5. Update this document with new current values.

Cost-Aware Scaling

  • Use Spot instances for batch workers (up to 70% savings).
  • Use Reserved Instances for baseline API and database capacity.
  • Use Graviton (arm64) instances for 20% better price-performance.
  • Scale down non-production environments during off-hours:

Load Testing Before Scaling

Before major scaling changes, validate with load tests:
Key metrics to capture during load tests:
  • P50, P95, P99 latency at each VU level.
  • Error rate at each VU level.
  • Database connection count and query time.
  • Redis memory and ops/sec.
  • Pod CPU and memory utilization.