2 puan yazan GN⁺ 2024-12-17 | Henüz yorum yok. | WhatsApp'ta paylaş
  • Sunucusuz ve edge ortamlarda birden fazla SQLite örneği birlikte çalıştırıldığında senkron I/O beklemesi kuyruk gecikmesini artırıyor; Helsinki ve Cambridge araştırmacıları bunu asenkron I/O ve depolama ayrıştırmasıyla azaltma yöntemini denedi
  • Linux io_uring, gönderim kuyruğu ve tamamlama kuyruğu üzerinden I/O istekleri sırasında uygulamanın başka işleri sürdürmesini sağlayarak thread bloklanmasını azaltan bir temel sunuyor
  • SQLite, sqlite3_step() çalışırken gereken B-Tree sayfası önbellekte yoksa POSIX read() gibi senkron I/O ile diski okuyor; I/O bitene kadar thread duruyor
  • Araştırmacılar yalnızca POSIX çağrılarını değiştirmek yerine, Rust tabanlı yeniden yazım projesi Limbo içinde VM ve BTree’yi asenkron yürütme modeline uyacak şekilde değiştirdi
  • Benchmark’larda p999 kuyruk gecikmesi en fazla 100 kat azaldı; ancak p90 ve p99 SQLite ile neredeyse aynı, çoklu reader/writer değerlendirmesi ise gelecekteki çalışma olarak kalıyor

SQLite’ı daha hızlı yapmaya yönelik araştırma

  • University of Helsinki ve Cambridge araştırmacıları, “Serverless Runtime / Database Co-Design With Asynchronous I/O” çalışmasında SQLite’a asenkron I/O ve depolama ayrıştırması uygulama yöntemini ele alıyor
  • Bu makale, Rust tabanlı SQLite yeniden yazım projesi Limbo için temel oldu
  • Bir workshop makalesi olduğu için kısa; odak noktası sunucusuz ve edge computing
  • Temel fikir şu: SQLite’ın kendisi zaten hızlı olsa bile, çok kiracılı ortamlardaki kuyruk gecikmesi yürütme modeli değiştirilerek daha da azaltılabilir

io_uring’in azalttığı I/O beklemesi

  • Linux çekirdeğindeki io_uring, asenkron I/O arayüzü sağlar
  • Adı, kullanıcı alanı ile çekirdek alanı tarafından paylaşılan ring buffer’dan gelir ve iki alan arasındaki buffer kopyalama ek yükünü azaltır
  • Uygulama bir I/O isteği gönderdikten sonra, OS tamamlandığını bildirene kadar başka işleri paralel yürütebilir
  • Çalışma akışı şöyledir
    • io_uring_setup() sistem çağrısıyla gönderim kuyruğu ve tamamlama kuyruğu adlı iki bellek alanı ayarlanır
    • Uygulama, gönderim kuyruğuna I/O isteğini koyar ve io_uring_enter() ile OS’ye işlemeyi başlatmasını bildirir
    • read() ve write() gibi thread’i bloklamadan denetimi kullanıcı alanına geri verir
    • Uygulama başka işler yaparken, I/O tamamlanmasını kontrol etmek için tamamlama kuyruğunu periyodik olarak poll eder

SQLite sorgu yürütmesinde senkron I/O darboğazı

  • SQLite uygulaması, veritabanı dosyasını sqlite3_open() ile açar; bu süreçte POSIX open gibi düşük seviyeli OS I/O çağrılır
  • sqlite3_prepare(), SELECT, INSERT gibi SQL ifadelerini bir bytecode komutları dizisine dönüştürür
  • sqlite3_step(), sorgunun okuyacağı satırı üretinceye veya yürütme bitinceye kadar bytecode komutlarını çalıştırır
    • Okunacak satır varsa SQLITE_ROW döndürür
    • İfade tamamlandığında SQLITE_DONE döndürür
  • Yürütme sırasında backend pager çağrılır ve tablolar ile satırları temsil eden B-Tree dolaşılır
  • Gereken B-Tree sayfası SQLite sayfa önbelleğinde yoksa disk erişimi gerçekleşir
    • SQLite, POSIX read gibi senkron I/O ile sayfa içeriğini diskten belleğe okur
    • Bu sırada sqlite3_step() çekirdek thread’ini bloklar
    • I/O beklerken eşzamanlı iş yapmak için uygulamanın daha fazla thread kullanması gerekir

