You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Comprehensive reference for every background queue in the TalentTrust Backend
system. Covers queue names, processor files, job payloads, concurrency, retry
semantics, and an operator runbook for draining and replaying queues.
Architecture Overview
All background work flows through BullMQ (Redis-backed), orchestrated by
the QueueManager singleton. Each queue maps
1:1 to a JobType enum member and a dedicated processor function registered in
src/queue/processors/index.ts.
Retry policies are managed centrally by the
RetryPolicyManager, which merges built-in
defaults with environment-variable overrides and enforces safety bounds.
After 5 failed attempts, the job moves to the BullMQ failed state in
Redis. It is not routed to the Webhook DLQ (that DLQ is for webhook
delivery failures only — see § Webhook DLQ). Failed email
jobs can be listed via QueueManager.getFailedJobs() and replayed via
QueueManager.reprocessFailedJob().
interfaceReputationRecomputePayload{batchSize?: number;// Subjects per page (default 100)forceRecompute?: boolean;// Skip 24h freshness checkresumeFromCheckpoint?: boolean;// Resume from last checkpointcorrelationId?: string;requestId?: string;}
Checkpointing
The recompute processor persists progress via
reputationCheckpointStore.
On restart with resumeFromCheckpoint: true, it picks up from the last
processed subject ID. Each subject that faults is logged and skipped — a
single failure does not abort the batch.
interfaceBlockchainSyncPayload{network: 'stellar'|'soroban';startBlock?: number;// Omit to resume from last cursorendBlock?: number;// Omit for current chain headcorrelationId?: string;requestId?: string;}
Detects divergence between the backend's indexed milestone state and the
on-chain milestone state (missed events, reorgs, partial ingestion). The
job is report-only: it never writes canonical milestone/contract state —
it persists divergence report rows (milestone_divergence_reports) and logs
structured summaries. See src/milestones/divergence
for the full design.
Payload: MilestoneDivergenceScanPayload
interfaceMilestoneDivergenceScanPayload{tenantId?: string;// Scope the scan (reports are tenant-tagged)maxContracts?: number;// Bounded per run (default 100, cap 500)cursor?: string;// Offset cursor for incremental runsrunId?: string;// Opaque id; reports upsert under it (retry-safe)correlationId?: string;requestId?: string;}
Bounding, failure isolation, and retry semantics
One run compares at most maxContracts contracts, walking the contract
provider with a cursor — a large contract set is processed across many
runs, never loaded at once.
A per-contract RPC failure becomes an unavailable report and the run
continues.
A head-ledger RPC failure aborts the run so the queue retries it
(retried runs are idempotent: reports upsert under runId).
The Webhook Dead Letter Queue is a separate subsystem from the BullMQ
job queues above. It is backed by SQLite (not Redis) and stores failed
webhook delivery attempts for later inspection and replay.
Property
Value
Storage
SQLite (data/webhook-dlq.db or WEBHOOK_DLQ_PATH)
Max capacity
10,000 entries
Overflow policy
oldest-evict (removes oldest pending entry)
Max replay attempts
5
Poison message handling
Permanently dropped after reaching max replay attempts
Webhook Retry Policy
Parameter
Value
Max retries
5
Initial delay
1,000 ms
Max delay
30,000 ms
Multiplier
2×
Jitter
10%
Deduplication
Each DLQ entry is keyed by a SHA-256 hash of webhookId + JSON.stringify(payload).
Duplicate entries are rejected with a DUPLICATE_ENTRY error.
Incoherent combinations (e.g., MULTIPLIER on a fixed backoff) are
detected and corrected with a structured log warning.
Operator Runbook
Check Queue Health
// Programmaticconsthealth=awaitQueueManager.getInstance().getHealth();// Returns QueueHealthInfo[] with waiting/active/completed/failed/delayed counts// Or check the health probe// GET /health → probes[].name === 'queue'
failed count is growing — jobs are exhausting retries. Check
getRecentFailures() for the latest errors.
active count > 0 with no completions — a processor may be hung.
Check for timeout errors in structured logs (JobTimeoutError).
delayed count is high — jobs are backing off or scheduled. Normal
during retry storms but monitor the trend.
waiting count is growing — the producer is outpacing the consumer.
Consider increasing QUEUE_CONCURRENCY.
Drain a Queue (Pause Consumption)
constqm=QueueManager.getInstance();// 1. Stop accepting new jobsqm.stopAccepting();// 2. Wait for active jobs to completeawaitqm.drain();// 3. Persist any checkpointable stateawaitqm.checkpoint();// 4. Close workers and connectionsawaitqm.close();
The shutdown sequence in src/shutdown.ts orchestrates these steps
automatically for the entire process.
Replay Failed Jobs
// Replay a single failed job (deduplicated by replay key)constresult=awaitQueueManager.getInstance().reprocessFailedJob(JobType.CONTRACT_PROCESSING,'failed-job-id-here',);// result.replayJobId — the new job's ID// result.deduplicated — true if a replay was already in flight