Module: Wurk::Fetcher::OomDrain
- Defined in:
- lib/wurk/fetcher/oom_drain.rb
Overview
The way out of a full Redis.
LMOVE and ZADD are denyoom commands, so at maxmemory under the
noeviction policy Wurk requires, every worker would be refused its next
job and could therefore never free the memory that is refusing it. A
Redis 7 script flagged allow-oom runs those commands anyway, and moving a
job between two lists grows nothing but a small key.
Deliberately not under lib/wurk/lua/: everything there is uploaded at boot,
and Dragonfly refuses to compile a #!lua shebang, so one script in that
directory would crash every child at boot. The same refusal is why these
go by EVAL and not EVALSHA: a path taken only while Redis is full has no
use for a NOSCRIPT recovery arm. A server that refuses the script raises
the original OOM, which is exactly the behaviour without this class.
Constant Summary collapse
- HEADER =
"#!lua flags=allow-oom\n"- HEARTBEAT_TTL =
Heartbeat::TTL_SECONDS. Spelled out because the fetcher loads without the heartbeat, which pulls in the Processor.
60- CLAIM =
Claiming proves this process alive, and it has to say so itself: the heartbeat's own HSET is refused too. The hash then lapses (a booting process never gets one), other hosts' reapers drain the jobs it is running, and the swarm's liveness check sees its beat stand still.
KEYS = [processes, identity, public, private, public, private, ...] ARGV = [info_json, beat, ttl_seconds] Returns payload for the n-th queue, nil when all were empty.
<<~LUA.freeze #{HEADER}if redis.call('EXISTS', KEYS[2]) == 0 then redis.call('HSET', KEYS[2], 'info', ARGV[1]) redis.call('SADD', KEYS[1], KEYS[2]) end redis.call('HSET', KEYS[2], 'beat', ARGV[2]) redis.call('EXPIRE', KEYS[2], ARGV[3]) for i = 3, #KEYS, 2 do local job = redis.call('LMOVE', KEYS[i], KEYS[i + 1], 'RIGHT', 'LEFT') if job then return { (i - 1) / 2, job } end end return nil LUA
- REQUEUE =
(HEADER + Lua::RELIABLE_REQUEUE).freeze
- LOCK =
KEYS = [lock], ARGV = [ttl_ms]. The reaper's sweep lock: refused, no sweep would ever run past maxmemory. Same reply as the SET it stands in for.
<<~LUA.freeze #{HEADER}return redis.call('SET', KEYS[1], '1', 'NX', 'PX', ARGV[1]) LUA
- ZADD =
KEYS = [sorted_set], ARGV = [score, member]
<<~LUA.freeze #{HEADER}return redis.call('ZADD', KEYS[1], ARGV[1], ARGV[2]) LUA
Class Method Summary collapse
-
.claim(conn, error, keys:, info:, beat:) ⇒ Object
erroris the OOM that sent the caller here, raised again if the server cannot run the script. - .lock(conn, error, key, ttl_ms) ⇒ Object
-
.requeue(conn, error, private_list, public_q, job) ⇒ Object
Returns 1 when moved, 0 when the job has left the private list since.
- .zadd(conn, error, key, score, member) ⇒ Object
Class Method Details
.claim(conn, error, keys:, info:, beat:) ⇒ Object
error is the OOM that sent the caller here, raised again if the server
cannot run the script. Returns [queue_index, payload] or nil.
70 71 72 |
# File 'lib/wurk/fetcher/oom_drain.rb', line 70 def claim(conn, error, keys:, info:, beat:) run(conn, error, CLAIM, [Keys::PROCESSES, Component.identity, *keys], [info, beat, HEARTBEAT_TTL]) end |
.lock(conn, error, key, ttl_ms) ⇒ Object
79 80 81 |
# File 'lib/wurk/fetcher/oom_drain.rb', line 79 def lock(conn, error, key, ttl_ms) run(conn, error, LOCK, [key], [ttl_ms]) end |
.requeue(conn, error, private_list, public_q, job) ⇒ Object
Returns 1 when moved, 0 when the job has left the private list since.
75 76 77 |
# File 'lib/wurk/fetcher/oom_drain.rb', line 75 def requeue(conn, error, private_list, public_q, job) run(conn, error, REQUEUE, [private_list, public_q], [job]) end |
.zadd(conn, error, key, score, member) ⇒ Object
83 84 85 |
# File 'lib/wurk/fetcher/oom_drain.rb', line 83 def zadd(conn, error, key, score, member) run(conn, error, ZADD, [key], [score, member]) end |