Bagaimana Anda mereproduksi kondisi kesalahan dan melihat apa yang terjadi ketika aplikasi dijalankan?
Bagaimana Anda memvisualisasikan interaksi antara berbagai bagian aplikasi yang berbeda?
Berdasarkan pengalaman saya, jawaban untuk dua aspek ini adalah sebagai berikut:
Pelacakan terdistribusi
Pelacakan terdistribusi adalah teknologi yang menangkap data pengaturan waktu untuk setiap komponen bersamaan dari sistem Anda, dan menyajikannya kepada Anda dalam format grafis. Representasi eksekusi bersamaan selalu disisipkan, memungkinkan Anda untuk melihat apa yang berjalan secara paralel dan apa yang tidak.
Pelacakan terdistribusi berawal pada (tentu saja) sistem terdistribusi, yang menurut definisi asinkron dan sangat konkuren. Sistem terdistribusi dengan penelusuran terdistribusi memungkinkan orang untuk:
a) mengidentifikasi kemacetan penting, b) mendapatkan representasi visual dari 'berjalan' ideal aplikasi Anda, dan c) memberikan visibilitas ke dalam perilaku konkuren apa yang sedang dieksekusi, d) memperoleh data waktu yang dapat digunakan untuk menilai perbedaan antara perubahan dalam Anda sistem (sangat penting jika Anda memiliki SLA yang kuat).
Namun, konsekuensi dari pelacakan yang didistribusikan adalah:
Itu menambahkan overhead ke semua proses bersamaan Anda, karena diterjemahkan menjadi lebih banyak kode untuk mengeksekusi dan mengirimkan berpotensi melalui jaringan. Dalam beberapa kasus, overhead ini sangat signifikan - bahkan Google hanya menggunakan sistem penelusuran Dapper pada sebagian kecil dari semua permintaan agar tidak merusak pengalaman pengguna.
Ada banyak alat yang berbeda, tidak semuanya saling beroperasi satu sama lain. Ini agak diperbaiki oleh standar seperti OpenTracing, tetapi tidak sepenuhnya diselesaikan.
Ini memberitahu Anda apa-apa tentang sumber daya bersama dan status mereka saat ini. Anda mungkin bisa menebak, berdasarkan pada kode aplikasi dan grafik apa yang Anda lihat tunjukkan kepada Anda, tetapi itu bukan alat yang berguna dalam hal ini.
Alat saat ini mengasumsikan Anda memiliki memori dan penyimpanan untuk cadangan. Hosting server jangka waktu mungkin tidak murah, tergantung pada kendala Anda.
Perangkat lunak pelacakan kesalahan
Saya menautkan ke Sentry di atas terutama karena itu adalah alat yang paling banyak digunakan di luar sana, dan untuk alasan yang bagus - perangkat lunak pelacakan kesalahan seperti eksekusi runtime Sentry membajak eksekusi secara bersamaan meneruskan tumpukan jejak kesalahan yang ditemui ke server pusat.
Manfaat bersih dari perangkat lunak khusus tersebut dalam kode bersamaan:
- Kesalahan duplikat tidak diduplikasi . Dengan kata lain, jika satu atau lebih sistem bersamaan menemukan pengecualian yang sama, Sentry akan menambah laporan insiden, tetapi tidak mengirimkan dua salinan dari insiden tersebut.
Ini berarti Anda dapat mengetahui sistem konkuren mana yang mengalami jenis kesalahan apa tanpa harus melalui laporan kesalahan simultan yang tak terhitung jumlahnya. Jika Anda pernah menderita spam email dari sistem terdistribusi, Anda tahu seperti apa rasanya.
Anda bahkan dapat 'menandai' aspek berbeda dari sistem konkuren Anda (meskipun ini mengasumsikan Anda tidak memiliki pekerjaan yang disisipkan tepat di atas satu utas, yang secara teknis tidak bersamaan juga karena utas tersebut hanya melompat di antara tugas-tugas secara efisien tetapi masih harus memproses penangan acara sampai selesai) dan melihat rincian kesalahan dengan tag.
- Anda dapat memodifikasi perangkat lunak penanganan kesalahan ini untuk memberikan detail tambahan dengan pengecualian runtime Anda. Sumber daya terbuka apa yang dimiliki proses ini? Apakah ada sumber daya bersama yang dimiliki proses ini? Pengguna mana yang mengalami masalah ini?
Ini, selain jejak tumpukan yang teliti (dan sumber peta, jika Anda harus menyediakan versi yang diperkecil dari file Anda), membuatnya mudah untuk menentukan apa yang salah sebagian besar dari waktu.
- (Khusus sentry) Anda dapat memiliki dasbor pelaporan Sentry yang terpisah untuk uji coba sistem, memungkinkan Anda menangkap kesalahan dalam pengujian.
Kerugian dari perangkat lunak tersebut meliputi:
Seperti semuanya, mereka menambahkan massal. Anda mungkin tidak ingin sistem seperti itu pada perangkat keras yang disematkan, misalnya. Saya sangat merekomendasikan melakukan uji coba perangkat lunak tersebut, membandingkan eksekusi sederhana dengan dan tanpa itu sampel lebih dari beberapa ratus berjalan pada mesin idle.
Tidak semua bahasa sama-sama didukung, karena banyak dari sistem ini mengandalkan secara implisit menangkap pengecualian dan tidak semua bahasa memiliki pengecualian yang kuat. Yang sedang berkata, ada klien untuk banyak sistem.
Mereka mungkin dinaikkan sebagai risiko keamanan, karena banyak dari sistem ini pada dasarnya adalah sumber tertutup. Dalam kasus seperti itu, lakukan uji tuntas Anda dalam meneliti mereka, atau, jika lebih disukai, lakukan sendiri.
Mereka mungkin tidak selalu memberi Anda informasi yang Anda butuhkan. Ini adalah risiko dengan semua upaya untuk menambah visibilitas.
Sebagian besar layanan ini dirancang untuk aplikasi web yang sangat konkuren, jadi tidak setiap alat mungkin cocok untuk kasus penggunaan Anda.
Singkatnya : memiliki visibilitas adalah bagian paling penting dari sistem konkuren. Dua metode yang saya jelaskan di atas, bersama dengan dasbor khusus tentang perangkat keras dan data untuk mendapatkan gambaran holidtik sistem pada titik waktu tertentu, banyak digunakan di seluruh industri tepatnya untuk mengatasi aspek itu.
Beberapa saran tambahan
Saya telah menghabiskan lebih banyak waktu daripada saya peduli untuk memperbaiki kode oleh orang-orang yang mencoba untuk memecahkan masalah bersamaan dengan cara yang mengerikan. Setiap kali, saya telah menemukan kasus di mana hal-hal berikut dapat sangat meningkatkan pengalaman pengembang (yang sama pentingnya dengan pengalaman pengguna):
Mengandalkan tipe . Mengetik ada untuk memvalidasi kode Anda, dan dapat digunakan saat runtime sebagai penjaga tambahan. Di mana pengetikan tidak ada, mengandalkan penegasan dan penangan kesalahan yang sesuai untuk menangkap kesalahan. Kode bersamaan membutuhkan kode defensif, dan tipe berfungsi sebagai jenis validasi terbaik yang tersedia.
- Tautan uji antara komponen kode , bukan hanya komponen itu sendiri. Jangan bingung ini dengan uji integrasi lengkap - yang menguji setiap tautan antara setiap komponen, dan bahkan saat itu hanya mencari validasi global dari kondisi akhir. Ini adalah cara yang mengerikan untuk menangkap kesalahan.
Tes tautan yang baik memeriksa untuk melihat apakah, ketika satu komponen berbicara ke komponen lain secara terpisah , pesan yang diterima dan pesan yang dikirim adalah sama seperti yang Anda harapkan. Jika Anda memiliki dua komponen atau lebih yang mengandalkan layanan bersama untuk berkomunikasi, putar semuanya, minta mereka bertukar pesan melalui layanan pusat, dan lihat apakah mereka semua mendapatkan apa yang Anda harapkan pada akhirnya.
Memecah pengujian yang melibatkan banyak komponen menjadi pengujian komponen itu sendiri dan pengujian tentang bagaimana masing-masing komponen berkomunikasi juga memberi Anda peningkatan kepercayaan diri terhadap validitas kode Anda. Memiliki tubuh pengujian yang ketat memungkinkan Anda untuk menegakkan kontrak antara layanan serta menangkap kesalahan tak terduga yang terjadi ketika mereka menjalankan sekaligus.
- Gunakan algoritma yang tepat untuk memvalidasi keadaan aplikasi Anda. Saya berbicara tentang hal-hal sederhana, seperti ketika Anda memiliki proses master menunggu semua pekerjanya menyelesaikan tugas dan hanya ingin pindah ke langkah berikutnya jika semua pekerjanya selesai sepenuhnya - ini adalah contoh mendeteksi global terminasi, yang metodologi yang dikenal seperti algoritma Safra ada.
Beberapa alat ini dibundel dengan bahasa - Rust, misalnya, menjamin kode Anda tidak akan memiliki kondisi balapan pada waktu kompilasi, sementara Go memiliki detektor kebuntuan inbuilt yang juga berjalan pada waktu kompilasi. Jika Anda dapat menangkap masalah sebelum mencapai produksi, itu selalu merupakan kemenangan.
Aturan umum: desain untuk kegagalan dalam sistem bersamaan . Mengantisipasi bahwa layanan umum akan macet atau pecah. Ini berlaku bahkan untuk kode yang tidak didistribusikan di mesin - kode bersamaan pada satu mesin dapat mengandalkan dependensi eksternal (seperti file log bersama, server Redis, server MySQL sialan) yang dapat menghilang atau dihapus kapan saja .
Cara terbaik untuk melakukan ini adalah dengan memvalidasi status aplikasi dari waktu ke waktu - lakukan pemeriksaan kesehatan untuk setiap layanan, dan pastikan konsumen dari layanan tersebut diberitahu tentang kesehatan yang buruk. Alat kontainer modern seperti Docker melakukan ini dengan sangat baik, dan harus dimanfaatkan untuk hal-hal kotak pasir.
Bagaimana Anda mengetahui apa yang bisa dibuat bersamaan dan apa yang bisa dibuat berurutan?
Salah satu pelajaran terbesar yang saya pelajari bekerja pada sistem yang sangat konkuren adalah ini: Anda tidak akan pernah memiliki metrik yang cukup . Metrik harus benar-benar mengarahkan semua hal dalam aplikasi Anda - Anda bukan seorang insinyur jika Anda tidak mengukur semuanya.
Tanpa metrik, Anda tidak dapat melakukan beberapa hal yang sangat penting:
Nilai perbedaan yang dibuat oleh perubahan pada sistem. Jika Anda tidak tahu apakah tombol tuning A membuat metrik B naik dan metrik C turun, Anda tidak tahu cara memperbaiki sistem Anda ketika orang-orang mendorong kode ganas yang tidak terduga pada sistem Anda (dan mereka akan mendorong kode ke sistem Anda) .
Pahami apa yang perlu Anda lakukan selanjutnya untuk meningkatkan hal-hal. Sampai Anda tahu aplikasi kehabisan memori, Anda tidak dapat membedakan apakah Anda harus mendapatkan lebih banyak memori atau membeli lebih banyak disk untuk server Anda.
Metrik sangat penting dan esensial sehingga saya telah berupaya secara sadar untuk merencanakan apa yang ingin saya ukur sebelum saya bahkan berpikir tentang apa yang dibutuhkan suatu sistem. Bahkan, metrik sangat penting sehingga saya percaya mereka adalah jawaban yang tepat untuk pertanyaan ini: Anda hanya tahu apa yang bisa dibuat berurutan atau bersamaan ketika Anda mengukur apa yang dilakukan bit dalam program Anda. Desain yang tepat menggunakan angka, bukan dugaan.
Yang sedang berkata, tentu ada beberapa aturan praktis:
Berurutan menyiratkan ketergantungan. Dua proses harus berurutan jika satu bergantung pada yang lain dalam beberapa cara. Proses tanpa ketergantungan harus bersamaan. Namun, rencanakan cara untuk menangani kegagalan di hulu yang tidak mencegah proses hilir menunggu tanpa batas.
Jangan pernah mencampur tugas yang terikat I / O dengan tugas yang terikat CPU pada inti yang sama. Jangan (misalnya) menulis perayap web yang meluncurkan sepuluh permintaan bersamaan di utas yang sama, mengikisnya segera setelah masuk, dan berharap untuk menskalakan hingga lima ratus - permintaan I / O menuju antrian secara paralel, tetapi CPU masih akan melalui mereka secara serial. (Model single-threaded ini didorong oleh model populer, tetapi terbatas karena aspek ini - daripada memahami ini, orang-orang hanya memeras tangan mereka dan mengatakan Node tidak skala, untuk memberi Anda contoh).
Satu utas dapat melakukan banyak pekerjaan I / O. Tetapi untuk sepenuhnya menggunakan konkurensi perangkat keras Anda, gunakan threadpools yang bersama-sama menempati semua inti. Dalam contoh di atas, meluncurkan lima proses Python (masing-masing dapat menggunakan inti pada mesin enam-inti) hanya untuk pekerjaan CPU dan utas keenam hanya untuk pekerjaan I / O akan skala jauh lebih cepat daripada yang Anda pikirkan.
Satu-satunya cara untuk memanfaatkan konkurensi CPU adalah melalui threadpool khusus. Satu utas seringkali cukup baik untuk banyak pekerjaan terikat I / O. Inilah sebabnya mengapa server web yang digerakkan oleh peristiwa seperti skala Nginx lebih baik (mereka benar-benar bekerja dengan I / O) daripada Apache (yang mengonfigurasi pekerjaan terikat I / O dengan sesuatu yang membutuhkan CPU dan meluncurkan proses per permintaan), tetapi mengapa menggunakan Node untuk melakukan puluhan ribu perhitungan GPU yang diterima secara paralel adalah ide yang buruk .