Perintah dan konteks
Sebuah layanan asinkron sering kali memenuhi permintaan dari cache dalam memori tanpa melakukan I/O, namun setiap coroutine tetap menjadi sebuah task dan menunggu penjadwalan event-loop. Tim mengusulkan untuk menyetel asyncio.eager_task_factory secara global. Evaluasi trade-off tersebut dan usulkan rollout yang aman.
Dokumentasi Python menyatakan bahwa eksekusi eager memulai coroutine secara sinkron saat Task-nya dibuat; Task hanya dijadwalkan ketika coroutine memblokir. API ini ditambahkan di Python 3.12 dan mengubah semantik penjadwalan yang dapat diamati.
Apa yang diuji oleh pewawancara
Kuncinya adalah membedakan penyelesaian sinkron dari pemblokiran asinkron serta menjelaskan efeknya terhadap keadilan (fairness), pengurutan, timing exception, pembatalan, dan TaskGroup. Jawaban yang kuat membatasi optimasi pada batasan yang terukur dan mencakup pemeriksaan versi, feature flag, rollback, serta metrik event-loop.
Pertanyaan klarifikasi untuk diajukan terlebih dahulu
- Berapa versi minimum Python di seluruh deployment?
- Apakah coroutine ini merupakan pembacaan memory-cache, atau dapatkah mereka melakukan I/O jaringan, database, file, atau lock?
- Apakah kode bergantung pada urutan pembuatan task, keadilan loop, atau urutan
call_soon? - Apakah kegagalan, pembatalan, dan batas waktu (deadlines) dimiliki oleh
TaskGroupatau request scope? - Apakah targetnya adalah CPU pembuatan task, tail latency, atau throughput, dan apa baseline-nya?
Kerangka jawaban 30 detik
“Saya tidak akan mengaktifkannya secara global terlebih dahulu. Eager factory memulai coroutine selama pembuatan Task, sehingga cache hit dapat menghindari satu giliran penjadwalan event-loop; coroutine yang memblokir dijadwalkan secara normal. Hal itu mengubah urutan, timing exception, dan keadilan: loop yang membuat banyak task sinkron dapat menunda timer dan permintaan lain. Saya akan memeriksa versi Python, meluncurkannya di titik panggilan cache yang terukur, membandingkan lag event-loop dan tail latency, serta mempertahankan factory default sebagai rollback instan.”
Pembahasan mendalam langkah demi langkah
Langkah 1: Nyatakan model default dan eager
Dengan factory default, create_task menjadwalkan coroutine untuk segera berjalan. Dengan eager factory, konstruksi segera menjalankannya hingga selesai me-return, melempar exception, atau mencapai await pemblokir pertamanya. Penyelesaian sinkron mungkin tidak pernah masuk ke dalam antrean event-loop.
loop.set_task_factory(asyncio.eager_task_factory)
task = asyncio.create_task(read_cached(key))Oleh karena itu, eksekusi eager adalah perubahan semantik yang dapat diamati, bukan sekadar penjadwal yang lebih cepat.
Langkah 2: Pilih batasan coroutine
Kandidat yang baik adalah operasi memory-cache atau memoized yang singkat dan memiliki hit-rate tinggi. Coroutine yang mungkin melakukan pekerjaan jaringan, database, file, lock, atau CPU tanpa batas harus mempertahankan batasan pemblokiran yang dapat diprediksi sehingga konstruksi tidak memonopoli task saat ini.
Langkah 3: Analisis urutan dan keadilan
Ketika task dibuat dalam sebuah loop, eager coroutine dapat selesai seketika dalam urutan pembuatan, mengubah interleaving yang sebelumnya bergantung pada event loop. Batch sinkron yang besar dapat menunda timer, callback I/O, dan permintaan lainnya. Lacak lag event-loop dan batasi ukuran batch.
Langkah 4: Tangani exception dan pembatalan
Exception yang dilempar sebelum blok pertama dapat muncul di dekat create_task, mengubah titik penangkapan dan bentuk stack. Simpan referensi Task, dan biarkan CancelledError menyebar setelah pembersihan. Jangan pernah menggunakan mode eager untuk mengubah pembatalan permintaan atau kegagalan cache menjadi respons yang berhasil.
Langkah 5: Gabungkan dengan TaskGroup dan batas waktu
TaskGroup tetap memiliki hierarki task, pembatalan sibling, dan join, tetapi sebuah child mungkin telah selesai atau gagal selama create_task. Gunakan penanganan try/except* terstruktur dan satu batas waktu luar; jangan berasumsi setiap child selalu menunggu di dalam antrean terlebih dahulu.
async with asyncio.TaskGroup() as group:
user = group.create_task(read_cached("user"))
orders = group.create_task(read_remote("orders"))Langkah 6: Periksa versi dan batasi cakupan fitur
Factory ini tersedia mulai dari Python 3.12. Python 3.14 juga mengekspos opsi eager_start pada create_task. Layanan multi-versi harus memvalidasi runtime saat startup dan lebih memilih eksperimen di call-site lokal daripada perubahan factory global tanpa syarat.
Langkah 7: Luncurkan dengan metrik rollback
Mulai pada call-site khusus cache di balik sebuah sakelar (switch). Bandingkan CPU, waktu pembuatan task, tail latency cache-hit, lag event-loop, tingkat exception, tingkat pembatalan, dan QPS downstream terhadap factory default. Jika keadilan atau kesalahan mengalami regresi, kembalikan ke default tanpa mengubah kode bisnis.
Langkah 8: Uji semantik, bukan hanya benchmark
Cakup return sinkron, pelemparan exception sinkron, await pertama, pembatalan eksternal, beberapa kegagalan TaskGroup, batas waktu, pembuatan task rekursif, keadilan timer, dan campuran batch cache/remote. Catat event dan pastikan urutannya benar; benchmark throughput saja tidak dapat memvalidasi semantik penjadwalan.
Model jawaban berkualitas tinggi
“Eksekusi eager cocok untuk coroutine cache yang singkat dan dapat diprediksi karena menghilangkan satu giliran penjadwalan; ini berisiko untuk I/O yang tidak dapat diprediksi atau jalur CPU yang panjang. Risiko utamanya adalah perubahan urutan, keadilan, dan timing exception. Saya akan memeriksa versi, mengaktifkannya secara lokal di balik sebuah flag, mempertahankan rollback default, dan memantau lag event-loop, tail latency, pembatalan, serta error. TaskGroup dan batas waktu bersama tetap dipertahankan, begitu pula pembatalan dan pembersihan normal.”
Kesalahan umum
- Menganggap mode eager bebas semantik → urutan dan timing exception berubah → tuliskan kedua model eksekusi dan ukur.
- Mengaktifkannya untuk setiap coroutine → I/O atau CPU yang lambat memblokir task saat ini → canary hanya pada jalur sinkron yang singkat.
- Mengabaikan versi Python → deployment lama gagal saat startup → validasi runtime dan pertahankan factory default.
- Hanya melihat throughput rata-rata → keadilan mengalami regresi secara diam-diam → tambahkan metrik loop-lag dan tail.
- Mengasumsikan TaskGroup tidak berubah → kegagalan pada saat konstruksi salah ditangani → uji kegagalan eager child dan grup exception.
- Menelan error pembatalan atau pembersihan → permintaan membocorkan pekerjaan → pertahankan pembatalan dan verifikasi jalur pelepasan sumber daya.
Pertanyaan lanjutan dan respons yang kuat
Lanjutan 1: Apakah cache hit sinkron masih meninggalkan sebuah Task?
Task mungkin sudah selesai selama konstruksi. Jangan mewajibkan giliran event-loop; baca objek yang dikembalikan dan uji jalur eager-completion secara eksplisit.
Lanjutan 2: Apakah mode eager menjamin urutan pembuatan untuk penyelesaian?
Tidak. Ini hanya mengubah timing mulai; setelah coroutine memblokir, penjadwalan normal berlaku. Jika urutan bisnis penting, koordinasikan secara eksplisit atau urutkan hasil berdasarkan kunci operasi.
Lanjutan 3: Bagaimana cara mencegah batch cache-hit membuat permintaan lain kelaparan (starvation)?
Batasi batch, lakukan yield di antara batch jika perlu, dan perhatikan lag loop. Menerapkan mode eager pada satu call-site berbiaya rendah lebih aman daripada kebijakan global.
Lanjutan 4: Bagaimana cara melakukan rollback setelah terjadi regresi?
Nonaktifkan sakelar, pulihkan task factory default, verifikasi bahwa metrik kembali ke baseline, dan simpan jejak (traces) yang berisi versi runtime dan urutan event untuk diagnosis.
Lanjutan 5: Bagaimana hubungan eager_start dengan factory?
eager_start adalah opsi eksplisit untuk satu Task; factory adalah kebijakan default event-loop. Konfirmasikan signature dan prioritas versi Python yang tepat sebelum menggabungkannya.