<

Trafik Bir Gecede 3 Katına Çıkınca: Bir Ölçekleme Günlüğü

16 Mart 2020 Pazartesi sabahı, okullar tatil edilmiş, sokağa çıkma kısıtlamaları konuşuluyor. Bizim Grafana panosunda ise başka bir film dönüyor: cihazlardan gelen mesaj hacmi bir haftada yüzde 40 artmış. Nisan ortasına geldiğimizde önceki yılın üç katındaydık. Sebep basitti aslında: herkes eve kapanınca ev teslimatı patladı; müşterilerimizin yarısı dağıtım filosu işletiyordu ve hepsi aynı anda araç kiralayıp filo büyütüyordu. Bizim kapasite planımız yıllık yüzde 30 büyüme varsayıyordu. Altı haftada üç yıllık büyüme yedik.

Bu yazı o dönemin günlüğü. Kahramanlık hikâyesi değil; sırayla patlayan darboğazların ve her birinde öğrendiklerimizin dökümü.

Hafta 1: Şişen Kuyruk ve İlk Yanlış Teşhis

İlk alarm mesaj işleme gecikmesinden geldi: cihazdan gelen konumun haritaya düşme süresi 2 saniyeden 40 saniyeye çıkmıştı. Kuyrukta 1,2 milyon işlenmemiş mesaj birikmişti. İlk refleks klasikti: "İşleyici sayısını artır." Artırdık; gecikme kötüleşti. Çünkü darboğaz işleyicilerde değil, hepsinin yazdığı veritabanındaydı — işleyici eklemek, tıkalı gişeye kuyruk eklemek gibi, sadece kilit çekişmesini büyüttü. Gerçek çözüm işleyicilerde toplu yazmaya geçmekti: mesajları tek tek INSERT etmek yerine 200'erlik gruplar halinde tek sorguda basmak. Yazma verimi 4 katına çıktı, kuyruk iki saatte eridi. Ders bir: ölçeklendirme kararından önce darboğazın adresini kanıtla. top çıktısı değil, bekleme istatistikleri konuşsun.

Hafta 2-3: Diskin Sessiz İsyanı

Kuyruk düzelince rahatladık sanmıştık; bir hafta sonra rapor sorguları sürünmeye başladı. CPU normal, bellek normal, ama iostat'ta await değerleri 2 ms'den 30 ms'ye tırmanmış. Veri merkezindeki sanal makinelerimizin disklerinde IOPS tavanına çarpıyorduk — üç kat veri, üç kat yazma, üç kat da gece özet işi yükü demekti ve hepsi aynı disk kotasından yiyordu. Kısa vadede iki hamle yaptık: binlog ve InnoDB log dosyalarını ayrı diske taşıdık, innodb_flush_log_at_trx_commit'i telemetri veritabanı için 2'ye çektik (son bir saniyenin verisini kaybetme riskini konum verisi için bilinçli kabul ettik — para verisi değil bu). await 8 ms'ye indi. Kalıcı çözüm, ay sonunda geldi: yerel NVMe'li fiziksel sunucuya taşınma. Bulut esnekliği güzel ama 2020'de İstanbul'da NVMe'li dedike makine, aynı paraya on kat IOPS veriyordu.

Hafta 4: max_connections Duvarı

Nisan başında, akşam sipariş sıçramasının ortasında "Too many connections" yağmuru başladı. API sunucularını yatay büyütürken her yeni PHP-FPM worker'ının bir MySQL bağlantısı açtığını, veritabanının ise hâlâ tek olduğunu hesaba katmamıştık. Uygulama katmanı esner, veritabanı bağlantı tablosu esnemez. O gece max_connections'ı 500'den 1.200'e çekip günü kurtardık; kalıcı çözüm bağlantı havuzlaması ve worker bütçelemesiydi ki bu başlı başına bir yazı hak ediyor (yazdım da — bağlantı havuzu tükenmesinin anatomisi diye arayın). Ders üç: yatay ölçeklenen her katman, ölçeklenmeyen katmana açtığı kaynakları çarpan etkisiyle taşır.

Aynı haftalarda okuma yükünü de ayrıştırdık. Raporlama sorguları ile canlı yazma trafiği aynı makinede boğuşuyordu; bir okuma replikası açıp rapor motorunu tamamen oraya yönlendirdik. Buradaki tuzağı da yiyerek öğrendik: replikasyon gecikmesi tepe saatlerde 20-30 saniyeye çıkabiliyordu ve "az önce eklediğim araç raporda yok" şikâyetleri başladı. Çözüm zarifti sayılmaz ama işledi — rapor ekranına replika gecikmesini gösteren küçük bir "veriler şu ana kadar günceldir" ibaresi koyduk ve gecikme 60 saniyeyi aşarsa sorguları ana makineye geri düşürdük. Dağıtık sistemlerde dürüstlük, çoğu zaman tutarlılıktan ucuzdur.

