Menjalankan asynchronous task berdurasi panjang seperti pipeline training AI, video encoding, atau batch processing pada Spot Instance (misalnya GKE Spot Nodes atau AWS Spot Instances) memangkas biaya infrastruktur hingga 60–80%. Konsekuensinya, infrastruktur dapat mencabut node kapan saja melalui pod eviction dengan toleransi waktu sempit (umumnya 25–30 detik antara SIGTERM dan SIGKILL).

Jika kontrak API dan arsitektur worker tidak dirancang toleran terhadap interupsi, beban kerja asinkronus akan mengalami mutasi data ganda, task berstatus menggantung (hanging state), atau kegagalan total yang memboroskan komputasi. Artikel ini menyajikan spesifikasi teknis kontrak API berbasis idempotency token dan mekanisme checkpointing untuk menjamin eksekusi task dapat dilanjutkan (resume) tanpa duplikasi.

Tiga Pitfall Integrasi Saat Eviction (SIGTERM)

Ketika Kubernetes memulai penggusuran pod (eviction), kubelet mengirimkan sinyal SIGTERM ke proses kontainer sebelum terminationGracePeriodSeconds berakhir. Tanpa penanganan deterministik pada boundary API dan worker, tiga kegagalan sistem berikut pasti terjadi:

  • Mutasi Ganda (Double Mutation): Worker baru mengambil task yang diasumsikan gagal, lalu memproses ulang step yang sebenarnya telah menghasilkan mutasi eksternal (seperti pemanggilan API pihak ketiga atau pemotongan saldo).
  • Hanging State: Database mencatat status task tetap IN_PROGRESS secara permanen karena worker mati mendadak sebelum sempat memperbarui state ke storage pusat.
  • Ambiguous Timeout: Klien atau orchestrator mendeteksi koneksi putus atau timeout HTTP tanpa mengetahui apakah batch step terakhir berhasil dikomit atau gugur di tengah jalan.

Pola Kontrak API 202 Accepted & Lifecycle Polling

Operasi asinkronus yang memakan waktu lebih dari 1 detik wajib mengembalikan status code 202 Accepted. Klien tidak menunggu hasil akhir, melainkan menerima resource locator untuk pemantauan.

POST /v1/ai/batch-inference HTTP/1.1
Host: api.internal.domain
Content-Type: application/json
Idempotency-Key: idemp_req_98a7cf810b

{
  "model_id": "llm-embed-v2",
  "dataset_uri": "s3://bucket/dataset-01.jsonl",
  "callback_url": "https://client.internal.domain/webhooks/task-finish"
}

Respons server wajib menyertakan ID tugas, status awal, dan header Location untuk polling:

HTTP/1.1 202 Accepted
Content-Type: application/json
Location: /v1/ai/batch-inference/task_01HY74M3K

{
  "task_id": "task_01HY74M3K",
  "status": "QUEUED",
  "created_at": "2025-05-10T10:00:00Z",
  "checkpoint": {
    "current_step": 0,
    "continuation_token": null
  }
}

Checkpointing Contract & Token Idempotensi Bertahap

Untuk workload berukuran besar, unit eksekusi harus dibagi menjadi partisi diskrit (chunks/batches). Setiap partisi memiliki step idempotency key yang dihitung secara deterministik: hash(task_id + step_index).

Ketika pod mengalami eviction di tengah proses, worker wajib menyimpan snapshot kemajuan terakhir (checkpoint). Respons polling status mengekspos properti ini agar proses pemulihan dapat memverifikasi batas komputasi yang valid.

Skema Kontrak Polling & State Handshake

GET /v1/ai/batch-inference/task_01HY74M3K HTTP/1.1
Host: api.internal.domain
HTTP/1.1 200 OK
Content-Type: application/json

{
  "task_id": "task_01HY74M3K",
  "status": "SUSPENDED",
  "total_steps": 100,
  "completed_steps": 42,
  "checkpoint": {
    "last_committed_step": 42,
    "continuation_token": "chk_step42_sha256_9f8e7d6c",
    "last_heartbeat": "2025-05-10T10:04:15Z"
  },
  "error": null
}

