2.20

View in English

2.20 Penanganan galat dan pola ketahanan

Tinjauan dan motivasi

Setiap program yang Anda tulis akan gagal. Disk penuh, jaringan terputus, layanan timeout, pemanggil mengoper sampah, dependensi mengembalikan sesuatu yang tidak pernah disebut dokumentasi. Pertanyaannya bukan apakah kegagalan terjadi; melainkan apakah kode Anda menemui kegagalan itu dengan rencana atau dengan kejutan. Penanganan galat adalah keterampilan memutuskan, baris demi baris dan fungsi demi fungsi, apa yang dilakukan kode Anda ketika dunia tidak bekerja sama. Ini bagian konstruksi yang paling tidak glamor dan bagian yang menentukan, lebih dari fitur apa pun, apakah orang memercayai sistem Anda.

Bab ini membahas ketahanan pada tingkat kode dan komponen: pilihan di dalam fungsi, modul, atau API. Ia melengkapi bab 3.5, yang membahas ketahanan pada tingkat sistem (penyeimbangan beban, replikasi, failover lintas layanan). Bab 3.5 menjaga seluruh platform tetap berdiri ketika satu wilayah gelap; bab ini menjaga satu permintaan agar tidak merusak data Anda atau lenyap tanpa jejak. Keduanya saling memperkuat. Circuit breaker dalam arsitektur Anda berarti sedikit jika kode di baliknya menelan exception, dan fungsi defensif tidak dapat menyelamatkan Anda jika sistem di sekelilingnya tidak memiliki redundansi. Bab ini juga membangun di atas bab 2.9 (konstruksi perangkat lunak), di mana menangani galat adalah satu disiplin di antara banyak; di sini ia menjadi seluruh pokok bahasan.

Bagi tim besar, konsistensi adalah hadiahnya. Ketika ratusan insinyur menangani galat dengan ratusan cara berbeda, setiap layanan menjadi teka-teki dan setiap insiden menjadi penggalian. Dalam lingkungan enterprise, inkonsistensi itu menaikkan biaya setiap audit dan setiap integrasi. Dalam sistem pemerintah dan taruhan tinggi lainnya, taruhannya lebih tajam: kebenaran, kegagalan aman, dan jejak audit yang jelas bukan fitur yang Anda tambah belakangan melainkan sifat yang harus dimiliki sistem sejak commit pertama. Sistem tunjangan yang diam-diam salah menghitung, atau sistem catatan yang kehilangan kegagalan tanpa mencatatnya, bukan sekadar bermasalah. Ia tak tepercaya dengan cara yang mengikis institusi di baliknya.

Prinsip utama

  • Bedakan galat, kesalahan (fault), dan kegagalan, dan tangani masing-masing pada lapisan yang tepat.
  • Pilih fail-fast atau fail-safe dengan sengaja, per konteks, tidak pernah secara kebetulan.
  • Buat kontrak penanganan galat setiap fungsi dan API eksplisit dan jujur.
  • Validasi di batas; percaya di dalamnya; bertahan tanpa paranoia.
  • Jangan pernah menelan galat secara diam-diam; munculkan, bungkus, atau tangani dengan sengaja.
  • Buat retry aman dengan idempotensi, timeout, backoff, dan jitter.
  • Beri jalur galat perhatian desain yang sama seperti jalur bahagia.

Rekomendasi

Bedakan galat, kesalahan, dan kegagalan

Kosakata yang ceroboh menghasilkan penanganan yang ceroboh, jadi mulailah dengan istilah yang jelas. Kesalahan (fault) adalah cacat dalam sistem: bug, konfigurasi buruk, dependensi yang mati. Galat (error) adalah keadaan internal yang tidak benar yang dihasilkan kesalahan: null di tempat seharusnya ada nilai, saldo yang tidak lagi cocok. Kegagalan (failure) adalah apa yang dilihat pengamat luar: permintaan mengembalikan jawaban keliru, atau tidak ada jawaban. Satu kesalahan dapat menyebabkan banyak galat, dan banyak galat dapat tertangkap sebelum ada yang menjadi kegagalan yang terlihat. Inti penanganan galat adalah memutus rantai itu, menangkap galat sebelum menjadi kegagalan yang dialami pengguna atau auditor.

Kosakata ini juga memberi tahu di mana bertindak. Kesalahan ditangani dalam tinjauan, pengujian, dan konfigurasi. Galat ditangani saat runtime oleh pola dalam bab ini. Kegagalan ditangani oleh observabilitas (bab 9.2) dan oleh ketahanan tingkat sistem bab 3.5. Ketika tim Anda berbagi kata-kata ini, tinjauan insiden menjadi lebih tajam: Anda dapat mengatakan persis di mana rantai seharusnya diputus dan tidak, alih-alih berdebat tentang apa “bug”-nya.

Pilih fail-fast atau fail-safe per konteks

