Varsayılan Mod ve Sınırları
SQLite varsayılan olarak rollback journal kullanır. Yazma sırasında SQLite, orijinal sayfaları bir -journal dosyasına kopyalar, asıl veritabanını günceller ve commit sırasında journal'ı siler. Yazma yarıda kalırsa, journal tersine oynatılarak yapılan kısmi değişiklik geri alınır.
Yöntem basit ve güvenli, ama can sıkıcı bir yan etkisi var: yazanlar ve okuyanlar aynı dosya için kavga ediyor. Bir yazıcı veritabanı kilidini tuttuğu sürece hiçbir okuyucu yeni bir işlem başlatamaz. Okuyucular aktifken de yazıcı bekler. Yoğun bir uygulamada — örneğin birkaç eşzamanlı istek alan bir web sunucusunda — SQLITE_BUSY hatalarını umduğunuzdan çok daha çabuk görmeye başlarsınız.
İşte SQLite WAL modu tam burada devreye giriyor.
SQLite WAL Modu Nedir ve Nasıl Çalışır?
Write-ahead logging, modeli baştan aşağı değiştirir. Yazıcı, asıl veritabanı dosyasını yerinde değiştirmek yerine commit edilen sayfaları -wal uzantılı ayrı bir dosyaya ekler. Okuyucular ana dosyayı okumaya devam eder, ama ihtiyaç duydukları sayfaların daha yeni sürümleri var mı diye WAL dosyasına da göz atarlar.
Sonuç: bir yazıcı ve istediğiniz kadar okuyucu aynı anda iş yapabilir. Her okuyucu, işleminin başladığı andaki tutarlı bir anlık görüntüyü görür; yazıcı ise okuyucuların baktığı yere dokunmadan WAL'a eklemeye devam eder.
Bu tek bir pragma ile veritabanının modunu değiştiriyorsunuz. Ayar kalıcıdır — dosya başlığında saklandığı için bundan sonra açılan her bağlantı WAL modunu otomatik olarak devralır. Yani bu komutu her bağlantıda çalıştırmanıza gerek yok; veritabanını ilk hazırladığınızda (ya da migration aracınızda) bir kez çalıştırmanız yeterli.
Pragma, geçerli olan yeni modu döndürür. Dönen değer wal ise işiniz tamam. Başka bir şey dönüyorsa büyük ihtimalle dosya sisteminiz paylaşımlı belleği desteklemiyordur (buna birazdan değineceğiz).
WAL Modunu Açma ve Doğrulama
Hangi modda olduğunuzu istediğiniz an kontrol edebilirsiniz:
İlk çağrı WAL'ı etkinleştirir ve yeni modu döndürür. İkinci çağrı (= olmadan) sadece mevcut modu sorgular. Bu işlemden sonra, etkinlik olduğunda messages.db dizininizde üç dosya bulunacak: messages.db, messages.db-wal ve messages.db-shm. Son ikisi, açık bağlantı olup olmamasına göre ortaya çıkar ve kaybolur.
sqlite -wal ve -shm dosyaları nedir?
WAL ile birlikte iki ek dosya gelir ve bunların ne işe yaradığını bilmenizde fayda var:
-wal, henüz ana veritabanına geri işlenmemiş (commit edilmiş) işlemleri tutar. Yazma işlemleri oldukça büyür, checkpoint zamanında küçülür ya da sıfırlanır.-shmise paylaşımlı bellek dosyasıdır. WAL içindeki bir indeks gibi davranır; böylece tüm bağlantılar, her sorguda WAL'ı baştan sona taramadan hangi sayfanın nerede olduğu konusunda anlaşır.
Pratikteki sonucu şu: WAL modundaki bir veritabanını sadece .db dosyasını kopyalayarak asla yedeklemeyin. En güncel veri -wal dosyasında durur; o olmadan kopyanız ya eski ya da bozuk olur. Ya hiçbir bağlantı yazmıyorken üç dosyayı birden kopyalayın ya da — çok daha iyisi — SQLite backup API'sini kullanın (bir sonraki bölümde anlatılıyor).
Eşzamanlılık: tek yazıcı, çok sayıda okuyucu
WAL size eşzamanlı yazma imkânı vermez. SQLite yazmaları yine sıraya sokar: herhangi bir anda yazma kilidini yalnızca tek bir işlem tutar. Asıl değişen şu: yazmalar okumaları, okumalar da yazmaları artık bloklamıyor.
Yani WAL üzerinde çalışan tipik bir web uygulaması şöyle davranır:
- Okuma ağırlıklı uç noktalar paralel olarak, çekişmesiz şekilde çalışır.
- Yazma uç noktaları kısa süreliğine birbirinin arkasında kuyruğa girer ama okumaları bloklamaz.
- Uzun süren okuma işlemleri (analitik sorgular, dışa aktarımlar) yazıcıları bekletmez.
İki bağlantı aynı anda yazmaya çalışırsa, ikincisi SQLITE_BUSY alır. Çözüm genelde makul bir busy timeout ayarlamaktır — yani SQLite'a, vazgeçmeden önce biraz beklemesini söyleyin:
busy_timeout=5000 şu anlama gelir: "Eğer bir kilit tutuluyorsa, hata fırlatmadan önce 5 saniyeye kadar bekle." WAL ile birlikte kullanıldığında, çoğu uygulamanın gerçekte karşılaştığı çekişme sorunlarını rahatlıkla çözer. BEGIN IMMEDIATE ise yazma kilidini ilk yazma anında değil, transaction başlar başlamaz alır; böylece birden fazla bağlantının aynı anda yazmaya niyetlenmesinden doğan bir grup upgrade deadlock durumunun önüne geçilir.
Checkpoint: WAL'ı Ana Veritabanına Geri Aktarma
WAL dosyası sonsuza kadar büyüyemez. Checkpoint, WAL içindeki commit edilmiş sayfaları ana veritabanı dosyasına yazıp ardından WAL'ı sıfırlama işlemidir; yani sqlite wal checkpoint dediğimiz şey tam olarak budur.
SQLite, WAL dosyası ~1000 sayfayı geçtiğinde otomatik olarak checkpoint alır (varsayılan wal_autocheckpoint değeri). Çoğu uygulama için bu ayara dokunmanıza gerek yok. Ama ince ayar yapmak ya da manuel olarak tetiklemek isterseniz:
wal_checkpoint pragma'sı bir mod parametresi alır:
PASSIVE— Okuyucuları/yazıcıları rahatsız etmeden mümkün olduğunca checkpoint alır. Varsayılan davranış budur.FULL— Aktif yazıcıların işini bitirmesini bekler, sonra commit edilmiş her şeyi checkpoint'ler.RESTART—FULL'e ek olarak, yeni okuyucuların eski WAL'ı kullanmasını engeller.TRUNCATE—RESTART'a ek olarak, WAL dosyasını sıfır bayta indirir.
Sunucu uygulamalarının çoğunda bunu elle çağırmaya gerek kalmaz. Ama dosya boyutlarını derli toplu tutmak isteyen bir masaüstü uygulaması geliştiriyorsanız, son bağlantıyı kapatmadan önce TRUNCATE modunda bir checkpoint almak makul bir alışkanlıktır.
WAL ile İyi Çalışan Birkaç Pragma
WAL tek başına da iyidir. Ama production ortamındaki uygulamalar genellikle WAL'ı birkaç ek ayarla birlikte kullanır:
Hızlıca üzerinden geçelim:
synchronous=NORMAL, WAL ile birlikte önerilen ayardır. Uygulama çökmelerine ve işletim sistemi çökmelerine karşı güvenlidir; yalnızca yanlış anda yaşanan bir elektrik kesintisi en son işlemlerin kaybolmasına neden olabilir, o durumda bile veritabanı tutarlı kalır. Varsayılan olanFULLdaha güvenli ama belirgin biçimde daha yavaştır.busy_timeout'tan yukarıda bahsetmiştik.foreign_keys=ONWAL ile ilgili değil ama her bağlantıda açmaya değer — SQLite, geriye dönük uyumluluk için yabancı anahtar denetimini varsayılan olarak kapalı bırakır.
Bunlar bağlantı bazındadır (journal_mode hariç, o kalıcıdır). Uygulama kodunuzda bağlantıyı açar açmaz çalıştırın.
WAL'ın Doğru Seçim Olmadığı Durumlar
WAL varsayılan tavsiyedir ama bazı senaryolar bu kararı sorgulatır:
- Ağ dosya sistemleri. WAL, veritabanına erişen süreçler arasında paylaşımlı bellek (
mmap) kullanır. NFS, SMB ve benzerleri bunu güvenilir biçimde desteklemez. Veritabanınız bir ağ paylaşımındaysa rollback journal'da kalın — daha iyisi, SQLite'ı ağ paylaşımına hiç koymayın. - Salt okunur ortamlar. WAL'ın
-walve-shmdosyalarını yazması gerekir. CD-ROM gibi bir ortamdaki veritabanı, yazmayan bir journal modu kullanmalı (veyamode=roile salt okunur açılmalıdır). - Eşzamanlı okuyucusu olmayan tek yazıcılı toplu işler. WAL zarar vermez ama bir kazancınız da olmaz. Varsayılan rollback journal yeterlidir.
Uygulamaların %95'inde — web backend'leri, masaüstü uygulamaları, mobil uygulamalar, yerel depolamalı gömülü cihazlar — WAL doğru tercihtir.
Gerçekçi Bir Kurulum
Üretimdeki SQLite kurulumlarının çoğu aşağı yukarı şu kalıba oturur, çalıştırılabilir pragma'lar halinde:
temp_store=MEMORY ayarı, geçici tablo ve indeksleri diske değil RAM'e alır — boşta belleğiniz varsa bedavaya gelen küçük bir kazanç.
Bunu uygulamanızın veritabanı kurulum kısmında bağlantı açılırken bir kez ayarlayın; SQLite tabanlı bir uygulamanın eşzamanlı yük altında düzgün çalışması için yapmanız gerekenlerin büyük kısmını halletmiş olursunuz.
Sırada: Yedekleme ve Geri Yükleme
Veritabanınızın artık yanında -wal ve -shm dosyaları olduğuna göre, dosyayı kopyalamak güvenli bir yedekleme yöntemi olmaktan çıktı. Bir sonraki bölümde canlı bir SQLite veritabanını doğru şekilde yedeklemenin yollarını ele alacağız: .backup komutu, online backup API'si ve uygulamayı kapatmadan tutarlı bir anlık görüntü almak gerektiğinde ne yapılacağı.
Sıkça Sorulan Sorular
SQLite'ta WAL modu nedir?
WAL, write-ahead logging'in kısaltması. SQLite, değişiklikleri doğrudan ana veritabanı dosyasına yazıp hata durumunda rollback journal ile geri almak yerine, değişiklikleri ayrı bir -wal dosyasına ekliyor ve belirli aralıklarla bunları ana dosyaya birleştiriyor. Asıl kazanç eşzamanlılıkta: okuyucular ve bir yazıcı birbirini bloklamadan aynı anda çalışabiliyor.
SQLite'ta WAL modunu nasıl etkinleştiririm?
Tek seferlik PRAGMA journal_mode=WAL; çalıştırman yeterli. Ayar kalıcı — veritabanı dosyasının başlığında saklanıyor, dolayısıyla sonraki bağlantılar da otomatik olarak WAL kullanıyor. Her bağlantıda tekrar ayarlamana gerek yok. Komut başarılı olduğunda yeni modu (wal) döndürür.
WAL modu eşzamanlı yazmaya izin verir mi?
Hayır — SQLite yazma işlemlerini hâlâ sıraya sokar. Aynı anda yalnızca bir yazıcı write lock'u tutabilir. WAL'ın değiştirdiği şey şu: artık okuyucular yazıcıyı, yazıcı da okuyucuları bloklamıyor. Çoğu uygulamada zaten asıl dert olan kısım buydu.
-wal ve -shm dosyaları ne işe yarar?
-wal dosyası, henüz ana veritabanına birleştirilmemiş commit edilmiş değişiklikleri tutar. -shm dosyası ise bağlantıların WAL içindeki sayfaları hızlıca bulmasını sağlayan küçük bir paylaşımlı bellek indeksidir. İkisi de gerektiğinde otomatik olarak yeniden oluşturulur — ama veritabanını kopyalıyorsan ya bu iki dosyayı da birlikte kopyalamalı ya da backup API'sini kullanmalısın.