Worker Domains
Domain-specialized worker containers for horizontal scaling and fault isolation
The Vezta backend worker process is partitioned into eight specialized worker domains (traders, market, misc, websocket, rtds, ai, sports, combos), alongside the HTTP-only api process. Each domain owns a subset of BullMQ queues and NestJS providers, gating execution via the backgroundOnly() helper. This enables horizontal scaling — add more worker containers per domain independently — and fault isolation — a SIGKILL in the misc domain won't drop traders order execution.
Domain Overview
| Domain | Role | Key Queues |
|---|---|---|
traders | Order execution, automated trading, and smart-money signals | insider-signals, insider-cluster-refresh, telegram-signals, top-trader, leaderboard-calc, tracker-refresh, prediction-refresh, copy-trade-exec, copy-trade-tp-sl |
market | Market data ingestion and sync | market-sync, trade-sync, short-duration-sync, holders-sync, catalog-sync, match-sync, price-snapshot, signal-detector |
misc | Notifications, rewards, alerts, and everything else | notification-dispatch, push, price-alert-check, tp-sl-monitor, order-monitor, sniper-monitor, spread-scanner, share-card-gen, wallet-profile, wallet-list-refresh, rewards-sync, standings, weather |
websocket | Persistent exchange WebSocket connections | No BullMQ queues — Polymarket CLOB WS, Kalshi WS, Binance WS |
rtds | Polymarket RTDS crypto price feed | No BullMQ queues — Chainlink price stream for BTC/ETH/SOL/XRP |
ai | Agent chat + research pipeline | agent-request, agent-resume, agent-summarize, research-request (gated by FF_AGENT_QUEUE_SPLIT) |
sports | ESPN sports-stats pipeline | sports-stats, prematch-reminder |
combos | Multi-leg combo / parlay trades | combo catalog sync + eligibility / settlement |
all | Legacy fallback (single-worker mode) | All queues (pre-Phase 4 configuration) |
How Domain Gating Works
The backgroundOnly() function in src/common/background/ acts as a provider gate. It accepts a module/provider list and an optional domain constraint:
// src/modules/monitor/monitor.module.ts
@Module({
providers: [
backgroundOnly([
SignalIngestionProcessor,
SignalClassifierProcessor,
], 'misc'), // Only runs when WORKER_DOMAIN=misc or all
],
})The shouldRunInDomain(domain) helper checks the WORKER_DOMAIN environment variable. Providers gated with backgroundOnly() are conditionally registered — they only exist in the DI container when the domain matches.
Deployment Topology
In production, nine application containers run from the same Docker image:
| Container | WORKER_DOMAIN | BACKGROUND_JOBS_ENABLED | Liveness Port |
|---|---|---|---|
vezta-production-api | api | false | 3001 (HTTP) |
vezta-production-worker-traders | traders | true | 3002 |
vezta-production-worker-market | market | true | 3003 |
vezta-production-worker-misc | misc | true | 3004 |
vezta-production-worker-websocket | websocket | true | 3005 |
vezta-production-worker-ai | ai | true | 3006 |
vezta-production-worker-rtds | rtds | false | 3007 |
vezta-production-worker-sports | sports | true | 3008 |
vezta-production-worker-combos | combos | true | 3009 |
The API container always has BACKGROUND_JOBS_ENABLED=false — it never consumes BullMQ queues. Worker containers use BACKGROUND_JOBS_ENABLED=true to activate queue consumption, then filter by domain.
Liveness Endpoint
Each worker container exposes a minimal HTTP server on its WORKER_HEALTH_PORT (3002–3009, one per domain) with a /health endpoint. This is used by Docker health checks and the deployment orchestration to determine when a worker is ready.
Entrypoints
The Docker image has two entrypoints wired via separate CMD directives:
| Entrypoint | File | Role |
|---|---|---|
| API | dist/src/main | Fastify HTTP server, controllers, WebSocket gateway |
| Worker | dist/src/worker | BullMQ consumer, liveness HTTP server |
The dist/src/worker entrypoint uses createApplicationContext() (not createApplication()) — it bootstraps the full DI container but skips HTTP controller registration, since workers have no REST API surface.
Scaling Behavior
traders— Scale with order volume. Each instance consumes from the same Redis-backed queue; BullMQ ensures each job is processed exactly once.market— Typically one instance. Market sync usesrepeatablejobs with fixed schedules; multiple instances would duplicate work.misc— One instance for leaderboard, notifications, email. Share card generation is CPU-bound (Satori SVG → sharp PNG); if this becomes a bottleneck, decouple into its own domain.websocket— Scale independently. Each instance maintains persistent WS connections (Polymarket CLOB WS, Kalshi WS, Sports WS, User WS, Binance). Multiple instances provide connection redundancy.rtds— Typically one instance. Streams Chainlink crypto prices from Polymarket RTDS; low resource footprint (384 MB limit).ai— Scale with concurrent agent chats. Runs theagent-request/agent-resume/agent-summarize(+research-request) queues over a Redis-Streams event bus; active only whenFF_AGENT_QUEUE_SPLIT=true, otherwise the chat loop runs in-process on the API container.sports— Typically one instance. ESPN pollers (live scores, standings, prematch/resolve) are BullMQ schedulers, so it must run withBACKGROUND_JOBS_ENABLED=true.combos— Typically one instance. Combo catalog sync plus eligibility and settlement for multi-leg parlay trades.