Blog

Bagaimana Satu Chat Berat Bisa Menjatuhkan API Kami

Pada 28 September, sekitar 17 menit setelah sebuah rilis, kedua server API produksi kami kehabisan memori pada saat yang sama, restart, dan selama sekitar lima menit load balancer tidak punya server sehat untuk menerima lalu lintas. Perbaikan pertama kami memberi API dua kali lipat memori. Penyebab sebenarnya ditemukan kemudian di hari yang sama: membuka satu chat tertentu membuat API memuat 53 MB pesan dalam satu permintaan. Riwayat chat sekarang dimuat per halaman yang dibatasi oleh ukuran sekaligus jumlah, dan chat itu terbuka dengan halaman pertama 5 MB, bukan crash.

Apa yang mati

API kami berjalan sebagai container di masing-masing dari dua server di belakang load balancer. Setiap container punya batas memori 384 MiB. Saat idle, ia memakai 130–140 MiB, sehingga batas itu tampak punya banyak ruang.

Kedua container mencapai batas itu bersamaan dan crash dengan “JavaScript heap out of memory”. Heap adalah memori yang dipakai proses Node.js untuk objek-objek yang sedang dikerjakannya. Karena kedua server gagal sekaligus, tidak ada lagi tempat untuk mengarahkan permintaan. Itulah jenis outage terburuk untuk setup dua server: redundansi hanya membantu jika kegagalannya tidak terjadi bersamaan.

Perbaikan pertama: lebih banyak ruang

Waktunya mengarah ke deploy. Setelah rilis, giliran agen yang terputus oleh deploy dilanjutkan dan menyimpan ulang transkripnya, dan lonjakan kerja itu tampak seperti sumber lonjakan memori yang paling jelas.

Jadi perubahan pertama menaikkan batas API dari 384 MiB ke 768 MiB. Kami menerapkannya langsung di kedua server pada hari yang sama, dan menyimpannya di konfigurasi deploy agar deploy berikutnya tidak diam-diam mengembalikan angka 384. Perbaikan yang dibuat manual di server, yang lalu dibatalkan oleh deploy berikutnya, adalah cara umum agar outage yang sama kembali.

Memori tambahan adalah margin pengaman yang masuk akal. Tetapi itu bukan penjelasan.

Penyebab sebenarnya: satu permintaan, 71 MB

Belakangan hari itu kami mereproduksi crash pada salinan satu chat, dengan batas memori lama produksi. Salinan itu membuat API crash sendirian, tanpa ada deploy yang terlibat.

Saat Anda membuka sebuah proyek, aplikasi web meminta API untuk riwayat chatnya. Endpoint itu mengembalikan 30 pesan terbaru secara utuh. Untuk sebagian besar chat, 30 pesan itu kecil. Untuk chat ini, 30 pesan terbaru berukuran 53 MB di database dan respons 71 MB. Beberapa balasan agen berukuran 4–10 MB masing-masing, sebagian besar berupa transkrip sub-agen: agen bantuan yang dimulai agen utama untuk mengerjakan sebagian tugas secara paralel, yang seluruh pekerjaannya disimpan bersama balasan.

Membangun respons sebesar itu membutuhkan heap lebih besar dari yang dimiliki API. Dan setiap membuka atau me-refresh proyek itu mengirim permintaan lagi. Satu chat bisa menjatuhkan server mana pun tempat ia mendarat, dan karena halamannya bisa di-reload, ia bisa mencapai kedua server.

“30 pesan” tidak pernah benar-benar menjadi batas ukuran. Itu adalah batas jumlah yang kebetulan kecil untuk chat normal.

Perbaikan: halaman dibatasi oleh ukuran sekaligus jumlah

Sebuah halaman riwayat sekarang berisi 30 pesan atau sekitar 4 MB, mana yang lebih dulu tercapai.

Cara kerjanya dalam dua langkah:

  1. Minta ukuran dulu. API meminta database untuk ID dan ukuran tersimpan dari pesan-pesan terbaru, tanpa isinya. Kueri itu murah berapa pun ukuran pesannya.
  2. Muat hanya yang muat. Dari yang terbaru ke yang terlama, API menambahkan pesan sampai pesan berikutnya akan membuat halaman melewati 4 MB. Baris-baris itu baru dimuat penuh.

