Alert yang Berbunyi Sekali, Bukan Sekali per Server
Salah satu alert operasional kami, “credit rendah” untuk aplikasi yang dipublikasikan oleh builder, dimaksudkan untuk sampai kepada kami paling banyak sekali sehari. Suatu sore, alert itu datang lima kali. Penyebabnya: setiap salinan API kami mengingat alert mana yang sudah dikirim di memorinya sendiri, sehingga dua server mengirimnya dua kali, dan setiap deploy menghapus memori itu sehingga mereka mengirimnya lagi. Pola yang sama juga membuat sebuah background job mengerjakan pekerjaannya dua kali. Keduanya sekarang berkoordinasi lewat penyimpanan bersama, dan keduanya gagal ke arah yang aman jika penyimpanan itu tidak tersedia.
Gejalanya
Mythex mengirim alert ke tim ketika ada sesuatu yang perlu diperiksa: sebuah layanan mati, sebuah job macet, atau saldo kredit builder sedang menipis sementara aplikasinya dipublikasikan. Setiap alert punya dedupe window, yaitu periode saat alert yang sama hanya dikirim sekali. Untuk alert kredit rendah, window itu 24 jam.
Lima salinan alert yang sama dalam satu sore berarti window itu tidak bertahan. Lebih buruk lagi, alert itu datang berpasangan, sekitar 11 menit terpisah.
Mengapa ini terjadi
Paket alert kami menyimpan dedupe-nya di memori: sebuah tabel “kunci alert → kapan terakhir dikirim”, di dalam proses yang berjalan. Itu bekerja jika hanya ada satu proses yang tidak pernah restart. Produksi tidak memenuhi keduanya:
- Dua replika API. Produksi menjalankan dua salinan API. Masing-masing punya tabelnya sendiri, sehingga masing-masing mengirim alert sekali. Itulah pasangannya.
- Deploy. Setiap deploy mengganti kedua replika dengan proses baru dan tabel yang kosong. Setelah deploy, alert itu dikirim lagi seolah-olah belum pernah dikirim.
Jeda sekitar 11 menit antara dua alert dalam setiap pasangan berasal dari job yang memunculkan alert ini. Job itu berjalan di kedua replika dan mengambil lock, sehingga replika bergantian memenangkannya, dan masing-masing memunculkan alert saat gilirannya tiba.
Ada bug kedua yang lebih diam di kode yang sama. Untuk mencegah tabel terus membesar, ia memangkas dirinya sendiri begitu berisi 500 kunci, dengan menghapus setiap entri yang lebih dari 15 menit, apa pun window-nya. Di bawah beban, dedupe 24 jam bisa hanya bertahan seperempat jam.
Perbaikannya
Satu catatan bersama per alert
Sebelum API mengirim alert, ia sekarang mengklaim kunci dedupe alert itu di Redis, sebuah penyimpanan dalam memori bersama yang diajak bicara oleh kedua replika dan yang tidak restart ketika API restart. Klaimnya adalah satu operasi tunggal “set kunci ini hanya jika belum ada, dan kedaluwarsakan setelah window”. Replika yang mengklaim lebih dulu mengirim alert; yang lain mendapati kunci sudah terisi dan diam. Proses baru setelah deploy menemukan kunci itu masih ada.
Kunci dan window berasal dari aturan yang sama yang dipakai paket alert, sekarang dibagikan di antara keduanya, sehingga kedua lapisan tidak bisa berbeda pendapat tentang apa yang dianggap “alert yang sama”.
Gagal terbuka, bukan tertutup
Keputusan paling penting adalah apa yang dilakukan ketika Redis tidak bisa dijangkau. Kami memilih tetap mengirim alert. Jika Redis mati, atau tidak menjawab dalam 1,5 detik, alert tetap keluar. Alert ganda itu hanya gangguan. Alert yang hilang berarti seseorang tidak diberi tahu.
Alert paling serius, P0, tidak pernah ditahan oleh dedupe sama sekali.
Setiap entri mempertahankan window-nya sendiri
Di dedupe dalam memori milik paket alert, setiap entri sekarang menyimpan kapan window-nya berakhir, dan pemangkasan hanya menghapus entri yang window-nya sudah habis. Kunci 24 jam tetap ada selama 24 jam, tidak peduli berapa banyak alert lain yang datang.
Bug yang sama, di tempat lain
Pola yang sama juga melipatgandakan sebagian pekerjaan background: sebuah job rekonsiliasi yang menjaga catatan kami tentang status setiap workspace tetap sesuai dengan penyedia sandbox kami. Setiap putaran mendaftar setiap sandbox di akun, ribuan di produksi, sekitar lima detik, ditambah setiap proyek yang sedang berjalan atau dijeda. Kedua replika menjalankannya setiap dua menit.
Penulisannya idempoten, yaitu menjalankannya dua kali memberi hasil yang sama seperti menjalankannya sekali, sehingga tidak ada yang rusak. Hanya saja pekerjaannya dua kali lipat. Sekarang setiap jendela dua menit diklaim di Redis dengan cara yang sama seperti meter pemakaian kami sudah mengklaim miliknya, dan hanya replika yang berhasil mengklaim yang menjalankan putaran itu. Jika Redis tidak bisa dijangkau, putaran tetap dijalankan: mengerjakan sesuatu dua kali tidak berbahaya, sedangkan tidak mengerjakannya memang berbahaya.
Cara kami mengujinya
Tes memodelkan setup sungguhan. Tes memuat modul alert dua kali, sebagai dua “replika” yang terpisah, mengarahkan keduanya ke satu penyimpanan bersama, memunculkan alert yang sama dari masing-masing, lalu memuat salinan ketiga yang baru untuk mewakili “setelah deploy” dan memunculkannya lagi. Tepat satu alert yang dikirim. Tes lain memastikan alert milik builder yang berbeda tetap terkirim, bahwa P0 terkirim dari kedua replika, dan bahwa alert tetap dikirim ketika penyimpanan mati atau macet melewati batas waktu. Perbaikan pemangkasan punya tesnya sendiri: kunci 24 jam bertahan melewati 600 alert lain yang datang 20 menit kemudian.
Untuk job rekonsiliasi, dua replika yang berjalan bersamaan, ditambah satu putaran ketiga dalam window yang sama, menghasilkan satu kali listing.
Yang kami pelajari
- State di memori bersifat per proses. Apa pun yang harus benar di seluruh server, seperti “terkirim sekali” atau “berjalan sekali”, perlu disimpan di tempat yang mereka bagi bersama.
- Deploy adalah restart. State yang hanya bertahan sampai restart berikutnya tidak bertahan melewati hari kerja yang normal.
- Putuskan ke arah mana gagal. Untuk alert, penyimpanan bersama yang mati seharusnya berarti lebih banyak alert, bukan lebih sedikit.
- Cari polanya, bukan hanya kejadiannya. Alert adalah kasus yang paling terasa; job rekonsiliasi yang terduplikasi adalah bug yang sama yang diam-diam memboroskan pekerjaan.
Tidak ada yang ini terlihat dari dalam Mythex. Untuk lebih lanjut tentang cara kami menjaga platform tetap stabil, lihat bagaimana kami menurunkan deploy menjadi nol error 502 dan file swap yang hilang. Jika Anda baru mengenal gagasan menjalankan lebih dari satu salinan layanan, panduan kami tentang apa itu server membahas dasarnya.