Fail-fast berarti berhenti begitu ada yang salah, menolak melanjutkan pada keadaan buruk agar masalah muncul dengan lantang dan dekat dengan penyebabnya. Fail-safe berarti merosot ke keadaan yang dikenal dan tidak berbahaya dan terus melayani apa yang dapat dilayani dengan aman. Tak satu pun benar secara universal, dan keterampilannya adalah memilih per konteks. Selama pengembangan dan pada batas internal, fail-fast adalah teman Anda: program yang berhenti pada invarian yang dilanggar memberi Anda stack trace pendek alih-alih misteri panjang. Di produksi, di tepi sistem yang menghadap pengguna, fail-safe sering menang: panel rekomendasi yang tidak mengembalikan apa-apa lebih baik daripada halaman checkout yang tidak mau dimuat.

Putuskan ini dengan sengaja untuk setiap batas dan tuliskan keputusannya. Komponen kendali penerbangan atau perangkat medis gagal aman ke keadaan terdefinisi karena melanjutkan pada data rusak dapat mencelakai seseorang. Pembukuan buku besar gagal cepat karena membukukan entri keliru lebih buruk daripada tidak membukukan. Pasangan yang salah berbahaya ke dua arah: fail-safe di tempat Anda butuh fail-fast menyembunyikan kerusakan, dan fail-fast di tempat Anda butuh fail-safe mengubah gangguan kosmetik menjadi pemadaman.

Pilih mekanisme pensinyalan galat dan pakai secara konsisten

Bahasa memberi Anda dua cara luas untuk menandakan ada yang salah. Penanganan exception melempar objek naik tumpukan panggilan sampai penangan menangkapnya, memisahkan jalur galat dari logika utama. Alternatifnya adalah nilai galat eksplisit: fungsi mengembalikan hasil sekaligus galat, dan pemanggil harus memeriksa keduanya. Banyak bahasa modern memformalkan yang terakhir dengan tipe Result, sering disebut Result atau Either, yang memaksa pemanggil membuka sukses atau kegagalan sebelum memakai nilai. Setiap pendekatan punya biaya. Exception menjaga jalur bahagia bersih tetapi dapat menyembunyikan alur kendali dan menggoda pengembang ke blok tangkap-semua yang menghapus informasi. Hasil eksplisit membuat setiap kegagalan terlihat dalam tanda tangan tipe tetapi menambah upacara dan dapat diabaikan jika bahasa tidak memaksa pemeriksaan.

Jawaban yang tepat kurang soal mekanisme mana daripada konsistensi dan kejujuran. Pilih idiom yang disukai bahasa dan ekosistem Anda, dan terapkan secara seragam di seluruh layanan agar pembaca selalu tahu bagaimana kegagalan berjalan. Sisakan exception untuk kondisi yang benar-benar luar biasa, bukan alur kendali biasa seperti “pengguna tidak ditemukan,” yang lebih baik dimodelkan sebagai hasil normal. Apa pun yang Anda pilih, jangan pernah biarkan kegagalan menjadi tak terlihat: nilai galat yang tidak diperiksa sama berbahayanya dengan blok catch kosong. Dalam basis kode besar, konvensi tertulis ditambah linter yang menandai galat yang diabaikan mengalahkan preferensi individu mana pun.

Buat kontrak penanganan galat eksplisit

Setiap fungsi dan setiap API memiliki kontrak penanganan galat, entah ada yang menuliskannya atau tidak. Ia menjawab: apa yang bisa salah di sini, bagaimana Anda akan mengetahuinya, dan apa yang dijamin tentang status ketika itu terjadi? Buat kontrak itu eksplisit. Dokumentasikan galat mana yang dapat dikembalikan atau dilempar fungsi, bedakan galat yang dapat dipulihkan (pemanggil dapat dengan wajar mencoba ulang atau beralih ke cadangan) dari yang tidak dapat dipulihkan (pemanggil tidak dapat memperbaikinya dan harus meneruskan atau membatalkan), dan nyatakan apakah fungsi membiarkan status tidak berubah saat gagal. Sifat terakhir ini, kadang disebut jaminan exception kuat, berarti panggilan yang gagal seolah tidak pernah terjadi, yang persis memungkinkan pemanggil mencoba ulang dengan aman.

Untuk API publik atau lintas tim, kontrak ini bagian dari antarmuka, senyata tipe parameter. Rancang taksonomi galat yang kecil dan stabil: himpunan kategori terbatas seperti galat validasi, tidak ditemukan, konflik, tidak berwenang, dependensi-tidak-tersedia, dan galat internal. Pemanggil kemudian dapat bercabang menurut kategori tanpa mengurai string. Taksonomi yang jelas membuat penanganan galat dapat dikomposisikan di banyak layanan, dan membuat kegagalan dapat diaudit, karena setiap kegagalan terpetakan ke jenis yang dikenal dan bernama.

