SSD Nodes Learn Hosting plans →
Rehberler Matt ConnorYazan Matt Connor · Güncellendi 2026-08-07

Sunucuda DuckDB ve SQLite Kullanımı: Hangisi Seçilmeli?

SQLite ve DuckDB arasındaki temel farkları ve neden aynı VPS üzerinde birlikte kullanılmaları gerektiğini öğrenin. OLTP ve OLAP iş yükleri için en uygun yapılandırma.

Sunucuda DuckDB ve SQLite karşılaştırması: tek cümlelik yanıt

SQLite bir OLTP (çevrimiçi işlem işleme) motorudur: verileri satır bazlı depolar ve az sayıda satırı aynı anda güvenli ve hızlı bir şekilde okuyup yazmak için tasarlanmıştır. DuckDB bir OLAP (çevrimiçi analitik işleme) motorudur: verileri sütun bazlı 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ü kütüphanelerdir, her ikisi de düz bir dosya üzerinden çalışır ve ikisi de yönetmeniz gereken bir sunucu süreci çalıştırmaz.

Dolayısıyla "hangisi" sorusuna verilecek dürüst yanıt neredeyse her zaman "ikisi de, aynı VPS üzerinde" şeklindedir. Uygulamanız canlı durumunu SQLite içinde tutar. Raporlama katmanınız ise DuckDB ile Parquet ve CSV dosyalarını okur. Aynı işi yapmadıkları için birbirleriyle rekabet etmezler.

Satır tabanlı ve sütun tabanlı depolama neden sonucu değiştirir

SQLite, bir satırı sayfa üzerinde bitişik bir parça olarak yazar. Birincil anahtarı üzerinden tek bir siparişi getirmek, bir dizin sayfası ve bir veri sayfasına erişim gerektirir; bu da iki okuma işlemi demektir. Bir uygulamanın saniyede binlerce kez yaptığı işlem tam olarak budur: kullanıcıyı oku, oturumu güncelle, siparişi ekle.

DuckDB, her sütunu ayrı ayrı yazar ve sıkıştırır. Beş milyon satır üzerinden amount_cents toplamını almak, yalnızca amount_cents sütununu okur, dosyadaki diğer tüm baytları atlar ve toplamı değer grupları üzerinde vektörize edilmiş kod ile çalıştırır. Diğer sütunlar diskten asla okunmaz; hızın kaynağı da budur.

Şimdi her motoru diğerinin iş yükü üzerinde çalıştıralım. Bir sütunu toplayan SQLite, her satırı tek tek gezmek ve bir alana ulaşmak için tüm satırı sayfadan çekmek zorundadır; bu nedenle ihtiyaç duyduğundan çok daha fazla disk okuması yapar. Tek bir sipariş ekleyen DuckDB ise tek bir değer için her sütunun depolama alanına erişmek zorundadır ve bunu yapmak için tüm veritabanı dosyası üzerinde bir yazma kilidi alır. Her iki motor da hatalı değildir. Her biri, tasarlanmadığı bir soruya yanıt vermeye çalışmaktadır.

SQLite'ın avantajlı olduğu durumlar: işlemsel uygulama durumu

Yazma işlemlerinin küçük, sık olduğu ve kaybolmaması gereken durumlarda SQLite tercih edilmelidir. Oturumlar, siparişler, kuyruk satırları, ayarlar ve bir web isteğinin oluşturduğu her türlü veri buna dahildir.

sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/app

Tabloyu oluşturun ve aynı adımda write-ahead logging (WAL) modunu etkinleştirin.

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 şeklindedir. Bu, PRAGMA değerinin geçiş yaptığı modu bildirmesidir ve bir sunucudaki en kullanışlı ayardır. Varsayılan rollback journal modunda bir yazıcı, tüm okuyucuları engeller. WAL modunda ise okuyucular son onaylanmış durumu okumaya devam ederken bir yazıcı veriyi sona ekler; böylece yavaş bir rapor, arkasındaki web isteğini artık durdurmaz.

Satırın geri geldiğini doğrulayın:

sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"

1|ana|2026-07-30T09:14:00Z|4200 çıktısını alırsınız. Veritabanının yanında app.db-wal ve app.db-shm olmak üzere iki dosya daha belirir ve her ikisi de veritabanına aittir. Uygulama çalışırken yalnızca app.db dosyasını kopyalamak, tutarsız bir yedek oluşturmanıza neden olur; bu konu aşağıda daha ayrıntılı ele alınmıştır.

