1 puan yazan GN⁺ 2024-09-13 | 1 yorum | WhatsApp'ta paylaş
  • Rails’te SQLite’ı varsayılan haliyle üretim ortamında kullanmak hâlâ yeterli değil; ancak birkaç ayar ekleyerek yüksek performanslı ve dayanıklı uygulamalar oluşturmak mümkün
  • Eşzamanlı yazma yükü altında SQLite’ın global yazma kilidi nedeniyle SQLITE_BUSY oluşur; Rails’in tipik yazma transaction akışı da varsayılan deferred moduyla pek iyi uyuşmaz
  • sqlite3-ruby 1.6.9’dan itibaren database.yml içinde varsayılan transaction modu IMMEDIATE olarak değiştirilebilir; böylece yazma kilidi alma başarısızlıklarında daha güvenli biçimde bekleme ve yeniden deneme yapılabilir
  • busy_timeout, bekleme sırasında bile Ruby GVL’yi tuttuğu için Puma paralelliğini zedeler; busy_handler içinde sleep ile GVL’yi bırakıp 1 ms aralıklı yeniden deneme kullanmak uzun kuyruk gecikmelerini azaltır
  • WAL modu, IMMEDIATE transaction’lar, GVL’yi serbest bırakan busy handler, Rails 7.1’in SQLite varsayılan ayarları ve isteğe bağlı okuma/yazma connection pool ayrımı, Rails üzerinde SQLite performansını iyileştirmenin ana eksenleridir

Varsayılan Rails + SQLite ayarlarında görülen sınırlar

  • Rails uygulamasını SQLite tabanlı olarak performanslı ve dayanıklı biçimde çalıştırmak için mevcut varsayılan ayarlar tek başına yeterli değildir
  • Amaç, Rails 8’de SQLite’ın varsayılan deneyimini üretim ortamına hazır hale getirmektir
  • Demo uygulaması Lorem News’tur
    • Kullanıcılar, gönderiler ve yorumlar içeren temel bir Hacker News klonudur
    • İçerik Lorem Ipsum’dan oluşur
  • Yük testlerinde oha load testing CLI ve uygulamaya yerleşik benchmarking routes kullanılır
  • post#create endpoint’ine 5 saniye boyunca sıralı istek gönderildiğinde RPS istikrarlıdır ve tüm istekler başarılı olur
  • Aynı endpoint’e 5 saniye boyunca 4 eşzamanlı istek gönderildiğinde bazı istekler 500 yanıtı döndürür

SQLITE_BUSY ve IMMEDIATE transaction’lar

  • Loglarda ilk görülen sorun SQLITE_BUSY istisnasıdır
  • SQLite, aynı anda yalnızca tek bir yazma işlemine izin vermek için veritabanında yazma kilidi kullanır
    • Aynı anda yalnızca bir connection yazma kilidine sahip olabilir
    • Başka bir connection kilidi tutarken yeni bir connection yazma kilidi almaya çalışırsa SQLITE_BUSY oluşur
  • Rails uygulaması daha büyük eşzamanlı yük aldığında SQLITE_BUSY nedeniyle başarısız olan istek oranı da artar
  • SQLite’ın varsayılan transaction modu deferred’dır ve gerçek yazma işlemi gerçekleşene kadar kilit almaz
    • Tek connection’lı ortamlarda veya salt-okunur transaction’ların çok olduğu ortamlarda performans açısından avantajlıdır
    • Üretimdeki Rails uygulamaları birden çok thread’de birden çok connection kullanır ve Rails yazma sorgularını transaction içine aldığı için bu varsayılanla çatışır
  • Transaction ortasında yazma kilidi alma başarısız olursa SQLite, serializable isolation’ı bozmamak için ilgili sorguyu güvenli biçimde yeniden deneyemez ve hemen istisna fırlatır
  • IMMEDIATE transaction, transaction başlar başlamaz yazma kilidi almaya çalışır
    • SQLite yazma sorgusunu kuyruğa alıp daha sonra yeniden kilit almayı deneyebilir
    • Rails’in yazma transaction kalıbı için bu yöntem deferred’dan daha uygundur
  • sqlite3-ruby gem’i 1.6.9’dan itibaren varsayılan transaction modu ayarını destekler
    • Rails, database.yml içindeki üst seviye anahtarları sqlite3-ruby veritabanı ilklendirmesine iletir
    • database.yml içinde Rails’in SQLite transaction’larını IMMEDIATE modunda çalışacak şekilde ayarlayabilirsiniz
  • Bu değişiklikten sonra basit yük testlerinde eşzamanlı yük altında neredeyse hiç 500 hatası olmadan işlem yapılır; ancak 16 eşzamanlı istekte yeniden bazı hatalar görülür