Validasi di batas dan bertahan tanpa paranoia

Perlakukan data yang melintasi batas kepercayaan (permintaan jaringan, berkas, masukan pengguna, pesan dari layanan lain) sebagai bermusuhan sampai divalidasi, dan validasi di batas, sekali, dengan menyeluruh. Ini pemrograman defensif yang diterapkan dengan penilaian. Di dalam modul yang masukannya sudah Anda validasi, pemeriksaan redundan di setiap baris menyembunyikan logika dan menekan kegagalan yang justru ingin Anda lihat. Disiplinnya: bertahan keras di tepi, percaya di dalamnya. Validasi struktur, rentang, dan invarian di tempat data masuk, ubah menjadi tipe yang membuat keadaan ilegal tidak dapat direpresentasikan, dan biarkan kode interior mengasumsikan bekerja dengan data bersih.

Paranoia punya biaya nyata. Kode yang diselimuti pemeriksaan null dan cabang defensif lebih sulit dibaca, dan lebih buruk, sering mengubah kegagalan yang jelas menjadi angkat bahu yang diam, mengembalikan bawaan di tempat seharusnya membunyikan alarm. Sikap defensif yang menutupi bug bukan keamanan; itu penundaan.

Buat retry aman, terbatas, dan sopan

Banyak kesalahan bersifat sementara: gangguan jaringan sesaat, layanan yang restart, perebutan lock singkat. Dalam sistem terdistribusi apa pun (bab 3.3), kegagalan parsial ini adalah kasus normal alih-alih pengecualian. Mencoba ulang adalah respons alami, tetapi loop retry naif adalah senjata berisi peluru. Pertama, buat operasi yang Anda coba ulang idempoten, artinya melakukannya dua kali berefek sama dengan melakukannya sekali. Tanpa idempotensi, retry setelah timeout dapat menagih kartu dua kali atau membuat dua catatan, karena Anda tidak dapat mengatakan apakah percobaan pertama gagal atau hanya konfirmasinya yang hilang. Gunakan kunci idempotensi untuk tulis agar penerima dapat mengenali dan menghapus duplikat dari pengulangan.

Kedua, pasang timeout pada setiap panggilan jarak jauh agar dependensi yang menggantung tidak menggantungkan Anda. Ketiga, beri jarak retry dengan exponential backoff, menggandakan waktu tunggu setelah setiap percobaan, dan tambahkan jitter (penundaan acak kecil) agar seribu klien yang pulih sekaligus tidak tersinkron menjadi stampede yang menjatuhkan kembali layanan yang sedang pulih. Keempat, batasi jumlah retry dan total waktu, lalu menyerah dengan anggun. Retry tanpa batas, backoff, jitter, dan idempotensi adalah salah satu cara paling umum gangguan kecil menjadi pemadaman yang ditimbulkan sendiri.

Tambahkan circuit breaker, bulkhead, dan degradasi anggun dalam kode

Ketika dependensi benar-benar mati, mencobanya ulang hanya membuang upaya dan memperdalam lubang. Circuit breaker mengawasi tingkat kegagalan panggilan ke dependensi dan, begitu kegagalan melewati ambang, “terbuka” untuk gagal seketika selama masa pendinginan alih-alih menunggu panggilan yang ditakdirkan. Setelah pendinginan ia meloloskan panggilan percobaan dan menutup lagi jika dependensi telah pulih. Ini melindungi pemanggil Anda (kegagalan cepat dan dapat diprediksi alih-alih timeout menumpuk) dan dependensi yang kesulitan (ruang bernapas untuk pulih). Pola bulkhead, dinamai menurut sekat kedap air kapal, mengisolasi sumber daya agar satu dependensi jenuh tidak dapat memakan setiap thread atau koneksi dan menenggelamkan seluruh proses; Anda memberi setiap dependensi kolam terbatasnya sendiri.

Pola ini berpasangan dengan degradasi anggun pada tingkat kode: ketika dependensi non-esensial tidak tersedia, kembalikan hasil yang berkurang tetapi berguna alih-alih galat. Tampilkan data cache dengan catatan kebasian, sembunyikan panel personalisasi, antrekan tulisan untuk nanti. Ini pelengkap lokal dari ketahanan tingkat sistem bab 3.5: arsitektur menyediakan redundansi lintas mesin, dan kode Anda menyediakan perilaku waras ketika satu bagian hilang.

Bungkus galat dengan konteks dan jangan pernah menelannya

Galat yang berbunyi “connection refused” sepuluh lapisan di atas tempat ia terjadi nyaris tak berguna. Saat galat merambat, bungkus dengan konteks: apa yang sedang Anda coba lakukan, entitas atau permintaan mana, dependensi mana, sambil mempertahankan penyebab asli agar akar tidak hilang. Bahasa dan pustaka yang baik mendukung perantaian galat ini secara langsung. Tujuannya adalah satu baris log memberi tahu insinyur on-call apa yang gagal, selama operasi apa, untuk masukan mana. Ini bahan mentah observabilitas bab 9.2 dan debugging bab 2.15.

