Sunucuda DuckDB mi SQLite mı? Hangisi seçilmeli
SQLite işlem durumunu, DuckDB ise Parquet ve CSV üzerindeki analitiği yönetir. Tek VPS üzerinde ikisini birlikte kullanmanın nedenini ve örnekleri görün.
Sunucuda DuckDB ve SQLite karşılaştırması: tek cümlelik yanıt
SQLite, OLTP (çevrim içi işlem işleme) motorudur: verileri satır olarak depolar ve aynı anda birkaç satırı güvenli ve hızlı şekilde okuyup yazmak için tasarlanmıştır. DuckDB, OLAP (çevrim içi analitik işleme) motorudur: verileri sütun olarak depolar ve milyonlarca satırı tarayıp tek bir toplu sonuç döndürmek için tasarlanmıştır. Her ikisi de gömülü kitaplıktır, her ikisi de düz bir dosyayı açar ve hiçbirinin yönetilmesi gereken bir sunucu süreci yoktur.
Bu nedenle "hangisi" sorusunun dürüst yanıtı neredeyse her zaman "aynı VPS üzerinde ikisi de" olur. Uygulama, canlı durumunu SQLite içinde tutar. Raporlama işlemleri Parquet ve CSV dosyalarını DuckDB ile okur. Aynı işi yapmadıkları için birbirleriyle rekabet etmezler.
Satır depolama ve sütun depolama yanıtı neden değiştirir
SQLite, bir satırı sayfanın tek ve bitişik bir parçası olarak yazar. Bir siparişi birincil anahtarıyla almak için bir dizin sayfasına ve bir veri sayfasına erişilir; bu, iki okuma demektir. Uygulama saniyede binlerce kez tam olarak bunu yapar: bu kullanıcıyı okur, bu oturumu günceller, bu siparişi ekler.
DuckDB her sütunu ayrı yazar ve sıkıştırır. Beş milyon satırda amount_cents toplamını hesaplamak için yalnızca amount_cents sütunu okunur, dosyadaki diğer tüm baytlar atlanır ve toplam, değer grupları üzerinde vektörleştirilmiş kod kullanılarak hesaplanır. Diğer sütunlar diskten hiç okunmaz. Hızın kaynağı budur.
Şimdi her altyapıyı diğerinin iş yüküyle çalıştırın. SQLite bir sütunun toplamını hesaplarken her satırı taramak ve tek bir alana ulaşmak için satırın tamamını sayfadan almak zorundadır. Bu nedenle gerekenden çok daha fazla disk okur. DuckDB tek bir siparişi eklerken tek bir değer için her sütunun depolama alanına dokunmak zorundadır. Bunu yapmak için veritabanı dosyasının tamamında yazma kilidi alır. Her iki altyapı da hatalı değildir. Her biri, tasarlanmadığı bir soruyu yanıtlamaya çalışmaktadır.
SQLite'ın kazandığı alan: işlemsel uygulama durumu
Yazma işlemleri küçük ve sık olduğunda ve verilerin kaybolmaması gerektiğinde SQLite seçilmelidir. Oturumlar, siparişler, kuyruk satırları, ayarlar ve bir web isteğinin oluşturduğu diğer tüm veriler için kullanılabilir.
sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/appTablo oluşturulmalı ve write-ahead logging aynı adımda etkinleştirilmelidir.
sqlite3 /srv/app/app.db <<'SQL'
PRAGMA journal_mode = WAL;
CREATE TABLE IF NOT EXISTS orders (
id INTEGER PRIMARY KEY,
customer TEXT NOT NULL,
placed_at TEXT NOT NULL,
amount_cents INTEGER NOT NULL
);
CREATE INDEX IF NOT EXISTS orders_customer ON orders (customer);
INSERT INTO orders (customer, placed_at, amount_cents)
VALUES ('ana', '2026-07-30T09:14:00Z', 4200);
SQLÇıktının ilk satırı wal olur. Bu, geçiş yaptığı modu bildiren PRAGMA değeridir ve sunucudaki en yararlı ayardır. Varsayılan rollback journal modunda bir yazma işlemi tüm okuma işlemlerini engeller. WAL modunda, tek bir yazma işlemi yeni verileri eklerken okuma işlemleri son commit edilmiş durumu okumayı sürdürür. Böylece yavaş bir rapor, arkasındaki web isteğini artık durdurmaz.
Satırın döndüğünü kontrol edin:
sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"1|ana|2026-07-30T09:14:00Z|4200 elde edilir. Veritabanının yanında app.db-wal ve app.db-shm adlı iki dosya daha oluşur ve her ikisi de veritabanına aittir. Uygulama çalışırken yalnızca app.db dosyasının kopyalanması tutarsız bir yedek oluşturur. Bu konu aşağıda ele alınmaktadır.
SQLite aynı anda yalnızca bir yazma işlemine izin verir. Bu sınır bir kilittir, kuyruk değildir. Bu nedenle çok uzun süre bekleyen ikinci bir yazma işlemi sonsuza kadar engellenmek yerine database is locked hatasıyla başarısız olur. Uygulamanın açtığı her bağlantıda bekleme süresi PRAGMA busy_timeout = 5000; ile artırılmalıdır. Normal bir web iş yükünde beş saniyelik bekleme süresi bu hataların çoğunu ortadan kaldırır.
DuckDB'nin öne çıktığı yer: mevcut dosyalar üzerinde analitik
Soru "how many", "how much" veya "which top ten" ile başlıyor ve girdi çok sayıda CSV ya da Parquet dosyasından oluşuyorsa DuckDB seçilmelidir. Komut satırı istemcisi kurulmalıdır. Temmuz 2026 itibarıyla sürüm 1.5.5:
curl https://install.duckdb.org | shBetik, binary dosyasını ~/.duckdb/cli/latest/duckdb altına kurar ve bu yolu PATH üzerine ekleyen satırı yazdırır. Çalıştığı doğrulanmalıdır:
~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"Sorgulanabilecek gerçekçi bir dosya oluşturulmalıdır. Bu işlem, zstd ile sıkıştırılmış Parquet biçiminde beş milyon sipariş satırı yazar:
mkdir -p /srv/data
duckdb :memory: "
COPY (
SELECT i AS id,
'cust_' || (i % 5000) AS customer,
TIMESTAMP '2026-01-01 00:00:00' + INTERVAL (i) MINUTE AS placed_at,
(i * 37) % 20000 AS amount_cents
FROM range(5000000) t(i)
) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);"Şimdi analitik soru sorulmalıdır. Shell açılmalı, süre ölçümü etkinleştirilmeli ve dosya herhangi bir içe aktarma adımı olmadan doğrudan sorgulanmalıdır:
.timer on
SELECT customer,
count(*) AS orders,
sum(amount_cents)/100.0 AS revenue
FROM '/srv/data/orders.parquet'
GROUP BY customer
ORDER BY revenue DESC
LIMIT 5;Yayımlanmış bir değere güvenmek yerine kendi değerinizi .timer üzerinden okuyun; sonuç diskinize ve çekirdek sayınıza bağlıdır. Önemli olan sonuç biçimidir. Herhangi bir CREATE TABLE, INSERT veya yükleme adımı yoktu: DuckDB, Parquet footer bilgisini okudu, sorgunun hangi sütun parçalarına ihtiyaç duyduğunu belirledi ve yalnızca bunları okudu. Bir dizinin tamamı da FROM '/srv/data/orders-*.parquet' glob ifadesiyle aynı şekilde işlenir. Böylece bir aylık günlük dışa aktarımlar tek bir sorguya dönüştürülebilir.
Disk hızı, tüm bu işlemlerin temel sınırıdır. Sütun taraması uzun bir sıralı okumadır. Bu nedenle VPS üzerindeki NVMe ve eski SATA depolama arasındaki fark burada SQLite'ın küçük rastgele okumaları altındaki farktan daha belirgin olur.
SQLite veritabanının DuckDB üzerinden okunması
İki motor, DuckDB'nin sqlite uzantısı üzerinden birlikte çalışır. Uygulama veritabanı yalnızca okunabilir olarak eklenmelidir. Böylece hiçbir analitik sorgu canlı duruma yazamaz:
INSTALL sqlite;
LOAD sqlite;
ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY);
SELECT customer, count(*) AS orders
FROM app.orders
GROUP BY customer
ORDER BY orders DESC;Bu işlem, SQLite dosyasındaki satırları kopyalama yapmadan sorgu sırasında okur. Kullanışlıdır, ancak hızlı değildir. Bunun nedeni, diskteki verilerin hâlâ satır tabanlı olarak saklanması ve DuckDB'nin bu veriler üzerinde tarama yapmasıdır. Dışa aktarma işlemi için kullanılmalıdır. Her otuz saniyede bir yeniden yüklenen bir pano için kullanılmamalıdır:
COPY (SELECT * FROM app.orders)
TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);Bu tek ifade, desenin tamamıdır. SQLite, yakın zamandaki canlı satırların sahibi olur. Zamanlanmış bir dışa aktarma, kapanmış dönemleri Parquet biçimine dönüştürür. DuckDB, ayları aşan tüm soruları yanıtlar. Uygulama veritabanı küçük kalır ve yazma işlemleri hızlı olur.
Dışa aktarma işlemi elle değil, bir zamanlamaya göre çalıştırılmalıdır. Bunun için systemd service ve timer çifti uygun boyuttadır: COPY komutunu çalıştıran bir birim ve bunu her gece tetikleyen bir timer.
Tek bir VPS üzerinde her ikisini çalıştırma
Burada container gerekmez ve port gerekmez. Her iki engine de library olduğundan kurulum bir package ve bir file path işleminden ibarettir. Stack'inizin geri kalanı aynı VPS üzerinde zaten Docker Compose altında çalışıyorsa database service eklemek yerine data directory'yi buna ihtiyaç duyan container'a mount edin; çünkü eklenecek bir service yoktur.
Bu düzenin sorunsuz çalışmasını iki kural sağlar.
Her engine için ayrı bir directory kullanın: uygulamanın yazdığı SQLite file için /srv/app, analytics'in okuduğu Parquet file'lar için /srv/data. Aynı directory paylaşıldığında, birini snapshot'layan backup job diğer engine ile yarışır.
İki process'i tek bir DuckDB database file'ına read-write mode'da yönlendirmeyin. DuckDB file'ına writing amacıyla yalnızca bir process erişebilir; ikinci process file'ı hiç açamaz. Her process access_mode = 'READ_ONLY' değerini ayarladığında birden çok reader kullanılabilir. SQLite'tan gelenler için bu durum şaşırtıcı olabilir; SQLite'ta birden çok process aynı file'ı rutin olarak paylaşır. Analytics yalnızca Parquet file'larını okuyorsa bu sorun ortaya çıkmaz. Bu da kalıcı state'i SQLite'ta tutmak için bir nedendir.
Yedekler farklıdır ve bu fark sorun çıkarır
Çalışan bir SQLite veritabanı üç dosyadan oluşur. Bu dosyalar yazma işlemi sürerken cp ile kopyalanırsa açılan ancak hatalı bir kopya elde edilir. Uygulama yazmaya devam ederken tutarlı bir anlık görüntü almak için motorun kendi yedekleme komutunu kullanın:
sqlite3 /srv/app/app.db ".backup '/srv/backup/app-$(date -u +%Y%m%dT%H%M%SZ).db'"
sqlite3 /srv/backup/app-20260730T091400Z.db "PRAGMA integrity_check;"integrity_check iyi bir kopyada ok çıktısını verir. Başka bir çıktı, söz konusu anlık görüntünün atılması ve yeni bir yedek alınması gerektiği anlamına gelir.
Parquet dosyaları yazıldıktan sonra değişmez. Bu nedenle özel bir işlem gerekmez; dizini yedekleyin. Her iki yolu VPS'nizden restic ile yedekleme kullanarak sunucu dışına gönderin. Böylece veri katmanının tamamı tek bir yedekleme işinde iki dizinden oluşur.
Hata durumları ve göreceğiniz tam dizeler
SQLite kaynaklı Error: database is locked, başka bir bağlantının yazma kilidini izin verilen zaman aşımından daha uzun süre tuttuğu anlamına gelir. Bu bir bozulma değildir. Her bağlantıda PRAGMA busy_timeout ayarlayın, ardından birkaç kısa işlem olması gerekirken uzun süren bir işlem olup olmadığını kontrol edin.
İzin değişikliğinden sonra görülen Error: unable to open database file genellikle işlemin dosyaya yazabildiği, ancak dosyanın bulunduğu dizine yazamadığı anlamına gelir. SQLite, veritabanının yanında app.db-wal ve app.db-shm oluşturur. Bu nedenle yalnızca .db dosyasının değil, dizinin kendisinin de yazılabilir olması gerekir.
DuckDB kaynaklı IO Error: Could not set lock on file, başka bir işlemin veritabanını yazmak üzere zaten açmış olduğu anlamına gelir. Diğer kabuğu kapatın veya kendi bağlantınızı yalnızca okuma modunda açın.
Küçük bir VPS üzerinde DuckDB kaynaklı Out of Memory Error, bir sorgunun kullanılabilir çalışma belleğinden fazlasına ihtiyaç duyduğu anlamına gelir. DuckDB, mümkün olduğunda verileri diske aktarır. Bu nedenle :memory: yerine diskteki bir veritabanı dosyasını açarak aktarım için alan sağlayın ve bellek kullanımını SET memory_limit = '2GB'; ile sınırlandırın. Diğer hizmetleri çalıştıran bir sistemde bu sınır, geçici bir sorgunun uygulamanın RAM dışına itilmesini önler.
Parquet sorgulanırken görülen Binder Error: Referenced column "amount" not found neredeyse her zaman dosyanın şemasının hatırladığınız şema olmadığı anlamına gelir. DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; komutunu çalıştırın ve gerçek sütun adlarını okuyun.
Uygulamada nasıl seçim yapılır
Yazma düzeninin nasıl olduğunu belirleyin. Elektrik kesintisinden sonra korunması gereken çok sayıda küçük yazma işlemi varsa SQLite kullanılır. Okuma düzeninin nasıl olduğunu belirleyin. Uzun bir geçmiş üzerinde toplama işlevleriyle tam taramalar yapılıyorsa DuckDB kullanılır. Gerçek sistemlerin çoğu her iki soruya da olumlu yanıt verir. Bu durumda doğru yaklaşım, motorlardan birini diğerinin görevlerini de üstlenmeye zorlamak yerine her birine iyi olduğu işi vermektir.
Kaçınılması gereken migration, bir rapor yavaş olduğu için canlı uygulama durumunu DuckDB'ye taşımaktır. Raporun yavaş olmasının nedeni depolama düzenidir. Bu nedenle çözüm, yazma yolunu yeniden yazmak değil, dışa aktarma yapmaktır.
FAQ
DuckDB, uygulama veritabanım için SQLite'ın yerini alabilir mi?
Sık yazma işlemi yapan bir veritabanı için hayır. DuckDB, veritabanı dosyasının tamamında yazma kilidi alır, aynı anda yalnızca tek bir okuma-yazma işlemine izin verir ve tek satırlık eklemeler yerine toplu değişiklikler için optimize edilmiştir. İşlemsel durumu SQLite'ta tutun ve bir rapor gerektiğinde DuckDB'nin ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); ile bu durumu okumasını sağlayın.
Analiz işlemleri için DuckDB gerçekten SQLite'tan daha mı hızlı?
Büyük bir tablo üzerinde tarama ve toplama işlemleri için evet. Bunun nedeni bir ayar hilesi değil, depolama düzenidir. DuckDB yalnızca sorguda belirtilen sütunları okur ve değerleri toplu olarak işler. SQLite ise tek bir alana ulaşmak için satırların tamamını dolaşmak zorundadır. Birincil anahtara göre tek satır getirme işleminde durum tersine döner. Bunun nedeni SQLite'ın iki sayfaya, DuckDB'nin ise her sütunun depolama alanına erişmesidir.
DuckDB'yi bir VPS üzerinde çalıştırmak için çok fazla RAM gerekir mi?
Hayır, ancak bir sınır ve disk alanı tanımlanmalıdır. :memory: yerine bir veritabanı dosyası açın. Böylece DuckDB ara sonuçları diske taşıyabilir. Ardından SET memory_limit = '2GB'; değerini VPS'in ayırabileceği bir değere ayarlayın. Sınır tanımlanmazsa büyük bir GROUP BY, Out of Memory Error değerini artırabilir veya diğer hizmetlerin RAM dışına itilmesine neden olabilir.
SQLite verilerimi Parquet'e nasıl aktarabilirim?
SQLite dosyasını DuckDB'ye bağlayın ve COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd); ile bir sorgunun sonucunu doğrudan dışarı aktarın. Bu işlemi geçen ayın satırları gibi kapanmış dönemler için zamanlanmış olarak çalıştırın. Uygulamanın yazmaya devam ettiği güncel satırları SQLite'ta bırakın.
Hangisini, nasıl yedeklemeliyim?
Her ikisi de farklı yöntemlerle yedeklenmelidir. SQLite anlık görüntülerini cp yerine sqlite3 app.db ".backup '/srv/backup/app.db'" ile alın. Bunun nedeni, çalışmakta olan bir veritabanının aynı zamanda bir -wal ve -shm dosyası olmasıdır; düz bir kopya tutarsız durumda kalabilir. Parquet dosyaları yazıldıktan sonra hiç değişmez. Bu nedenle dizinin kopyalanması yeterlidir.