Topik wawancara representatif

Wawancara Umum: Jelaskan Structured Concurrency dan Rancang Batasan Pembatalan

UmumSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Jelaskan structured concurrency dan rancang masa hidup tugas untuk layanan yang mengambil profil pengguna, pesanan, dan rekomendasi secara paralel. Cakup hubungan induk-anak, penyebaran pembatalan, kegagalan parsial, batas waktu, pembersihan sumber daya, dan observabilitas tanpa mengikat jawaban pada satu bahasa pemrograman saja.

Prompt dan konteks

Pewawancara memberi Anda sebuah request handler yang menjalankan tiga operasi terkait secara paralel: mengambil profil pengguna, mengambil pesanan, dan membuat rekomendasi. Klien terputus, tugas anak kritis gagal, atau tenggat waktu (deadline) keseluruhan habis. Sistem tidak boleh membiarkan pekerjaan yatim (orphan work) terus berjalan setelah permintaan berakhir. Jelaskan structured concurrency, lalu petakan ke dalam task groups, coroutine scopes, atau structured task scopes tanpa membuat jawabannya bergantung pada satu API tertentu.

Pertanyaan ini cocok untuk peran backend tingkat menengah dan senior, platform, mobile, serta infrastruktur lintas bahasa. Tujuannya adalah penalaran siklus hidup: masuk, keluar, kebijakan kegagalan, dan kepemilikan sumber daya.

Apa yang sedang diuji oleh pewawancara

Pewawancara ingin mengetahui apakah Anda memperlakukan pekerjaan konkuren sebagai bagian dari tugas induk alih-alih mengirimkan pekerjaan ke pool global lalu langsung me-return. Jawaban yang kuat menyatakan bahwa tugas anak tidak hidup lebih lama dari scope-nya, pembatalan induk menyebar ke bawah, kegagalan dapat berupa fail-fast atau parsial berdasarkan nilai bisnis, serta proses penggabungan (joining) dan pembersihan terjadi di dalam batas yang terlihat.

Jawaban yang lemah hanya mengatakan "gunakan async/await secara paralel." Jawaban yang kuat membedakan structured concurrency dari detached futures dan menjelaskan kapan pekerjaan harus dialihkan ke antrean persisten (durable queue) terpisah dengan pemilik baru.

Pertanyaan untuk diklarifikasi terlebih dahulu

  • Apakah ketiga tugas anak harus berhasil, atau profil dan pesanan bersifat wajib sementara rekomendasi bersifat opsional?
  • Apakah batas waktu tersebut merupakan hard deadline permintaan, atau server dapat menyelesaikannya di bawah anggaran lunak (soft budget) terpisah?
  • Apakah tugas anak hanya melakukan I/O yang dapat dibatalkan, atau dapatkah mereka memicu efek samping eksternal yang tidak dapat dibatalkan?
  • Setelah klien terputus, bisakah pekerjaan dipindahkan ke notifikasi asinkron atau background job?
  • Apakah kegagalan harus mengembalikan satu error, hasil parsial, atau respons yang terdegradasi secara eksplisit?

Jawaban-jawaban ini mengubah batas scope: pekerjaan permintaan yang singkat tetap berada di dalam scope induk; pekerjaan yang berlanjut setelah permintaan membutuhkan transfer kepemilikan yang eksplisit.

Jawaban 30 detik

Saya memperlakukan permintaan sebagai tugas induk dan tiga pembacaan paralel sebagai tugas anak. Induk hanya keluar setelah anak selesai, gagal, atau dibatalkan dan dibersihkan. Kegagalan profil atau pesanan membatalkan saudara-saudaranya (siblings) dan mengembalikan error yang dapat dicoba lagi (retryable); kegagalan rekomendasi mengembalikan respons terdegradasi yang eksplisit. Pemutusan klien dan batas waktu menyebarkan pembatalan ke bawah, dan setiap anak menutup koneksi serta handle pada jalur pembersihannya. Jika pekerjaan harus berlanjut setelah permintaan, saya menulisnya ke durable queue dan memulai siklus hidup baru alih-alih melepaskan background future yang tidak terikat. Saya memverifikasi pembatalan, batas waktu, kegagalan parsial, dan metrik kebocoran dengan fault injection.

Jawaban langkah demi langkah

Langkah 1: Gambar pohon tugas (task tree)

Jadikan request handler sebagai root. Profil, pesanan, dan rekomendasi adalah anak langsung. Anak mewarisi batas waktu induk, konteks pelacakan (tracing), dan sinyal pembatalan. Induk memiliki kontrol atas proses menunggu, komposisi error, dan penutupan scope; seorang anak tidak boleh menyerahkan pekerjaan ke global executor dan membiarkan induk keluar tanpa transfer kepemilikan.

Invarian utamanya dapat diperiksa: ketika induk keluar, setiap anak telah selesai, dibatalkan, atau secara eksplisit ditransfer ke pemilik tercatat lainnya. Tanpa invarian tersebut, kebocoran thread, penulisan duplikat, dan tail latency yang tidak dapat dijelaskan akan tetap tersembunyi.

Langkah 2: Pilih kebijakan kegagalan dan hasil parsial

Klasifikasikan berdasarkan nilai bisnis. Profil dan pesanan diperlukan untuk tampilan checkout, sehingga kegagalan salah satunya membatalkan tugas selevel (siblings) dan mengembalikan error yang dapat dicoba lagi. Rekomendasi adalah peningkatan (enhancement), sehingga kegagalan mengembalikan respons tanpa rekomendasi dan mencatat degradasi tersebut. Jangan biarkan kegagalan bernilai rendah merusak seluruh permintaan, dan jangan menyamarkan data kritis yang hilang sebagai objek kosong.

Pseudocode dapat mengekspresikan kebijakan ini tanpa memilih bahasa pemrograman tertentu:

text
within request_scope(deadline):
  profile = child(fetch_profile)
  orders = child(fetch_orders)
  recommendations = child(fetch_recommendations)
  wait(profile, orders)
  if profile.failed or orders.failed:
    cancel_all_children()
    return retryable_error
  return compose(profile, orders, recommendations.or_empty)

Langkah 3: Pastikan pembatalan mencapai batasan sumber daya

Pembatalan bukan sekadar boolean pada objek tugas. Klien jaringan, driver database, dan operasi baca file harus memantau sinyal tersebut; proses menunggu harus dapat diinterupsi; loop percobaan ulang (retry loops) harus memeriksa ulang batas waktu sebelum mencoba lagi. Untuk efek samping yang tidak dapat dibatalkan, seperti pembayaran yang sudah diterima atau email yang sudah terkirim, pembatalan dapat menghentikan langkah-langkah selanjutnya tetapi tidak dapat mengklaim bahwa efek samping yang telah selesai telah di-rollback.

Ketika klien terputus, titik masuk membatalkan root. Root menyebarkan pembatalan, dan anak-anak menutup response body, koneksi, langganan, dan file sementara pada jalur pembersihannya. Pembersihan itu sendiri membutuhkan batasan waktu agar "menunggu pembersihan" tidak memblokir penutupan permintaan selamanya.

Langkah 4: Pisahkan timeout, pembatalan, dan kegagalan

Timeout berarti anggaran waktu telah habis. Pembatalan berarti upstream tidak lagi membutuhkan hasilnya. Kegagalan berarti tugas tidak dapat diselesaikan. Ketiganya bisa bertepatan, tetapi log dan status pengguna tidak boleh disatukan menjadi satu kode 500 saja. Catat penyebab asli, pemicu, dan jalur tugas. Biasanya jangan mencoba ulang (retry) pada pemutusan klien; izinkan satu retry singkat setelah timeout dependensi hanya jika sisa anggaran waktu memungkinkan; kirim error bisnis melalui kebijakan degradasi yang eksplisit.

Jangan menempatkan retry buta atau sleep di luar scope. Tindakan tersebut menghabiskan anggaran yang sudah kedaluwarsa dan terus menghasilkan beban setelah induk selesai me-return.

Langkah 5: Buktikan bahwa struktur tetap terjaga

Uji penyelesaian normal, kegagalan anak kritis, kegagalan anak opsional, pembatalan induk, habisnya batas waktu, dan pembatalan sebelum anak dimulai. Setiap pengujian memastikan bahwa tugas anak yang aktif kembali ke nol, koneksi downstream ditutup, jejak trace menunjukkan hubungan induk-anak, efek samping tidak terduplikasi, dan klasifikasi error cocok dengan status yang terlihat oleh pengguna.

Lacak tugas aktif, latensi penyebaran pembatalan, tingkat timeout batas waktu, pengecualian pada anak, durasi pembersihan, tugas yang masih berjalan setelah pembatalan, dan fan-out maksimum per permintaan. Respons yang sukses saja tidak membuktikan bahwa structured concurrency telah diterapkan.

Contoh jawaban berkualitas tinggi

"Saya akan memperlakukan satu permintaan sebagai tugas induk dan menempatkan ketiga operasi pembacaan dalam satu scope yang terikat batas waktu. Induk membuat, menunggu, dan menutup anak-anaknya; tidak ada anak yang boleh hidup lebih lama dari scope tersebut. Profil dan pesanan adalah dependensi kritis, jadi kegagalan salah satunya akan membatalkan siblings dan mengembalikan error yang dapat dicoba lagi. Rekomendasi bersifat opsional; jika gagal, saya mengembalikan status rekomendasi kosong yang eksplisit dan mencatat degradasi tersebut.

