Seorang insinyur perangkat lunak berhasil mengubah endpoint ingest PDF yang awalnya bersifat sinkron dan memblokir thread permintaan hingga beberapa menit, menjadi arsitektur asinkronus berbasis antrean pekerja dengan Redis dan arq. Perubahan ini memungkinkan klien menerima respons instan berupa ID pekerjaan, sementara proses berat OCR dan ekstraksi LLM berjalan di latar belakang tanpa mengganggu event loop server.
Masalahnya klasik dalam pengembangan API modern: sebuah endpoint menerima unggahan berkas, memprosesnya melalui pipeline OCR dan model bahasa besar, lalu mengembalikan hasil. Pada dokumen ringan, proses ini memakan tiga hingga lima detik. Namun pada PDF padat dengan banyak halaman, eksekusi bisa berlangsung selama menit penuh — mengunci worker FastAPI dan menumpuk antrean permintaan masuk. Kode asli bahkan menyertakan komentar yang mengakui kekurangan arsitektur: endpoint sengaja dibuat sinkron karena pipeline internal memanggil `anyio.run()` yang tidak dapat bersarang di dalam event loop permintaan.
Komunitas pengembang Python sudah lama mengenal pola ini sebagai anti-pattern. Blocking I/O dan komputasi berat di dalam handler HTTP merusak skalabilitas, terutama saat aplikasi dijalankan dengan model worker seperti Uvicorn atau Gunicorn yang bergantung pada event loop tunggal per proses. Solusi standar industri adalah memindahkan pekerjaan berat ke sistem antrean pesan terpisah — Celery, RQ, atau arq — yang memisahkan siklus hidup permintaan HTTP dari eksekusi tugas.
Pemilihan jatuh pada arq, pustaka antrean pekerja asinkronus yang dibangun di atas Redis dan dirancang khusus untuk ekosistem asyncio. Berbeda dengan Celery yang lebih tua dan berat, arq ringan, tidak memerlukan broker terpisah selain Redis, dan mengadopsi pola `async/await` secara native. Penulis catatan teknis ini menemukan arq sudah tercantum di `pyproject.toml` versi 0.26, sehingga integrasi hanya soal mengaktifkan dan mengonfigurasi komponen yang sudah tersedia.
Langkah pertama adalah memvalidasi permukaan API arq versi 0.28 yang akan digunakan. Penulis memverifikasi enum `JobStatus` — yang berisi `deferred`, `queued`, `in_progress`, `complete`, dan `not_found` — serta memastikan `RedisSettings.from_dsn()` berfungsi dengan benar untuk string koneksi `redis://localhost:6379/0`. Detail ini krusial karena nilai-nilai status akan mengalir ke loop polling frontend dan skema OpenAPI, sehingga ketidaksesuaian kecil akan memecah kontrak antarmuka.
Modul pekerja dibuat di `src/pacos/platform/worker.py` dengan fungsi tugas `run_ingest` yang menerima konteks, nama berkas, dan data biner. Fungsi ini hanya memanggil pipeline `ingest_bytes` yang sudah ada — yang bersifat sinkron — dan mengembalikan hasil yang sudah diserialisasi ke kamus. Kelas `WorkerSettings` mendaftarkan fungsi tersebut, mengatur koneksi Redis dari konfigurasi aplikasi, membatasi jumlah pekerja bersamaan ke empat, dan menetapkan batas waktu eksekusi sepuluh menit untuk PDF terburuk.
Integrasi ke FastAPI dilakukan melalui manajer siklus hidup `lifespan` berbasis `@asynccontextmanager`. Saat aplikasi mulai, kolam koneksi Redis dibuat dan disimpan di `app.state.arq_pool`. Desain *best-effort* dipilih: jika Redis tidak tersedia saat startup — misalnya selama pengujian atau pengembangan offline — aplikasi tetap berjalan, dan endpoint yang memerlukan antrean akan mengembalikan 503 Service Unavailable. Pola ini menjaga ketahanan layanan tanpa membuat dependensi infrastruktur menjadi titik kegagalan tunggal.
Agar router tetap bersih, dependensi `arq_pool` didefinisikan di `deps.py` dengan anotasi `Annotated[ArqRedis, Depends(arq_pool)]`. Pola ini konsisten dengan cara penulis menangani sesi basis data dan pengguna saat ini. Endpoint lama yang tunggal kini terpecah menjadi pasangan: `POST /ingest` yang mengantrekan pekerjaan dan mengembalikan 202 Accepted beserta ID pekerjaan, serta `GET /ingest/status/{job_id}` yang dipolling klien hingga status berubah menjadi `complete`. Kode status 202 dipilih dengan sengaja — menandakan permintaan diterima tetapi belum selesai diproses.
Pengujian otomatis mengungkap sisi tersembunyi dari perubahan arsitektur ini. Suite pengujian yang biasanya berjalan dalam 3,45 detik tiba-tiba memakan 103 detik karena `lifespan` mencoba menghubungi Redis yang tidak tersedia di lingkungan sandbox, memicu percobaan ulang koneksi berulang. Solusinya sederhana: mematch kolam arq di *fixture* pengujian dengan `monkeypatch.setattr(app.state, "arq_pool", None)`. Setelah perbaikan, kecepatan pengujian kembali normal dan semua sembilan tes tetap lolos.
Bagi pengembang Indonesia yang membangun layanan berbasis dokumen — seperti platform e-KYC, pemrosesan faktur otomatis, atau sistem manajemen kontrak hukum — pola ini sangat relevan. Banyak startup lokal masih mengandalkan endpoint sinkron untuk pipeline PDF karena kemudahan implementasi awal, namun menghadapi keluhan latensi saat volume naik. Migrasi ke arq menawarkan jalan tengah: tidak sekompleks Celery dengan RabbitMQ, namun cukup kuat untuk beban produksi nyata. Redis sendiri sudah tersedia di hampir semua penyedia cloud lokal dan internasional, sehingga hambatan infrastruktur minimal.
Ke depan, pola *fire-and-forget* dengan polling status bisa diperluas ke alur kerja lain: pelaporan bulanan, sinkronisasi data eksternal, atau pemicu notifikasi. Tim juga bisa mengeksplorasi *webhook* callback agar klien tidak perlu polling aktif, serta *dead letter queue* untuk menangani kegagalan tugas yang tidak terduga. Arsitektur yang dipisah ini membuka pintu untuk penskalaan horizontal pekerja secara independen dari layanan API — fondasi yang kokoh untuk pertumbuhan jangka panjang.