Dosa utama adalah menelan galat: blok catch kosong, nilai kembali yang diabaikan, catch yang mencatat pada tingkat debug lalu melanjutkan seolah tidak terjadi apa-apa. Galat yang ditelan tidak lenyap; ia muncul kembali kelak sebagai data rusak atau cacat yang tak terjelaskan, kini terlepas dari penyebabnya. Setiap galat harus menemui salah satu dari tiga nasib: tangani (pulih atau merosot), bungkus dan teruskan, atau, di puncak tumpukan, catat dengan konteks penuh dan gagal. Jika Anda menangkap galat dan tidak melakukan satu pun dari ini, Anda telah memilih menyembunyikan insiden masa depan dari diri Anda di masa depan.

Trade-off: kelebihan dan kekurangan

PendekatanKelebihanKekurangan
ExceptionJalur bahagia bersih; sulit diabaikan bila tak diperiksaAlur kendali tersembunyi; menggoda penghapusan tangkap-semua
Nilai galat eksplisit / tipe ResultKegagalan terlihat dalam tanda tangan; memaksa penangananLebih banyak upacara; dapat diabaikan tanpa penegakan
Fail-fastMemunculkan bug dengan lantang, dekat dengan penyebabPengalaman pengguna buruk bila dipakai di tepi
Fail-safeTerus melayani; melindungi pengguna dan dataDapat menutupi kerusakan bila dipakai di tempat Anda butuh fail-fast
Retry dengan backoffMelewati kesalahan sementara secara otomatisMemperkuat beban dan tulis ganda tanpa idempotensi
Circuit breakerKegagalan cepat; memberi dependensi waktu pulihStatus tambahan dan penyetelan; dapat menutupi masalah persisten
Validasi defensif di batasMenangkap data buruk lebih awal, sekali, dengan lantangBila berlebihan, mengotori logika dan menyembunyikan kegagalan nyata

Ketegangan pusatnya adalah antara visibilitas dan derau. Tangani galat terlalu diam dan Anda menyembunyikan masalah sampai mahal; tangani terlalu lantang dan di mana-mana, dan Anda menenggelamkan sinyal dalam upacara serta menutupi kegagalan yang penting. Selesaikan menurut lokasi dan maksud. Lantang dan ketat di batas, tempat data buruk dan kegagalan dependensi masuk. Diam dan percaya di interior, tempat masukan sudah bersih. Putuskan fail-fast versus fail-safe per batas dan tuliskan. Tujuannya adalah kode di mana setiap kegagalan punya tepat satu pemilik yang jelas dan satu nasib yang jelas, dan tidak ada yang jatuh diam-diam di antara celah.

