Zig'inlibc Yeniden Yazımı SQLite Performansını 12,000 Kat Daha Yavaş Yaptı

Zig'inlibc Yeniden Yazımı SQLite Performansını 12,000 Kat Daha Yavaş Yaptı
audio-thumbnail
Zig'inlibc Yeniden Yazımı SQLite Performansını 12,000 Kat Daha Yavaş Yaptı
0:00
/0

Zig'inlibc Yeniden Yazımı SQLite Performansını 12,000 Kat Daha Yavaş Yaptı

2026 yılında, teknoloji dünyasında yine aynı eski sorunlar ortaya çıktı. Bu seferki hikâye, bir satranç analiz hizmetinin performansını kilitlenmeye iten, beklenmedik bir performans düşüşünden kaynaklanıyor. Ve merak edilecek bir şey varsa, bu sorunun kökeni, oldukça modern ve gelişmiş bir dille yazılmış bir kütüphanede yatıyordu: Zig.

Bu hikâye, Joel Spolsky'ın 2001'de yazdığı "Back to Basics" adlı ünlü makalesinde tanımladığı, "Shlemiel the Painter" (Şlemiel, Boyama Makinesi) modelini yeniden canlandırıyor. Bu model, küçük bir tasarım seçimi, uygulama boyunca karmaşık ve tahmin edilemez bir performans çöküşüne yol açabileceğini gösteriyor. Ve bu kez, bu hikâye, Zig'in libc kütüphanesinin yeniden yazımıyla başlıyor.

Bir Hizmetin Performansı Kilitleniyor

Bir satranç analiz hizmeti geliştiriyorum. Bu hizmet, Rust ile yazıldı ve SQLite veritabanı ile çalışır. Çalışma mantığı şu: bir dizi satranç oyunu işlemek için çalışanlar (runners) kullanılır. Bu çalışanlar, her biri yüzlerce oyunu işler ve sonuçları JSON olarak geri döndürür. Ardından, ana hizmet bu verileri işler ve SQLite'ya kaydeder.

Her şey çok güzel ve hızlı görünürken, bir gün büyük bir oyun seti ile karşılaştığımda, işin sıradan bir kısmı olan JSON verilerini SQLite'ya kaydetme işlemi, dakikalarca süremeye başladı. 40,000'den fazla kayıt, saniyeler içinde kaydedilmesi gereken bir işlem, dakikalar alıyordu.

Bu durum, CPU kullanımının tek bir çekirdeği tamamen kaplamasına neden oldu. Yani, bu işlem tamamen CPU yoğunuydu. Peki, neden sadece SQLite'ya veri kaydetme işlemi bu kadar uzun sürebilirdi?

Sorun, Zig'in cc Derleyicisinde Yatıyordu

Hizmetimiz Rust ile yazılmıştı, ancak SQLite, C dilinde yazılmıştır. Bu yüzden, hizmetin derlenmesi sırasında SQLite'nın C kodunu derlemek gerekiyordu. Bu işlemi kolaylaştırmak için, Zig’in cc derleyicisi kullanıldı. Zig, farklı platformlara (Mac ARM64, Linux ARM64, x86-64) kolayca derleme yapabilmeyi sağlar. Bu süreçte, Zig'in kendi libc kütüphanesi olan libzigc, SQLite'nın derlenmesi sırasında kullanıldı.

Bu, aslında çok akıllıca bir çözüm gibi görünüyor. Zig, geleneksel C libc kütüphanesine bağımlılığı azaltarak, daha temiz ve daha güvenilir bir yapı sunuyor. Ancak, bu yeniden yazım sürecinde, bir kazara geliştirilmiş bir fonksiyon, büyük bir performans sorununa yol açtı.

Shlemiel the Painter: Tekrar Başlıyor

Zig, libc kütüphanesinin bir kısmını kendi dilinde yazarak (libzigc) yeniden oluşturuyor. Bu süreçte, strspn fonksiyonu gibi bazı temel string işlevleri yeniden yazıldı. Ancak, bu yeniden yazımın bir hata sonucu, her tekrar eden döngüde, tüm stringin uzunluğunu tekrar ölçmek gibi bir davranış ortaya çıktı.

