16 puan yazan GN⁺ 2026-05-04 | 11 yorum | WhatsApp'ta paylaş
  • Linux 7.0’da, önceki sunucu varsayılanı olan PREEMPT_NONE preemption modu kaldırılınca, aynı donanım üzerinde PostgreSQL throughput’unda yarı yarıya düşüşe varan ciddi bir performans regresyonu ortaya çıktı
  • Bir AWS mühendisi, 96-vCPU’lu Graviton4 makinede pgbench çalıştırdığında, Linux 6.x’e kıyasla Linux 7.0’da saniye başına işlem sayısının 98.565’ten 50.751’e düştüğünü ve CPU’nun %55’inin tek bir spinlock fonksiyonunda harcandığını gördü
  • PostgreSQL’in shared buffer pool erişimini koruyan spinlock, 4KB bellek sayfalarındaki minor page fault’larla birleşince, kilit tutulurken scheduler tarafından preemption yaşanması durumunda bekleyen tüm backend’ler CPU’yu boşa döndürmeye başlıyor
  • Huge Pages (2MB veya 1GB) etkinleştirildiğinde, potansiyel page fault sayısı 31 milyondan on binler ya da yüzler seviyesine düşerek regresyon ortadan kalkıyor
  • Kernel tarafı Restartable Sequences (rseq) kullanılmasını önerse de, PostgreSQL topluluğu kernel yükseltmesinden kaynaklanan performans düşüşünün başlı başına “userspace’i bozmaz” ilkesine aykırı olduğu görüşünde

Sorunun görünümü

  • AWS mühendisi Salvatore Dipietro, 96-vCPU Graviton4 işlemcide pgbench çalıştırarak scale factor 8.470 (yaklaşık 847 milyon satırlık tablo), 1.024 istemci ve 96 thread yapılandırmasıyla yüksek paralellikli yük testi yaptı
  • Linux 6.x’te 98.565 TPS, Linux 7.0’da 50.751 TPS ile throughput neredeyse yarıya düştü
  • perf profilleme sonucunda CPU süresinin %55,60’ının s_lock fonksiyonu içinde harcandığı görüldü
    • Çağrı yolu: StartReadBufferGetVictimBufferStrategyGetBuffers_lock

Preemption nedir?

  • OS scheduler’ın çalışan bir thread’i durdurup CPU’yu başka bir thread’e vermesi preemption olarak adlandırılır
  • Linux 7.0 öncesinde üç seçenek vardı
    • PREEMPT_NONE: Thread, gönüllü olarak CPU’yu bırakana kadar (syscall, I/O bloklanması, sleep) neredeyse hiç kesilmez. Geleneksel sunucu varsayılanıydı; context switch sayısı az, throughput yüksekti
    • PREEMPT_FULL: Güvenli kabul edilen hemen her noktada çalışan thread durdurulabilir. Yanıt süresi azalır ama context switch overhead’i artar. Geleneksel masaüstü varsayılanıydı
    • PREEMPT_LAZY: Linux 6.12’de gelen bir ara çözüm; doğal sınırları beklerken gerektiğinde preemption’a izin verir. PREEMPT_NONE’ın throughput özelliklerini yaklaşık olarak korumak için tasarlandı
  • Linux 7.0’da PREEMPT_NONE modern CPU mimarilerinde kaldırıldı ve geriye yalnızca PREEMPT_FULL ile PREEMPT_LAZY kaldı
    • PREEMPT_LAZY çoğu sunucu yazılımında yerine geçebilse de, PostgreSQL’de kritik bir fark yarattı

PostgreSQL bellek yönetimi

  • PostgreSQL, sabit boyutlu veri sayfalarını (varsayılan 8KB) temel depolama birimi olarak kullanır; tablo satırları, B-tree indeks düğümleri ve metadata bu sayfalarda tutulur
  • Disk okumalarını azaltmak için, yakın zamanda okunan veri sayfalarını büyük bir paylaşımlı bellek alanı olan shared buffer pool içinde cache’ler
  • Bir istemci bağlandığında özel bir backend process oluşturulur; buffer pool’da olmayan bir sayfa diskten okunur ve ardından boş ya da çıkarılabilir bir buffer bulunması gerekir
    • Bu buffer seçimini yapan fonksiyon StrategyGetBuffer’dır

PostgreSQL’in spinlock’ları

  • Spinlock, kilidi beklerken uyumak yerine döngü içinde sürekli kontrol eden bir kilit mekanizmasıdır
    • Çok kısa kritik bölgelerde, thread’i uyutup uyandırmanın maliyetinden daha verimli olabilir
  • Temel varsayım şudur: Kilidi tutan thread çok hızlı şekilde serbest bırakacaktır
  • StrategyGetBuffer, buffer seçimini korumak için tek bir global spinlock kullanır
    • 96-vCPU ve 1.024 istemcili ortamda tüm backend’ler aynı kilit için yarışır