Dua aturan menjaga ini tetap aman:

  • Pesan terbaru selalu masuk ke halaman, berapa pun ukurannya. Halaman yang tidak mengembalikan apa-apa akan membuat chat macet, tanpa cara untuk menggulir ke riwayat sebelumnya.
  • Tidak ada yang dipotong. Tidak ada pesan yang dipotong atau dibuang. Sisa chat dimuat lewat kontrol “Scroll up for earlier messages” yang sudah dipakai pesan-pesan lama, satu halaman dalam satu waktu.

Kami menemukan satu tempat lagi dengan bentuk yang sama. Saat Anda menghentikan sebuah giliran, API memeriksa beberapa balasan terakhir untuk melihat apakah ada yang kosong dan perlu dibersihkan. Caranya dengan memuat delapan pesan utuh. Pada chat dengan balasan berukuran multi-megabyte, itu adalah masalah yang sama dalam bentuk lebih kecil. Sekarang API mengajukan pertanyaan ya-atau-tidak ke database, yaitu apakah pesan ini punya isi, tanpa memuat isinya.

Cara kami mengujinya

Pada salinan chat itu, dijalankan dengan batas lama 384 MiB:

  • Halaman pertama berukuran 5 MB. Sebelumnya, permintaan yang sama membuat API crash.
  • Lima tab browser yang membuka chat itu bersamaan mencapai puncak 154 MB memori.
  • Menggulir ke atas sepanjang chat mengembalikan semua 190 pesan, masing-masing tepat sekali, berurutan.

Logika pemilihan halaman juga punya unit test, termasuk satu yang dibangun dari ukuran nyata pesan-pesan terbaru chat itu. Halaman pertama berisi satu pesan, menyatakan masih ada lagi, dan halaman berikutnya dimulai dari pesan sesudahnya.

Apa yang kami pelajari

  • Jumlah bukan batas ukuran. Setiap endpoint yang mengembalikan “N terbaru” dari sesuatu yang bisa terus membesar juga membutuhkan anggaran byte. Data pada akhirnya akan menemukan celah itu.
  • Tersangka yang paling jelas tidak selalu penyebabnya. Deploy memang pemicu kerja tambahan, dan memori tambahan memang berharga. Tetapi crash itu bisa direproduksi tanpa deploy, dan tanpa reproduksi itu kami akan berhenti pada kenaikan memori.
  • Dua server gagal bersamaan saat keduanya menerima permintaan yang sama. Redundansi melindungi dari satu server yang mati. Redundansi tidak melindungi dari permintaan yang bisa mematikan server mana pun yang dicapainya, terutama permintaan yang akan diulang pengguna dengan me-refresh.
  • Perbaiki di konfigurasi, bukan hanya di server. Kenaikan memori dimasukkan ke konfigurasi deploy di hari yang sama, sehingga deploy berikutnya mempertahankannya.

Ini adalah cerita memori kedua yang baru-baru ini melibatkan output agen. Yang pertama adalah label status yang mengantre 690 MB string. Jika Anda memakai sub-agen di Mythex, Anda bisa membaca cara kerjanya di dokumentasi.

Mythex · Pricing · Bangun apa pun dengan AI · Template website dan aplikasi · Mythex dibandingkan · Glosarium membuat aplikasi dengan AI · Alat gratis untuk membuat aplikasi dengan AI · English

English አማርኛ العربية Български বাংলা Bosanski Català Čeština Dansk Deutsch Ελληνικά Español Español (España) Eesti فارسی Suomi Français Français (Canada) ગુજરાતી हिन्दी Hrvatski Magyar Հայերեն Íslenska Italiano 日本語 ქართული Қазақ тілі ಕನ್ನಡ 한국어 Lietuvių Latviešu Македонски മലയാളം Монгол मराठी Bahasa Melayu မြန်မာ Norsk bokmål Nederlands ਪੰਜਾਬੀ Polski Português (Brasil) Português (Portugal) Română Русский Slovenčina Slovenščina Soomaali Shqip Srpski Svenska Kiswahili தமிழ் తెలుగు ไทย Tagalog Türkçe Українська اردو Tiếng Việt 简体中文 繁體中文(香港) 繁體中文(台灣)