E2.02.1 · Jobs, queues & workers · dive 1
The job model
A Job is a thin Elixir struct over a Redis hash — the struct is a view, the hash is the truth.
EchoMQ.Job.new(queue_name, name, data, opts \\ []) (arity 4) builds a
%EchoMQ.Job{}. The struct is a runtime materialization; the job's canonical state lives in
Redis as a hash (E2.01). The opts map is the router — :delay,
:priority, :parent, :job_id — and the
struct carries pure predicates over its own fields. Pick the options on the right to read where the job lands.
Job.new(opts) · pick the options
The struct
new/4 sets the loud fields and folds the rest of opts into the
opts map. The id is opts[:job_id] if given, else auto-generated;
the three positional values become name, data, and
queue_name.
# EchoMQ.Job.new/4 — the loud fields (lib/echomq/job.ex, verified) def new(queue_name, name, data, opts \\ []) do opts = opts_to_map(opts) %EchoMQ.Job{ id: Map.get(opts, :job_id) || generate_id(), # custom or auto name: name, data: data, queue_name: queue_name, opts: opts, prefix: Map.get(opts, :prefix, "bull"), timestamp: Map.get(opts, :timestamp, ...), delay: Map.get(opts, :delay, 0), priority: Map.get(opts, :priority, 0), # 0 = highest parent: Map.get(opts, :parent), parent_key: build_parent_key(Map.get(opts, :parent)), deduplication_id: get_in(opts, [:deduplication, :id]) } end
The full verified defstruct carries id,
name, data, queue_name,
token, connection, worker,
parent_key, parent, processed_by,
repeat_job_key, deduplication_id,
deferred_failure, processed_on,
finished_on, failed_reason,
return_value, opts (default %{}),
prefix ("bull"), timestamp,
delay, priority, progress,
stacktrace, plus attempts_made,
attempts_started, and stalled_counter. The compressed hash field
names that back them — atm (attemptsMade), ats,
stc, deid, defa — belong to
E1 · the protocol; this dive names the model, and the protocol decodes the hash field by field.
The hash schema, field by field, is E1 · the job hash — this dive names the struct and its options; the protocol owns the wire layout.
The options route the job
The opts map decides where a freshly added job lands, and the add script differs per
target. The interactive above reports the target state and the add script for each option set; the table below is
its data.
# the opts that route a job → target state → add* Lua script delay > 0 → delayed addDelayedJob-6 priority > 0 → prioritized addPrioritizedJob-9 # 0 = highest priority neither → wait addStandardJob-9 # other opts (the full job_opts map) :job_id custom id :prefix default "bull" :timestamp enqueue time :parent → parent_key (flows) :deduplication → deduplication_id :lifo LIFO ordering :attempts max retries :backoff %{type: :fixed | :exponential, delay: ms}
The options decide the location, and the location decides the add script. A plain job lands in
wait; a delay or a priority routes it elsewhere — and priority 0 is the highest.
State predicates — pure functions on the struct
The Job carries pure predicates over its own fields. Pick one to read what it tests and where it reads.
The pure predicates · pick one
The predicates are pure functions of the struct's fields — no Redis call. They read the materialized view; the canonical state is still the hash plus the state-key membership.
The hash is L1 — the runtimes only materialize it
A Job's canonical state is a Redis hash, and the hash is L1 — immutable and shared across the
three runtimes. EchoMQ.Job.from_redis(job_id, queue_name, data, opts \\ []) reconstructs a
Job from a Redis HGETALL of that hash; to_redis/1 serializes it
back. The same hash, with the same field names, exists whether the queue is driven from Elixir, Go, or Node.js; only
the object each runtime builds from it varies.
The job hash schema in Redis and its field names — emq:{q}:{id}, the same location
and the same field set in every runtime.
Elixir materializes the hash into %EchoMQ.Job{} via
Job.from_redis/4; Go materializes it into its own struct; Node.js into its own class.
Each runtime builds a different object from the same hash; the wire hash does not move.
A Job is a thin view, not the source of truth. Restart the materializer and the hash is still in Redis — re-read it and you have the same Job again.
Next: the stateless queue — the
add/4 path that admits this Job with a string name and a connection, and nothing held in
memory.
References
The Redis hash behind the model, and the BullMQ job options it mirrors.
Sources
- BullMQ — Documentation — the job model and its options.
- BullMQ — Home — the project EchoMQ implements.
- Redis — Hashes — the HGETALL the Job is reconstructed from.
- Redis — HSET — the write that creates a job hash.
Related in this course
- E2.02 · Jobs, queues & workers — the module hub.
- E2.02.2 · The stateless queue — the queue that admits the job.
- E1 · The job hash — the hash field by field.
- E2.01.1 · The eight states — the states a job's options target.
- redis-patterns R3 · Reliable queues — states-as-locations, applied.