<

Sıcak Tablo, Soğuk Disk: InnoDB Buffer Pool'u Okumayı Öğrenmek

Şikâyet tuhaf bir düzenlilikteydi: uygulama her sabah 06:00 ile 06:40 arasında yavaşlıyor, sonra kendi kendine düzeliyordu. Gündüz p95 gecikmesi 80 milisaniyeyken o pencerede 900 milisaniyeye çıkıyordu. İlk şüpheli, o saatte koşan zamanlanmış işlerdi; tek tek kapattık, yavaşlık yerinde durdu. İkinci şüpheli sabah trafiğiydi; ama trafik 08:00'de zirve yaparken sorun 06:40'ta bitiyordu. Suçlu, gecenin içinde saklanıyordu: 02:00'de başlayıp 05:50'de biten veritabanı yedeği.

Yedeğin CPU'su ve IO'su gece bittiğine göre sabahki yavaşlıkla ilgisi ne olabilirdi? İlgisi bellekti. mysqldump bütün tabloları baştan sona okurken InnoDB buffer pool'daki sıcak sayfaları — gündüz trafiğinin sürekli dokunduğu indeks ve veri sayfalarını — dışarı itmişti. Sabahın ilk kullanıcıları geldiğinde, dün akşam bellekte olan her şey artık diskteydi. 06:00-06:40 penceresi, havuzun yeniden ısınma süresiydi.

Hit Ratio'nun Aldatıcı Virgülleri

Teşhisi kesinleştiren şey buffer pool metrikleriydi ve burada öğrendiğimiz ilk ders ölçekle ilgili. Innodb_buffer_pool_read_requests toplam sayfa isteğini, Innodb_buffer_pool_reads bunlardan diske inmek zorunda kalanları sayar. Gündüz oranımız yüzde 99.97'ydi; sorunlu pencerede 98.9'a düşüyordu. İki sayı da "99 civarı, gayet iyi" gibi okunur. Oysa aradaki fark, disk okumalarının 35 kat artması demek. Buffer pool sağlığına hit ratio yüzdesiyle değil, saniyedeki fiziksel okuma sayısıyla bakmayı o hafta öğrendik: bizde normal seyir saniyede 60 fiziksel okumayken, ısınma penceresinde 2.100'e fırlıyordu. Grafiğe bu metriği koyduktan sonra sorun çıplak gözle görünür oldu.

InnoDB Aslında Bu Saldırıyı Biliyor

İşin ilginci, InnoDB'nin tam da bu senaryo için bir savunması var. Buffer pool LRU listesi ikiye bölünmüştür: genç (sıcak) ve yaşlı bölge. Diskten okunan yeni sayfa önce yaşlı bölgeye girer; genç bölgeye terfi etmesi için, innodb_old_blocks_time süresi (varsayılan 1 saniye) geçtikten sonra ikinci bir dokunuş görmesi gerekir. Tek geçişlik tarama trafiği böylece sıcak bölgeye hiç bulaşmadan yaşlı bölgeden akıp gider. Peki bizde neden işlemedi? Ölçek yüzünden: yedek üç saat 50 dakika sürüyor ve bu sürede yaşlı bölgeden geçen devasa hacim, havuzun yüzde 37'lik yaşlı payını sürekli çalkalıyordu; yaşlı bölgede bekleyen ılık sayfalar da tarama seline kapanıp gidiyordu. Savunma mekanizması, kuşatmanın büyüklüğü karşısında kısmen yetersiz kalmıştı.

Aradaki bağlantıyı kurduktan sonra bile bir doğrulama adımı gerekiyordu; korelasyon sebep değildir. Kanıtı ucuz bir deneyle topladık: bir gece yedeği bilerek atlattık ve ertesi sabah 06:00 penceresi tertemiz geçti. Ertesi gece yedek geri geldi, yavaşlık da geri geldi. İki gecelik bu deney, haftalarca sürebilecek "belki de başka bir şeydir" tartışmasını kökten bitirdi. Üretimde deney yapmak gözü kara bir iş gibi anlatılır; oysa kontrollü, geri alınabilir ve tek değişkenli olduğu sürece, teşhisin en dürüst aracı.

Üç Hamlelik Onarım

Birinci hamle en temizi: yedeği master'dan almayı bıraktık. Yedek artık bir replikadan alınıyor; replika sabaha kadar soğusa kimin umurunda, onun buffer pool'u kullanıcı trafiği taşımıyor. İkinci hamle, buffer pool boyutuydu. Sunucuda 32 GB RAM varken havuz 12 GB'ta duruyordu; yıllar önce, makinenin başka işler de yaptığı dönemden kalma bir ayar. Aktif veri kümemizi ölçtük — sık dokunulan tablolar ve indeksler yaklaşık 19 GB tutuyordu — ve havuzu 24 GB'a çıkardık. Üçüncü hamle küçük ama zarif: innodb_buffer_pool_dump_at_shutdown ve load_at_startup ayarları. MySQL artık kapanırken sıcak sayfa listesini diske yazıyor, açılışta aynı listeyi geri yüklüyor. Bakım amaçlı yeniden başlatmalardan sonraki ısınma sancısı — eskiden yarım saat süren o tutuk dönem — birkaç dakikaya indi.