Sunucusuz ve edge’de SQL’i gömme nedeni

  • Sunucusuz computing edge’de çalıştığında ve veritabanı bulut ortamında olduğunda, sunucusuz fonksiyon ile bulut arasında ağ gidiş-dönüş maliyeti oluşur
  • Veriyi edge’e birlikte yerleştirme yöntemi de var; ancak daha iyi bir yaklaşım olarak veritabanını edge runtime içine gömmek öneriliyor
  • Cloudflare Workers zaten bu biçimi sağlıyor, fakat KV arayüzü sunuyor
  • KV her problem alanına iyi uymaz
    • Tablo biçimli verileri KV modeline eşlemek geliştirici deneyimini kötüleştirir
    • Serileştirme ve deserileştirme maliyeti de oluşur
  • SQL daha uygun olabilir; SQLite gömülü bir veritabanı olduğundan sunucusuz runtime’a doğrudan dahil edilebilir

SQLite’ı basitçe io_uring’e geçirmek neden zor

  • SQLite, geleneksel POSIX read() ve write() tabanlı senkron I/O kullanır
  • Küçük uygulamalarda büyük sorun olmasa da, tek bir sunucuda yüzlerce SQLite veritabanı çalıştırıldığında darboğaz olabilir
  • Sunucu kaynak kullanımının maksimize edilmesi gereken ortamlarda senkron I/O bir kısıt haline gelir
  • SQLite’ta eşzamanlılık ve çok kiracılık sorunları var
    • I/O senkron ve bloklayıcı olduğu için aynı makinedeki uygulamalar kaynaklar için rekabet eder
    • Sonuç olarak gecikme artar
  • POSIX I/O çağrılarını basitçe io_uring ile değiştirmek zordur
    • Bloklayıcı I/O kullanan uygulamaların io_uring’in asenkron I/O modeline uyacak şekilde yeniden tasarlanması gerekir
    • SQLite kütüphanesi, I/O devam ederken denetimi uygulamaya geri verebilmelidir
  • Araştırmacılar SQLite’ın yalnızca bazı çağrılarını değiştirmek yerine, SQLite’ı Rust ile yeniden yazıp io_uring kullanma yaklaşımını seçti

Limbo’nun asenkron yürütme modeli

  • Limbo, SQLite’ın Rust ile yeniden yazıldığı bir projedir; VM ve BTree bileşenleri asenkron I/O destekleyecek şekilde değiştirildi
  • Senkron bytecode komutları, asenkron karşılıklarıyla değiştirildi
  • Örneğin Next komutu imleci ilerletir ve gerekirse sonraki sayfayı getirir
    • Mevcut senkron sürümde disk I/O oluşursa, sayfayı okuyup çağırana döndürünceye kadar bloklanır
    • Asenkron sürümde NextAsync gönderildikten sonra hemen döner
    • Çağıran taraf daha sonra bloklanabilir veya başka işler yapabilir
  • Asenkron I/O bloklamayı ortadan kaldırır ve eşzamanlılığı iyileştirir
  • Kaynak kullanımını daha da artırmak için sorgu motoru ile depolama motorunu ayıran depolama ayrıştırması da öneriliyor
  • İlgili açıklama olarak Disaggregated Storage - a brief introduction da bağlantılanmış

Benchmark sonuçları ve kalan sorular

  • Benchmark, çok kiracılı sunucusuz runtime’ı simüle ediyor
  • Her kiracının kendi gömülü veritabanına sahip olduğu bir kurulum kullanılıyor
  • Kiracı sayısı 1’den 100’e kadar 10’luk adımlarla değiştiriliyor
  • SQLite, her kiracı için ayrı thread kullanıyor ve ölçüm her thread’de sorgu çalıştırılarak yapılıyor
  • Çalıştırılan sorgu SELECT * FROM users LIMIT 100 ve 1000 kez tekrarlanıyor
  • Limbo da aynı deneyi yürütüyor, ancak Rust coroutine’leri kullanıyor
  • Sonuç olarak p999’da kuyruk gecikmesi en fazla 100 kat azaldı
  • SQLite sorgu gecikmesi, thread sayısının artmasıyla kademeli biçimde kötüleşmedi
  • Çalışma hâlâ sürüyor ve makalede birkaç açık soru kalıyor
    • Future Work bölümünde birden fazla reader ve writer içeren ek benchmark’lar ele alınıyor
    • Fayda yalnızca p999 sonrasında belirginleşiyor
    • p90 ve p99 performansı SQLite ile neredeyse aynı
  • Limbo kodu açık kaynak olarak yayımlanmış durumda
  • Limbo şu anda resmi bir Turso projesi ve tanıtım yazısı da yayımlandı

Henüz yorum yok.

Henüz yorum yok.