Locking insights: an alternative to redis set nx ex / memcache add

A lock that does not timeout can lead to a standstill and manual cleanup. Simple solution: redis ‘set ex nx’ and memcached ‘add’.

When indefinite locks happen, getting information on why they happen helps to debug the locking mechanism and see if the processes always fail to unlock.

A softer locking approach to receive feedback when locks expire:

Code

def lock
  timeout = 30
  key = 'lock'
  now = Time.now.to_i
  if redis.setnx(key, "#{now}-#{Process.pid}-#{Socket.gethostname}")
    yield
  elsif (old = redis.get(key)) && now > old.to_i + timeout
    logger.error("Releasing expired lock #{old}")
    redis.delete(key) # next process can get the lock 
  end
end

Not 100% safe since the delete could cause multiple processes to get a lock, but depending on your usecase this might be an ok tradeoff.

Enqueue into Sidekiq via pure Redis (without loading sidekiq)

We want to enqueue jobs, but do not want to blow up the app with sidekiq and it’s dependencies.

Usage

RawSidekiq.enqueue("XyzJob", [1,2,3], :namespace => "custom")

Code

# https://grosser.it/2013/01/17/enqueue-into-sidekiq-via-pure-redis-without-loading-sidekiq
require "json"
require "redis"
require "securerandom"

class RawSidekiq
  def self.enqueue(queue, klass, args, options={})
    payload = {
      'class' => klass,
      'args' => args,
      'jid' => SecureRandom.hex(12),
      #'retry' => true
    }.to_json

    conn = Redis.new
    conn.multi do
      conn.sadd([options[:namespace], "queues"].compact.join(":"), queue)
      conn.lpush([options[:namespace], "queue", queue].compact.join(":"), payload)
    end
  end
end