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

Solid Queue internals, externals and all the th...

Rosa
October 17, 2024

Solid Queue internals, externals and all the things in between

We’ve used Resque and Redis to run background jobs in multiple apps for many years at 37signals. However, performance, reliability, and our own apps’ idiosyncrasies led us to use a lot of different gems, some developed by us, some forked or patched to address our struggles. After multiple war stories with background jobs, looking at our increasingly complex setup, we wanted something we could use out-of-the-box without having to port our collection of hacks to every new app and with fewer moving pieces. After exploring existing alternatives, we decided to build our own and aim to make it the default for Rails 8. In this talk, I’ll present Solid Queue, explain some of the problems we had over the years, how we designed Solid Queue to address them, and all the Fun™ we had doing that.

Rosa

October 17, 2024
Tweet

More Decks by Rosa

Other Decks in Programming

Transcript

  1. Solid Queue internals, externals and all the things in between

    Rails World 2024 Toronto - September 26th, 2024
  2. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job"
  3. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job"
  4. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job" Enqueue and run jobs
  5. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job" Don’t lose jobs when a worker is killed
  6. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job" Run and manage multiple workers
  7. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job" Support delayed and recurring jobs
  8. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job" Pause queues
  9. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job" Run some jobs sequentially
  10. Our Resque setup # Jobs
 gem "resque", "~> 2.0.0"
 gem

    "resque_supervised_fork", bc: "resque_supervised_fork"
 gem "resque-pool", bc: "resque-pool"
 gem "resque-scheduler", github: "resque/resque-scheduler"
 gem "resque-pause", bc: "resque-pause"
 gem "sequential_jobs", bc: "sequential_jobs"
 gem "scheduled_job", bc: "scheduled_job" Schedule a large number of jobs in the far-ish future and enqueue them fast in batches
  11. Requirements ⬜ Enqueue and run jobs 
 ⬜ Don’t lose

    jobs when a worker is killed 
 ⬜ Run and manage multiple workers 
 ⬜ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 
 

  12. Requirements ⬜ Enqueue and run jobs 
 ⬜ Don’t lose

    jobs when a worker is killed 
 ⬜ Run and manage multiple workers 
 ⬜ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 

  13. What if we used a relational database? 🤔 Solid Cache

    inspiration Some successful examples: GoodJob, Delayed
  14. What if we used a relational database? 🤔 Solid Cache

    inspiration Leverage Active Record Some successful examples: GoodJob, Delayed
  15. What if we used a relational database? 🤔 Solid Cache

    inspiration Simplicity, working out-of-the-box, one fewer moving piece Leverage Active Record Some successful examples: GoodJob, Delayed
  16. What if we used a relational database? 🤔 Solid Cache

    inspiration Simplicity, working out-of-the-box, one fewer moving piece Leverage Active Record Some successful examples: GoodJob, Delayed Maybe a new Rails default?
  17. Requirements ⬜ Enqueue and run jobs 
 ⬜ Don’t lose

    jobs when a worker is killed 
 ⬜ Run and manage multiple workers 
 ⬜ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 
 

  18. Requirements ⬜ Enqueue and run jobs 
 ⬜ Don’t lose

    jobs when a worker is killed 
 ⬜ Run and manage multiple workers 
 ⬜ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ⬜ Support MySQL, PostgreSQL, SQLite 
 ⬜ Support all Active Job features (priorities) 

  19. Requirements ⬜ Enqueue and run jobs 
 ⬜ Don’t lose

    jobs when a worker is killed 
 ⬜ Run and manage multiple workers 
 ⬜ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ⬜ Support MySQL, PostgreSQL, SQLite 
 ⬜ Support all Active Job features (priorities) 

  20. The performance problem with polling id queue priority claimed 1

    default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table
  21. The performance problem with polling id queue priority claimed 1

    default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table → 1, 2
  22. The performance problem with polling id queue priority claimed 1

    default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table → 1, 2
  23. The performance problem with polling id queue priority claimed 1

    default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table All other workers waiting → 1, 2
  24. The performance problem with polling id queue priority claimed 1

    default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table All other workers waiting → 1, 2
  25. The performance problem with polling id queue priority claimed 1

    default 0 1 2 default 1 1 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table → 1, 2 All other workers waiting
  26. The performance problem with polling id queue priority claimed 1

    default 0 1 2 default 1 1 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table → 1, 2 All other workers waiting
  27. The performance problem with polling id queue priority claimed 1

    default 0 1 2 default 1 1 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table All other workers waiting
  28. The performance problem with polling id queue priority claimed 1

    default 0 1 2 default 1 1 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table → 4, 3
  29. The performance problem with polling id queue priority claimed 1

    default 0 1 2 default 1 1 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE; Claim the jobs… 
 Commit transaction… jobs table → 4, 3
  30. Enter SELECT … FOR UPDATE SKIP LOCKED Available in PostgreSQL

    from version 9.5 (2016) and MySQL 8.0 (2018)
  31. Enter SELECT … FOR UPDATE SKIP LOCKED id queue priority

    claimed 1 default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE SKIP LOCKED; Claim the jobs… 
 Commit transaction… jobs table
  32. Enter SELECT … FOR UPDATE SKIP LOCKED id queue priority

    claimed 1 default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE SKIP LOCKED; Claim the jobs… 
 Commit transaction… jobs table → 1, 2
  33. Enter SELECT … FOR UPDATE SKIP LOCKED id queue priority

    claimed 1 default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE SKIP LOCKED; Claim the jobs… 
 Commit transaction… jobs table → 1, 2
  34. Enter SELECT … FOR UPDATE SKIP LOCKED id queue priority

    claimed 1 default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE SKIP LOCKED; Claim the jobs… 
 Commit transaction… jobs table
  35. Enter SELECT … FOR UPDATE SKIP LOCKED id queue priority

    claimed 1 default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE SKIP LOCKED; Claim the jobs… 
 Commit transaction… jobs table → 4, 3
  36. Enter SELECT … FOR UPDATE SKIP LOCKED id queue priority

    claimed 1 default 0 0 2 default 1 0 3 default 2 0 4 default 1 0 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE SKIP LOCKED; Claim the jobs… 
 Commit transaction… jobs table → 4, 3
  37. Enter SELECT … FOR UPDATE SKIP LOCKED id queue priority

    claimed 1 default 0 1 2 default 1 1 3 default 2 1 4 default 1 1 Start transaction… SELECT id 
 FROM jobs 
 WHERE queue = "default" 
 AND claimed = 0 
 ORDER BY priority, id 
 LIMIT 2 
 FOR UPDATE SKIP LOCKED; Claim the jobs… 
 Commit transaction… jobs table
  38. The first prototype poll solid_queue_jobs 
 solid_queue_ready_executions 
 solid_queue_claimed_executions 


    solid_queue_scheduled_executions 
 solid_queue_failed_executions worker dispatcher poll supervisor manage
  39. The first prototype move polled 
 jobs here solid_queue_jobs 


    solid_queue_ready_executions 
 solid_queue_claimed_executions 
 solid_queue_scheduled_executions 
 solid_queue_failed_executions worker dispatcher move polled 
 jobs here (in batches)
  40. Requirements ⬜ Enqueue and run jobs 
 ⬜ Don’t lose

    jobs when a worker is killed 
 ⬜ Run and manage multiple workers 
 ⬜ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ⬜ Support MySQL, PostgreSQL, SQLite* 
 ⬜ Support priorities
  41. Requirements ✅ Enqueue and run jobs 
 ✅ Don’t lose

    jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  42. Requirements ✅ Enqueue and run jobs ⬜ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  43. Requirements ✅ Enqueue and run jobs ⬜ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  44. Requirements ✅ Enqueue and run jobs ⬜ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  45. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ⬜ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  46. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ⬜ Pause queues
  47. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ⬜ Pause queues Yep, another table! 👯 solid_queue_pauses
  48. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  49. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  50. Further tuning the polling queries CREATE TABLE `solid_queue_ready_executions` ( 


    `id` bigint NOT NULL AUTO_INCREMENT, 
 `job_id` bigint DEFAULT NULL, 
 `queue_name` varchar(255) NOT NULL, 
 `priority` int NOT NULL DEFAULT '0', 
 `created_at` datetime(6) NOT NULL, 
 PRIMARY KEY (`id`), 
 UNIQUE KEY `index_solid_queue_ready_executions_on_job_id` (`job_id`),
  51. Further tuning the polling queries CREATE TABLE `solid_queue_ready_executions` ( 


    `id` bigint NOT NULL AUTO_INCREMENT, 
 `job_id` bigint DEFAULT NULL, 
 `queue_name` varchar(255) NOT NULL, 
 `priority` int NOT NULL DEFAULT '0', 
 `created_at` datetime(6) NOT NULL, 
 PRIMARY KEY (`id`), 
 UNIQUE KEY `index_solid_queue_ready_executions_on_job_id` (`job_id`),
  52. Further tuning the polling queries queues: recycling
 queues: [ incineration,

    action_mailbox_incineration ] 
 queues: beta*
 queues: * # all queues + paused queues
  53. Further tuning the polling queries KEY `index_solid_queue_poll_by_queue` (`queue_name`,`priority`,`job_id`)) KEY `index_solid_queue_poll_all`

    (`priority`,`job_id`)) SELECT job_id 
 FROM solid_queue_ready_executions 
 WHERE queue_name = "incineration" 
 ORDER BY priority ASC, job_id ASC 
 LIMIT 4 
 FOR UPDATE SKIP LOCKED;
  54. Further tuning the polling queries KEY `index_solid_queue_poll_by_queue` (`queue_name`,`priority`,`job_id`)) KEY `index_solid_queue_poll_all`

    (`priority`,`job_id`)) SELECT job_id 
 FROM solid_queue_ready_executions 
 WHERE queue_name = "incineration" 
 ORDER BY priority ASC, job_id ASC 
 LIMIT 4 
 FOR UPDATE SKIP LOCKED; SELECT job_id 
 FROM solid_queue_ready_executions 
 ORDER BY priority ASC, job_id ASC 
 LIMIT 4 
 FOR UPDATE SKIP LOCKED;
  55. Further tuning the polling queries KEY `index_solid_queue_poll_by_queue` (`queue_name`,`priority`,`job_id`)) KEY `index_solid_queue_poll_all`

    (`priority`,`job_id`)) Covering indexes SELECT job_id 
 FROM solid_queue_ready_executions 
 WHERE queue_name = "incineration" 
 ORDER BY priority ASC, job_id ASC 
 LIMIT 4 
 FOR UPDATE SKIP LOCKED; SELECT job_id 
 FROM solid_queue_ready_executions 
 ORDER BY priority ASC, job_id ASC 
 LIMIT 4 
 FOR UPDATE SKIP LOCKED;
  56. KEY `index_solid_queue_poll_by_queue` (`queue_name`,`priority`,`job_id`)) KEY `index_solid_queue_poll_all` (`priority`,`job_id`)) Current version 4,900 queries/second

    
 110 microseconds/query 
 0.04 examined rows/query Zero or one queue per query Always the minimum number of queries
  57. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ⬜ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  58. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ✅ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  59. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ✅ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  60. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ✅ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities 🫣😰😱
  61. Sequential jobs → concurrency controls Semaphores 🚦 Limit on enqueuing

    Make order not important New execution type: blocked
  62. Sequential jobs → concurrency controls Semaphores 🚦 Limit on enqueuing

    Make order not important New execution type: blocked And yes, you guessed it, more tables! 🕺
  63. Many new write ops in our DB Worst case: 11

    write operations for a scheduled job with concurrency limits
  64. Many new write ops in our DB Worst case: 11

    write operations for a scheduled job with concurrency limits
  65. Many new write ops in our DB Peak 400 jobs/second

    Worst case: 11 write operations for a scheduled job with concurrency limits
  66. Many new write ops in our DB Peak 400 jobs/second

    Worst case: 11 write operations for a scheduled job with concurrency limits
  67. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ⬜ Support running some jobs sequentially 
 ✅ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  68. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ⬜ Support cron-style recurring jobs 
 ✅ Support running some jobs sequentially 
 ✅ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  69. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ⬜ Support cron-style recurring jobs
  70. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ⬜ Support cron-style recurring jobs Left as an exercise for the reader* 🤭
  71. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ⬜ Support cron-style recurring jobs Left as an exercise for the reader* 🤭 *Hint: add some new tables 💃
  72. Requirements ✅ Enqueue and run jobs ✅ Don’t keep a

    job claimed forever if worker dies 
 ✅ Don’t lose jobs when a worker is killed 
 ✅ Run and manage multiple workers 
 ✅ Support delayed jobs, a large number, enqueue them fast in batches 
 ✅ Pause queues 
 ✅ Support cron-style recurring jobs 
 ✅ Support running some jobs sequentially 
 ✅ Allow for ~650 workers, 100ms polling interval, ~19M jobs / day 
 ✅ Support MySQL, PostgreSQL, SQLite* 
 ✅ Support priorities
  73. There’s so much more 🫢 Bulk operations, logging, instrumentation, error

    handling, hooks, Mission Control dashboard, so many bug fixes, so many edge cases…
  74. There’s so much more 🫢 Bulk operations, logging, instrumentation, error

    handling, hooks, Mission Control dashboard, so many bug fixes, so many edge cases…
  75. ~20M jobs/day 74 VMs 800 workers, 4 dispatchers 📥✉📆 Same

    latency as with Resque for user-facing jobs
  76. ~20M jobs/day 74 VMs 800 workers, 4 dispatchers 📥✉📆 Same

    latency as with Resque for user-facing jobs Speed increase when enqueuing in bulk
  77. ~20M jobs/day 74 VMs 800 workers, 4 dispatchers 📥✉📆 Same

    latency as with Resque for user-facing jobs Speed increase when enqueuing in bulk No impact to response time
  78. Thank you! Find me to talk more about queues, jobs

    and Fun™, and/or to get some Mochi stickers! 🐶