Pertanyaan untuk didiskusikan dengan tim Anda

  1. Apakah kita punya satu taksonomi galat dan konvensi penanganan bersama di seluruh layanan kita, atau setiap tim berimprovisasi? Pada tim besar ini adalah beda antara kegagalan yang dapat dikomposisikan dan yang membingungkan. Ketika satu layanan mengembalikan HTTP 500 untuk masalah validasi, yang lain melempar exception bertipe, dan yang ketiga mengembalikan null, setiap integrasi menjadi negosiasi dan setiap insiden menjadi latihan penerjemahan. Bawa contoh kegagalan logis yang sama, katakanlah “catatan tidak ditemukan,” seperti yang muncul di tiga layanan Anda, dan lihat betapa berbedanya mereka memberi sinyal. Jawabannya harus menjadi standar tertulis: himpunan kategori galat terbatas, cara konsisten menandakannya, dan linter atau daftar periksa tinjauan yang menegakkannya. Konsistensi di sini membayar dalam setiap integrasi, audit, dan giliran on-call mendatang.

  2. Untuk setiap batas kritis, apakah kita memilih fail-fast atau fail-safe dengan sengaja, dan apakah kode cocok dengan pilihan itu? Kebanyakan tim belum pernah membuat keputusan ini secara eksplisit, artinya keputusan itu dibuat untuk mereka oleh siapa pun yang menulis kode lebih dulu, dan tidak konsisten. Pertimbangan yang bersaing itu nyata: gagal aman menjaga pengguna terlayani tetapi dapat membiarkan kerusakan menyebar, sementara gagal cepat melindungi data tetapi dapat mengubah pemadaman dependensi kecil menjadi kegagalan yang terlihat. Bawa riwayat insiden Anda dan tanyakan, untuk beberapa yang terburuk, apakah kode gagal seperti yang akan Anda pilih jika ditanya di muka. Bukti yang Anda inginkan adalah peta batas Anda dengan label sengaja pada masing-masing, terutama di mana pun uang, keselamatan, atau catatan warga terlibat. Di mana label dan kode tidak sepakat, Anda telah menemukan perbaikan berikutnya.

  3. Kapan terakhir kali kita menjalankan jalur galat dengan sengaja, dan apakah ia berperilaku seperti yang dirancang? Jalur galat biasanya kode paling sedikit teruji yang Anda miliki, namun di situlah kepercayaan dimenangkan atau hilang, dan “kami gagal dengan aman” adalah klaim yang tidak dapat Anda dukung jika Anda belum pernah melihatnya terjadi. Loop retry tanpa idempotensi, circuit breaker dengan ambang keliru, exception tertelan di cabang yang jarang tercapai: ini bersembunyi sampai insiden nyata menemukannya untuk Anda. Bawa hasil menyuntikkan kegagalan dengan sengaja (dependensi yang dimatikan, timeout yang diinduksi, payload cacat) ke lingkungan realistis. Tindakan yang menyusul adalah menjadikan injeksi kegagalan rutin, agar pemulihan, degradasi, dan perilaku gagal-aman diverifikasi terus-menerus alih-alih diharapkan. Jalur galat apa pun yang belum pernah Anda picu adalah janji yang belum Anda uji.

  4. Operasi tulis kita yang mana yang idempoten, dan di mana retry setelah konfirmasi yang hilang akan menduplikasi efek dunia nyata seperti pembayaran atau catatan? Mencoba ulang adalah refleks ketahanan paling umum dan, bila dilakukan ceroboh, cara paling umum gangguan sementara berubah menjadi uang atau data ganda. Pada tim besar, logika retry sering hidup di klien bersama, middleware, dan layanan individual sekaligus, sehingga satu tulis dapat dicoba ulang di beberapa lapisan tanpa ada yang memiliki perilaku totalnya. Tarikan yang bersaing adalah bahwa kunci idempotensi, deduplikasi, dan hasil permintaan tersimpan menambah penyimpanan dan kode, dan tim di bawah tekanan pengiriman melewatkannya untuk tulis yang keliru dianggap aman. Bawa inventaris tulis Anda yang terlihat secara eksternal, masing-masing ditandai apakah membawa kunci idempotensi dan bagaimana penerima mengenali dan menghapus duplikat dari pengulangan. Dalam lingkungan enterprise dan pemerintah, tandai yang memindahkan uang atau mengubah catatan warga lebih dulu, karena pembayaran ganda atau tunjangan duplikat adalah temuan audit dan kadang paparan hukum, bukan sekadar cacat.

  5. Apakah timeout, circuit breaker, dan bulkhead kita berasal dari satu pustaka bersama yang teruji, atau setiap tim meraciknya sendiri? Pola ini mudah dijelaskan dan mudah keliru secara halus: timeout yang hilang, ambang breaker yang tidak pernah memicu, kolam koneksi berukuran sehingga satu dependensi lambat membuat seluruh proses kelaparan. Ketika setiap tim mengimplementasikannya ulang, Anda menumpuk banyak salinan yang sedikit rusak dan tak ada satu tempat untuk memperbaiki cacat sekali Anda menemukannya. Pertimbangan yang bersaing adalah bahwa pustaka bersama memaksakan antarmuka dan irama pembaruan umum, dan tim dengan runtime atau kebutuhan latensi tak lazim mungkin keberatan atau memutarinya. Bawa survei berapa banyak implementasi retry-dan-breaker berbeda yang sebenarnya berjalan di produksi, dan layanan mana yang masih tidak punya timeout pada panggilan keluarnya sama sekali. Bagi enterprise atau lembaga besar, pustaka bersama yang diperiksa juga memberi peninjau keamanan dan auditor satu komponen untuk disertifikasi alih-alih lusinan, yang menurunkan biaya setiap tinjauan.

  6. Jika insiden terjadi semalam, dapatkah insinyur on-call mana pun menelusurinya dari satu baris log, dan dapatkah auditor kemudian melihat setiap kegagalan yang dicatat sistem? Galat yang dibungkus, dikategorikan, dan dicatat dengan baik adalah beda antara diagnosis sepuluh menit dan penggalian tengah malam, dan yang ditelan adalah insiden masa depan yang telah Anda sembunyikan dari diri sendiri. Pada tim besar, kegagalan melintasi banyak lompatan layanan, sehingga nilainya datang dari konteks konsisten dan pengenal korelasi yang bertahan melewati lompatan itu, bukan dari ketekunan satu tim mana pun. Ketegangan yang bersaing adalah biaya dan derau: catat segalanya dan Anda menenggelamkan sinyal dan membayar penyimpanannya; catat terlalu sedikit dan Anda tidak dapat merekonstruksi apa yang terjadi. Bawa kegagalan nyata terbaru dan telusuri jejaknya ujung ke ujung, mencatat setiap lompatan di mana konteks dijatuhkan atau galat ditangkap dan dibuang. Dalam sistem yang diatur dan pemerintah, perlakukan ini sebagai sifat kepatuhan, karena kegagalan yang tidak dapat diaudit, atau keputusan yang tidak dapat Anda jelaskan bertahun-tahun kemudian, adalah paparan hukum dan bukan sekadar celah operasional.

