Menyetel API live tanpa merusak situs di atasnya

Cara kami mengaudit dan mempercepat API Atlas dalam enam rilis kecil sementara dua situs live tetap menerima jawaban yang sama, lengkap dengan angkanya.

Sebelum memulai bagian besar Atlas berikutnya, kami ingin intinya membosankan: cepat, bisa ditebak, dan tidak menyisakan kejutan. Jadi kami mengaudit API publik dari ujung ke ujung dan memperbaiki semua yang kami temukan. Tantangannya, dua situs brand sudah berjalan di atasnya di produksi, salah satunya memakai hampir dua ribu URL API yang berbeda. Apa pun yang kami ubah, kedua situs itu harus tetap menerima jawaban yang persis sama. Begini cara kami melakukannya dalam enam rilis kecil selama dua hari, dan apa kata angkanya sekarang.

Aturan pertama: bekukan jawabannya sebelum menyentuh kode

Audit yang mengubah jalur paling sibuk butuh wasit yang tidak peduli niat baik. Wasit kami adalah golden snapshot. Sebuah skrip kecil menelusuri setiap URL publik yang dipakai situs live (halaman di setiap bahasa, daftar entri, arsip term, menu, pengaturan, sitemap), sekitar 2.500 URL, lalu menyimpan setiap respons sebagai JSON kanonis bersama kode statusnya. Skrip itu berjalan pelan, beberapa request per detik, dari dalam jaringan sehingga tidak ada yang lewat edge.

Setiap rilis diperlakukan sama: ambil snapshot, promosikan, tunggu deploy, ambil snapshot lagi, bandingkan. Perbedaan tidak pernah diloloskan begitu saja. Setiap perbedaan harus dijelaskan oleh konten yang memang berubah. Sebagian besar rilis menunjukkan tepat tiga perbedaan di situs kami sendiri, semuanya karena entri changelog yang dipublikasikan oleh rilis itu sendiri. Situs satunya nol.

Sekali wasit ini benar-benar berjasa. Setelah satu rilis, dua daftar arsip term kembali dengan entri yang sama dan term yang sama, tapi urutannya berbeda. Tidak ada yang menyentuh term di rilis itu. Penyebabnya lebih lama: query yang memuat term sebuah entri tidak punya urutan eksplisit, jadi database mengembalikannya sesuai rencana eksekusinya, dan rencana itu berubah saat cache dibangun ulang. Tidak ada pengunjung yang bisa melihatnya, karena tidak ada halaman yang menampilkan term pertama secara khusus. Tapi API yang menjawab pertanyaan yang sama dengan dua cara adalah bug yang menunggu klien yang bergantung padanya. Perbaikannya satu baris, urut berdasarkan waktu dibuat, dan snapshot berikutnya bersih.

Apa yang ditemukan audit

  • Query sibuk tanpa index yang tepat. Pencarian halaman dan pemindaian penjadwal berjalan di index yang hanya menutup sebagian filternya. Kami menambah enam index, dibangun online sehingga tabel live tidak pernah terkunci.
  • Satu pencarian media per gambar. Halaman dengan banyak gambar memuat setiap gambar sendiri-sendiri. Sekarang semuanya dimuat dalam satu batch per respons, dan sebuah fixture membandingkan hasil batch dengan hasil lama byte per byte.
  • Sitemap membaca kolom yang tidak pernah dipakai. Sekarang hanya memilih field yang ditulisnya.
  • Setiap request mencari brand dan token di database. Pencarian itu sekarang di-cache lima menit. Bagian sulitnya adalah pencabutan: request yang membaca baris lama tepat sebelum token dicabut bisa menulisnya kembali ke cache tepat setelah pencabutan membersihkannya. Tombstone singkat yang ditulis setelah transaksi pencabutan selesai menutup celah itu, dan sebuah test menjalankan race tersebut dengan sengaja.
  • Menu, pengaturan, dan tema melewati cache respons. Sekarang ketiganya memakai cache berversi dan validator yang sama dengan halaman, sehingga request berulang dijawab tanpa satu query pun.
  • Gerbang modul punya celah. Beberapa jalur tulis melewati pengecekan modul yang dimatikan, dan pembacaan modul serta pengaturan berulang dalam satu request. Celahnya ditutup dan pembacaannya disimpan per request.
  • Beberapa tabel hanya terus membesar. Log request, log pengiriman, log run AI dan autopost, baris audit, dan job gagal sekarang punya jendela retensi dan pembersihan setiap malam yang menghapus dalam batch kecil, sehingga tidak pernah memegang kunci lama.
  • Pekerjaan masuk berbagi antrean dengan yang lain. Sekarang punya worker sendiri, dan test untuk worker yang berumur panjang mendapat kasus baru, sehingga tidak ada state yang bocor dari satu request ke request berikutnya.
  • Pencarian memindai revisi semua brand. Subquery full text sekarang menyaring per brand dulu, sehingga satu brand besar tidak bisa memperlambat pencarian brand lain.