SQLite, aynı anda yalnızca bir yazıcıya izin verir. Bu sınır bir kuyruk değil, bir kilittir; bu nedenle çok uzun süre bekleyen ikinci bir yazıcı, sonsuza kadar engellenmek yerine database is locked hatasıyla başarısız olur. Uygulamanızın açtığı her bağlantıda PRAGMA busy_timeout = 5000; kullanarak bekleme süresini artırın. Beş saniyelik bir bekleme süresi, normal bir web iş yükündeki bu hataların çoğunu ortadan kaldırır.

DuckDB'nin avantajlı olduğu durumlar: mevcut dosyalar üzerinde analitik

Soru "kaç tane", "ne kadar" veya "ilk on hangisi" ile başlıyorsa ve girdi olarak bir yığın CSV veya Parquet dosyası varsa DuckDB'yi tercih edin. Temmuz 2026 itibarıyla 1.5.5 sürümü olan komut satırı istemcisini yükleyin:

curl https://install.duckdb.org | sh

Betik, ikili dosyayı ~/.duckdb/cli/latest/duckdb altına kurar ve onu PATH değişkeninize ekleyen satırı yazdırır. Çalıştığını doğrulayın:

~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"

Sorgulanacak gerçekçi bir dosya oluşturun. Bu komut, zstd ile sıkıştırılmış beş milyon satırlık sipariş verisini Parquet formatında 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 soruyu sorun. Shell'i açın, zamanlayıcıyı etkinleştirin ve herhangi bir içe aktarma adımı olmadan dosyayı doğrudan sorgulayın:

.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;

Sonuç disk hızınıza ve çekirdek sayınıza bağlı olduğundan, yayınlanmış bir değere güvenmek yerine .timer üzerindeki kendi değerinizi okuyun. Önemli olan işlemin yapısıdır. Herhangi bir CREATE TABLE, INSERT veya yükleme adımı gerçekleşmedi: DuckDB, Parquet alt bilgisini (footer) okudu, sorgunun hangi sütun parçalarına ihtiyaç duyduğunu belirledi ve yalnızca onları okudu. Bir dizinin tamamı, FROM '/srv/data/orders-*.parquet' gibi bir glob ifadesiyle aynı şekilde çalışır; günlük dışa aktarılan bir aylık veri bu sayede tek bir sorgu haline gelir.

Disk hızı tüm bu sürecin temelidir ve sütun taraması uzun süreli ardışık bir okuma işlemidir; bu nedenle bir VPS üzerinde NVMe ve eski SATA depolama arasındaki fark, SQLite'ın küçük rastgele okumalarına kıyasla burada çok daha net bir şekilde ortaya çıkar.

SQLite veritabanınızı DuckDB üzerinden okuma

İki motor, DuckDB'nin sqlite eklentisi aracılığıyla bir araya gelir. Analitik sorguların canlı durum üzerinde yazma işlemi yapmasını engellemek için uygulama veritabanını salt okunur (read-only) modda bağlayın:

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ı sorgu anında herhangi bir kopyalama yapmadan okur. Bu yöntem pratiktir ancak hızlı değildir; çünkü disk üzerindeki veriler hala satır tabanlı depolama (row storage) formatındadır ve DuckDB'nin bu veriler üzerinde gezinmesi gerekir. Bu yöntemi dışa aktarma işlemleri için kullanın; otuz saniyede bir yenilenen bir gösterge paneli (dashboard) için tercih etmeyin:

COPY (SELECT * FROM app.orders)
  TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);

Bu tek ifade, tüm modelin temelidir. SQLite güncel canlı satırları tutar. Zamanlanmış bir dışa aktarma işlemi, tamamlanmış dönemleri Parquet formatına dönüştürür. DuckDB aylara yayılan tüm soruları yanıtlar; uygulama veritabanı ise küçük kalarak yazma işlemlerinin hızlı sürmesini sağlar.

Dışa aktarma işlemini manuel olarak değil, bir zaman çizelgesine göre çalıştırın. Bunun için systemd servis ve zamanlayıcı çifti en uygun yöntemdir: COPY komutunu çalıştıran bir birim ve bunu her gece tetikleyen bir zamanlayıcı.

