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
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
begin # take some money rescue PaymentGateway::TotallyDown => ohno Rediswitch.disable(:payment_gateway) # notify the troops end Tuesday, August 10, 2010
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
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
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
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
# 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
class StatusesController < ApplicationController def update $redis.incr "statuses#update" $redis.incr "statuses#update:#{user.id}" end end Tuesday, August 10, 2010
# 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
class Post < ActiveRecord::Base after_save :process_with_stapler def process_with_stapler Resque.enqueue(Staple, self.id, @tempfile) end end Tuesday, August 10, 2010
job queue lessons guaranteed atomic actions, no row locking blocking commands simplify daemons many more queue commands in redis itself! Tuesday, August 10, 2010
# MORE TABLES!!!! class Global < ActiveRecord::Base belongs_to :project end class Project < ActiveRecord::Base has_many :globals end Tuesday, August 10, 2010
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
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
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
# 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
% 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
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
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
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
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
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
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
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
# bad idea, dude class Download < ActiveRecord::Base belongs_to :rubygem end class Rubygem < ActiveRecord::Base has_many :downloads end Tuesday, August 10, 2010