Request Lifecycle
Every Recommend API request passes through a series of stages. Understanding where caching and rate limiting apply helps you tune for your workload.Caching Strategy
KaireonAI uses Redis as its caching layer. All cache reads go through a single read-through helper that transparently handles cache misses by querying PostgreSQL and storing the result with a configurable TTL, so callers never need to manage cache invalidation themselves.What gets cached
Tuning TTLs
Enrichment sources support per-source TTL configuration in the Decision Flow’s enrichment stage:cacheTtlSeconds lower for volatile data (real-time signals, session context)
and higher for stable data (customer demographics, account details).
Cache Invalidation
Entity caches (offers, rules, policies) use a TTL-based expiration strategy. After updating an offer or policy via the CRUD API, changes propagate within the TTL window (up to 5 minutes for offers/policies, 30 seconds for guardrails). For immediate invalidation, restart the API process or reduce the TTL via environment configuration.Rate Limiting
The Recommend API enforces per-tenant, per-endpoint rate limiting using a Redis sorted-set sliding window algorithm.Algorithm
- ZREMRANGEBYSCORE — remove entries outside the current window
- ZADD — add the current request with its timestamp as score
- ZCARD — count entries remaining in the window
- EXPIRE — set key TTL to window duration + 1 second for cleanup
Tier Configuration
Rate limits are tenant-scoped with three built-in tiers:
Configure per-tenant tiers via environment variables:
Response Headers
When a request is rate-limited, the API returns HTTP 429 with:Fail-Open vs Fail-Closed
The rate limiter supports two failure modes when Redis is unavailable:- Fail-open (default): Falls back to in-memory rate limiting. Use for standard API endpoints where availability matters more than strict enforcement.
- Fail-closed: Returns 429 when Redis is down. The Recommend API uses
failOpen: falseto prevent abuse when rate limit state is unavailable.
Edge-Layer Rate Limiting
For DDoS protection, layer edge-level rate limiting in front of the API: nginxlimit_req_zone, AWS WAF rate rules, or Cloudflare rate limiting rules.
The application-level limiter handles tenant-scoped business logic limits;
the edge layer handles volumetric protection.
Scoring Failure Fallback
The scoring stage does not wrap models in a stateful circuit breaker. A model error is caught per request and a fallback score is applied so the decision still completes:- For an external scoring endpoint that fails, the candidate score becomes
0.5 * fitMultiplier(the0.5default is a constant in the scoring stage). - When a model is missing, scoring falls through to
priority_weighted(priority / 100). - The response carries
degradedScoring: true(and each affected candidate is marked) so callers can tell the decision ran degraded.
lib/circuit-breaker.ts, defaults 5 failures / 5-minute cooldown / 3
half-open probes, state optionally persisted to Redis under kaireon:cb:)
guards outbound integrations — alert and trigger webhooks, connector tests,
CMS sync, and audit forwarding — not scoring. See
Operations — Circuit Breakers.
Monitoring
Thekaireon_decision_flow_execution_latency_ms histogram tracks end-to-end
pipeline latency; kaireon_scoring_latency_ms isolates the scoring stage.
Decisions that fell back on a scoring error surface via the degradedScoring
flag on the response payload.
Atomic Cap Checking
The engine ships anatomicCapCheck helper (lib/decision-flow-engine.ts) that
performs race-free daily counting via Redis INCR:
INCR kaireon:cap:{key}— single atomic read-and-increment (the caller supplieskey, e.g.mandatory:{offerId}:{YYYY-MM-DD})- On first increment (
current === 1), set EXPIRE to end of current UTC day - If
current > cap, the check returnsallowed: false(the caller supplies thecapvalue; there is no built-in default)
This helper is present in the codebase but is not currently wired into the
active decision pipeline — the composable pipeline enforces mandatory-offer
and frequency rules through the contact-policy stage instead. Treat this
section as reference for the helper’s behavior, not a description of a live
per-decision cap.
Connection Pooling
KaireonAI uses Prisma 7 with the@prisma/adapter-pg driver adapter. Connection
pooling is handled by the underlying pg Pool.
Configuration
The database connection is configured inprisma.config.ts via the DATABASE_URL
environment variable. Pool sizing is controlled through connection string parameters:
Sizing Guidance
Rule of thumb: Total connections across all replicas should not exceed your
database’s
max_connections minus a buffer for admin/monitoring connections.
Horizontal Scaling
The KaireonAI API is stateless — all shared state lives in Redis and PostgreSQL. This means you can scale API instances horizontally with no coordination overhead.Architecture
Key Properties
- No session affinity required: Any API pod can handle any request. Rate limit state and caching are in Redis; all persistent state is in PostgreSQL.
- In-memory circuit breakers are per-process: Each pod tracks its own model failure counts. This is intentional — a model failure on one pod does not cascade to others, and each pod independently probes recovery.
- In-memory rate limit fallback is per-process: When Redis is down, each pod
maintains its own rate limit counters. Effective limits become
configured_limit x num_podsduring Redis outages. - Scale API pods independently from worker pods: Decision API pods handle synchronous request/response. Data pipeline worker pods handle asynchronous ETL. Size each tier based on its workload.
- In-process schedulers need attention at multi-replica: by default each API
container also runs the internal Flow scheduler and the maintenance (cron)
scheduler in-process (see Process Model).
The Flow scheduler is multi-replica safe via a PostgreSQL advisory lock, so it
is fine to leave enabled on every replica. The maintenance scheduler assumes a
single replica — its jobs are idempotent so duplicate passes are harmless, but
for a clean multi-replica setup disable it (
MAINTENANCE_SCHEDULER_ENABLED=false) and drive the/api/**cron routes from one external scheduler (Kubernetes CronJobs or EventBridge). Move queue and outbox processing to dedicated pods withWORKER_INPROCESS=0.
Batch vs Streaming Pipelines
Data pipelines support two execution modes, configured per-pipeline in theexecutionConfig JSON field:
Batch Mode
For scheduled or on-demand data loads:Streaming Mode (planned)
The config shape below is reserved for the future streaming runtime — today it has no effect beyond being persisted on the pipeline record:K8s Worker Pod Configuration
Pipeline execution runs on dedicated worker pods, separate from the API tier. Configure resource limits based on pipeline complexity:- CPU-bound transforms (expression evaluation, hashing): Scale
parallelismup to available CPU cores. - I/O-bound transforms (external lookups, PII masking): Higher
parallelismwith moderate CPU allocation. - Memory-bound transforms (large batch joins): Increase pod memory limits
and reduce
batchSize.
Production Tuning Checklist
Under 1K decisions/day
- Single API instance with default settings
- In-memory rate limiting and caching are sufficient
- Default connection pool (5-10 connections)
- No Redis required (in-memory fallbacks handle the load)
1K — 100K decisions/day
- Redis required for rate limiting, enrichment caching, and atomic cap checks
- Tune enrichment TTLs: increase to 300s+ for stable customer data
- Increase connection pool to 15-20 per instance
- Enable decision tracing with a sample rate (e.g., 10%) rather than 100%
- Monitor
decisionLatencyMsandscoringModelFailureTotalmetrics
100K+ decisions/day
- Multiple API replicas behind a load balancer (3+ pods recommended)
- Dedicated Redis instance (ElastiCache or equivalent) with sufficient memory for rate limit sorted sets + enrichment cache
- PostgreSQL read replicas for offer/policy reads; primary for writes only
- Use PgBouncer or RDS Proxy to multiplex database connections
- Keep the offer scan set bounded by tightening the
inventorynode’s scope and status filters (it has no built-in scan limit) - Lower guardrail TTL if policy changes need sub-30s propagation
- Configure edge-layer rate limiting (AWS WAF, Cloudflare) for DDoS protection
- Enable budget pacing for high-volume offers to spread delivery across the day
- Override the model-degraded fallback score in the decision flow engine if your scoring distribution warrants a different default than 0.5
Environment Variables Reference
The decisioning defaults below are currently compiled-in constants, not
environment variables. To change one, edit the corresponding constant in the
circuit-breaker module or the decision-flow engine module and redeploy:
Related
Architecture Overview
System architecture and module layout
Operations
Monitoring, dashboards, and alerting
Decision Engine
How the decision pipeline works