busy_timeout, GVL, özel busy_handler

  • Bir sonraki darboğaz, eşzamanlı istek sayısı Puma worker sayısına yaklaştığında veya onu aştığında p99 gecikmesinin keskin biçimde artmasıdır
  • Gerçek istek işleme süresi, Puma worker sayısının 3 katı eşzamanlı yükte bile istikrarlıdır; ancak yaklaşık 5 saniye süren istekler oluşmaya başladığında bunlarla birlikte SQLITE_BUSY 500 yanıtları da ortaya çıkar
  • Bu 5 saniye, database.yml içindeki timeout ayarıyla eşleşir ve SQLite’ın busy_timeout ayarına map edilir
  • busy_timeout, hemen BUSY istisnası fırlatmak yerine belirtilen milisaniye kadar bekleyerek yazma kilidini yeniden almaya çalışır
    • SQLite, kilidi yeniden almak için bir tür üstel backoff yöntemi kullanır
    • Yalnızca timeout içinde kilit alınamazsa BUSY istisnası fırlatır
    • Web uygulamaları birden çok connection açsa bile yazma sırasını doğrudan düzenlemek yerine bunu SQLite’a bırakabilir
  • Darboğaz, SQLite’ın Ruby süreci içine gömülü olmasından ve sqlite3-ruby’nin SQLite C kodunu çağırırken Ruby GVL’yi serbest bırakmamasından kaynaklanır
    • Bir Puma worker veritabanı sorgusunun dönmesini beklerken GVL’yi tutmaya devam eder
    • Diğer Puma worker’ların aynı anda SQLite’a yazma sorgusu göndermesi bile zorlaşır
    • SQLite’ın sıralı yazma özelliği, Rails istek işlemesini de daha doğrusal hale getirerek throughput’u ciddi biçimde düşürür
  • SQLite, busy_timeout’tan daha düşük seviyeli bir busy_handler hook’u sağlar
    • busy_timeout, SQLite’ın sağladığı belirli bir busy_handler implementasyonudur
    • sqlite3-ruby, sqlite3_busy_handler C fonksiyonuna binding sağladığından, sorgu kuyruğa girdiğinde çağrılan bir Ruby callback’i oluşturulabilir
  • Ruby callback’inde Kernel.sleep kullanılırsa sorgu yazma kilidi için yeniden denemeyi beklerken GVL serbest bırakılabilir
  • Bu yöntem, eşzamanlı yükte p99 gecikmesini ciddi biçimde iyileştirir; ancak p99.99 gecikmesinde, eşzamanlı yük arttıkça en yavaş isteklerin daha da yavaşlaması durumu devam eder

Üstel backoff’un uzun kuyruğu ve 1 ms yeniden deneme

  • Ruby ile yeniden implemente edilen SQLite busy_timeout mantığı, uzun süre beklemiş sorguları daha dezavantajlı hale getiren bir yapıya sahiptir
  • İlk yeniden denemelerde bekleme süresi küçüktür; ancak callback çağrısı sayısı arttıkça bekleme süresi büyür
    • İlk beklemede 1 ms bekler
    • Onuncu çağrıda 50 ms bekler
      1. çağrıdan sonra 100 ms’lik beklemeler yapar
    • Birikimli bekleme süresi timeout olan 5000 ms’yi aşarsa istisna oluşur
  • Sürekli yeni yazma sorguları gelirse yeni sorgular kısa bekleme süreleriyle kilit almayı daha sık yeniden deneyebilir
    • Zaten üç kez beklemiş eski bir sorgu 10 ms beklerken yeni bir sorgu 1 ms, 2 ms, 5 ms beklemelerden geçerek üç kez yeniden deneyebilir
    • Bu artan backoff, eski sorgunun yazma kilidini alamadan timeout’a düşme olasılığını artırır
  • Çözüm, sorgunun yaşından bağımsız olarak tüm sorguların aynı sıklıkta yeniden denemesini sağlamaktır
  • sqlite3-ruby main branch’inde bu değişiklik yer alır
    • Bu özellik yazının yazıldığı an itibarıyla tagged release’e henüz dahil değildir
    • Ruby callback’i sleep ile bekleme sırasında GVL’yi serbest bırakır
    • Her zaman yalnızca 1 ms sleep eder
  • Bu değişiklikten sonra benchmark’ta p99.99 gecikme eğrisi düzleşir
    • Eşzamanlılık Puma worker sayısının yarısını geçtiğinde görülen sıçrama devam eder
    • Sonrasında uzun kuyruk gecikmesi yaklaşık 0,5 saniye seviyesinde düzleşir

