redis: persistence power

75
Redis: Persistence Power Nick Quaranto / @qrush / [email protected] Tuesday, August 10, 2010

Upload: nick-quaranto

Post on 10-Apr-2015

6.720 views

Category:

Documents


0 download

DESCRIPTION

A practical, use case approach to looking into Redis.

TRANSCRIPT

Page 1: Redis: Persistence Power

Redis: Persistence PowerNick Quaranto / @qrush / [email protected]

Tuesday, August 10, 2010

Page 2: Redis: Persistence Power

What is Redis?

“advanced key-value store”

REmote DIctionary Server

data structures server

Tuesday, August 10, 2010

Page 3: Redis: Persistence Power

YOUR APP

Tuesday, August 10, 2010

Page 4: Redis: Persistence Power

YOUR APP

REDIS

Tuesday, August 10, 2010

Page 5: Redis: Persistence Power

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

Page 6: Redis: Persistence Power

http://try.redis-db.com

Tuesday, August 10, 2010

Page 7: Redis: Persistence Power

use it: redis-cli

% ./redis-cli SET user:1:name qrushOK

% ./redis-cli GET user:1:name "qrush"

Tuesday, August 10, 2010

Page 8: Redis: Persistence Power

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

Page 9: Redis: Persistence Power

FEATURE SWITCHESSTRINGS

based onhttp://github.com/blog/677http://github.com/bvandenbos/redis_feature_control

Tuesday, August 10, 2010

Page 10: Redis: Persistence Power

Rediswitch.features << :super_secretRediswitch.features << :payment_gatewayRediswitch.features << :twitter

if Rediswitch.enabled?(:twitter) # post to twitterelse # failwhale ahoy!end

Tuesday, August 10, 2010

Page 11: Redis: Persistence Power

begin # take some moneyrescue PaymentGateway::TotallyDown => ohno Rediswitch.disable(:payment_gateway) # notify the troopsend

Tuesday, August 10, 2010

Page 12: Redis: Persistence Power

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) endend

Tuesday, August 10, 2010

Page 13: Redis: Persistence Power

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

Page 14: Redis: Persistence Power

RATE LIMITERSTRINGS

soon to be in place athttp://hoptoadapp.com

Tuesday, August 10, 2010

Page 15: Redis: Persistence Power

class Choker def restrict? track count_for > 60 endend

Tuesday, August 10, 2010

Page 16: Redis: Persistence Power

class Choker def count_for $memcache.get(key, true).to_i endend

Tuesday, August 10, 2010

Page 17: Redis: Persistence Power

class Choker def track if !$memcache.get(key, true) $memcache.add(key, "0", 1.minute.from_now, true) end $memcache.incr(key) endend

Tuesday, August 10, 2010

Page 18: Redis: Persistence Power

class Choker def track if !$redis.exists(key) $redis.setex(key, 60, 0) end $redis.incr(key) endend

Tuesday, August 10, 2010

Page 19: Redis: Persistence Power

class Choker def count_for $redis.get(key).to_i endend

Tuesday, August 10, 2010

Page 20: Redis: Persistence Power

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

Page 21: Redis: Persistence Power

API USAGE LOGGINGSTRINGSSORTED SETS

based offhttp://www.production-hacks.com/2010/07/10/redis-api-access-logger/

Tuesday, August 10, 2010

Page 22: Redis: Persistence Power

# one way to do itclass ActionHit < ActiveRecord::Base # t.string :controller # t.string :action # t.integer :counterend

class UserHit < ActiveRecord::Base # t.string :controller_action # t.integer :user_id # t.integer :counterend

Tuesday, August 10, 2010

Page 23: Redis: Persistence Power

# 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

Page 24: Redis: Persistence Power

class StatusesController < ApplicationController def update $redis.incr "statuses#update" $redis.incr "statuses#update:#{user.id}" endend

Tuesday, August 10, 2010

Page 25: Redis: Persistence Power

