- Docs
- Architecture
- Domain Layer
The domain layer, no I/O.
The memory/SQLite domain layer contains pure queue logic: no I/O, just core algorithms and data structures. PostgreSQL shares the public job model but owns ordering and coordination in database transactions.
This page describes the base memory/SQLite engine. PostgreSQL servers use the same public states and payload types, but authoritative queues, limits, leases, and claims live in PostgreSQL rather than these in-memory shards. See Storage backends and the application layer.
Module Structure
Section titled “Module Structure”src/domain/├── types/ # Type definitions└── queue/ # Core queue logic ├── shard.ts # Shard container ├── priorityQueue.ts # 4-ary indexed heap ├── dlqShard.ts # Dead letter queue ├── uniqueKeyManager.ts # Deduplication ├── limiterManager.ts # Rate/concurrency ├── groupLimiterManager.ts # Per-group rate/concurrency ├── groupScheduler.ts # Secondary priority/FIFO lanes + rotation ├── dependencyTracker.ts # Job dependencies ├── temporalManager.ts # Temporal index + delayed jobs ├── waiterManager.ts # Long-poll waiters └── shardCounters.ts # Running shard totalsSharding Architecture
Section titled “Sharding Architecture”In memory/SQLite mode, jobs are distributed across N shards (auto-detected from CPU cores) for parallelism:
Shard count is a power of 2, based on CPU cores, max 64.
Shard Composition
Section titled “Shard Composition”Each shard is a composition of managers:
The shard counters make aggregate queued, delayed, and DLQ totals constant-time.
Splitting ready jobs into waiting versus prioritized still examines current
queue entries. Multi-queue summary calls batch that work and traverse global
processing/completed/dependency collections once, instead of once per queue.
Priority Queue Flow
Section titled “Priority Queue Flow”4-ary indexed heap with lazy deletion:
Long-Poll Waiters
Section titled “Long-Poll Waiters”Waiters are isolated by queue. Each queue keeps an append-only entry array with a head cursor, an active count, and one coalesced pending-notification bit. Notification clears a waiter’s timer immediately and advances the cursor; it does not repeatedly filter or splice the full array. Consumed prefixes are compacted once the head reaches 1,024 entries and at least half the array is stale. Surplus batch notifications collapse into one retry hint rather than accumulating credits that would cause repeated empty pulls.
Job State Machine
Section titled “Job State Machine”Dependency Resolution Flow
Section titled “Dependency Resolution Flow”Reverse Index:
dependencyIndex: Map<JobId, Set<JobId>>DLQ (Dead Letter Queue) Flow
Section titled “DLQ (Dead Letter Queue) Flow”Rate & Concurrency Limiting
Section titled “Rate & Concurrency Limiting”FIFO Groups
Section titled “FIFO Groups”Groups preserve claim order within each group without making group execution
serial by default. Active ownership is counted: activeGroupCounts is the
authoritative per-group count, while activeGroups is its set-shaped view for
telemetry and membership. With no group concurrency option, the limit is
unbounded. A Worker can supply a default per-group concurrency cap, and an
explicit server-side override can replace it for one group. Per-group fixed
window rate limits are checked by the same eligibility path.
The primary priority queue remains authoritative. GroupScheduler is a lazy
secondary view built only when grouped work appears: one heap for ready
ungrouped jobs, one delayed/TTL heap, and one immutable priority/FIFO lane per
group. Lower BullMQ Pro group priorities run first; equal-priority entries keep
their durable admission order.
These indexes let a rate- or concurrency-blocked group remain parked while
other groups continue round-robin, avoiding queue-head blocking and temporary
pop/reinsert cycles. Primary and secondary membership change together under the
same synchronous shard lock.