Angkanya

Kami tidak mau bilang lebih cepat tanpa mengukurnya. Snapshot database terakhir sebelum deploy kami muat ke salinan terpisah dengan cache sendiri, lalu versi sebelum audit dan versi sekarang dijalankan di atasnya, dengan 150 request per endpoint diukur di dalam aplikasi, tanpa jaringan. Baca perbandingannya, bukan angka mutlaknya:

  • Halaman, daftar entri, atau sitemap yang sudah di-cache: dulu 2 query database, sekarang 0, dari sekitar 0,6 ms menjadi 0,43 ms.
  • Menu, pengaturan, dan tema pada request berulang: dulu 2,2 ms dengan 5 sampai 8 query, sekarang 0,44 ms tanpa query.
  • Daftar 100 artikel dengan cache kosong: 37 query menjadi 8.
  • Sitemap brand terbesar kami dengan cache kosong: 71 ms menjadi 40 ms.

Ada satu angka yang justru naik: request pertama untuk menu atau tema sekarang 0,2 sampai 0,6 ms lebih lambat, karena saat cache kosong ia juga menulis cache. Setiap request sesudahnya membayar itu berkali-kali lipat.

Kami juga menjalankan explain untuk setiap query yang dikirim halaman dengan cache kosong, terhadap skema produksi. Yang paling berat adalah sitemap brand terbesar, pemindaian sekitar 1.200 baris yang memakan 2 ms. Hari ini itu belum butuh index. Nanti akan butuh saat satu brand mencapai puluhan ribu entri, dan itu sudah dicatat untuk hari itu.

Kode tercepat tetap duduk di belakang halaman yang lambat

Situs pemasaran kami sendiri terasa lambat padahal API di bawahnya menjawab dalam kurang dari satu milidetik. Cache halaman situs itu hilang. Perubahan environment rutin lewat panel hosting menulis ulang konfigurasi server dan membuang aturan cache kami tanpa pemberitahuan, sehingga setiap halaman di-render dari awal lagi: sekitar 300 ms di origin, bukan sekitar 10. Kami memasang kembali aturannya, dan deploy sekarang memasangnya lagi setiap kali hilang, sehingga perubahan panel berikutnya tidak bisa diam-diam menghapusnya.

Lalu kami membiarkan CDN ikut menyimpan HTML untuk waktu singkat, dengan tiga pengecualian: rute API, gambar untuk berbagi di media sosial, dan request yang membawa cookie preview, supaya draft tidak pernah masuk ke cache bersama. Setiap publish membersihkannya. Waktu sampai byte pertama dari luar turun dari 300 sampai 650 ms menjadi sekitar 100 ms.

Cara kami merilisnya

Tujuh issue, tujuh pull request, enam rilis. Setiap perubahan melewati test lengkap di branch integrasi, lalu promosi yang sampai ke produksi dalam sekitar lima belas menit. Database di-snapshot sebelum setiap migrasi, dan perbandingan golden berjalan setelah setiap deploy. Rilis kecil membuat setiap perbedaan cukup kecil untuk dijelaskan.

Pesan kami untuk yang mau melakukan hal serupa

  1. Bangun wasitnya sebelum perbaikan pertama. Snapshot jawaban nyata lebih bisa dipercaya daripada argumen apa pun tentang kenapa sebuah perubahan aman.
  2. Jelaskan setiap perbedaan, termasuk yang terlihat tidak berbahaya. Perbedaan yang hampir kami abaikan ternyata bug sungguhan.
  3. Ukur sebelum dan sesudah di data yang sama, terpisah dari produksi, dan tampilkan juga angka yang memburuk.
  4. Setiap daftar butuh urutan eksplisit pada kunci yang unik. Tanpa itu, database bebas berubah pikiran.
  5. Periksa lapisan di atas kode Anda. Bagian paling lambat ternyata aturan cache yang dihapus oleh alat milik pihak lain.

//Baca juga

Lainnya dari blog

Semua artikel →

//Kelola setiap brand dari satu tempat.

Mulai gratis untuk satu brand. Ajak tim Anda saat sudah siap.

$ npm install @atlas/client @atlas/nuxt

Gratis untuk satu brandTanpa kartu kreditUpgrade kapan saja