Prompt dan cakupan
Python 3.14 menambahkan InterpreterPoolExecutor; setiap worker interpreter memiliki GIL-nya sendiri dan dapat mengeksekusi kode Python secara paralel, sementara pemanggilan dan hasil melintasi batasan isolasi dan serialisasi. Pertanyaan ini menguji pertukaran (trade-offs) konkurensi dan termasuk dalam coding. Ini bukan jaminan bahwa setiap beban kerja mengalahkan proses atau ekstensi native.
Apa yang dinilai oleh pewawancara
Jawaban yang kuat menjelaskan isolasi interpreter, kemampuan pickling (picklability), state modul dan global, perilaku initializer, pembatalan, serta biaya memori. Mereka membedakan Python yang CPU-bound dari pekerjaan yang I/O-bound dan membandingkan pool thread, interpreter, dan proses menggunakan beban kerja yang sama. Mereka juga merencanakan pembatasan kegagalan dan shutdown yang deterministik.
Pertanyaan untuk diklarifikasi terlebih dahulu
- Apakah beban kerja berupa bytecode Python, kode native yang melepaskan GIL, atau I/O?
- Apakah argumen dan hasil dapat diserialisasi dengan murah, dan apakah objek berukuran besar atau dibagikan?
- Apakah tugas bergantung pada cache proses-global, soket terbuka, atau state modul yang dapat berubah (mutable)?
- Berapa anggaran untuk latensi, throughput, memori, dan startup?
- Bagaimana kegagalan initializer atau worker memengaruhi pekerjaan yang ada di antrean?
- Apakah lingkungan deployment kompatibel dengan Python 3.14 dan semantik pool-nya?
Kerangka jawaban 30 detik
"Saya akan melakukan benchmark terhadap tiga kontrol: thread, InterpreterPoolExecutor, dan proses. Interpreter dapat menjalankan bytecode Python pada beberapa core, tetapi setiap worker terisolasi dan callable, argumen, serta hasil yang dikirimkan memerlukan serialisasi. Saya akan menjaga tugas cukup besar untuk mengamortisasi biaya tersebut, membuat resource dalam initializer, menghindari global mutable bersama, dan menentukan perilaku pembatalan serta restart. Keputusan ini memerlukan bukti throughput, latensi p95, CPU, memori, waktu serialisasi, dan tingkat kegagalan."
Jawaban langkah demi langkah
Langkah 1: Klasifikasikan beban kerja
Ukur waktu CPU, perebutan GIL, waktu ekstensi native, I/O yang memblokir, dan durasi tugas. Thread mungkin cukup untuk I/O atau pustaka yang melepaskan GIL. Interpreter menargetkan paralelisme CPU tingkat Python; proses tetap menjadi kontrol isolasi dan kompatibilitas yang berguna.
Langkah 2: Rancang batasan serialisasi
Kirim callable tingkat atas yang dapat diimpor dan nilai data yang ringkas. Hindari closure, file descriptor yang terbuka, lock, dan graf objek yang besar. Jika serialisasi mendominasi, lakukan batching record atau pindahkan data yang tidak dapat diubah (immutable) ke penyimpanan eksternal bersama daripada menyalinnya berulang kali.
Langkah 3: Inisialisasi resource per interpreter
Gunakan initializer untuk mengimpor modul, mengonfigurasi state deterministik, dan membuat klien lokal untuk interpreter tersebut. Jangan berasumsi bahwa singleton tingkat modul dibagikan di antara worker. Catat identitas pool dan interpreter dalam diagnostik tanpa membocorkan data pengguna.
Langkah 4: Tentukan penanganan kegagalan dan pembatalan
Perlakukan kegagalan initializer sebagai peristiwa tingkat pool dan buat perilaku tugas dalam antrean menjadi eksplisit. Batasi future yang belum selesai, teruskan exception dengan ID tugas, dan batalkan pekerjaan pada batas batch. Restart worker harus membuat ulang resource lokal-interpreter dan tidak boleh menduplikasi efek samping non-idempoten.
Langkah 5: Benchmark dan peluncuran (roll out)
Bandingkan ukuran tugas dan konkurensi yang sama di seluruh pool thread, interpreter, dan proses. Catat waktu startup, serialisasi, komputasi, dan penggabungan hasil, ditambah RSS, throughput, p95, exception, dan durasi shutdown. Lakukan canary untuk satu beban kerja, pertahankan fallback ke process pool, dan hentikan jika biaya isolasi atau memori menghapus keuntungan CPU.
Contoh jawaban model
"InterpreterPoolExecutor adalah kandidat untuk kode Python yang berat CPU yang tidak dapat mengandalkan pustaka native pelepas GIL. Saya akan membuktikan beban kerjanya terlebih dahulu, lalu membandingkan thread, interpreter, dan proses di bawah input yang identik. Tugas harus melintasi batasan serialisasi, jadi saya akan menggunakan callable yang dapat diimpor, batch yang ringkas, inisialisasi lokal-interpreter, dan tidak ada global mutable bersama. Saya akan menentukan kegagalan initializer, pembatalan, idempotensi, dan shutdown, kemudian mendasarkan peluncuran pada bukti throughput, p95, RSS, overhead serialisasi, dan kegagalan dengan fallback ke proses."
Kesalahan umum
- Menganggap interpreter berbagi global → state terisolasi dan inisialisasi berulang → buat resource per interpreter.
- Mengirimkan tugas yang terlalu kecil → serialisasi dan penjadwalan mendominasi → buat batch pekerjaan dan ukur overhead.
- Meneruskan objek yang tidak dapat di-pickle → pengiriman gagal saat runtime → gunakan callable yang dapat diimpor dan nilai sederhana.
- Menggunakan interpreter untuk I/O → kompleksitas tidak menambah manfaat CPU → bandingkan thread terlebih dahulu.
- Mengabaikan efek samping saat percobaan ulang (retry) → restart menduplikasi penulisan data → jadikan tugas idempoten atau gunakan commit eksternal.
- Hanya mengukur throughput → regresi memori dan tail latency tersembunyi → lacak RSS, p95, dan shutdown.
Pertanyaan lanjutan
Pertanyaan lanjutan 1: Bagaimana interpreter mencapai paralelisme?
Setiap worker memiliki interpreter dan GIL sendiri, sehingga bytecode Python dapat dieksekusi pada banyak core. Worker tidak berbagi state interpreter biasa.
Pertanyaan lanjutan 2: Kapan proses lebih disukai?
Gunakan proses ketika isolasi ruang alamat yang lebih kuat, pustaka yang kompatibel dengan proses yang sudah ada, atau semantik deployment yang lebih sederhana lebih penting daripada biaya startup dan memori proses.
Pertanyaan lanjutan 3: Apa yang terjadi pada state modul?
Impor dan variabel global modul yang mutable bersifat lokal untuk interpreter. Inisialisasi state yang diperlukan di setiap worker dan jangan pernah bergantung pada singleton yang dibuat di interpreter pengirim.
Pertanyaan lanjutan 4: Bagaimana Anda memilih granularitas tugas?
Tingkatkan ukuran batch hingga serialisasi dan penjadwalan menjadi fraksi kecil dari waktu eksekusi, lalu verifikasi bahwa batch tersebut masih memenuhi persyaratan latensi, pembatalan, dan percobaan ulang.