Sanal bellek ve TLB

  • Tüm process’ler sanal bellek adresleri kullanır ve donanım bunları page table’lar (çok seviyeli ağaç yapısı) üzerinden fiziksel adreslere çevirir
  • Her seferinde page table üzerinde gezinmek yavaş olduğundan, CPU son çevirileri cache’leyen bir TLB (Translation Lookaside Buffer) bulundurur
    • TLB hit hızlı erişim sağlar; TLB miss olduğunda page table walk gerekir ve bu zaman alır
  • Linux, lazy allocation ilkesini kullanır; sanal bellek ayrıldığında gerçek fiziksel sayfa ilk erişimde eşlenir
    • İlk erişimde minor page fault oluşur: kernel fiziksel sayfayı ayırır, mapping’i kaydeder ve bu işlem normal okuma/yazmadan mikrosaniye düzeyinde daha yavaştır

4KB sayfaların sorunu

  • Benchmark’ta shared_buffers 120GB olarak ayarlandı; 4KB bellek sayfası bazında bu yaklaşık 31 milyon bellek sayfası, yani 31 milyon potansiyel ilk erişim page fault’u demek
  • 120GB shared buffer pool kullanan uzun süreli benchmark’larda yeni bellek bölgeleri working set’e sürekli girdiğinden, page fault’lar yalnızca başlangıçta değil, sürekli olarak meydana gelir
  • StrategyGetBuffer içinde spinlock tutulurken paylaşımlı belleğe erişildiğinde, ilgili bölge henüz eşlenmemişse minor page fault oluşur
  • PREEMPT_NONE (Linux 7.0 öncesi): Backend A page fault handler’a girse bile, gönüllü yeniden zamanlama noktalarından kaçındığı için fault çözülmeden schedule out edilme olasılığı düşüktür. Bekleme süresi uzar ama etkisi sınırlı kalır
  • PREEMPT_LAZY (Linux 7.0 sonrası): Scheduler, backend A’yı page fault handler içindeyken preempt edip başka bir process’i çalıştırabilir. Fault tamamlandıktan sonra bile scheduler kontrolü geri verene kadar ek bir t bekleme süresi oluşur
    • Bu ek süre yalnızca t değil, o anda spin halinde bekleyen tüm backend sayısı × t kadar CPU israfına dönüşür
    • 96-vCPU ve yüzlerce backend bulunan ortamda bu çarpan etkisi yıkıcı olur; sonuçta CPU’nun %56’sı s_lock içinde harcanır

Huge Pages ile çözüm

  • shared_buffers 120GB iken, bellek sayfası boyutu değiştirildiğinde potansiyel page fault sayısı dramatik biçimde azalır
    • 4KB sayfalar: ~31.000.000 potansiyel page fault
    • 2MB Huge Pages: ~61.440
    • 1GB Huge Pages: ~120
  • Sayfa boyutunun artması yalnızca page fault sayısını azaltmaz, aynı zamanda TLB baskısını da hafifletir: çok daha az TLB girdisiyle aynı bellek kapsanabildiği için TLB miss ve page table walk sayısı düşer
  • StrategyGetBuffer, kilit tutulurken fault üretmemeye başlar; böylece kilit sahibi hızlıca tamamlar, diğer backend’ler milisaniyeler yerine yalnızca mikrosaniyeler bekler. Regresyon ortadan kalkar
  • PostgreSQL’de huge pages ayarı huge_pages parametresiyle kontrol edilir
    • off, on, try (varsayılan) olmak üzere üç değer desteklenir
    • try, mümkünse huge pages kullanır; değilse sessizce 4KB’ye fallback yapar, bu da yanlış yapılandırmanın fark edilmemesi riskini doğurur
    • on olarak ayarlanırsa, huge pages kullanılamadığında PostgreSQL başlangıçta hata verir ve sorun hemen fark edilir
  • Trade-off: huge pages ön ayırma ve rezervasyonla çalışır; PostgreSQL hepsini kullanmasa bile bu bellek sistemin geri kalanı tarafından kullanılamaz. Sayfanın yalnızca bir kısmı kullanılırsa kalanı boşa gider. Ancak büyük shared_buffers kullanan üretim ortamlarında bu trade-off çoğunlukla kabul edilebilir

Sonraki gelişmeler

  • Preemption değişikliğini tasarlayan Intel kernel mühendisi Peter Zijlstra, PostgreSQL’in Restartable Sequences (rseq) benimsemesini önerdi
    • rseq, userspace kodunun kritik bölüm sırasında preemption veya migration olup olmadığını algılayıp ilgili bölümü yeniden başlatabilmesini sağlayan bir Linux kernel özelliğidir
    • PostgreSQL’in spinlock yoluna rseq uygulanırsa, preempt edilen kilit sahibinin bekleyen tüm backend’leri geciktirmesi senaryosundan kaçınılabilir
  • PostgreSQL topluluğunun tepkisi olumsuz oldu
    • Linux 7.0 öncesinde ücretsiz elde edilen performansı geri kazanmak için ek bir kernel özelliğinin benimsenmesi kabul edilebilir bulunmuyor
    • Bunun, kernel’in uzun süredir benimsediği “userspace’i bozmaz” ilkesine aykırı olduğu düşünülüyor; yani kernel yükseltmesinden önce düzgün çalışan yazılımın yükseltmeden sonra da düzgün çalışması gerekir