Lensa sektor

Startup. Dengan segelintir insinyur dan tanpa runway tersisa, belanjakan anggaran penanganan galat Anda di tempat kegagalan memakan pelanggan atau data Anda: pasang timeout pada setiap panggilan keluar, jadikan tulis yang memindahkan uang idempoten, dan tambahkan aturan lint terhadap galat yang diabaikan. Lewati kerangka rumit; tipe Result untuk fungsi inti dan degradasi anggun pada dependensi non-kritis membeli sebagian besar keamanan dengan beberapa hari kerja. Gagal cepat dalam pengembangan agar bug muncul dengan lantang, dan tahan diri dari meracik circuit breaker sendiri sebelum Anda benar-benar punya dependensi yang membenarkannya.

Bisnis kecil. Tanpa spesialis ketahanan dan dengan anggaran ketat, bersandarlah pada yang sudah diberikan bahasa, kerangka kerja, dan penyedia cloud Anda alih-alih membangun pola dari nol: antrean terkelola, retry sisi penyedia, dan timeout pustaka mencakup lebih banyak daripada yang diperkirakan kebanyakan tim. Bingkai keputusan sebagai beli versus bangun, dan beli di mana dependensi matang menangani retry, backoff, dan idempotensi untuk Anda. Fokuskan perhatian langka Anda pada satu atau dua batas di mana transaksi keliru atau hilang akan benar-benar menyakitkan, dan pastikan itu gagal dengan aman dan meninggalkan jejak.

Enterprise. Di banyak tim hadiahnya adalah konsistensi: satu taksonomi galat bersama, pustaka umum untuk timeout, retry, circuit breaker, dan bulkhead, serta linter dan daftar periksa tinjauan yang menegakkannya di pipeline. Salurkan setiap galat ke platform observabilitas terpadu dengan pengenal korelasi agar kegagalan dapat dilacak lintas lompatan layanan, dan bakukan keputusan fail-fast versus fail-safe per batas agar audit menemukan pola terdokumentasi dan dapat dipertanggungjawabkan alih-alih sebaran kebiasaan lokal. Atur pustaka bersama sebagai produk nyata, karena cacat yang diperbaiki sekali di sana adalah cacat yang diperbaiki di mana-mana.

Pemerintah. Kebenaran, kegagalan aman, dan jejak audit tahan lama adalah kewajiban, bukan preferensi. Gagal cepat pada invarian yang dilanggar apa pun yang menyentuh uang atau kelayakan, validasi setiap masukan yang menghadap warga di batas, dan tulis setiap kegagalan ke log tak berubah dengan konteks cukup sehingga keputusan dapat dijelaskan dan ditinjau bertahun-tahun kemudian. Pengadaan dan umur sistem yang panjang berarti kontrak galat harus didokumentasikan agar pegawai negeri dapat memelihara kode lama setelah penulis aslinya pergi, dan komponen vendor mana pun harus mengekspos perilaku kegagalannya alih-alih menyembunyikannya di balik antarmuka buram.

Contoh

Startup. Sebuah startup empat orang merilis aplikasi yang memanggil penyedia pembayaran pihak ketiga dan layanan email. Pada awalnya mereka menambahkan loop retry naif dan segera menagih pelanggan dua kali ketika timeout menutupi tagihan yang sukses. Perbaikannya mengajarkan pelajaran: mereka menambahkan kunci idempotensi ke setiap tulis, memasang timeout pada setiap panggilan keluar, dan beralih ke exponential backoff dengan jitter. Mereka mengadopsi tipe Result untuk fungsi layanan inti agar kegagalan tampak dalam tanda tangan, dan aturan lint menandai galat yang diabaikan. Ketika pengiriman email gagal, checkout merosot dengan anggun dengan mengantrekan pesan alih-alih memblokir penjualan. Disiplin itu memakan beberapa hari dan menyelamatkan mereka dari kelas insiden yang akan memakan biaya jauh lebih besar dalam pengembalian dana dan kepercayaan.

Enterprise. Sebuah perusahaan logistik global menjalankan ratusan layanan dan membakukan penanganan galat di semuanya. Setiap layanan memetakan kegagalan ke taksonomi bersama (validasi, tidak ditemukan, konflik, dependensi-tidak-tersedia, internal), sehingga pemanggil bercabang menurut kategori alih-alih mengurai pesan. Pustaka umum menyediakan circuit breaker, retry terbatas dengan backoff dan jitter, dan kolam koneksi ber-bulkhead, sehingga tak seorang pun meraciknya sendiri dengan keliru. Setiap galat dicatat dengan konteks korelasi yang memberi makan platform observabilitas bab 9.2, sehingga insinyur on-call dapat melacak kegagalan lintas lompatan layanan dari satu baris. Karena standar seragam dan ditegakkan di pipeline, insinyur berpindah dengan percaya diri lintas layanan asing dan auditor dapat melihat bahwa setiap kegagalan dicatat, dikategorikan, dan dapat dilacak.