Her ikisini de tek bir VPS üzerinde çalıştırma

Burada hiçbir şeyin container veya port ihtiyacı yoktur. Her iki motor da kütüphane olduğundan, kurulum yalnızca bir paket ve dosya yolundan ibarettir. Eğer yığınınızın geri kalanı zaten aynı VPS üzerinde Docker Compose altında çalışıyorsa, veri dizinini ihtiyaç duyan container içine mount edin. Bir veritabanı servisi eklemeyin, çünkü eklenecek bir servis yoktur.

Bu düzenin sorunsuz işlemesi için iki kurala uyulmalıdır.

Her motora kendi dizinini atayın: Uygulamanın yazdığı SQLite dosyası için /srv/app, analitik motorunun okuduğu Parquet dosyaları için /srv/data. Dizinleri paylaştıklarında, birinin yedeğini alan işlem diğeriyle çakışır.

İki süreci, okuma-yazma modunda tek bir DuckDB veritabanı dosyasına yönlendirmeyin. Bir DuckDB dosyasını yazma amacıyla yalnızca tek bir süreç tutabilir; ikinci süreç dosyayı açamaz ve hata verir. Her biri access_mode = 'READ_ONLY' ayarını yaptığında birden fazla okuyucu sorunsuz çalışır. Bu durum, süreçlerin dosyayı rutin olarak paylaştığı SQLite'tan gelen kullanıcıları şaşırtır. Eğer analitik işlemleriniz yalnızca Parquet dosyalarını okuyorsa bu sorun hiç oluşmaz; kalıcı veriyi SQLite'ta tutmak için bir neden daha.

Yedekleme yöntemleri farklılık gösterir ve bu fark sorun yaratır

Çalışan bir SQLite veritabanı üç dosyadan oluşur; bunları cp ile yazma işlemi sırasında kopyalamak, açılabilen ancak hatalı bir dosya elde etmenize neden olur. Uygulama yazmaya devam ederken tutarlı bir anlık görüntü (snapshot) alan 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, başarılı bir kopyalama işleminde ok çıktısını verir. Bunun dışındaki herhangi bir sonuç, o anlık görüntünün atılıp yenisinin alınması gerektiği anlamına gelir.

Parquet dosyaları yazıldıktan sonra asla değişmez, bu nedenle özel bir işleme ihtiyaç duymazlar: dizini yedeklemeniz yeterlidir. Her iki yolu da VPS üzerinden restic yedekleri ile sunucu dışına gönderin; böylece tüm veri katmanı tek bir yedekleme işinde iki dizin haline gelir.

Hata modları ve göreceğiniz kesin dizeler

Error: database is locked hatası, SQLite'tan geliyorsa başka bir bağlantının yazma kilidini zaman aşımı sürenizden daha uzun süre tuttuğu anlamına gelir. Bu bir bozulma değildir. Her bağlantıda PRAGMA busy_timeout ayarını yapın ve ardından birkaç kısa işlem olması gerekirken tek bir uzun işlem olarak kalmış sorguları inceleyin.

Error: unable to open database file hatası, bir izin değişikliğinden sonra alınıyorsa genellikle sürecin dosyaya yazabildiği ancak dizinine yazamadığı anlamına gelir. SQLite, veritabanının yanında app.db-wal ve app.db-shm dosyalarını oluşturur; bu nedenle sadece .db dosyası değil, dizinin kendisi de yazılabilir olmalıdır.

IO Error: Could not set lock on file hatası, DuckDB'den geliyorsa ikinci bir sürecin veritabanını yazma amacıyla zaten açık tuttuğu anlamına gelir. Diğer kabuğu kapatın veya kendi oturumunuzu salt okunur (read only) olarak açın.

Out of Memory Error hatası, küçük bir VPS üzerinde DuckDB'den geliyorsa sorgunun sahip olduğundan daha fazla çalışma belleğine ihtiyaç duyduğu anlamına gelir. DuckDB mümkün olduğunda diske taşma (spill) yapar; bu nedenle :memory: yerine disk üzerinde bir veritabanı dosyası açarak ona taşma alanı sağlayın ve SET memory_limit = '2GB'; ile bellek kullanımını sınırlayın. Başka servislerin çalıştığı bir sunucuda bu sınır, geçici bir sorgunun uygulamanızı RAM dışına itmesini engelleyen mekanizmadır.