Buffer pool'u büyütürken iki ayrıntı daha öğrendik; ikisi de "büyüt geç" kadar basit değil. Birincisi, havuzu büyütmek bedava değil: InnoDB'nin sayfa tarama ve temizlik işleri havuz boyutuyla birlikte büyür ve 5.7 sonrasında boyut çevrimiçi değiştirilebilse de, değişim sırasında kısa duraksamalar olabiliyor; biz bu işlemi yine de düşük trafik saatine aldık. İkincisi, birden çok buffer pool instance ayarı: 24 GB'lık havuzu 8 instance'a böldük ki eş zamanlı erişimlerdeki mutex çekişmesi azalsın. Bu ayarın etkisini sentetik testte ölçtük; yüksek eşzamanlılıkta yüzde 6-8'lik bir iyileşme, dramatik değil ama bedava. Dirty page tarafında ise innodb_max_dirty_pages_pct ile oynamak yerine redo log boyutunu büyütmek daha çok işe yaradı; checkpoint baskısı azalınca yazma sivrilikleri yumuşadı.

Yedek yöntemini de bu vesileyle değiştirdik. mysqldump'ın derdi yalnızca havuzu yıkamak değil; mantıksal yedek olduğu için geri dönüşü de saatler sürüyor. Replika üzerinde xtrabackup'a geçtik: fiziksel kopya, buffer pool'a neredeyse hiç dokunmuyor ve geri yükleme, dosyaları yerine koymak kadar hızlı. Geri dönüş süresini gerçekten ölçmek için bir tatbikat yaptık: 300 GB'lık yedekten tam işlevli bir sunucuya dönüş 40 dakika. Bu sayıyı bilmek, herhangi bir kriz gecesinde panik ile plan arasındaki farkı oluşturuyor.

Havuzu Panoya Bağlamak

Havuz boyutunu belirlerken kullandığımız ölçüm de bir cümleyi hak ediyor: "aktif veri kümesi" dediğimiz 19 GB, tablo boyutlarının toplamı değil, bir hafta boyunca gerçekten dokunulan sayfaların toplamı. Toplam veri 300 GB'ın üzerindeydi; hepsini belleğe sığdırmaya çalışmak hem imkânsız hem gereksizdi. Bellek planlaması veriye değil, erişim desenine göre yapılıyor.

Bu vakadan sonra buffer pool, "kur ve unut" ayarı olmaktan çıkıp sürekli baktığımız bir gösterge oldu. Panoda artık dört eğri duruyor: saniyedeki fiziksel okuma, genç/yaşlı bölge terfi oranı, free list uzunluğu ve dirty page yüzdesi. Bu eğrilerin her tuhaf hareketi bir hikâye anlatıyor. Bir keresinde fiziksel okumaların öğlen saatlerinde sebepsiz tırmandığını gördük; izini sürünce, yeni eklenen bir raporun indekssiz bir sütunda full scan yaptığını ve her çalıştığında havuzu yıkadığını bulduk. Sorgu daha kimse şikâyet etmeden yakalandı ve düzeltildi. Yavaşlık şikâyeti gelmeden sorun bulmak, bu mesleğin nadir zevklerinden.

Bu arada işin adli tıp kısmına merak salanlara bir not: hangi tabloların havuzda ne kadar yer kapladığını information_schema.innodb_buffer_page üzerinden görebilirsiniz. Sorgunun kendisi büyük havuzlarda pahalıdır, üretimde sık koşulacak şey değildir; ama haftalık tek bir fotoğraf bile çok şey anlatıyor. Bizim ilk fotoğrafımız utandırıcıydı: havuzun yüzde 11'ini, iki yıldır kimsenin sorgulamadığı bir eski bildirim log tablosunun indeksi işgal ediyordu — gecelik bir işin gereksiz yere taradığı bir tablo. İş düzeltildi, tablo arşive alındı ve 2.6 GB'lık bellek, gerçekten sıcak veriye geri döndü. Bellek de disk gibi çöp biriktiriyor; arada envanter çıkarmak gerekiyor.

Özetlemem gerekirse: veritabanınızın gerçek performans sınırı çoğu zaman diskin hızı değil, belleğin neyi hatırladığıdır. Ve belleğin neyi hatırladığını değiştiren şeyler — yedekler, migration'lar, full scan'ler, yeniden başlatmalar — çoğunlukla sorgu istatistiklerinde görünmez, çünkü hasarı kendileri değil, kendilerinden sonra gelenler yaşar. Sabahları sebepsiz yavaşlayan bir sisteminiz varsa gece kimin belleği yıkadığına bakın. Bulamazsanız yazın, beraber bakalım.

📅 Yayınlanma:  ·  Yakup Zengin