Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Redis: Persistence Power

Redis: Persistence Power

A practical, use case approach to looking into Redis.

Given at RubyMidwest 2010. http://rubymidwest.com/

Nick Quaranto

January 12, 2012
Tweet

More Decks by Nick Quaranto

Other Decks in Programming

Transcript

  1. the basics persist data as you think of it in

    memory, sync to disk in background ridiculously fast master-slave replication keys = strings, value = data structures Tuesday, August 10, 2010
  2. use it: redis-cli % ./redis-cli SET user:1:name qrush OK %

    ./redis-cli GET user:1:name "qrush" Tuesday, August 10, 2010
  3. use it: redis-rb % gem install redis % irb -rubygems

    -rredis >> $redis = Redis.new => #<Redis client v2.0.3 connected... >> $redis.set "user:1:name", "qrush" => "OK" >> $redis.get "user:1:name" => "qrush" Tuesday, August 10, 2010
  4. Rediswitch.features << :super_secret Rediswitch.features << :payment_gateway Rediswitch.features << :twitter if

    Rediswitch.enabled?(:twitter) # post to twitter else # failwhale ahoy! end Tuesday, August 10, 2010
  5. class Rediswitch def self.enabled?(feature) $redis.exists(feature) end def self.enable(feature) $redis.incr(feature) end

    def self.disable(feature) $redis.del(feature) end end Tuesday, August 10, 2010
  6. feature switch lessons the real win: no-deploy configuration fast enough

    to be transparent next step: separate users into buckets with sets http://github.com/jamesgolick/rollout Tuesday, August 10, 2010
  7. class Choker def track if !$memcache.get(key, true) $memcache.add(key, "0", 1.minute.from_now,

    true) end $memcache.incr(key) end end Tuesday, August 10, 2010
  8. class Choker def track if !$redis.exists(key) $redis.setex(key, 60, 0) end

    $redis.incr(key) end end Tuesday, August 10, 2010
  9. rate limiter lessons expire semantics are changing in redis 2.2

    benchmark the crap out of it could use a sorted set instead of strings Tuesday, August 10, 2010
  10. # one way to do it class ActionHit < ActiveRecord::Base

    # t.string :controller # t.string :action # t.integer :counter end class UserHit < ActiveRecord::Base # t.string :controller_action # t.integer :user_id # t.integer :counter end Tuesday, August 10, 2010
  11. # for all controllers { "statuses#update" => 1410, "users#create" =>

    931, "home#index" => 2936 } # users hitting an action { "101" => 42, "102" => 13, "103" => 34 } Tuesday, August 10, 2010
  12. class StatusesController < ApplicationController def update key = "statuses#update" $redis.zincrby

    "actions", 1, key $redis.zincrby "users:#{key}", 1, user.id end end Tuesday, August 10, 2010
  13. # hits for a specific user >> $redis.zscore "users:statuses#update", 1001

    => 42 # list all the controller actions, sorted >> $redis.zrevrange "actions", 0, -1, :with_scores => true => ["home#index", "2936", "statuses#update", "1410", "users#create", "931"] Tuesday, August 10, 2010
  14. api usage logging lessons sorted set = high score list

    bad at historical usage, trends good for a simple heartbeat or pulse Tuesday, August 10, 2010
  15. class Staple @queue = :default def self.perform(post_id, tempfile) # complex

    image resizing, cropping end end Tuesday, August 10, 2010
  16. module Resque extend self def push(queue, item) redis.rpush "q:#{queue}", encode(item)

    end def pop(queue) decode redis.lpop("q:#{queue}") end end Tuesday, August 10, 2010
  17. class Resque::Worker def work loop do if job = Resque.pop(queue)

    job.perform else sleep 5 end end end end Tuesday, August 10, 2010
  18. job queue lessons guaranteed atomic actions, no row locking blocking

    commands simplify daemons many more queue commands in redis itself! Tuesday, August 10, 2010
  19. # text :globals, :default => '', :null => false class

    Project < ActiveRecord::Base def has_global?(name) @globals ||= globals.gsub(/,/,' ').split @globals.include?(name) end end Tuesday, August 10, 2010
  20. # MORE TABLES!!!! class Global < ActiveRecord::Base belongs_to :project end

    class Project < ActiveRecord::Base has_many :globals end Tuesday, August 10, 2010
  21. class Project < ActiveRecord::Base def global_key "project-#{id}-globals" end def has_global?(name)

    $redis.sismember(global_key, name) end end Tuesday, August 10, 2010
  22. class Project < ActiveRecord::Base after_save :save_globals def save_globals $redis.del global_key

    @globals.each do |g| $redis.sadd global_key, g end end end Tuesday, August 10, 2010
  23. class Project < ActiveRecord::Base after_save :save_globals def save_globals $redis.multi do

    $redis.del global_key @globals.each do |g| $redis.sadd global_key, g end end end end Tuesday, August 10, 2010
  24. global error lessons avoid joins for simple data consider race

    conditions use append-only file (AOF) Tuesday, August 10, 2010
  25. # usage: ruby pub.rb room username data = {"user" =>

    ARGV[1]} loop do msg = STDIN.gets $redis.publish ARGV[0], data.merge('msg' => msg.strip).to_json end Tuesday, August 10, 2010
  26. # sub.rb $redis = Redis.new(:timeout => 0) $redis.subscribe('rubyonrails', 'rubymidwest') do

    |on| on.message do |room, msg| data = JSON.parse(msg) puts "##{room} - [#{data['user']}]: #{data['msg']}" end end Tuesday, August 10, 2010
  27. % ruby pub.rb rubymidwest qrush i give up, i hate

    markdown % ruby sub.rb #rubymidwest - [qrush]: i give up, i hate markdown #rubyonrails - [railsn00b]: undefined method posts_path? wtf? #rubymidwest - [turbage]: seriously. Tuesday, August 10, 2010
  28. multiplayer notepad lessons combine with other data structures can subscribe

    to channels via patterns concurrency in ruby is hard use eventmachine! (or node.js) Tuesday, August 10, 2010
  29. more to learn know your data! (via @antirez) command reference

    on the wiki active IRC, mailing list Tuesday, August 10, 2010
  30. class RedisUrl attr_accessor :url, :id def initialize(url) @url = url

    @id = seed # unique string algorithm end def save $redis.set("relink.url|#{@id}", @url) $redis.set("relink.url.rev|#{@url}", @id) end end Tuesday, August 10, 2010
  31. get %r{/(.+)} do |url| u = RedisUrl.find(url) if u u.clicked

    redirect u.url else status 404 end end Tuesday, August 10, 2010
  32. class RedisUrl def self.find(id) u = $redis.get("relink.url|#{id}") if u redis_url

    = RedisUrl.new(u) redis_url.id = id redis_url end end def clicked $redis.incr("relink.url.clicks|#{@id}") end end Tuesday, August 10, 2010
  33. url shortener lessons common pattern: namespacing incr/decr assumes value is

    an integer wrap behavior into ActiveRecord-like objects next step: store URLs in a list Tuesday, August 10, 2010
  34. def after_save begin # make request to external service rescue

    Exception => ex logger.error "this shouldn't ever happen!" logger.error ex logger.error ex.backtrace end end Tuesday, August 10, 2010
  35. class Redisk::IO def write(string) redis.rpush "#{name}:_list", string end def self.readlines(name)

    redis.lrange("#{name}:_list", 0, -1) end end Tuesday, August 10, 2010
  36. live debugging lessons enables real-time data about your system dump

    serialized/marshalled data fast run the redis instance on a different box dive deeper: hummingbird Tuesday, August 10, 2010
  37. # bad idea, dude class Download < ActiveRecord::Base belongs_to :rubygem

    end class Rubygem < ActiveRecord::Base has_many :downloads end Tuesday, August 10, 2010
  38. class Download def self.rollover(version) $redis.rename "today", "yesterday" dls = Hash[*$redis.zrange("yesterday",

    0, -1, :with_scores => true)] dls.each do |key, score| $redis.hincrby key, Date.today, score Rubygem.find_by_name(key).increment!(:downloads, score) end end end Tuesday, August 10, 2010
  39. get "/api/v1/downloads/rails.json" do $redis.hgetall("rails").to_json end # returns... { "2010-07-09" =>

    1908, "2010-07-10" => 1032, "2010-07-11" => 1091, } Tuesday, August 10, 2010
  40. counting downloads lessons hybrid approach does work! redis is really

    not for search test your migration away from SQL Tuesday, August 10, 2010