Also consider if the machine running that process just disappears and that process dies. Putting work into a task queue allows you to do it durably until it can be processed so that it's not lost in some typical "that machine/instance died" scenario.
Every problem is not the same. Work queues introduce a magnitude or more of complexity to an http application. Sometimes that is very needed. Sometimes its overengineering. Sometimes it is a gray area.
You have an operating system that you can use. You don't really _need_ concurrency when you have a machine that can timeshare amongst processes. That's the world Python was designed for
> In most other languages you can get away with just running tasks in the background for a really long time before you need spin up a distributed task queue
This is partly true, but not a lot of people do this because you need to persist those tasks unless you want them dropped during a reboot. Same logic goes for entire machines disappearing. You _can_ get by without a distributed system, but you will need to tolerate loss in those scenarios. Those losses are non-trivial for most apps/companies, so it doesn't seem all that practical to me to consider a world without a distributed system (and yes, persisting things in postgres/mysql before they are being worked on is still using a distributed system)
What happens when you want to retry jobs with exponential back off, or rate limit a task, or track completed / failed jobs?
You can wire all of this stuff up yourself but it's a hugely complicated problem and a massive time sink, but Celery gives you this stuff out of the box. With a decorator or 2 you can do all of those things on any tasks you want.
I use it in pretty much every Flask project, even on single box deploys.
In Python too:
# or ThreadPoolExecutor depending of the type of work
from concurrent.futures import ProcessPoolExecutor, as_completed
with ProcessPoolExecutor(max_workers=2) as executor:
a = executor.submit(any_function)
b = executor.submit(any_function)
for future in as_completed((a, b)):
print(future.result())
Any task you would put in celery would be a good candidate for being first passed to a process pool executor.No, the real reason we use celery is that it solves many problems at once:
- it features configurable task queues with priorities
- it comes as an independent service accessible from multiple processes
- its activity can be inspected and monitored with stuff like flower
- it has the ability to create persistent queues that survive restart
- error handling is backed in, they are logged and won't break your process
- it offers an optional result backend for persisting results and errors
- many distribution and error handling strategies can be configured
- tasks are composable, can depend on each others or be grouped
- celery also does recurring tasks, and better than cron
- you can start tasks from other languages than Python
Now some People use celery when they should use a ProcessPool, because they don't know it exists. But that's hardly because of the language: you didn't seem to know about it either.It is true that Python concurrency story is not comparable to something like Go, Erlang or Rust, but very common use cases are solved.
In the same way, we can perfectly use shelves instead of redis.
We use redis beacuse it solves many problems at once:
- it's very performant on a single machine, and can be load balanced if needed
- it has expiration baked in
- it offers many powerful data structures
- it embeds numerous strategies to deal with persistence and resilience
- it's accessible from multiple processes, and multiple languages
- its ecosystem is great, and it's hugely versatile
It's almost never a bad choice to add redis for your website. It's easy to setup, cheap to run, and and it shines to manage sessions and caching for any service with up to a few million users a day.Once you are at it, why not later use it for other stuff, like storing the result of background tasks, log stream, geographical pin pointing, hyperlolog stats, etc. ? There are so many things it does better than your DBMS, faster, easier or for less resources.
It's such fantastic software really.
But no, nothing prevents you in Python to create a queue manually and serialize it manually. It's just more work for less features.
I also understand the benefits of task queues. However, there are many cases in which you do not need any of those. Specifically, everything you wrote applies to the web backend/distributed systems usecase. Doing things in the background in a simple application, not so much. My problem is exactly with introducing distributed systems machinery for a local process on a single machine that doesn't need any of that.