Bu nasıl olur? Imagine edin, bir JSON dosyasını işlemek için, bir tokenizasyon işlemi (kelimeleri ayırmak) gerekiyor. Bu işlem, bir döngü içinde tekrar edilir. Her adımda, boşlukları atlamak, yeni bir metin konumuna geçmek gerekir. Ancak, strspn fonksiyonu bu süreçte her seferinde tüm stringin uzunluğunu yeniden ölçer. Yani, 1.2MB'lik bir dosyayı işlerken, ilk adımda 1.2MB'yi, ikinci adımda 1.199MB'yi, üçüncüde 1.198MB'yi, vs. ölçmeye çalışır.

Bu, kuadratik zaman karmaşıklığına (O(n²)) yol açar. Yani, veri boyutu arttıkça, süre çok hızlı artar. Bu, Shlemiel the Painter modelinin tam olarak örneği.

Neden Mac'te Sorun Yoktu?

Sorun, yalnızca Linux üzerinde ortaya çıkmıştı. Neden? Çünkü benim geliştirme ortamım, Mac üzerindeydi. Mac'te, Zig kullanılmadı; yerine geleneksel C libc kullanıldı. Bu yüzden, bu performans sorunu Mac'te görülmedi. Ancak, Linux üzerinde bu kod derlenirken, Zig'in cc derleyicisi ve libzigc kullanıldığı için, bu hata ortaya çıktı.

Çözüm: Profiller ve Hızlı Uygulamalar

Sorunu çözmek için önce profiler kullanıldı. Profil, işlemcinin nerede çok fazla çalıştığını gösterdi. Bu, SQLite'nın içindeki tokenizasyon sürecini işaret etti. Ardından, çözüm olarak birkaç yol denendi:

  • JSON verilerini önceden temizleme (whitespace atma): Bu, tokenizasyon sürecini daha az veri ile yapmaya yönlendirerek performansı artırdı.
  • Linux build süreçlerini Docker konteynerlerinde yapma: Bu, Zig'in etkisini dışlamak için bir geçici çözüm oldu.

Ancak, çözümün kalıcı olması için, Zig'in libzigc kütüphanesindeki bu fonksiyonun yeniden yazılması gerekiyor. Bu, kütüphane geliştiricilerinin dikkatli olması gerektiğini gösteriyor.

Tarihin Tekrar Eden Öyküsü

Bu durum, tarihin bir kez daha tekrar ettiği bir örnek. 2000'li yıllarda Firefox, string birleştirme işlemi sırasında Shlemiel the Painter modeliyle karşı karşıya kalmıştı. Her string birleştirme işlemi, yeni bir bellek bloğu oluşturup, eski veriyi kopyalayıp, yeni bloğa aktarıyordu. Bu, çok fazla zaman harcarıyordu.

Ancak, geliştiriciler bu modeli fark ettikten sonra, büyük bir boyutlarda önceden bellek ayırma (overallocation) yöntemini uyguladılar. Bu, Firefox'ın performansını ciddi şekilde artırdı ve hatta Sunspider testinde lider konumuna geldi.

Bugün de aynı şey oluyor. Çok küçük bir işlem, çok büyük bir sistemde dramatik bir performans düşüşüne yol açabiliyor.

Sonuç: Temel Bilgileri Unutmayalım

Bu hikâye, bize şu mesajı veriyor: Her zaman temel algoritmaları ve karmaşıklıkları bilin. Geliştirme sürecinde, en küçük işlem bile, büyük bir uygulamada kritik bir rol oynayabilir.

Zig, çok güçlü bir dil ve gelişmiş bir ekosistem sunuyor. Ancak, bu gibi yeniden yazılmış kütüphaneler, gizli performans tuzaklarına da yol açabilir. Bu nedenle, profiling araçlarını kullanmak, kodun içindeki her adımı kontrol etmek ve her küçük adımın potansiyel etkisini değerlendirmek, bir geliştiricinin en önemli becerilerinden biri olmalıdır.

2026 yılında bile, "Shlemiel the Painter" hikâyesi, bir kez daha devreye giriyor. Ve bu sefer, Zig'inlibc yeniden yazımı, SQLite'yi 12,000 kat daha yavaş yaptı.