11 yorum

 
y15un 2026-05-04

Bunu nasıl düşünsem başlık yanlış görünüyor.

https://tr.news.hada.io/topic?id=28241#cid54772
Kernel maintainer'ının bunu postgres'e çok uzun zamandır tavsiye ettiği söyleniyorsa, aslında doğru başlık "Postgres'in Linux 7.0'da neden yavaşladığı" olur; 7.0'ın Postgres'i bozduğu değil.

Kernel teknik olarak semver'i birebir takip etmese bile bu bir major sürüm yükseltmesi; bunu kendi hatasının sonucunu yaşıyormuş gibi çerçevelemek mi??

 
domuji6 2026-05-05

rseq alternatif olarak önerilmiş olsa da, Linux’a özel kod eklemeyi gerektirmesi nedeniyle çapraz platformu gözetmek zorunda olan açık kaynak projeleri açısından kolayca kabul edilebilecek bir öneri gibi görünmüyor.

Büyük sürüm yükseltmelerinde davranış değişiklikleri olması anlaşılır, ancak sonuçta %50 performans düşüşü yaşandıysa, altyapı işletenler açısından çekirdek yükseltmesinin kendisine temkinli yaklaşmaktan başka seçenek kalmıyor gibi görünüyor.

 
y15un 2026-05-06

Vay, iş seyahatindeyken yorum yazıp otele gelince üç kişinin birden görüş bildirdiğini gördüm. Teşekkür ederim.

Belirttiğiniz bakış açısını ben de fazlasıyla anlıyorum, ancak ben bunu yine de PostgreSQL tarafındaki bir teknik borç olarak görüyorum; sonuçta bunun düğümünü çözmesi gereken tarafın PostgreSQL olduğunu düşünüyorum. (Anlık performans için hack vb. yöntemler kullanıp sonradan bedelini ödemenin nasıl bir şey olduğunu Spectre ile fazlasıyla görmüş gibiyiz...)
Sonuçta bu konuyu bir süre daha izleyip görmek gerekecek gibi duruyor.

İyi günler dilerim. :)

 
ilsubyeega 2026-05-06

Katılıyorum. Intel'deki Linux mühendislerinin emekli olduğuna dair haberleri sık sık görüyorum; mevcut davranışları böyle bırakmaya devam ederlerse bir gün Windows gibi olacaktır o,o..

 
xenoside 2026-05-05

Bu uygulamanın ilgili kısmı zaten en iyi performans için platforma özel assembly kodlarıyla dolu, bu yüzden
belirli bir platforma özel kod eklenecek olması bunun yapılamayacağına dair bir gerekçe olamaz gibi görünüyor.
(Gemini'ye sorup özetledim.)

 
nanashi222 2026-05-06

Koda baktım; o kadar da assembly koduyla dolu bir fonksiyon değil ve assembly koduyla dolu olmasının da belirli bir platforma özel kod eklemenin sorun olacağı anlamına geldiğini düşünmüyorum. Assembly kodu denilen şeyin atomik işlem fonksiyonları (GCC'nin yerleşik __atomic__ fonksiyonları) olduğu anlaşılıyor ama yalnızca fonksiyonun içine bakınca Linux için özel olarak kod eklemek mümkün görünmüyor.

 
jjw9512151 2026-05-11

Açık kaynak olduğu için bunu değiştirip test etmek de muhtemelen külfetli olacaktır.. Kullanıcı da çok fazla

 
hiseob 2026-05-05

Kesin konuşmak gerekirse, Linus tanrısının eskiden WE DO NOT BREAK USERSPACE! diye azarlayıp çıkıştığı bir durum olmuştu; o yüzden bunun için bir seçenek vermek gerekir mi diye düşündürüyor.
Ama öte yandan userspace'te ille de inatla spinlock kullanacağız demek de pek mantıklı gelmiyor.
Bende bıraktığı his bu yönde.

 
cafedead 2026-05-04

"Userspace'i bozma" vs "Userspace'te spinlock yapma"

 
savvykang 2026-05-05

30 yıl boyunca OS işlevlerini kısmen yeniden icat ederken kimsenin sınırları ve nedenlerini belgelemeyi düşünmemiş olması bana pek anlaşılır gelmiyor. 30 yıl önce kendi senkronizasyonunu, kendi bellek yönetimini, hatta kendi süreç modelini oluşturmak için kesinlikle makul nedenler vardı.

 
hungryman 2026-05-05

Tam da yapay zeka çağındayız; buna rağmen böyle olumsuz tepkilerin olması, mimari açıdan o tarafta işlerin karmaşık biçimde düğümlendiği anlamına mı geliyor?