S
Sheba ISP ERPDOCS
Architecture

Asynchronous & Background Task Pipeline

Celery workers, Redis message broker, distributed locks, and concurrency management.

Asynchronous & Background Task Pipeline

IMPLEMENTED

Long-running I/O operations, network polling, payment ingestion, and recurring billing runs are decoupled from synchronous HTTP cycles via Celery and Redis.


1. Asynchronous Invariants

  1. Explicit Tenant Scope: Every Celery task must accept an explicit tenant_id parameter. Celery workers do not have an HTTP request context and must never assume request.tenant.
  2. Distributed Locking: All recurring mutations (recharge, auto-lock, invoice generation, router telemetry sync) must acquire a Redis distributed lock (apps.core.lock.distributed_lock) to prevent race conditions across parallel workers.
  3. Idempotent Tasks: Tasks are written to be safely retried without creating duplicate invoices or double-crediting balances.

2. Core Background Tasks (backend/apps/core/tasks.py)

Task NameSchedule / TriggerPurposeLock Key
expire_customersDaily midnight cronLocks subscribers whose expiry date has passed (respecting grace periods), terminates sessionslock:expiry:{tenant_id}
generate_monthly_invoices1st of every month / APIGenerates monthly recurring subscription invoices, itemized lines, and auto-settles advancelock:invoice_gen:{tenant_id}
process_network_sync_jobPost-commit dispatchAsynchronously executes MikroTik/OLT actions (ENABLE_USER, DISABLE_USER, etc.) after DB commitlock:network_sync:{tenant_id}:{job_id}
process_payment_eventInbound SMS webhookMatches unlinked MFS transactions against subscribers, extends expiry, posts ledger entrieslock:payment_event:{tenant_id}:{event_id}
process_rechargeStaff manual rechargeApplies subscription renewals, updates expiry dates, updates queueslock:recharge:{customer_id}
sync_router_telemetryEvery 5 minutesPolls MikroTik routers for CPU, memory, uptime, and active PPPoE countlock:router_sync:{router_id}
execute_database_backupScheduled / On-demandDumps PostgreSQL database and uploads to backup storagelock:backup:{tenant_id}

3. Distributed Lock Pattern

from apps.core.lock import distributed_lock, LockAcquisitionError

@shared_task(bind=True, max_retries=3)
def process_recharge(self, tenant_id, customer_id, amount):
    lock_key = f"lock:recharge:{customer_id}"
    try:
        with distributed_lock(lock_key, timeout=60, blocking=False):
            with transaction.atomic():
                customer = Customer.objects.select_for_update().get(id=customer_id, tenant_id=tenant_id)
                # ... perform recharge, ledger entry, and MikroTik queue profile update ...
    except LockAcquisitionError:
        logger.warning("Recharge task for customer %s skipped: lock active", customer_id)

On this page