Hafta 5-6: İnsan Darboğazı ve Ucuz Zaferler

Mayıs'a doğru sistemsel yangınlar söndü, sırayı insan süreçleri aldı. Yeni müşteri aktivasyonu elle yapılıyordu: cihaz tanımı, araç eşleme, kullanıcı açma. Normalde haftada 3-4 aktivasyon yapan operasyon ekibi, haftada 30 talep altında ezildi. İki günlük bir iç araçla aktivasyonu self-servis forma bağladık. Teknik borç listemizde iki yıldır duran bu madde, meğer en yüksek getirili "ölçekleme" işiymiş. Ders dört: darboğaz her zaman makinede değildir; süreç de kuyruk teorisine tabidir.

Aktivasyon aracının etkisini sayıyla vereyim: aktivasyon başına operasyon süresi 45 dakikadan 6 dakikaya indi, bekleyen talep kuyruğu iki haftada sıfırlandı ve o çeyrekte kaybettiğimiz tek bir yeni müşteri olmadı. Krizin ortasında yazılan en sıkıcı kod, en çok gelir koruyan kod oldu.

Bu arada ucuz zaferlerin dökümünü de vereyim, çünkü toplamı büyük: sorgu önbelleğine alınmış üç ağır dashboard sorgusu (dakikada 400 çalışıyordu, kimse fark etmemiş), sıkıştırılmadan dönen API cevapları (gzip ile ortalama cevap 5'te 1'e indi), 90 günü geçmiş verinin hâlâ ana tabloda tutulduğu iki müşteri özel tablosu. Hiçbiri mimari değişiklik değil; üçü birlikte tepe yükün yüzde 20'sini geri verdi.

Autoscaling Hayali ve 2020 Gerçeği

"Kubernetes'e koysaydınız otomatik ölçeklenirdi" diyenler oldu, o dönem biz de tartıştık. İki sebepten girmedik. Birincisi, darboğazlarımızın hiçbiri stateless katmanda değildi — API sunucusu klonlamak zaten kolaydı, batan hep veritabanı, disk ve kalıcı bağlantılardı; bunları hiçbir autoscaler kurtaramaz. İkincisi, telemetri yükü aslında tahmin edilebilir: cihaz sayısı sözleşmeyle artar, trafik güneşle döner (sabah 07:00 ve akşam 18:00 tepeleri şaşmaz). Bizim ihtiyacımız saniyeler içinde tepki veren sihir değil, haftalık ufukta doğru kapasite kararlarıydı. O gün kurduğumuz basit model — cihaz başına mesaj/sn, mesaj başına yazma maliyeti, yazma başına IOPS — sonraki her büyüme kararında Kubernetes'ten çok işimize yaradı.

Bir de işin insan-iletişim tarafı var, teknik günlüğe yazılmayan. Kriz haftalarında müşterilere hiçbir şey söylemedik ve bu hataydı; gecikmeyi fark eden operasyon müdürleri destek hattını kilitledi, destek ekibi mühendisleri bölmeye başladı, mühendisler yavaşladı — kendi kendini besleyen bir döngü. Nisan ortasında basit bir durum sayfası açıp gecikme yaşandığında oraya iki cümle yazmaya başladık. Destek çağrıları anında yarıya indi. Beş kuruşluk şeffaflık, iki sunucuya bedelmiş.

Haziranda döndük ve hasar tespiti yaptık: altı haftada bir kez 40 dakikalık kısmi kesinti (o max_connections gecesi), üç kez 10 dakikayı aşan gecikme, sıfır veri kaybı. Utanılacak bir karne değil; ama en kritik bulgu şuydu: yaşadığımız sorunların hepsinin işareti, krizden haftalar önce grafiklerde vardı. Kuyruk derinliğinin günlük tepesi, disk await'in yavaş tırmanışı, bağlantı sayısının doluluk oranı... hepsi mart başından itibaren düzenli tırmanıyordu ve alarma bağlı olmadıkları için kimse bakmıyordu. Grafiği olup alarmı olmayan metrik, çekilmemiş sigorta poliçesi gibidir: kâğıt var, koruma yok. Şimdi her pazartesi, dört kapasite grafiğine bakan yarım saatlik bir toplantımız var ve o toplantı, yaşadığım en ucuz sigorta.

Filonuz, trafiğiniz ya da müşteri tabanınız beklenmedik biçimde büyüyor ve neyin önce patlayacağını kestirmek istiyorsanız bir mesaj bırakın; darboğaz falcılığı, üç kat büyüme yaşamış birinden dinlenince daha ikna edici oluyor.

📅 Yayınlanma:  ·  Yakup Zengin