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

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

SQLite ve DuckDB arasındaki temel farkları keşfedin. OLTP işlemleri için SQLite, analitik sorgular için DuckDB kullanımı ile tek bir VPS üzerinde verimli mimari kurun.

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 bir yığın CSV veya Parquet dosyasıysa DuckDB'yi tercih edin. 27 Ekim 2026 itibarıyla LTS sürümü olan 2.0.0 sürümündeki komut satırı istemcisini kurun:

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

Betik, siz sabitlemediğiniz sürece en son kararlı sürümü kurar; örneğin önceki sürümde kalmak için curl https://install.duckdb.org | DUCKDB_VERSION=1.5.6 sh kullanabilirsiniz. İkili 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();"

Sorgulamak için 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ı çalıştırın 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;

Yayınlanmış bir sayıya güvenmek yerine kendi sonucunuzu .timer üzerinden okuyun, çünkü sonuç diskinize ve çekirdek sayınıza bağlıdır. Önemli olan sonucun genel yapısıdır. Herhangi bir CREATE TABLE, INSERT veya yükleme adımı yoktu: DuckDB, Parquet alt bilgisini okudu, sorgunun hangi sütun parçalarına ihtiyaç duyduğunu belirledi ve yalnızca onları okudu. Tüm bir dizin, FROM '/srv/data/orders-*.parquet' gibi bir glob ifadesiyle aynı şekilde çalışır; günlük dışa aktarımlardan oluşan bir ay, bu sayede tek bir sorgu haline gelir.

Disk hızı tüm bu sürecin temelidir ve sütun taraması uzun bir ardışık okuma işlemidir; bu nedenle bir VPS üzerindeki 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 karşılaşacağınız kesin ifadeler

SQLite'tan gelen database is locked hatası, ister uygulamanızın sürücüsü üzerinden ister sqlite3 shell üzerinden gelsin, 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, ardından birkaç kısa işlem olması gerekirken tek bir uzun işlem olarak kalmış olan sorguları inceleyin.

İzin değişikliğinden sonra alınan unable to open database file hatası, genellikle sürecin dosyaya yazabildiğini ancak dizine yazamadığını gösterir. 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.

DuckDB'den gelen IO Error: Could not set lock on file hatası, ikinci bir sürecin veritabanını zaten yazma modunda açık tuttuğu anlamına gelir. Diğer shell'i kapatın veya kendi oturumunuzu salt okunur modda açın.

Küçük bir VPS üzerinde DuckDB'den gelen Out of Memory Error hatası, sorgunun sahip olduğundan daha fazla çalışma belleğine ihtiyaç duyduğu anlamına gelir. DuckDB, ara sonuçları varsayılan olarak diske aktarır ve buna :memory: oturumu da dahildir; bu nedenle veritabanı dosyasını açmak, aktarma işleminin çalışmasını sağlamaz. Bu geçici dosyaları veritabanı dosyasının yanındaki bir .tmp dizinine veya bellek içi oturumlar için başladığınız yere göre bir .tmp dizinine yazar; bu yüzden boş disk alanı olan yazılabilir bir konumda başlatın veya SET temp_directory = '/var/tmp/duckdb'; ile başka bir yer gösterin. Önemli olan ayar sınırdır: SET memory_limit = '2GB';. Varsayılan değer makine RAM'inin %80'idir; bu nedenle diğer servislerin çalıştığı bir sunucuda bu sınır, geçici bir sorgunun uygulamanızı RAM dışına itmesini engeller. string_agg() gibi bazı toplama işlemleri ve bazı sıralama tabanlı toplama işlemleri diske aktarılamaz; bu nedenle bunlara dayalı bir sorgu hala bellek yetersizliği hatası verebilir.

Parquet sorgularken alınan Binder Error: Referenced column "amount" not found hatası, 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ı 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, uygulama veritabanım için SQLite'ın yerini alabilir mi?

Sık yazma işlemi yapan uygulamalar için hayır. DuckDB, veritabanı dosyasının tamamı üzerinde bir yazma kilidi tutar, aynı anda yalnızca tek bir okuma-yazma sürecine izin verir ve tek satırlık eklemeler yerine 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 tarama ve toplu işlemler için evet; bunun nedeni bir ayar hilesi değil, depolama düzenidir. DuckDB, bir 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 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 sınır belirleyin ve disk kullanımı için alan bırakın. DuckDB, geçici dizini yazılabilir olduğu ve boş alana sahip olduğu sürece, :memory: oturumunda bile ara sonuçları varsayılan olarak diske yazar. Ardından SET memory_limit = '2GB'; değerini VPS'nizin ayırabileceği bir miktara ayarlayın. Varsayılan olan RAM'in %80'i değerinde bırakılırsa, büyük bir GROUP BY işlemi Out of Memory Error durumuna yol açabilir veya diğer servisleri RAM dışına itebilir.

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

SQLite dosyasını DuckDB'den bağlayın ve COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd); ile doğrudan bir sorgu kopyalayın. Bunu, geçen ayın satırları gibi kapalı dönemler için bir zamanlamaya göre çalıştırın ve uygulamanın hala yazmaya devam ettiği güncel satırları 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 nedenle basit bir kopyalama işlemi verinin bozulmasına neden olabilir. Parquet dosyaları yazıldıktan sonra asla değişmez, bu yüzden dizini kopyalamak yeterlidir.

Bu yazı işinize yaradı mı?