Design

Warning

This section is a work in progress.

Chancy is designed to fit the vast majority of use cases, and to do so in a way that requires just the availability of a Postgres database.

The Application

A Chancy application is made when you define a Chancy instance. This object is responsible for managing the database connection, and exposes common functionality like pushing jobs to the queue or running migrations.

from chancy import Chancy

async with Chancy("postgresql://localhost/postgres") as chancy:
    pass

The Worker

Now when we want to run a job, we do so using a long-running Worker process. This worker process is responsible for pulling jobs from the queue, co-ordinating the execution of the job, and updating the job status in the database as it progresses.

from chancy import Chancy, Worker

async with Chancy("postgresql://localhost/postgres") as chancy:
    async with Worker(chancy) as worker:
        await worker.wait_for_shutdown()

The worker will use Postgres’ SELECT...FOR UPDATE SKIP LOCKED to guarantee that only one worker is running a given job at a time. If the worker happens to crash while running a job, the Recovery plugin will periodically restore them.

Each worker also listens for realtime events using Postgres’ LISTEN/NOTIFY mechanism. This allows the worker to be notified of new jobs being pushed to the queue (among other things) nearly instantly. In the case that this fails, the worker will also poll the queue periodically.

Leadership

Instead of having a hardcoded “coordinator” process or requiring separate setup for periodic tasks like Celery’s “beat” process, all of the workers in a Chancy application will periodically attempt to become the “leader”. Certain plugins like cron and workflows will only run on the worker that currently holds leadership to prevent race conditions.

The default leadership process is handled by the Leadership plugin, and can easily be replaced with a custom implementation.

Multi-mode concurrency

Each queue can use its own concurrency model, and multiple queues can mix and match models in the same worker. By default, queues use the ProcessExecutor which is similar to the default in most other task queues. This means you can mix asyncio-based jobs which are crawling external APIs with a CPU-bound task aggregating the results and it’ll just work.

Extendable

Almost all functionality beyond queue management and job fetching in Chancy is implemented as a plugin. This allows us to easily add new features without breaking backwards compatibility, and to make it easy to swap out the underlying implementation as your needs change. Workflows, cron jobs, job recovery, job pruning, and more are all implemented as swappable plugins.

This is especially useful for busy applications where you might need to tweak queries or behaviors to optimize for your specific use case.

Reliable By Default

In Chancy, jobs are guaranteed to be run at least once. In the case of a worker crash, networking issues, or other failure, the job can be recovered with the Recovery plugin. This ensures that jobs are never lost, but care must be taken to ensure that jobs are idempotent. That is, the job should be able to be run multiple times without causing any side effects.

This contrasts to Celery, which may lose jobs in the case of a worker crash if the acks_late setting is left on its default of disabled.