feat(backend): implement postgres job queue
This commit is contained in:
108
backend/jobs/models.py
Normal file
108
backend/jobs/models.py
Normal file
@@ -0,0 +1,108 @@
|
||||
import uuid
|
||||
|
||||
from django.db import models
|
||||
from django.db.models import Q
|
||||
|
||||
|
||||
class Job(models.Model):
|
||||
class Status(models.TextChoices):
|
||||
QUEUED = "queued", "Queued"
|
||||
RUNNING = "running", "Running"
|
||||
SUCCEEDED = "succeeded", "Succeeded"
|
||||
FAILED = "failed", "Failed"
|
||||
|
||||
id = models.UUIDField(primary_key=True, default=uuid.uuid4, editable=False)
|
||||
type = models.CharField(max_length=120)
|
||||
payload = models.JSONField(default=dict, blank=True)
|
||||
status = models.CharField(max_length=20, choices=Status.choices, default=Status.QUEUED, db_index=True)
|
||||
priority = models.SmallIntegerField(default=50)
|
||||
available_at = models.DateTimeField(db_index=True)
|
||||
attempts = models.PositiveIntegerField(default=0)
|
||||
max_attempts = models.PositiveIntegerField(default=3)
|
||||
idempotency_key = models.CharField(max_length=255, null=True, blank=True)
|
||||
locked_by = models.CharField(max_length=255, null=True, blank=True)
|
||||
locked_until = models.DateTimeField(null=True, blank=True)
|
||||
last_error = models.TextField(blank=True, default="")
|
||||
result = models.JSONField(null=True, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
updated_at = models.DateTimeField(auto_now=True)
|
||||
finished_at = models.DateTimeField(null=True, blank=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ("-created_at",)
|
||||
constraints = [
|
||||
models.CheckConstraint(
|
||||
name="job_status_valid_shape",
|
||||
condition=(
|
||||
Q(
|
||||
status="queued",
|
||||
locked_by__isnull=True,
|
||||
locked_until__isnull=True,
|
||||
finished_at__isnull=True,
|
||||
)
|
||||
| Q(
|
||||
status="running",
|
||||
locked_by__isnull=False,
|
||||
locked_until__isnull=False,
|
||||
finished_at__isnull=True,
|
||||
)
|
||||
| Q(
|
||||
status__in=["succeeded", "failed"],
|
||||
locked_by__isnull=True,
|
||||
locked_until__isnull=True,
|
||||
finished_at__isnull=False,
|
||||
)
|
||||
),
|
||||
),
|
||||
models.CheckConstraint(
|
||||
name="job_attempts_valid",
|
||||
condition=Q(attempts__gte=0, max_attempts__gte=1, attempts__lte=models.F("max_attempts")),
|
||||
),
|
||||
models.UniqueConstraint(
|
||||
fields=["idempotency_key"],
|
||||
condition=Q(idempotency_key__isnull=False),
|
||||
name="job_idempotency_key_unique",
|
||||
),
|
||||
]
|
||||
indexes = [
|
||||
models.Index(
|
||||
fields=["available_at", "-priority", "created_at", "id"],
|
||||
name="job_claim_idx",
|
||||
condition=Q(status="queued"),
|
||||
),
|
||||
models.Index(fields=["locked_until"], name="job_timeout_idx", condition=Q(status="running")),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.type}:{self.id}"
|
||||
|
||||
|
||||
class JobEvent(models.Model):
|
||||
class Type(models.TextChoices):
|
||||
CREATED = "created", "Created"
|
||||
CLAIMED = "claimed", "Claimed"
|
||||
PROGRESS = "progress", "Progress"
|
||||
LEASE_RENEWED = "lease_renewed", "Lease renewed"
|
||||
SUCCEEDED = "succeeded", "Succeeded"
|
||||
RETRY_SCHEDULED = "retry_scheduled", "Retry scheduled"
|
||||
FAILED = "failed", "Failed"
|
||||
TIMEOUT_REQUEUED = "timeout_requeued", "Timeout requeued"
|
||||
MANUAL_RETRY = "manual_retry", "Manual retry"
|
||||
|
||||
job = models.ForeignKey(Job, on_delete=models.CASCADE, related_name="events")
|
||||
type = models.CharField(max_length=40, choices=Type.choices, db_index=True)
|
||||
attempt = models.PositiveIntegerField(default=0)
|
||||
worker_id = models.CharField(max_length=255, null=True, blank=True)
|
||||
message = models.TextField(blank=True, default="")
|
||||
data = models.JSONField(default=dict, blank=True)
|
||||
created_at = models.DateTimeField(auto_now_add=True, db_index=True)
|
||||
|
||||
class Meta:
|
||||
ordering = ("id",)
|
||||
indexes = [
|
||||
models.Index(fields=["job", "id"], name="job_event_job_id_idx"),
|
||||
models.Index(fields=["id"], name="job_event_id_idx"),
|
||||
]
|
||||
|
||||
def __str__(self):
|
||||
return f"{self.type}:{self.job_id}:{self.id}"
|
||||
Reference in New Issue
Block a user