Pemerintah. Sebuah lembaga tunjangan nasional membangun sistem kelayakan dan pembayaran di mana jawaban keliru dapat menolak uang sewa seseorang atau membayar berlebihan dari kas publik. Kebenaran dan kegagalan aman tidak dapat ditawar, sehingga kode gagal cepat pada invarian keuangan yang dilanggar apa pun: perhitungan yang tidak dapat direkonsiliasi menolak membukukan alih-alih membukukan angka keliru. Setiap masukan yang menghadap warga divalidasi di batas, dan keadaan ilegal dibuat tidak dapat direpresentasikan dalam tipe domain. Setiap kegagalan ditulis ke log audit tak berubah dengan konteks penuh, memenuhi persyaratan hukum bahwa keputusan dapat dijelaskan dan ditinjau bertahun-tahun kemudian. Di tempat dependensi non-kritis seperti pratinjau dokumen mati, sistem merosot dengan anggun sehingga petugas kasus tetap dapat memproses klaim. Pegawai negeri baru mewarisi kode yang kontrak galatnya terdokumentasi, sehingga mereka dapat memeliharanya dengan aman lama setelah penulis aslinya berpindah.

Kasus bisnis: motivasi, ROI, dan TCO

Imbal hasil penanganan galat yang disiplin tampak sebagai insiden yang lebih sedikit, lebih singkat, dan lebih murah. Sebagian besar pemadaman produksi tidak eksotis; mereka dapat ditelusuri ke exception yang ditelan, timeout yang hilang, badai retry, atau batas yang memercayai data yang seharusnya divalidasi. Masing-masing dapat dicegah dengan pola di sini, dan setiap insiden yang dicegah menghemat bukan hanya biaya langsung downtime tetapi biaya majemuk tanggap darurat, pelanggan yang pergi, dan penyelidikan. Karena galat yang dibungkus dan dicatat dengan baik dapat didiagnosis dalam menit alih-alih jam, waktu rata-rata pemulihan turun, dan tingkat kegagalan perubahan turun bersamanya saat insinyur berhenti takut pada jalur galat.

Biaya mengadopsi sederhana dan sebagian besar sekali jalan. Anda menuliskan taksonomi galat, menyediakan pustaka bersama untuk retry dan circuit breaker agar tim tidak menciptakannya ulang dengan buruk, menambahkan aturan lint terhadap galat yang diabaikan, dan membangun kebiasaan injeksi kegagalan. Biaya pengabaian berlipat diam-diam: galat tertelan menumpuk menjadi data rusak yang mahal diurai, dan penanganan tidak konsisten melipatgandakan biaya setiap integrasi dan setiap audit. Dalam lingkungan yang diatur dan pemerintah, kegagalan yang tidak dapat diaudit adalah paparan kepatuhan dan hukum, bukan sekadar masalah rekayasa. Untuk meyakinkan pimpinan, hubungkan disiplin penanganan galat dengan metrik yang sudah mereka awasi: frekuensi insiden, waktu rata-rata pemulihan, tingkat kegagalan perubahan, dan temuan audit.

Anti-pola dan jebakan

  • Penelanan diam: blok catch kosong dan nilai kembali yang diabaikan yang mengubah kegagalan menjadi misteri tertunda dan terlepas.
  • Penghapusan tangkap-semua: catch luas yang mencatat pesan generik dan membuang galat asli beserta konteksnya.
  • Retry tanpa idempotensi: menjalankan ulang tulis yang tidak idempoten setelah timeout, menagih ganda atau menduplikasi catatan.
  • Badai retry: tanpa backoff, tanpa jitter, dan tanpa batas, sehingga klien tersinkron dan menghantam dependensi yang pulih sampai jatuh lagi.
  • Tanpa timeout: panggilan jarak jauh tak terbatas yang membiarkan satu dependensi menggantung menghabiskan thread dan membekukan seluruh proses.
  • Exception sebagai alur kendali: melempar dan menangkap untuk hasil biasa seperti “tidak ditemukan,” menyembunyikan logika dan memperlambat kode.
  • Paranoia defensif: pemeriksaan di setiap baris yang mengubur logika dan mengubah kegagalan nyata menjadi bawaan diam.
  • Galat berbasis string: pemanggil mengurai teks pesan galat karena tidak ada taksonomi stabil dan terkategori untuk dijadikan cabang.
  • Fail-safe di tempat Anda butuh fail-fast: melanjutkan pada keadaan rusak dalam sistem di mana jawaban keliru lebih buruk daripada tidak ada.

