Slide 1

Slide 1 text

SIDEKIQ BACKGROUND JOBS IN RUBY © Tymon Tobolski, 2015

Slide 2

Slide 2 text

WHAT & WHY © Tymon Tobolski, 2015

Slide 3

Slide 3 text

class CommentsController < ApplicationController def create @comment = @post.comments.create!(comment_params) Notifications.notify_commenters(@comment) head :ok end end © Tymon Tobolski, 2015

Slide 4

Slide 4 text

class Notifications def self.notify_commenters(comment) comment.post.comments_authors.each do |user| send_email_about_new_comment(user, comment) end end end © Tymon Tobolski, 2015

Slide 5

Slide 5 text

SYNCHRONOUS DELIVERY class CommentsController < ApplicationController def create @comment = @post.comments.create!(comment_params) # T = 10ms Notifications.notify_commenters(@comment) # T = 100.000ms, boom, timeout! head :ok end end class Notifications def self.notify_commenters(comment) comment.post.comments_authors.each do |user| send_email_about_new_comment(user, comment) # T += 1000ms end end end © Tymon Tobolski, 2015

Slide 6

Slide 6 text

ASYNCHRONOUS DELIVERY class CommentsController < ApplicationController def create @comment = @post.comments.create!(comment_params) # T = 10ms NotifyCommentersWorker.perform_async(@comment.id) # T = 11ms, \o/ head :ok end end class Notifications def self.notify_commenters(comment) comment.post.comments_authors.each do |user| send_email_about_new_comment(user, comment) # T += 1000ms still, but we don't care anymore end end end © Tymon Tobolski, 2015

Slide 7

Slide 7 text

SIDEKIQ WORKER # app/workers/notify_commenters_worker.rb class NotifyCommentersWorker include Sidekiq::Worker def perform(comment_id) comment = Comment.find(comment_id) Notifications.notify_commenters(comment) end end © Tymon Tobolski, 2015

Slide 8

Slide 8 text

MAGIC? © Tymon Tobolski, 2015

Slide 9

Slide 9 text

REDIS © Tymon Tobolski, 2015

Slide 10

Slide 10 text

REDIS.IO Redis is an open source, BSD licensed, advanced key-value cache and store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps and hyperloglogs, blah blah blah ... © Tymon Tobolski, 2015

Slide 11

Slide 11 text

REDIS.IO Redis is an open source, BSD licensed, advanced key-value cache and store. It is often referred to as a data structure server since keys can contain strings, hashes, lists, sets, sorted sets, bitmaps and hyperloglogs, blah blah blah ... © Tymon Tobolski, 2015

Slide 12

Slide 12 text

LIST IN REDIS WORLD ... is really a list and an array and a stack and a queue © Tymon Tobolski, 2015

Slide 13

Slide 13 text

QUEUE FIFO - FIRST IN, FIRST OUT ACTION QUEUE STATUS --------------------------- [] PUSH 1 [1] PUSH 2 [2,1] PUSH 3 [3,2,1] POP [3,2] PUSH 4 [4,3,2] POP [4,3] POP [4] © Tymon Tobolski, 2015

Slide 14

Slide 14 text

QUEUE FIFO - FIRST IN, FIRST OUT ACTION mylist CONTENT -------------------------------- (nil) LPUSH mylist 1 [1] LPUSH mylist 2 [2,1] LPUSH mylist 3 [3,2,1] RPOP mylist [3,2] LPUSH mylist 4 [4,3,2] RPOP mylist [4,3] RPOP mylist [4] © Tymon Tobolski, 2015

Slide 15

Slide 15 text

FLOW © Tymon Tobolski, 2015

Slide 16

Slide 16 text

RULES Sidekiq Best Practices © Tymon Tobolski, 2015

Slide 17

Slide 17 text

ALWAYS USE IDS BAD: NotifyCommentersWorker.perform_async(@comment) GOOD: NotifyCommentersWorker.perform_async(@comment.id) Job arguments MUST be serializable to JSON def perform(comment_id) comment = Comment.find(comment_id) # ... end © Tymon Tobolski, 2015

Slide 18

Slide 18 text

IDEMPOTENT AND TRANSACTIONAL JOBS Sidekiq will execute your job at least once. def perform(...) # ... User.where(id: id1).update_all(["balance = balance + ?", amount]) User.where(id: id2).update_all(["balance = balance - ?", amount]) # ... end © Tymon Tobolski, 2015

Slide 19

Slide 19 text

IDEMPOTENT AND TRANSACTIONAL JOBS Sidekiq will execute your job at least once. def perform(...) # ... ActiveRecord.transation do User.where(id: id1).update_all(["balance = balance + ?", amount]) User.where(id: id2).update_all(["balance = balance - ?", amount]) end # ... end © Tymon Tobolski, 2015

Slide 20

Slide 20 text

CONCURRENCY & PARALLELISM def process(...) User.find_each do |user| send_email_to(user) end end © Tymon Tobolski, 2015

Slide 21

Slide 21 text

CONCURRENCY & PARALLELISM def process(...) User.select(:id).find_each do |user| SendEmailWorker.perform_async(user.id) end end © Tymon Tobolski, 2015

Slide 22

Slide 22 text

FOREMAN / HEROKU # Procfile web: bin/rails server -p $PORT worker: bin/sidekiq © Tymon Tobolski, 2015

Slide 23

Slide 23 text

SIDEKIQ WEB © Tymon Tobolski, 2015

Slide 24

Slide 24 text

SIDEKIQ WEB # Gemfile gem 'sinatra', :require => nil # config/routes.rb require 'sidekiq/web' authenticate :user, lambda { |u| u.admin? } do mount Sidekiq::Web => '/sidekiq' end © Tymon Tobolski, 2015

Slide 25

Slide 25 text

QUESTIONS? © Tymon Tobolski, 2015