celery / django-celery-results

Celery result back end with django
Other
696 stars 206 forks source link

The `delete_expired` method of ResultManager sometimes selects read-only database #357

Open jacklinke opened 1 year ago

jacklinke commented 1 year ago

In a django project with a database router and read-only databases, the delete_expired method may try to perform raw_delete() using a read-only database, resulting in an Internal Error: "ReadOnlySqlTransaction - cannot execute DELETE in a read-only transaction".

Relevant code:

def delete_expired(self, expires):
    """Delete all expired results."""
    with transaction.atomic(using=self.db):
        raw_delete(queryset=self.get_all_expired(expires))

My Database Router:

import random

from django.conf import settings

class CustomRouter:
    def db_for_read(self, model, **hints):
        """
        Return either default or one of the replicas
        """

        db_read = [
            "default",
        ]

        if settings.REPLICA1_URL is not None:
            db_read.append("replica1")
        if settings.REPLICA2_URL is not None:
            db_read.append("replica2")

        return random.choice(db_read)

    def db_for_write(self, model, **hints):
        # Always return the default database
        return "default"

    def allow_relation(self, obj1, obj2, **hints):
        return True

    def allow_migrate(self, db, app_label, model_name=None, **hints):
        return True

Occasionally, django-celery-results will attempt to conduct delete_expired using one of my replica databases:

connection: <DatabaseWrapper vendor='postgresql' alias='replica1'>

Recommend changing the using argument of the atomic transaction in delete_expired to explicitly use the writable database specified in the router.