Model kematangan

  • Tingkat 1, Memulai: Penanganan galat ad hoc dan reaktif, diputuskan per pengembang. Blok catch kosong dan nilai kembali yang diabaikan umum, retry naif, timeout hilang, dan kegagalan muncul sebagai data rusak atau cacat misterius tanpa pencatatan konsisten.
  • Tingkat 2, Mengembangkan: Tim mengadopsi praktik dasar, tetapi tidak konsisten. Galat dicatat dengan sedikit konteks, penelanan yang jelas ditentang dalam tinjauan, dan timeout serta retry sederhana ada, namun konvensi bervariasi antarlayanan, idempotensi tambal-sulam, dan jalur galat jarang diuji.
  • Tingkat 3, Membakukan: Taksonomi galat dan konvensi penanganan bersama didokumentasikan dan ditegakkan di seluruh organisasi. Validasi batas, retry idempoten dengan backoff dan jitter, circuit breaker, bulkhead, dan pembungkusan galat adalah standar, disediakan pustaka umum, dan setiap galat memberi makan pipeline observabilitas terpadu.
  • Tingkat 4, Mengelola: Perilaku penanganan galat diukur terhadap garis dasar dan dikendalikan dengan data. Tingkat retry, pemicuan circuit breaker, jumlah timeout, temuan galat tertelan dari analisis statis, waktu rata-rata pemulihan, dan tingkat kegagalan perubahan dilacak per layanan; ambang circuit breaker dan timeout disetel dari data latensi dan kegagalan teramati alih-alih ditebak; injeksi kegagalan berjalan terjadwal; dan tim meninjau metrik ini untuk menangkap regresi dan menahan setiap pilihan fail-fast atau fail-safe pada bukti.
  • Tingkat 5, Mengorkestrasi: Ketahanan terintegrasi dengan perencanaan pengiriman dan risiko dan terus diperbaiki. Taksonomi, pustaka bersama, dan standar berkembang dari setiap insiden, eksperimen chaos dan injeksi kegagalan rutin, dan organisasi menyesuaikan timeout, ambang breaker, strategi degradasi, dan keputusan batas seiring bergesernya lalu lintas, dependensi, dan gambaran risiko.

Gagasan untuk didiskusikan

  1. Di mana dalam basis kode Anda galat saat ini tertelan, dan bagaimana Anda akan tahu jika Anda keliru bahwa tidak?
  2. Operasi tulis Anda yang mana yang idempoten, dan mana yang akan dieksekusi ganda jika retry terpicu setelah konfirmasi yang hilang?
  3. Haruskah “pengguna tidak ditemukan” berupa exception, nilai galat, atau hasil normal, dan apakah tim Anda menjawabnya secara konsisten?
  4. Apa aturan nyata Anda tentang di mana validasi terjadi, dan dapatkah Anda menunjuk batas yang memercayai data yang tidak semestinya?
  5. Bagaimana Anda memutuskan ambang dan masa pendinginan circuit breaker, dan bagaimana Anda tahu pengaturan saat ini keliru?
  6. Jika auditor meminta melihat setiap kegagalan yang dialami sistem Anda bulan lalu, dapatkah Anda menghasilkannya, terkategori dan dengan konteks?

Poin-poin utama

  • Bedakan kesalahan, galat, dan kegagalan, dan putus rantai sebelum galat internal menjadi kegagalan yang terlihat.
  • Pilih fail-fast atau fail-safe dengan sengaja per batas, dan buat kontrak penanganan galat setiap fungsi eksplisit.
  • Validasi keras di batas kepercayaan dan percaya di dalamnya; sikap defensif yang menutupi kegagalan adalah penundaan, bukan keamanan.
  • Buat retry aman dengan idempotensi, timeout, exponential backoff, dan jitter, dan tambahkan circuit breaker serta degradasi anggun dalam kode.
  • Bungkus galat dengan konteks, salurkan ke observabilitas, dan jangan pernah menelannya; setiap galat harus ditangani, diteruskan, atau dicatat dan dimunculkan.

Referensi dan bacaan lanjutan

  • Michael T. Nygard, Release It! Design and Deploy Production-Ready Software
  • Andrew Hunt dan David Thomas, The Pragmatic Programmer
  • Steve McConnell, Code Complete: A Practical Handbook of Software Construction
  • Betsy Beyer, Chris Jones, Jennifer Petoff, dan Niall Richard Murphy (eds.), Site Reliability Engineering: How Google Runs Production Systems
  • Marc Brooker, “Timeouts, Retries, and Backoff with Jitter,” Amazon Builders’ Library
  • Martin Fowler, “CircuitBreaker,” martinfowler.com
  • Nassim Nicholas Taleb, Antifragile: Things That Gain from Disorder