Skip to content

Instantly share code, notes, and snippets.

@mperham
Last active September 8, 2016 19:56
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mperham/544327d176f9693df05d4d60548b0b16 to your computer and use it in GitHub Desktop.
Save mperham/544327d176f9693df05d4d60548b0b16 to your computer and use it in GitHub Desktop.
Fast counters with Redis, persisting to DB
class Counter
include Sidekiq::Worker
# I'd use a separate Redis instance from Sidekiq
REDIS = ConnectionPool.new(size: 5) { Redis.new(...) }
# Call this API in your Rails app code to increment counters
# as the user does things.
def self.incr(name, amount=1)
key = "counter-#{name}-#{current_window}"
REDIS.with do |conn|
conn.pipeline do
conn.incrby(key, amount)
conn.expire(key, 2.hours)
end
end
end
def self.current_window
Time.now.strftime("YYYYMMDDHH")
end
# Use Sidekiq Enterprise's cron feature to run this hourly or
# whenever each window closes.
def perform
REDIS.with do |conn|
to_ignore = /#{current_window}/
cursor = "0"
loop do
cursor, names = conn.scan(cursor, match: "counter-*", count: 100)
if names.size > 0
vals = conn.mget(*names)
names.zip(vals).each do |counter, value|
next if counter =~ to_ignore
next if value == nil # counter expired between scan and mget
# persist counter with value to PG or use a bulk insert
end
end
break if cursor == "0"
end
end
end
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment