Topik wawancara representatif

Wawancara system design: Merancang cache build terdistribusi untuk kompilasi jarak jauh

Desain sistemSulit
Tim Redaksi Offer.ccDipublikasikan Diperbarui

Pertanyaan

Perusahaan Anda memiliki 500 pengembang, 20.000 CI job per hari, dan 10.000 build target. Rancang cache build terdistribusi untuk kompilasi jarak jauh yang mendukung hit cepat, pembatalan validitas (invalidation) yang benar, isolasi multi-tenant, garbage collection, serta fallback yang mulus saat cache tidak tersedia.

Konteks dan instruksi

Asumsikan setiap job memanggil sekitar 25 action dan rata-rata output baru berukuran 300 MB. Build yang berulang sering kali berbagi input, tetapi hit yang salah dapat mengirimkan artifak yang dikompilasi dengan toolchain atau secret yang salah. Cache adalah sebuah optimasi: miss atau pemadaman (outage) harus dialihkan kembali ke eksekusi, sedangkan false hit adalah insiden kebenaran sistem (correctness incident). Anda harus memisahkan pencarian hasil action dari penyimpanan artifak yang tidak dapat diubah (immutable), serta menjelaskan bagaimana eksekusi jarak jauh mengubah model kepercayaan dan kapasitas.

Hal yang dinilai oleh pewawancara

  • Apakah Anda memodelkan action key dari setiap input yang memengaruhi hasil deterministik.
  • Apakah Anda memisahkan metadata action yang dapat diubah dari content-addressed store (CAS) yang immutable.
  • Apakah Anda membuat penulisan bersifat atomik, memverifikasi digest, dan mencegah cache poisoning lintas tenant.
  • Apakah kapasitas, garbage collection, observabilitas, fallback saat outage, dan migrasi dijelaskan secara eksplisit.
  • Apakah Anda membedakan cache build dari cache key-value generik: kebenaran (correctness) lebih penting daripada toleransi pembacaan data usang (stale-read).

Pertanyaan klarifikasi yang perlu diajukan

Tanyakan apakah build bersifat hermetik, sistem operasi dan arsitektur apa saja yang didukung, serta apakah eksekusi jarak jauh bersifat wajib atau opsional. Klarifikasi target retensi, batasan tenant dan repositori, ukuran artifak maksimum, target hit-rate yang diharapkan, residensi data, dan apakah secret atau kode sumber privat dapat masuk ke dalam output. Konfirmasikan apakah suatu hasil dapat dibagikan lintas branch, versi toolchain, atau hanya dalam satu tuple commit dan platform. Tanyakan juga apakah CI dapat menulis sementara mesin pengembang berstatus read-only.

Kerangka jawaban 30 detik

Saya akan melakukan hashing pada deskripsi action kanonikal yang berisi perintah, identitas toolchain dan platform, Merkle root dari input yang dideklarasikan, environment yang relevan, dan konfigurasi build. Action cache memetakan key tersebut ke digest output dan metadata hasil; CAS menyimpan blob immutable berdasarkan digest. Reader memverifikasi metadata dan setiap blob sebelum melakukan materialisasi. Eksekusi lokal atau jarak jauh yang berhasil akan mengunggah blob terlebih dahulu dan memublikasikan hasil action di tahap akhir, sehingga pekerjaan parsial tidak dapat menjadi hit. Namespace, penulisan terautentikasi, kuota, dan sandboxing mencegah kebocoran antar-tenant. Kegagalan cache akan mengembalikan miss dan menggunakan eksekusi terkontrol, sementara metrik serta sampel clean rebuild berkala mendeteksi false hit.

Pembahasan mendalam langkah demi langkah

1. Menentukan key dan batasan penyimpanan

Kanonikalisasi perintah action, versi compiler dan linker, platform, input yang dideklarasikan, flag yang relevan, environment yang masuk whitelist, dan lockfile dependensi eksternal. Hash struktur pohon input sebagai Merkle root. Kecualikan secret dan timestamp non-deterministik; jika suatu action tidak hermetik, tandai sebagai non-cacheable atau berikan cakupan yang sengaja dipersempit. Simpan hasil action secara terpisah dari CAS: hasil tersebut berisi nama file output, digest, ukuran, exit code, dan digest stdout/stderr opsional. Objek CAS bersifat immutable dan hanya dialamatkan berdasarkan digest-nya.

2. Merancang alur hit, miss, dan publikasi

Pada saat membaca, arahkan namespace tenant dan action-key ke layanan metadata yang direplikasi, ambil hasilnya, lalu ambil blob CAS yang belum ada secara paralel. Verifikasi ukuran dan digest sebelum mengekspos file. Jika terjadi miss, jalankan secara lokal atau pada worker yang berada dalam sandbox. Unggah blob yang telah diverifikasi dengan operasi digest yang idempoten, lalu lakukan commit pada hasil action dalam satu publikasi bersyarat. Writer yang berjalan konkuren dapat mengunggah blob yang sama, tetapi hanya hasil lengkap dengan semua blob yang direferensikan yang akan terlihat. Blob yang rusak atau ketidakcocokan metadata dianggap sebagai miss disertai peringatan (alert), dan tidak boleh menjadi hit yang berhasil.

3. Menambahkan distribusi, isolasi, dan keamanan

Gunakan consistent hashing atau partition map pada layanan metadata untuk pencarian hasil action, dengan replika di seluruh zona kegagalan. Tempatkan data CAS berukuran besar di object store atau tier blob yang di-shard, dan simpan metadata yang sering diakses (hot) pada penyimpanan berlatensi rendah. Autentikasi setiap permintaan, otorisasi namespace repositori dan tenant, serta atur mesin pengembang menjadi read-only secara default. Enkripsi data dalam transit dan penyimpanan, terapkan kuota per tenant, dan jalankan action jarak jauh di dalam sandbox. Jangan melakukan deduplikasi lintas tenant kecuali jika kebijakan secara eksplisit mengizinkannya; digest saja tidak boleh melewati otorisasi.

4. Merencanakan kapasitas dan manajemen siklus hidup

Beban kerja yang disebutkan adalah 20.000 job/hari × 25 action = 500.000 pencarian/hari, atau sekitar 5,8 permintaan/detik secara rata-rata. Lonjakan beban 20× berkisar 120 permintaan/detik, sebelum pembacaan blob paralel. Jika 10% dari action menghasilkan output baru sebesar 300 MB, ingress yang belum dikompresi adalah 1,5 TB/hari; kompresi dan deduplikasi mengurangi penyimpanan, tetapi desain harus mencadangkan kapasitas objek multi-terabita dan ruang cadangan (headroom) bandwidth. Garbage collection dimulai dari hasil action dan manifes yang aktif, mengikuti referensi digest-nya, menerapkan masa tenggang (grace period), kemudian menggabungkan kuota ukuran dengan kebijakan LRU atau usia. Jangan pernah menghapus blob yang masih dapat dijangkau (reachable), dan tegakkan kuota yang adil per tenant.

5. Memastikan observabilitas untuk kegagalan dan ketepatan data

Perlakukan batas waktu (timeout) cache, kegagalan izin, blob yang hilang, ketidakcocokan digest, dan ketidaktersediaan backend sebagai hasil yang terpisah. Miss dapat dialihkan ke eksekusi; pemadaman berkepanjangan memerlukan kontrol admisi, preferensi cache lokal, dan percobaan ulang (retry) yang dibatasi agar CI tidak mengalami retry storm. Lacak hit rate berdasarkan repositori, kelas action, platform, dan toolchain; latensi pencarian, bandwidth blob, pembatalan unggahan, penggusuran (eviction), kerusakan data, dan penolakan tenant. Lakukan clean rebuild secara berkala dengan menghapus cache lokal, lalu bandingkan log eksekusi atau digest output untuk mendeteksi non-determinisme dan false hit.

Contoh jawaban yang kuat