Pemutusan klien, timeout induk, atau pembatalan dari upstream merambat ke bawah pohon tugas. Setiap panggilan I/O menggunakan antarmuka yang dapat dibatalkan, retry memeriksa sisa anggaran waktu, dan pembersihan menutup koneksi serta langganan. Saya tidak berpura-pura bahwa efek samping eksternal dapat di-rollback setelah itu terjadi. Jika pekerjaan harus berlanjut setelah permintaan selesai, saya pertama-tama menulisnya ke durable queue dan membiarkan konsumen membuat pohon tugas baru.

Saya akan menginjeksikan kegagalan kritis, kegagalan opsional, race condition pembatalan, dan timeout pembersihan, lalu mengamati tugas aktif, latensi pembatalan, kebocoran sumber daya, dan jejak trace induk-anak. Bagian yang penting adalah siklus hidup dan kepemilikan, bukan API Java, Kotlin, atau Swift tertentu."

Pola kegagalan umum

  • Menyamakan structured concurrency dengan eksekusi paralel → Anda hanya menjelaskan memulai tugas bersamaan dan mengabaikan masa hidupnya → Tunjukkan batasan scope, join, pembatalan, dan pembersihan.
  • Mengirim ke global executor lalu me-return → Pekerjaan dapat mengakses sumber daya permintaan setelah induknya hilang → Pertahankan di dalam scope permintaan atau transfer secara eksplisit ke antrean persisten (durable queue).
  • Memperlakukan pembatalan sebagai penghentian paksa (forced kill) → Efek eksternal mungkin sudah selesai dan sumber daya mungkin tidak tertutup secara otomatis → Nyatakan pembatalan kooperatif, pekerjaan yang tidak dapat dibatalkan, dan kepemilikan pembersihan.
  • Mengembalikan hasil kosong untuk setiap kegagalan → Data kritis yang hilang menjadi tersembunyi dari pemanggil → Definisikan aturan fail-fast dan hasil parsial berdasarkan tingkat kekritisan bisnis.
  • Hanya menguji skenario sukses → Kondisi race pada pembatalan dan orphan work tetap tidak terlihat → Injeksikan pemutusan klien, batas waktu, kegagalan tugas selevel, dan pembatalan berulang, lalu periksa apakah pekerjaan aktif benar-benar mencapai nol.

Pertanyaan lanjutan

Pertanyaan lanjutan 1: Pembuatan rekomendasi memakan waktu beberapa detik, tetapi permintaan sudah berakhir. Apa yang Anda lakukan?

Pertama, tanyakan apakah pengguna masih membutuhkan hasil tersebut. Jika hanya untuk halaman saat ini, batalkan bersama dengan induknya. Jika bisnis menginginkan pembuatan asinkron, simpan input dan kunci idempoten (idempotency key), lalu biarkan konsumen membuat scope baru. Konsumen tersebut memiliki batas waktu, kebijakan retry, dan peringatan baru; ia tidak dapat meminjam konteks permintaan yang sudah selesai.

Pertanyaan lanjutan 2: Mengapa tidak membiarkan anak-anak lainnya selesai setelah satu anak gagal?

Anda bisa melakukannya, jika sisa pekerjaan memiliki nilai dan sesuai dengan anggaran waktu. Melanjutkan pencarian pesanan setelah data profil kritis gagal biasanya membuang-buang koneksi dan kapasitas downstream; menyelesaikan pembaruan cache independen atau pencatatan audit mungkin masuk akal. Nyatakan kondisi untuk pembatalan alih-alih memperlakukan fail-fast sebagai aturan universal.

Pertanyaan lanjutan 3: Sinyal pembatalan telah dikirim, tetapi kueri database masih berjalan. Bagaimana Anda menanganinya?

Periksa apakah driver mendukung pembatalan dan mengembalikan koneksi. Jika tidak, terapkan timeout kueri independen, isolasi pool koneksi, dan lindungi sistem dari penggunaan hasil yang terlambat datang. Ukur waktu dari pembatalan hingga pelepasan sumber daya; melewati ambang batas harus memicu degradasi, isolasi, atau peringatan. Kueri yang tidak dapat dibatalkan tidak boleh menahan kapasitas tingkat permintaan tanpa batas.

Pertanyaan lanjutan 4: Apakah structured concurrency menggantikan semua thread pool dan message queue?

Tidak. Ini cocok untuk pekerjaan berumur pendek dengan hubungan induk-anak yang jelas serta pembatalan dan pembersihan bersama. Pekerjaan lintas permintaan, pekerjaan terjadwal, dan retry persisten masih memerlukan antrean atau alur kerja (workflow). Pool bersama dapat menyediakan sumber daya eksekusi, tetapi scope yang mengirimkan tugas harus menentukan siapa yang menunggu, siapa yang membatalkan, dan siapa yang memiliki hasilnya.

Sumber publik

Pertanyaan terkait