Delayed Job vs. Sidekiq
blog.appsignal.com
blog.appsignal.com
Jobs in your queue in the database can be committed in the same transaction that makes the mutation. With redis you have no such thing. Leading to (been there, it hurt) potentially missing jobs or queuing jobs that have no associated mutation in the db.
Furthermore, a big downside of redis is that it's promise of consistency is low. Mostly by design: redis is not meant to be the primary, one and only store of truth. Commonly, people store stuff in redis that can be re-built from database or some eventlog or so. But not sidekiq. If your redis starts failing (been there, it hurts), e.g. though OOM errors, you are loosing data. Jobs, with their parameters, are dropped, often irrecoverably. And with Rails' common set-up, this means you are irrecoverably loosing data: there simply is no way to find what emails you did not send, nor any way to re-queue them. There is no way to re-enqueue those "generateReport" jobs using the data at that point in time and so on.
Basically: Redis is quite certainly not the best tool to store your job-queues with associated data, in. Sidekiq is the less sturdy approach of both.
Especially with transactions as mentioned, with elixir and ecto and Oban, you can ensure your jobs are inserted properly. And it's easily testable.
If you’re small enough that you don’t knock over your DB by putting jobs in it then the transactions are nice!
At Betterment we use our OSS mostly-compatible fork of Delayed::Job referenced elsewhere in the comments to enqueue and work millions of jobs a day and sleep much better at night with the at-least-once delivery semantics if-and-only-if the related transaction commits which you can’t get without some form of integration with your primary database.
Agreed on the Redis health being critical and if it's unhealthy you can't trust anything. We over-provision compute and memory and enable persistence as a result. We are also mindful of our payload sizes, which we also compress. Additionally any jobs scheduled beyond a certain time threshold are committed to the DB, not Redis, and we have a periodic job that adds them back to Redis as we approach their scheduled time to run. This is good for both durability and keeping the surface area of Redis work smaller.
It's not perfect and there is probably a day where we switch it out but for now we have developed a deep understanding of Sidekiq and Redis and get to leverage the larger Sidekiq ecosystem. This has worked well for us to the tune of about 35 million jobs a day.
If it happens though is it a big deal? I’ve seen Sidekiq jobs used to move money, for instance. This can result is Bad Times. Right tool for the job, if consistency is a requirement Sidekiq I think gets incorrectly applied in a lot of applications.
In this sense the job acts on data, it doesn't hold data, and you can build in backstops in the event of any kind of failure, not just Redis. You'd want to build this into your architecture regardless of what your background job tooling and infrastructure you use.
Which bring me to the next point: Sidekiq should never be the default choice. It brings distributed systems complexity to an audience that's trained to work on a monolithic database.
Delayed job or similar should be the default, with sidekiq relegated tp jobs ready to be lost
https://www.betterment.com/engineering/delayed-resilient-bac... does a great job explaining this.
You've re-implemented a job queue in the database.
Everything is a trade off.
The downside that comes with it is using your primary DB as your queue is that the queues in an app tend to absorb all the exceptional conditions (queue grows to millions during worker outage, backfill which needs to process millions of items). Using your primary DB means all that disk usage, memory usage, and IO hit your primary DB in exceptional ways and impacts your app in more ways than just queue delays. It also scales much less linearly than other queue options.
A nice middle ground is to store all your data and state in your DB transactionally, and only queue tiny lightweight jobs with database primary keys (database: sentWelcomeMailDate=nil, queue: sentWelcomeMailForUser:1234).
This is another case for neither DJ or Sidekiq. A fully hosted option like SQS addresses the durability concerns of Sidekiq, and the scaling concerns of both. I’ve queued 1B+ jobs in SQS and watched the workers slowly burn through them over the course of a month.
DJ and Sidekiq can go quite far. I’ve used both and patched some perf issues on DJ. The lesson learned was queues at scale are hard distributed systems problems - you probably don’t want to have to know how they work, or host the distributed system yourself.
I'd go so far to say that when you need such scale, Rails or Job Queues like Rails' are not the solution. Event-based architectures (event-sourced, event-buses, actors etc) are far better suited.
Rather than trying to solve all the problems that those architectures were explicitly designed for in redis/sidekiq/rails, just move to those architectures.
You won't have the nice "rails generate CreditCard" anymore /s, but you'll gain a lot of consistency, simplicity, performance and architectural sanity.
However, this recent addition to the space seems to be gaining traction and appears to have an excellent feature set -
As long as you stick to pure ActiveJob features it really shouldn't be too difficult to scale from in memory based queue to PostgresSQL backed Good Job and then to Redis backed Sidekiq if and only if you need to.
> If a Sidekiq process crashes while processing a job, that job is lost. [0]
We use Sidekiq Pro at work but I find it unfortunate that reliability is a pro feature. It's really hard to tell a product owner: Yeah, we might lose a few jobs once in a while.
Perhaps you could address my point? Open source background job software that isn't reliable by default? That makes sense?
Statistics https://github.com/davydovanton/sidekiq-statistic
Failure Handling https://github.com/mhfs/sidekiq-failures
Unique Jobs https://github.com/mhenrixon/sidekiq-unique-jobs
All of which also extend the web UI for Sidekiq which is incredibly useful for both debugging and having a handle on what's with your queues.
Finally, if you're going to be using Sidekiq in any serious way I'd recommend Nate Berkopec's "Sidekiq in Practice" - https://nateberk.gumroad.com/l/sidekiqinpractice
Beyond being an incredibly useful resource on its own - you get access to a very active private Slack that is filled with other very helpful developers who are using Sidekiq.
(Disclaimer: I used to work at Instructure.)
If you're not aware of that, simply switching repos may cause some surprises.
> 4.1.10 - January 18, 2022 (40 KB)
> 4.1.9 - December 09, 2020 (40 KB)
Ok last I checked it was before January and I wanted to upgrade to rails 7 and couldn't so ended up moving to SidekiqThis one seems to be the most performant. By a lot too, from my understanding (haven't ran any benchmark myself, but the readme shows some good postgres knowledge)
To me, Sidekiq is the perfect example of keeping something simple. A basic Redis + Sentinel deployment with persistence enabled and multiple read replicas has allowed me to achieve this.
Scaling Sidekiq workers is also incredibly easy. I have dedicated job queues for various categories, and I simply create a new Kubernetes deployment for each category. Each deployment is set to only process a specific queue. This allows me to throttle how quickly each queue gets processed, simply based on replica/pod count.
Reasons to avoid Sidekiq not mentioned:
- Sidekiq does not implement all of or discourages use of activejob abstractions (retry_on, globalid for example)
- Paid support can be curt, hostile, or down-right "works on my machine, sorry!"
- Sidekiq's better performance has never materially mattered for my applications; they're almost always waiting on IO from the db or some net connection.
I generally get multiple app issues per day so I can't afford to reproduce every one: just yesterday I asked a customer to provide a reproduction, they worked on it and found the issue was in another gem their app used. At my scale with ~2000 customers, this happens almost every day.
I've been toying around with this idea for a while and the main blocker is the incompatibility of Rails (mostly AR) with some of the Fiber stuff. That is slowly getting cleared out though.
Fibers have a lot of potential but not in the one request per thread/fiber model we have now. Or perhaps I'm thinking about this wrong?
- Some quick database lookups (~ 10 ms total)
- An API call (depends on the time of day, but between 500-800 ms)
- Another quick database insert (~ 20 ms usually)
The job only needs a db connection for about 5% of the total runtime of the job and can give the connection back to the pool while the API call is running. Many mailer jobs in the system have a similar db-to-api-call ratio.
(Also, I am of the opinion that all but the very biggest sites should not have any problems with db connections. Get a database that handles tons of connections gracefully like MySQL or use something like pgbouncer when using Postgres. Having low connection limits should not be a problem in 2022)
"Using jemalloc instead of regular malloc helps too. The exact way to do this depends on the platform you use, but it is pretty simple on Heroku. Just set heroku-buildpack-jemalloc as the first buildpack (ahead of the heroku/ruby buildpack)."
FYI, remember to set JEMALLOC_ENABLED=true in your env to actually turn it on.
No disrespect to sidekiq. I see the use case for it and it seems like it is better supported.
- Identify memory leaks
- Identity memory leaks on Postgres
Why spending so much time writing content when you could have focused on your product instead?
To echo prescriptivist's comment, neither DataDog nor New Relic's profilers offer memory profiling for Ruby applications.
Event-based architectures such as event-sourcing, -listeners, -subscribers, message-buses, actor pattern. All of them solve the problems that Rails tries to solve with delayed jobs. But does it with different tradeoffs.
Rails is first and foremost an MVC setup with a heavy and tight coupling to the database. That is a poor fit for apps or domains with lots of sequential logic, messaging, services or orchestration. Rails is perfect for reasonable simple "CRUD", its less perfect for "CRUD+business-logic" and a very poor fit for complex domain models with (eventual) consistency and/or message passing.
For the middle-scenario, Id -personally- forego Rails, and choose or build an archictecture that focuses more on logic than on "getting stuff in and out of a database". But I see why people will choose Rails there too, and then bolt the logic and consistency on top of it.
We had customers setting up tests which could cover thousands of pages and run for hours (or even days).
Sure, Rails probably wasn't the ideal tool for the job, but it worked fine.