Hash + Mutex + case/when dispatch

Before the code: what a key-value store actually is

So far the server understands the Redis language but has nothing to remember. Time to give it a brain. The brain is a Ruby Hash that lives in memory, plus a small companion table of timers for keys that should disappear on their own. That is the whole Redis storage model, in plain English. Ruby has a GIL, but the GIL is not enough on its own. A multi-step read-modify-write can still tear, so we wrap shared access in Mutex.synchronize.

The store, with and without timers

A plain Hash remembers forever. A Hash plus a timer table is what makes Redis a cache, a session store, and a rate limiter.

Quiz: Quiz

Loading practice…

The GIL serialises bytecode execution but does NOT make a Hash#[]= atomic across thread schedules. A multi-step read-modify-write can still race. Mutex.synchronize serialises the whole block, closing the window.

03-get-set/server.rb
ruby
$store = {}
$mu = Mutex.new

# inside SET:
$mu.synchronize { $store[rest[0]] = rest[1] }
encode_simple('OK')

# inside GET:
encode_bulk($mu.synchronize { $store[rest[0]] })

Shared $store + $mu. Mutex.synchronize is exception-safe by design.

03-get-set/server.rb
ruby
def handle_args(args)
  return encode_error('ERR empty') if args.empty?
  cmd = args[0].upcase
  rest = args[1..]
  case cmd
  when 'PING' then rest.empty? ? encode_simple('PONG') : encode_bulk(rest[0])
  when 'SET'  then ...
  when 'GET'  then ...
  when 'DEL'  then ...
  when 'KEYS' then ...
  else encode_error("ERR unknown command '#{args[0]}'")
  end
end

case/when over args[0].upcase. Ruby compiles this into an efficient internal dispatch.

Matching exercise: Pick the right Ruby concurrency primitive

Loading practice…