Nilai enum status yang valid dalam kontrak:

  • QUEUED: Task menunggu antrean worker.
  • PROCESSING: Worker aktif mengeksekusi batch.
  • SUSPENDED: Pod mati akibat SIGTERM; state aman disimpan pada checkpoint terakhir.
  • COMPLETED: Seluruh step selesai diproses.
  • FAILED: Terjadi eror unrecoverable/fatal.

Implementasi Worker: Signal Handling & State Sync

Worker bertugas mencegat sinyal OS, menghentikan iterasi batch baru, menulis checkpoint terakhir ke database persisten, lalu melepaskan lease lock sebelum kontainer dimatikan paksa.

Berikut implementasi minimal signal handling pada worker berbasis Python:

import signal
import sys
import time
import hashlib

class GracefulTaskWorker:
    def __init__(self, task_id: str, db_client):
        self.task_id = task_id
        self.db = db_client
        self.eviction_signaled = False
        
        # Daftarkan penanganan sinyal SIGTERM
        signal.signal(signal.SIGTERM, self._handle_sigterm)
        signal.signal(signal.SIGINT, self._handle_sigterm)

    def _handle_sigterm(self, signum, frame):
        # Beri flag untuk stop iterasi berikutnya, maksimalkan sisa waktu grace period
        self.eviction_signaled = True

    def _generate_step_token(self, step_idx: int) -> str:
        raw = f"{self.task_id}:{step_idx}"
        return "chk_" + hashlib.sha256(raw.encode()).hexdigest()[:16]

    def run(self, total_steps: int):
        checkpoint = self.db.get_checkpoint(self.task_id)
        start_step = checkpoint.get("last_committed_step", 0) + 1

        for step in range(start_step, total_steps + 1):
            if self.eviction_signaled:
                # Pod terkena eviction; simpan status SUSPENDED dan flush state
                self.db.update_state(
                    task_id=self.task_id,
                    status="SUSPENDED",
                    last_step=step - 1,
                    token=self._generate_step_token(step - 1)
                )
                sys.exit(0)

            step_token = self._generate_step_token(step)
            
            # Eksekusi unit atomik pekerjaan dengan idempotency key step
            self.process_step(step, step_token)
            
            # Update checkpoint
            self.db.update_checkpoint(self.task_id, step, step_token)

        self.db.update_state(self.task_id, status="COMPLETED", last_step=total_steps, token=None)

    def process_step(self, step: int, token: str):
        # ponytail: implementasi eksekusi actual via downstream API/storage
        pass

Catatan Konfigurasi Kubernetes: Pastikan terminationGracePeriodSeconds pada spesifikasi Deployment/StatefulSet diatur lebih besar dari latensi maksimum pemrosesan satu batch step (misalnya: 60s), agar handler memiliki cukup waktu untuk menyelesaikan checkpoint ke database.

Checklist Verifikasi Pemulihan Task

Gunakan kriteria berikut dalam automated test (chaos/e2e) untuk menjamin pemulihan task tanpa efek samping:

  1. Uji Interupsi Terkendali: Kirim kubectl delete pod <pod-name> --now=false saat task berada pada progress 50%. Verifikasi status berubah ke SUSPENDED, bukan FAILED.
  2. Validasi Batas Token: Jalankan kembali worker baru dengan task yang sama. Worker harus memulai eksekusi tepat pada last_committed_step + 1.
  3. Idempotensi Mutasi Hilir: Pastikan pemanggilan ulang (replay) step yang telah memiliki token di log audit eksternal mengembalikan hasil tersimpan (cached result) tanpa mengeksekusi mutasi sekunder.
  4. Pembersihan Lock Orphan: Bila worker mati sebelum sempat menangani SIGTERM (misal skenario Host Kernel Panic / SIGKILL instan), pastikan sistem memiliki mekanisme heartbeat TTL otomatis untuk merebut (reclaim) task yang menggantung.