WAL modu ve okuma/yazma connection pool ayrımı

  • Rails üzerinde SQLite performansı için gerekli dört koşul: IMMEDIATE transaction’lar, GVL’yi serbest bırakan busy handler, uygun SQLite ayarları ve WAL modudur
  • write-ahead log, SQLite’ın birden çok eşzamanlı okuma işlemini işleyebilmesini sağlar
    • Varsayılan rollback journal mode, okuma ya da yazma fark etmeksizin aynı anda yalnızca tek bir sorguya izin verir
    • WAL modu aynı anda birden çok reader’a izin verir; ancak writer yine aynı anda yalnızca bir tane olabilir
  • Rails 7.1’den itibaren Rails, SQLite veritabanlarına daha iyi varsayılan ayarlar uygular
    • Bu ayarlar, SQLite’ın web uygulaması bağlamında iyi çalışması için önemlidir
    • Ayrıntılı ayarlar ve gerekçeleri ayrı bir blog yazısında ele alınır
  • İsteğe bağlı beşinci performans kolu, salt-okuma pool’u ile salt-yazma pool’unu ayırmaktır
    • SQLite WAL modu birden çok okuma connection’ını ve tek bir yazma connection’ını destekler
    • Active Record connection pool’u yazma connection’larıyla dolarsa okuma işleri gereksiz yere engellenebilir
  • Rails’in multiple databases desteği kullanılarak reader ve writer ayarları aynı SQLite veritabanına yönlendirilebilir
    • Gerçekte ayrı bir veritabanı değil, aynı tek veritabanını işaret eder
    • Sonuç olarak birbirinden ayrılmış connection pool’ları ve connection ayarları oluşturulabilir
  • Reader connection pool’u salt-okunur connection’lardan oluşturulur; writer connection pool’unda ise yalnızca tek bir connection bulunur
  • Active Record modelleri, rollerine göre uygun connection pool’una bağlanacak şekilde ayarlanır
  • Rails’in automatic role switching özelliği kullanılarak tüm web isteklerinin varsayılan connection’ı reader pool yapılır; yalnızca veritabanına yazma gerektiğinde writer pool’a geçilir
    • Aynı veritabanı kullanıldığı için read your own writes garantisi için delay gerekmez
    • ActiveRecord adapter’ının transaction metodu patch’lenerek transaction’ın writer veritabanına bağlanması sağlanır
  • Bu “deferred requests” yaklaşımı ile izole connection pool’larının birleşimi, comment create endpoint testinde yalnızca RPS bazında performans iyileşmesi gösterir

Gem olarak paketlenen iyileştirmeler

  • Tüm iyileştirmeleri Rails uygulamasında doğrudan implemente etmek gerekmez
  • activerecord-enhancedsqlite3-adapter kurulursa ilgili iyileştirmeler uygulanabilir
  • İzole connection pool özelliği daha yeni ve deneysel bir özellik olduğundan opt-in için ek ayar gerekir
  • Bu yaklaşım, SQLite’ı Rails üretim ortamında hızlı ve esnek kullanmak için araçları, teknikleri ve varsayılanları bir arada sunar
  • Rails, SQLite ile çalışmak için iyi bir web uygulaması framework’üdür; ilgili araçlar ve gem ekosistemi büyümektedir

