The Gevent Pool: 5 Lessons Learned

Search for a command to run...

Imagine this: You are interacting with a web app where you upload an Excel workbook. When the upload is finished, the app lets you browse the workbook content, its version history and shows you the diffs between versions. Deep down in the engine room...

Before I wrote this article, I hadn't realised that SQLAlchemy is a highly controversial subject. Heated discussions on Reddit and X. Love it or hate it. The full drama. The polarising nature of the internet aside, what is true about SQLAlchemy is, t...

Celery dropped support for Windows a long time ago, somewhere around version 4. Celery 3 was the last version to support Windows, and back in 2018, when I published the first version of this article, it was a somewhat acceptable workaround. Not these...

By default, Celery routes all tasks to a single queue and all workers consume from this default queue. You can change this behaviour by telling Celery which tasks to send to which queues. This is known as task routing. This is useful if you have slow...

Using Redis or RabbitMQ as a Celery message broker can feel a bit over the top when you are just developing locally. In this blog post, I show you a simple alternative: the filesystem as a message broker. Kombu Kombu is a messaging library that provi...

If you run Celery tasks to perform API requests or other input/output operations and you want to increase throughput, then this is for you.
Gevent is a Python library that provides lightweight, cooperative concurrency via greenlets. Greenlets are the same thing as coroutines in the asyncio world: while one task waits for its result, it yields to another task to do its thing.
Both gevent and asyncio use an event loop for coordinating the execution of tasks. The difference between greenlet and asyncio is that greenlets do the switching between tasks magically, whereas asyncio makes you await explicitly.
In the gevent world, whenever your programme is blocked on a network request, it will automatically switch to another greenlet. This increases throughput for all input/output bound tasks, compared to the default prefork pool.
This magic works because gevent monkey-patches Python’s socket library. Gevent replaces attributes of the standard library's modules with its own modules. This allows you to write traditional synchronous code without noticing how gevent takes over under the hood.
Coming from prefork and getting started with gevent is more than just plug-and-play. Are you in the business of giving the gevent pool a try? Here are my top 5 lessons learned.
The very first thing gevent needs to do in the lifecycle of a programme is to patch parts of the standard library with gevent-friendly functions. Celery takes care of this. Do not monkey-patch. If something goes wrong, Celery not monkey-patching is never the issue. It's a red herring, contrary to what people say on StackOverflow.
Third-party libraries are the single biggest cause for gevent related headaches. Some libraries don't play nicely with the patched socket library. The more third-party packages you rely on the more time you will spend on this. Package dependency hell is a thing.
When things do go wrong, be mindful of the exceptions you see. They can be misleading and point you towards components that are not at fault. If you come across any of the following messages in your stack trace, chances are high that it is not your ORM but a third-party SDK (see point 2):
Module 'select' has no attribute 'epoll'
SynchronousOnlyOperation: You cannot call this from an async context - use a thread or sync_to_async
You cannot use AsyncToSync in the same thread as an async event loop
This is the worst one and is difficult to reproduce. A task might run 1,000 times and then fail with a stack trace that doesn't make sense (see point 3). A good starting point is point 2.
I just want to reiterate points 2-4. In 9 out of 10 cases, it is a third-party API wrapper.