Redis Key Layout¶
This is the complete map of every Redis key TaskRunner creates, read or writes.
Use it to namespace multi-tenant Redis, debug with redis-cli, size memory, or
reason about what gets cleaned up on a flush.
All keys are namespaced by ConsumerGroup unless noted. Substitute <group>
with your configured TaskRunnerConfig.ConsumerGroup, and <prefix>:<queue>
with your redisstream prefix/queue.
Streams & PEL¶
| Key | Type | Purpose |
|---|---|---|
<prefix>:<queue> |
Stream | The main message queue. XADD on dispatch, XREADGROUP by fetchers, XACK/XDEL on completion. |
stream:<...> PEL |
(implicit) | The Pending Entries List attached to the consumer group. Not a separate key -- it's part of the stream's group state. XINFO STREAM <key> shows its size. |
The consumer group itself is created lazily (XGROUP CREATE ... MKSTREAM) on
the first XREADGROUP that hits NOGROUP. Consumers within the group are
named <group>_<host>_<fetcherID> and cleaned up after 10 min of idle.
Delayed scheduling¶
| Key | Type | Purpose |
|---|---|---|
taskrunner:<group>:delayed_tasks |
ZSET | Members are DelayedTask JSON; scores are run_at unix seconds. Leader polls every 5s with ZRANGEBYSCORE 0..now, dispatches each, and ZREMs. |
Leader election¶
| Key | Type | Purpose |
|---|---|---|
taskrunner:leader:<base32(sha1(<group>))> |
String | The leadership lease. Value = random instance ID. TTL = 5s (renewed every ~2.5s). Operated atomically via Lua (SET NX PX, value-guarded PEXPIRE, value-guarded DEL). |
The key is a hash of the group name so it's stable across instances and free of special characters.
Worker tuning¶
| Key | Type | Purpose |
|---|---|---|
taskrunner:<group>:worker_tuning_capacity |
String | Published by the leader with the chosen pool capacity. TTL = 20s (or LongQueueThreshold, whichever is greater). Followers read it via applyRemoteWorkerTuning. |
Timing metrics¶
| Key | Type | Fields |
|---|---|---|
taskrunner:<group>:metrics |
Hash | <taskName>_sum (cumulative ms), <taskName>_count (samples), delayed_tasks_schedule_sum, delayed_tasks_schedule_count. Reset to zero on MetricsResetInterval by the leader. |
Replication detection¶
| Key | Type | Purpose |
|---|---|---|
taskrunner:replication:<host> |
String | Per-instance heartbeat. Value = instance UUID. TTL = 15s, renewed every 10s. GetNumberOfReplications counts these keys (cached 10s) to derive the auto-detected replication factor. |
Unique jobs¶
| Key | Type | Purpose |
|---|---|---|
taskrunner:unique:<taskName>:<uniqueKey> |
String | Distributed mutex (redsync). Held from Dispatch until the worker's afterProcess. TTL = Task.UniqueFor seconds. Value = random lock token, verified on release. |
When UniqueKey is nil, the key collapses to
taskrunner:unique:<taskName>.
Queue metrics ring (backend)¶
| Key | Type | Purpose |
|---|---|---|
ring:<prefix>:metrics:<queue> |
List | Circular buffer (capacity 1000) of per-message claim delays (nanoseconds), backing queue.GetMetrics. RPOPLPUSH rotates it. |
Full map¶
graph LR
subgraph "Per consumer group"
S["{prefix}:{queue}<br/>Stream + PEL"]
D["taskrunner:{group}:delayed_tasks<br/>ZSET"]
L["taskrunner:leader:{hash}<br/>String (lease)"]
W["taskrunner:{group}:worker_tuning_capacity<br/>String"]
M["taskrunner:{group}:metrics<br/>Hash"]
end
subgraph "Per instance"
R["taskrunner:replication:{host}<br/>String"]
end
subgraph "Per unique job"
U["taskrunner:unique:{task}:{key}<br/>String (lock)"]
end
subgraph "Per queue (backend)"
RG["ring:{prefix}:metrics:{queue}<br/>List"]
end
Producers --> S
Producers --> D
Leader --> D
Leader --> S
Leader --> L
Leader --> W
Leader --> M
Followers --> W
Followers --> M
Instances --> R
Workers --> U
Workers --> M
Backend --> RG
Flushing / cleanup¶
To wipe TaskRunner's state for a consumer group (useful in dev):
# Streams + group
XGROUP DESTROY <prefix>:<queue> <group>
DEL <prefix>:<queue>
# Delayed tasks
DEL taskrunner:<group>:delayed_tasks
# Tuning + metrics
DEL taskrunner:<group>:worker_tuning_capacity
DEL taskrunner:<group>:metrics
# Leadership (force a re-election)
DEL taskrunner:leader:<base32(sha1(<group>)>
# Replication (optional; will repopulate)
EVAL "return redis.call('del', unpack(redis.call('keys', ARGV[1])))" 0 "taskrunner:replication:*"
Don't flush in production
Flushing the stream drops in-flight messages. Flushing the leader key forces a re-election. Flushing unique locks can allow duplicate dispatch. Only do this in dev/staging.
Key count¶
For a deployment with G consumer groups, T registered tasks (across all groups), I instances, U in-flight unique jobs, and D delayed jobs:
streams: G
ZSETs: G
leader keys: G
tuning keys: <= G (only present when tuning enabled + a value published)
metrics hashes: G
replication: I
unique locks: U
metric rings: G
Even at moderate scale this is well under Redis's comfortable key limit (tens
of millions). Memory is dominated by the stream length (bounded by
deleteOnAck) and the PEL size (bounded by in-flight work).