1 yorum

 
GN⁺ 2024-09-13
Hacker News yorumları
  • SQLite + Rails kullanmayı düşünen herkesin Oldmoe’nun (X/GitHub) Litestack projesine mutlaka bakması değer
    Litestack, Ruby ve Ruby on Rails uygulamalarında SQLite’ın gömülü yapısından yararlanarak SQL veritabanı, hızlı cache, iş kuyruğu, mesaj broker’ı, tam metin arama motoru ve metrik platformunu tek pakette sunan bir Ruby gem’i
    Şu an bir projede kullanıyorum ve çok memnunum; bağlantı: https://github.com/oldmoe/litestack

  • Bu kadar ayrıntılı bir yazıyı yazmanın kaç gün sürdüğünü hayal bile edemiyorum
    Rails’in ötesinde SQLite web uygulamalarını ölçeklendirmeyi düşünen herkes için faydalı bir yazı

  • SQLite ile çalışan herkes, kullandığı dil veya framework ne olursa olsun bu yazıyı okumalı
    Birkaç yıl önce bunların çoğunu kendi başıma çözmek zorunda kalmış biri olarak, derlediğiniz için teşekkürler

  • Kurulumu kolay olması gereken bir FOSS analiz sistemi geliştiriyorum; bu yüzden olay verilerini ana uygulama verilerinden ayrı ayrı bir SQLite veritabanına göndermek istiyorum
    Orta düzeyde yoğun bir web sitesi bile saniyede 1000’den fazla olay üretebilir; ölçekleme konusunda endişeliyim
    Olayları sunucu belleğinde toplayıp her 1 saniyede bir toplu yazma yapmak, SQLite’ın çoklu yazma sınırını aşmak için makul bir yöntem mi, yoksa daha iyi fikirler var mı merak ediyorum

    • Bence oldukça makul ve iyi bir fikir. Birkaç sistemde benzer bir yaklaşım uyguladım; genel olarak toplu işleme, öğe başına overhead’i azaltır
      Bunu benchmark ile kolayca kanıtlayabilirsiniz; ayrıca tüm toplu öğeleri tek bir transaction içine koyabilirsiniz
      Toplu işleme yapıldığında, fiilen batch’i alıp gerçek yazmayı yapan tek bir thread olur; bu da SQLite’ın aynı anda 1 yazma sınırıyla iyi örtüşür
      Ancak karmaşıklık biraz artar. Tek bir toplu yazma 1 saniye içinde bitmezse ne yapılacağı, bellekte tutulan kuyruğun boyutunun sınırsız olup olmayacağı, sınırsızsa sunucu aşırı yük altında OOM ile ölmeyeceğinden emin olup olunamayacağı, sınır konursa öğe kaybının göze alınıp alınmayacağı, atılacaksa hangi öğelerin atılacağı ve kuyruk sınırının ne olacağı belirlenmeli
      Bu sorular, kuyruk gerektiren neredeyse her sistemde mutlaka ortaya çıkar ve yalnızca bugün için değil, gelecekteki durumlar için de faydalıdır
    • SQLite’ın yazma yapamadığı söylenemez. Sadece aynı anda yalnızca bir yazma transaction’ını destekler
      SQLite’ın transaction eşzamanlılık performansına güvenmek zor geliyorsa, tüm yazmaları belirli bir thread veya process üzerinde seri hale getirebilirsiniz
    • Bizde burada iyi çalıştı ve SQLite hakkındaki fikrimi değiştirdi. Hipp de birlikte çalıştı; SQLite muazzam performans verebilir: https://use.expensify.com/blog/scaling-sqlite-to-4m-qps-on-a...
    • Analiz verileri genelde yazma ağırlıklı olduğundan ClickHouse öneririm
      ClickHouse’un async-insert[0] özelliğini kullanırsanız uygulama tarafında olayları toplu işleme konusunda endişelenmeniz gerekmez
      Gömülü bir çözüm arıyorsanız ClickHouse tabanlı chDB’yi kullanabilirsiniz
      [0] https://clickhouse.com/blog/asynchronous-data-inserts-in-cli...
    • Toplu yazma muhtemelen iyi bir fikir, ancak SQLite ile böyle bir şey yaparken en iyi yöntem WAL kullanmak ve belirlenmiş tek bir writer bulundurmaktır
      İstediğiniz kadar reader worker olabilir; writer ise kuyruk gibi bir yerden girdileri alabilir
      Sadece bunu yapmak bile performansı çoğu zaman gerçekten şaşırtıcı hale getirir
  • SQLite’ı üretim arka uç veritabanı olarak kullanma eğilimini hâlâ pek anlayamıyorum
    SQLite, telefon rehberi gibi küçük ve gömülebilir istemci tarafı uygulama veritabanları için harika; ancak geliştiricileri de kapsamının bunun ötesine genişlemesine sürekli karşı çıktı
    Örneğin tarih/saat ya da UUID gibi kullanışlı yerel tipler eklemiyor. Çünkü kod ve gömülü nesne boyutları büyür; sonuçta “her şey string” durumuna sıkışıp kalıyorsunuz
    Referans bütünlüğü açılabiliyor ama kısıt seçenekleri de çok sınırlı
    Neden uygun olmayan ve doğru dürüst desteklenmeyen bir role zorla sokulmaya devam edildiğini anlamıyorum

    • Öncelikle Richard Hipp’in SQLite’ın “başta amaçlanan” rolü konusunda aynı fikirde olup olmadığını bilmiyorum
      Nedeni basit. Okuma ağırlıklı erişim desenlerinde SQLite inanılmaz hızlı; veritabanı erişim kodunu basitleştirecek kadar hızlı ve örneğin N+1 sorgularının çoğu durumda fiilen sorun olmamasını sağlayabiliyor
      Ayrıca SQLite, N katmanlı mimaride bir katmanı ortadan kaldırır ve böylece arızalanabilecek unsurları da azaltır. Postgres ya da MySQL’i doğrudan işlettiyseniz, gerçekten pek çok şeyin ters gidebileceğini bilirsiniz
      Her uygulama, hatta çoğu uygulama için mükemmel seçim değil; ama mevcut eğilim, SQLite’ın yalnızca “küçük gömülü istemci uygulaması veritabanı”na uygun olduğu şeklindeki açıkça yanlış düşünceye karşı bir denge ayarı gibi
    • Sonunda “API sunucusu”nun aslında baştan beri bir DBMS olduğunu fark ediyorsunuz
      Böyle bakınca, aynı verilerle çalışan bir DBMS’nin yanında başka bir DBMS daha olması epey komik hale geliyor
      Bu yüzden ya istemcilerin doğrudan Postgres’e bağlanmasına izin verme ya da Postgres’i çıkarıp kendi DBMS’inize daha fazla odaklanma yönünde ayrışılıyor
      İkinci yolu seçerseniz SQLite, üzerine bir şeyler inşa etmesi kolay bir motor. Mükemmel olmasa da elimizdeki araç bu
      Bu farkındalığın geniş ölçekte yayılması nispeten yeni, bu yüzden neyin olup neyin olamayacağını anlamaya yönelik çok sayıda deney yapılıyor
      Bu, bilişimin doğal döngüsü; eski olanın yeniden yeni hale gelmesi
      Postgres’i MySQL, MSSQL, Oracle ya da başka bir DBMS olarak da okuyabilirsiniz
    • UUID’yi string ya da BLOB olarak, tarihleri de string veya tamsayı/ondalıklı zaman damgası olarak kullanamaz mıyız?
      Bu tür basitleştirme yalnızca SQLite geliştiricilerine veya düşük donanımlı cihazlara değil, uygulama geliştiricilerine de birçok açıdan fayda sağlar
      Dokümantasyon sadeleşir, öğrenme eğrisi kısalır; hata yüzeyi ve ikili dosya boyutu da azalır
      Günümüz yazılımlarında her şeye şişkinlik ve karmaşıklık ekleme eğilimi var; SQLite gibi bazı projelerin buna karşı durması çok iyi
  • Harika bir yazı; Django için de benzer bir kaynak var mı merak ediyorum
    ArchiveBox, Django üzerinden SQLite kullanıyor ve yazıda Rails için anlatılanlarla birebir aynı sorunları oldukça sık yaşadım
    Uygulamanın başka yollarından tüm yazmaları seri hale getirmek zorunda bırakmayan bir SQLite katmanı çözümü olsa iyi olurdu

  • sqlite3-ruby gem’i tasarım gereği SQLite çağrıları sırasında GVL’yi serbest bırakmıyor; bağlantı verilen issue yorumuna https://github.com/sparklemotion/sqlite3-ruby/issues/287#iss... bakınca kilidi yeniden edinmenin maliyetli olduğuna dair bir şüphe varmış ama doğrulanmamış gibi görünüyor
    Tüm bu dolambaçlı işleri düşününce biraz kuşku uyandırıyor
    Python extension kültüründe bunun ters yönde tasarlanacağını sanırdım; orada gerçekte nasıl yapıldığını merak ediyorum
    Ek olarak, bağlantı verilen issue’da “extralite gem’i, bloklama sırasında GVL’yi serbest bırakan alternatif bir SQLite istemcisidir; eşzamanlılıkla ilgili bilgiler https://github.com/digital-fabric/extralite?tab=readme-ov-fi... adresindedir. Genel olarak bu gem’den çok daha hızlıdır ve eşzamanlılık sorunu yoktur” diyen bir yorum da var

    • Daha ayrıntılı tartışma https://github.com/sparklemotion/sqlite3-ruby/pull/528 ve https://github.com/digital-fabric/extralite/pull/46 üzerinde görülebilir
      SQLite sanal makinesinin her step’inde GVL’yi basitçe serbest bırakmanın tek iş parçacıklı performansı ciddi şekilde kötüleştirdiği doğrulandı
      Tek iş parçacıklı ve çok iş parçacıklı performans arasında orta noktayı bulmak zor
      Rails’te bağlantı havuzu nedeniyle çok iş parçacıklı olduğunu biliyoruz; ancak düşük seviyeli gem, tek iş parçacıklı ortamlarda kullanılan başka kütüphaneler ve araçlar tarafından da yaygın biçimde kullanılıyor
  • Kişisel oyuncak web servislerimde tuttuğum birkaç ayar değeri şöyle
    PRAGMA journal_mode = WAL;
    PRAGMA busy_timeout = 5000;
    PRAGMA synchronous = NORMAL;
    PRAGMA cache_size = 1000000000;
    PRAGMA foreign_keys = true;
    PRAGMA temp_store = memory;
    Ve BEGIN IMMEDIATE transaction kullanıyorum
    https://kerkour.com/sqlite-for-servers

    • cache_size ve mmap_size hakkında ne düşünüyorsunuz?
  • Hem SQLite’i hem de Rails’i seviyorum ama bu, üretim ortamında MS Access kullanmaya benziyor gibi görünüyor

    • Tamamen farklı değil, ancak okuma ağırlıklı senaryolarda SQLite, Jet’ten (MS Access) çok daha performanslıdır
      Üstelik Access kullanımının daha yaygın olduğu onlarca yıl öncesine göre bugün bilgisayarlar ve diskler çok daha hızlı
      Okuma ağırlıklıysa SQLite, hatta Jet bile saniyede on binlerce isteğe oldukça kolay ulaşabilir
      Çoğu uygulamada eşzamanlı kullanıcı sayısı yüz binlere varmadığından SQLite iyi uyabilir
      SQLite’ın neredeyse tüm platformlar ve diller için istemcisi olması da güçlü bir yanı
      Arşivleme, yedekleme ve taşınabilirlik de SQLite’a uygun kullanım alanlarıdır. Belirli bir dönemle sınırlı veri girişi olan bir projede kutu başına SQLite kullanılmasını güçlü biçimde savunmuştum; hâlâ bunun daha iyi olacağını düşünüyorum
      Karmaşık bir şema ve özel dışa aktarma/arşivleme özellikleri yapmak yerine tek bir dosyayı kopyalayıp arşiv ya da yedek olarak kullanabilirdiniz; zamanla ortaya çıkan şema değişikliklerini de o kadar derinlemesine düşünmek gerekmezdi
      Duruma göre değişir ama birçok sorun için oldukça iyi bir çözümdür. Çoğu uygulamada daha ölçeklenebilir NoSQL seçenekleri yerine PostgreSQL veya başka bir ilişkisel DBMS’in çoğu zaman daha iyi bir seçim olmasına benzer
      Aşırı tasarlama eğilimimiz vardı; artık bilişim ve G/Ç performansı, bu tür çabaları giderek daha az haklı çıkaran bir seviyeye yaklaşıyor
    • Web uygulamaları için MS Access benzeri bir şey olsa iyi olurdu. Birkaç web sitesi oluşturucu ürünü denedim ama hiçbiri Access kadar sezgisel değildi
    • Neden böyle olduğunu merak ediyorum. Özellikle okuma odaklı bir uygulamaysa hangi ölçeklenme sınırlarından endişe ediliyor?
    • Pieter Levels için bu yöntem işe yarıyor. Elbette kullanıcı sayısı belirli bir eşiği aşarsa sorun çıkacaktır
  • Çok faydalı ve iyi yazılmış bir yazı
    Varsayılan busy_timeout metodunun eski sorguları cezalandıran üstel gecikmeye neden sahip olduğunu merak ediyorum
    Bunun varsayılan değer olarak makul olmasının sebebi ne olabilir?