class StatusesController < ApplicationController def update key = "statuses#update" $redis.zincrby "actions", 1, key $redis.zincrby "users:#{key}", 1, user.id endend

Tuesday, August 10, 2010

Page 26: Redis: Persistence Power

# 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

Page 27: Redis: Persistence Power

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

Page 28: Redis: Persistence Power

JOB QUEUELISTS

based onhttp://github.com/defunkt/resque

Tuesday, August 10, 2010

Page 29: Redis: Persistence Power

class Staple @queue = :default

def self.perform(post_id, tempfile) # complex image resizing, cropping endend

Tuesday, August 10, 2010

Page 30: Redis: Persistence Power

class Post < ActiveRecord::Base after_save :process_with_stapler

def process_with_stapler Resque.enqueue(Staple, self.id, @tempfile) endend

Tuesday, August 10, 2010

Page 31: Redis: Persistence Power

module Resque extend self

def push(queue, item) redis.rpush "q:#{queue}", encode(item) end

def pop(queue) decode redis.lpop("q:#{queue}") endend

Tuesday, August 10, 2010

Page 32: Redis: Persistence Power

class Resque::Worker def work loop do if job = Resque.pop(queue) job.perform else sleep 5 end end endend

Tuesday, August 10, 2010

Page 33: Redis: Persistence Power

module Resque extend self def bpop(queue) decode redis.blpop("q:#{queue}") endend

Tuesday, August 10, 2010

Page 34: Redis: Persistence Power

class Resque::Worker def work loop do job = Resque.bpop(queue) job.perform end endend

Tuesday, August 10, 2010

Page 35: Redis: Persistence Power

job queue lessons

guaranteed atomic actions, no row locking

blocking commands simplify daemons

many more queue commands in redis itself!

Tuesday, August 10, 2010

Page 36: Redis: Persistence Power

GLOBAL ERRORSSETSMULTI/EXEC

a new feature athttp://hoptoadapp.com

Tuesday, August 10, 2010

Page 37: Redis: Persistence Power

# text :globals, :default => '', :null => false

class Project < ActiveRecord::Base def has_global?(name) @globals ||= globals.gsub(/,/,' ').split @globals.include?(name) endend

Tuesday, August 10, 2010

Page 38: Redis: Persistence Power

# MORE TABLES!!!!

class Global < ActiveRecord::Base belongs_to :projectend

class Project < ActiveRecord::Base has_many :globalsend

Tuesday, August 10, 2010

Page 39: Redis: Persistence Power

# Project#global_errors

["MySQL::Error", "MemCache::Error", "Net::HTTPFatalError"]

Tuesday, August 10, 2010

Page 40: Redis: Persistence Power

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

Page 41: Redis: Persistence Power

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 endend

Tuesday, August 10, 2010

Page 42: Redis: Persistence Power

[Mysql::Error, MemCache::Error, Net::HTTPFatalError]

SISMEMBER SISMEMBER

Tuesday, August 10, 2010

Page 43: Redis: Persistence Power

[]

DEL

Tuesday, August 10, 2010

Page 44: Redis: Persistence Power

[Mysql::Error]

DEL

SADD

Tuesday, August 10, 2010

Page 45: Redis: Persistence Power

[Mysql::Error, OpenURI::HTTPError]

DEL

SADD

SADD

Tuesday, August 10, 2010

Page 46: Redis: Persistence Power

[Mysql::Error, OpenURI::HTTPError]

DEL

SADD

SADD

SISMEMBER

[]

Tuesday, August 10, 2010

Page 47: Redis: Persistence Power

[Mysql::Error, OpenURI::HTTPError]

MULTI

EXEC

DEL

SADD

SADDSISMEMBER

Tuesday, August 10, 2010

Page 48: Redis: Persistence Power

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 endend

Tuesday, August 10, 2010

Page 49: Redis: Persistence Power

global error lessons

avoid joins for simple data