Saya akan mengekspos dua layanan: indeks action-cache yang terautentikasi dan CAS yang immutable. Action key mencakup perintah kanonikal, identitas compiler dan platform, Merkle root dari input yang dideklarasikan, environment yang masuk whitelist, flag, dan dependensi eksternal yang terkunci. Hit akan mengembalikan digest output; klien memverifikasi dan mengunduh blob tersebut sebelum melakukan materialisasi. Miss akan dieksekusi di dalam sandbox, mengunggah blob secara idempoten, memverifikasinya, dan memublikasikan hasil hanya setelah setiap referensi tersedia. Metadata action direplikasi berdasarkan tenant dan repositori, sedangkan data CAS di-shard atau ditempatkan di object storage lintas zona. Pembacaan dapat dialihkan ke eksekusi saat terjadi kegagalan cache; penulisan dibatasi hanya untuk CI tepercaya, dengan kuota, enkripsi, dan tanpa penggunaan ulang lintas tenant secara default. Saya akan mengalokasikan kapasitas untuk beban puncak sekitar 120 pencarian/detik dan penyimpanan multi-terabita, kemudian memvalidasi hit rate, ketidakcocokan digest, action non-deterministik, concurrent writer, pembaruan toolchain, keterjangkauan GC, dan pemulihan saat outage.

Kesalahan umum

  • Hanya melakukan hashing pada file sumber tanpa menyertakan compiler, flag, platform, environment, atau dependensi yang terkunci.
  • Memperlakukan hasil action dan blob output-nya sebagai satu rekaman yang dapat diubah, sehingga memungkinkan terjadinya publikasi parsial.
  • Membagikan digest lintas tenant tanpa memeriksa otorisasi namespace.
  • Merancang kondisi cache tidak tersedia sebagai kegagalan build total, alih-alih sebagai miss yang terkontrol dan dialihkan ke fallback.
  • Menggunakan satu LRU global dan menghapus blob tanpa menelusuri referensi dari hasil action yang aktif.
  • Menganggap volume stdout atau stderr sebagai metrik cache hit; strategi eksekusi dan penghitung hit eksplisit tetap diperlukan.
  • Mengklaim hit rate yang tinggi tanpa menguji clean build, reproduktifitas lintas mesin, dan action yang non-deterministik.

Pertanyaan lanjutan dan jawaban

Bagaimana jika compiler toolchain diperbarui tetapi command line tidak berubah?

Identitas toolchain harus menjadi bagian dari action key, biasanya melalui digest yang di-pin atau image eksekusi berversi. Proses migrasi dapat menerapkan dual-read pada namespace lama untuk kebutuhan rollback, tetapi harus menulis ke namespace baru dan mengukur miss yang terjadi. Jangan pernah menggunakan kembali output lama hanya karena kode sumber dan flag-nya cocok.

Bagaimana cara memulihkan sistem setelah terjadi cache poisoning?

Hentikan penulisan yang tidak tepercaya, isolasi (karantina) namespace yang terdampak, dan identifikasi hasil action yang buruk beserta blob yang dapat dijangkau dari log audit. Batalkan validitas indeks action, bangun ulang output tepercaya, dan isi kembali data dari eksekusi yang telah diverifikasi. Jadikan penggunaan cache sebagai opsi opsional selama pemulihan dan simpan bukti untuk tinjauan insiden.

Bagaimana jika sebuah build action mengunduh dependensi yang belum di-pin saat eksekusi?

Action tersebut bersifat non-hermetik: tandai sebagai non-cacheable hingga dependensi tersebut di-pin dan byte yang diunduh terwakili di dalam penutupan input (input closure). TTL pendek yang dicakupkan pada repositori dapat dijadikan pengecualian darurat eksplisit, tetapi tidak boleh dianggap sebagai penggunaan ulang yang deterministik.

Bagaimana Anda memigrasikan dari cache lokal tanpa menyebabkan gangguan pada CI?

Jalankan pembacaan jarak jauh dalam mode shadow, bandingkan key serta digest output lokal dan jarak jauh, lalu aktifkan remote hit untuk sebagian kecil kelompok repositori. Pertahankan eksekusi lokal dan fallback ke cache lokal, batasi penulisan hanya untuk CI tepercaya, dan perluas implementasi hanya setelah pemeriksaan hit rate, latensi, dan false hit terpenuhi.

Sumber publik

Pertanyaan terkait

Alat wawancara terkait

Gunakan Jawab untuk jawaban desain sistem

Perjelas persyaratan terlebih dahulu, lalu lanjutkan dengan skala, arsitektur, pilihan komponen, dan trade-off.

Lihat alat