Binder Error: Referenced column "amount" not found hatası, Parquet dosyaları sorgulanırken alınıyorsa neredeyse her zaman dosya şemasının hatırladığınızdan farklı olduğu anlamına gelir. DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; komutunu çalıştırın ve gerçek sütun adlarını tekrar okuyun.

Pratikte seçim nasıl yapılır

Yazma düzeninin ne olduğunu belirleyin. Elektrik kesintisi durumunda veri kaybı yaşanmaması gereken çok sayıda küçük yazma işlemi için SQLite uygundur. Okuma düzenini belirleyin. Uzun bir geçmişe sahip veriler üzerinde yapılan tam taramalar ve toplam alma işlemleri için DuckDB uygundur. Çoğu gerçek sistem her iki soruya da evet yanıtını verir; bu durumda doğru yaklaşım, bir motoru diğerinin görevini yapmaya zorlamak yerine, her iki motoru da kendi uzmanlık alanlarında kullanmaktır.

Kaçınılması gereken geçiş türü, bir raporun yavaş çalışması nedeniyle canlı uygulama durumunu DuckDB'ye taşımaktır. Raporun yavaş çalışmasının nedeni depolama düzenidir; dolayısıyla çözüm, yazma yolunuzu yeniden yazmak değil, veriyi dışa aktarmaktır.

FAQ

DuckDB, uygulamamın veritabanı olarak SQLite'ın yerini alabilir mi?

Sık yazma işlemi yapan uygulamalar için hayır. DuckDB, tüm veritabanı dosyası üzerinde bir yazma kilidi tutar, aynı anda yalnızca tek bir okuma-yazma sürecine izin verir ve tek satırlık eklemelerden ziyade toplu değişiklikler için optimize edilmiştir. İşlemsel durumu SQLite içinde tutun ve bir rapor gerektiğinde DuckDB'nin bunu ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); ile okumasını sağlayın.

DuckDB, analitik işlemler için SQLite'tan gerçekten daha mı hızlı?

Büyük bir tablo üzerindeki taramalar ve toplamalar için evet; bunun nedeni bir ayar hilesi değil, depolama düzenidir. DuckDB yalnızca sorguda belirtilen sütunları okur ve değerleri gruplar halinde işler; SQLite ise tek bir alana ulaşmak için tüm satırları taramak zorundadır. Birincil anahtar ile tek bir satır getirme işleminde ise durum tersine döner; çünkü SQLite iki sayfaya erişirken, DuckDB her sütunun depolama alanına erişir.

Bir VPS üzerinde DuckDB çalıştırmak için çok fazla RAM'e ihtiyacım var mı?

Hayır, ancak bir limit belirleyin ve disk alanı ayırın. Veritabanını :memory: yerine bir dosya olarak açın; böylece DuckDB ara sonuçları diske yazabilir. Ardından SET memory_limit = '2GB'; değerini VPS'nizin ayırabileceği bir miktara ayarlayın. Bir limit belirlenmediğinde, büyük bir GROUP BY işlemi Out of Memory Error hatasına neden olabilir veya diğer servisleri RAM dışına itebilir.

SQLite verilerimi Parquet formatına nasıl aktarırım?

SQLite dosyasını DuckDB içinden bağlayın ve COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd); ile bir sorguyu doğrudan kopyalayın. Bunu, geçen ayın satırları gibi kapalı dönemler için bir zamanlamaya bağlayarak çalıştırın; uygulamanın yazmaya devam ettiği güncel satırları ise SQLite içinde bırakın.

Hangisini yedeklemeliyim ve nasıl?

Her ikisini de farklı yöntemlerle yedekleyin. SQLite anlık görüntülerini cp yerine sqlite3 app.db ".backup '/srv/backup/app.db'" ile alın; çünkü çalışan bir veritabanı aynı zamanda bir -wal ve bir -shm dosyasıdır, bu yüzden doğrudan kopyalama işlemi verinin bozulmasına yol açabilir. Parquet dosyaları yazıldıktan sonra asla değişmez, bu nedenle dizini kopyalamak yeterlidir.