consider race conditions

use append-only file (AOF)

Tuesday, August 10, 2010

Page 50: Redis: Persistence Power

MULTIPLAYER NOTEPADPUB/SUB

based onhttp://github.com/laktek/realie

Tuesday, August 10, 2010

Page 51: Redis: Persistence Power

# 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_jsonend

Tuesday, August 10, 2010

Page 52: Redis: Persistence Power

# 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']}" endend

Tuesday, August 10, 2010

Page 53: Redis: Persistence Power

% ruby pub.rb rubymidwest qrushi 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

Page 54: Redis: Persistence Power

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

Page 55: Redis: Persistence Power

more to learn

know your data! (via @antirez)

command reference on the wiki

active IRC, mailing list

Tuesday, August 10, 2010

Page 56: Redis: Persistence Power

AKASENTAI.comredis in the cloud

Tuesday, August 10, 2010

Page 57: Redis: Persistence Power

Thanks!http://redis.io @qrushhttp://rediscookbook.com http://scr.bi/redispower

Tuesday, August 10, 2010

Page 58: Redis: Persistence Power

BONUS ROUND!I prepared way too many examples. Jackpot!

Tuesday, August 10, 2010

Page 59: Redis: Persistence Power

URL SHORTENERSTRINGS

based onhttp://github.com/mattmatt/relink

Tuesday, August 10, 2010

Page 60: Redis: Persistence Power

require 'sinatra'require 'redis_url'

post '/' do RedisUrl.new(params[:url]).saveend

Tuesday, August 10, 2010

Page 61: Redis: Persistence Power

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) endend

Tuesday, August 10, 2010

Page 62: Redis: Persistence Power

get %r{/(.+)} do |url| u = RedisUrl.find(url) if u u.clicked redirect u.url else status 404 endend

Tuesday, August 10, 2010

Page 63: Redis: Persistence Power

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}") endend

Tuesday, August 10, 2010

Page 64: Redis: Persistence Power

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

Page 65: Redis: Persistence Power

LIVEDEBUGGINGLISTS

based onhttp://github.com/quirkey/redisk

Tuesday, August 10, 2010

Page 66: Redis: Persistence Power

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 endend

Tuesday, August 10, 2010

Page 67: Redis: Persistence Power

# config/initializers/logger.rb

require 'redisk'path = "#{Rails.env}.log"config.logger = Redisk::Logger.new(path)

Tuesday, August 10, 2010

Page 68: Redis: Persistence Power

class Redisk::IO def write(string) redis.rpush "#{name}:_list", string end def self.readlines(name) redis.lrange("#{name}:_list", 0, -1) endend

Tuesday, August 10, 2010

Page 69: Redis: Persistence Power

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

Page 70: Redis: Persistence Power

COUNTING DOWNLOADSSTRINGSSORTED SETSHASHES

based onhttp://github.com/rubygems/gemcutter

Tuesday, August 10, 2010

Page 71: Redis: Persistence Power

# bad idea, dude

class Download < ActiveRecord::Base belongs_to :rubygemend

class Rubygem < ActiveRecord::Base has_many :downloadsend

Tuesday, August 10, 2010

Page 72: Redis: Persistence Power

class Download def self.incr(rubygem) $redis.incr("all") $redis.incr(rubygem) $redis.zincrby("today", 1, rubygem) endend

Tuesday, August 10, 2010

Page 73: Redis: Persistence Power

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 endend

Tuesday, August 10, 2010

Page 74: Redis: Persistence Power

get "/api/v1/downloads/rails.json" do $redis.hgetall("rails").to_jsonend

# returns...{ "2010-07-09" => 1908, "2010-07-10" => 1032, "2010-07-11" => 1091,}

Tuesday, August 10, 2010

Page 75: Redis: Persistence Power

counting downloads lessons

hybrid approach does work!

redis is really not for search

test your migration away from SQL

Tuesday, August 10, 2010