SSD Nodes Learn RAM 8GB — $66/tahun
Panduan Matt ConnorOleh Matt Connor · Diperbarui 2026-08-01

DuckDB vs SQLite di Server: Mengapa Biasanya Keduanya

SQLite menyimpan status transaksional, sedangkan DuckDB menganalisis Parquet dan CSV. Pelajari alasan satu VPS biasanya menjalankan keduanya, dengan contoh nyata masing-masing.

DuckDB vs SQLite di server: jawaban dalam satu kalimat

SQLite adalah mesin OLTP (online transaction processing): mesin ini menyimpan data sebagai baris dan dirancang untuk membaca serta menulis beberapa baris sekaligus dengan aman dan cepat. DuckDB adalah mesin OLAP (online analytical processing): mesin ini menyimpan data sebagai kolom dan dirancang untuk memindai jutaan baris lalu mengembalikan satu hasil agregat. Keduanya merupakan pustaka embedded, keduanya membuka file biasa, dan tidak satu pun menjalankan proses server yang harus Anda pantau.

Jadi, jawaban yang jujur untuk pertanyaan "yang mana" hampir selalu adalah "keduanya, pada VPS yang sama". Aplikasi Anda menyimpan status aktifnya di SQLite. Pelaporan Anda membaca file Parquet dan CSV dengan DuckDB. Keduanya tidak bersaing karena tidak menjalankan tugas yang sama.

Mengapa penyimpanan baris dan penyimpanan kolom mengubah hasilnya

SQLite menulis satu baris sebagai satu bagian halaman yang berurutan. Mengambil satu pesanan berdasarkan kunci primernya menyentuh satu halaman indeks dan satu halaman data, sehingga memerlukan dua operasi baca. Inilah yang dilakukan aplikasi ribuan kali per detik: membaca pengguna ini, memperbarui sesi ini, dan memasukkan pesanan ini.

DuckDB menulis setiap kolom secara terpisah dan mengompresinya. Menjumlahkan amount_cents pada lima juta baris hanya membaca kolom amount_cents, melewati setiap byte lain dalam file, lalu menjalankan penjumlahan melalui kode tervectorisasi pada sejumlah batch nilai. Kolom lainnya tidak pernah dibaca dari disk. Di situlah sumber kecepatannya.

Sekarang jalankan setiap mesin untuk beban kerja mesin lainnya. SQLite harus menelusuri setiap baris saat menjumlahkan sebuah kolom dan mengambil seluruh baris dari halaman untuk mencapai satu bidang, sehingga membaca jauh lebih banyak data dari disk daripada yang diperlukan. DuckDB harus menyentuh penyimpanan setiap kolom untuk satu nilai saat memasukkan satu pesanan, dan mengambil kunci tulis pada seluruh file database untuk melakukannya. Tidak ada mesin yang rusak. Masing-masing menjawab pertanyaan yang tidak menjadi tujuan perancangannya.

Keunggulan SQLite: status aplikasi transaksional

Pilih SQLite jika penulisan berukuran kecil, sering dilakukan, dan tidak boleh hilang. Sesi, pesanan, baris antrean, pengaturan, serta apa pun yang dibuat oleh permintaan web.

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

Buat tabel dan aktifkan write-ahead logging dalam langkah yang sama.

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

Baris output pertama adalah wal. Itu adalah PRAGMA yang melaporkan mode yang telah diaktifkan, dan merupakan pengaturan yang paling berguna di server. Dalam mode jurnal rollback default, penulis memblokir setiap pembaca. Dalam mode WAL, pembaca terus membaca status terakhir yang telah di-commit saat satu penulis menambahkan data. Dengan demikian, laporan yang lambat tidak lagi menahan permintaan web di belakangnya.

Periksa apakah baris tersebut muncul kembali:

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

Anda mendapatkan 1|ana|2026-07-30T09:14:00Z|4200. Dua file tambahan muncul di sebelah database, yaitu app.db-wal dan app.db-shm, dan keduanya terkait dengan database tersebut. Menyalin hanya app.db saat aplikasi sedang berjalan menghasilkan cadangan yang tidak konsisten. Hal ini dibahas lebih lanjut di bawah.

SQLite tetap hanya mengizinkan satu penulis pada satu waktu. Batas ini berupa penguncian, bukan antrean. Karena itu, penulis kedua yang menunggu terlalu lama akan gagal dengan database is locked, bukan memblokir tanpa batas. Tingkatkan waktu tunggu menggunakan PRAGMA busy_timeout = 5000; pada setiap koneksi yang dibuka aplikasi Anda. Waktu tunggu selama lima detik menghilangkan sebagian besar kesalahan ini pada beban kerja web normal.

Keunggulan DuckDB: analitik atas file yang sudah Anda miliki

Pilih DuckDB jika pertanyaannya dimulai dengan "berapa banyak", "berapa besar", atau "sepuluh teratas yang mana", dan inputnya berupa kumpulan file CSV atau Parquet. Instal klien baris perintahnya, versi 1.5.5 per Juli 2026:

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

Skrip ini menginstal biner di bawah ~/.duckdb/cli/latest/duckdb dan mencetak baris yang menambahkannya ke PATH Anda. Pastikan program tersebut dapat berjalan:

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

Buat file realistis untuk dikueri. Perintah ini menulis lima juta baris pesanan ke Parquet, dengan kompresi zstd:

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);"

Sekarang ajukan pertanyaan analitiknya. Buka shell, aktifkan pengukur waktu, lalu kueri file secara langsung tanpa langkah impor:

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

Baca sendiri hasilnya dari .timer, bukan mempercayai angka yang dipublikasikan, karena hasilnya bergantung pada disk dan jumlah core Anda. Bentuk prosesnya yang penting. Tidak ada CREATE TABLE, tidak ada INSERT, dan tidak ada langkah pemuatan: DuckDB membaca footer Parquet, menentukan chunk kolom yang diperlukan kueri, lalu hanya membaca chunk tersebut. Seluruh direktori dapat diproses dengan cara yang sama menggunakan glob, FROM '/srv/data/orders-*.parquet'. Dengan demikian, ekspor harian selama satu bulan dapat menjadi satu kueri.

Kecepatan disk menjadi batas dasar seluruh proses ini, dan pemindaian kolom merupakan pembacaan sekuensial yang panjang. Karena itu, perbedaan antara penyimpanan NVMe dan SATA lama pada VPS lebih terlihat di sini daripada pada pembacaan acak berukuran kecil oleh SQLite.

Membaca database SQLite dari DuckDB

Kedua engine terhubung melalui ekstensi sqlite milik DuckDB. Lampirkan database aplikasi dalam mode hanya-baca agar kueri analitik tidak pernah menulis ke status aktif:

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;

Tindakan ini membaca baris dari file SQLite saat kueri dijalankan tanpa membuat salinan. Cara ini praktis, tetapi tidak cepat karena data di disk masih menggunakan penyimpanan berbasis baris dan DuckDB harus menelusurinya. Gunakan cara ini untuk ekspor, bukan untuk dasbor yang dimuat ulang setiap tiga puluh detik:

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

Satu pernyataan itu sudah mencakup seluruh pola. SQLite menyimpan baris aktif terbaru. Ekspor terjadwal mengubah periode yang sudah ditutup menjadi Parquet. DuckDB menjawab setiap pertanyaan yang mencakup beberapa bulan, dan database aplikasi tetap kecil sehingga proses penulisannya tetap cepat.

Jalankan ekspor berdasarkan jadwal, bukan secara manual. Pasangan service dan timer systemd sesuai untuk kebutuhan ini: satu unit menjalankan COPY, dan satu timer menjalankannya setiap malam.

Menjalankan keduanya pada satu VPS

Tidak ada bagian di sini yang memerlukan container atau port. Kedua engine tersebut merupakan library, sehingga instalasinya berupa package dan path file. Jika bagian stack lainnya sudah berjalan menggunakan Docker Compose pada VPS yang sama, mount direktori data ke container yang memerlukannya, bukan menambahkan service database, karena tidak ada service yang perlu ditambahkan.

Dua aturan membantu menjaga konfigurasi ini tetap aman.

Berikan direktori terpisah untuk setiap engine: /srv/app untuk file SQLite yang ditulis aplikasi, dan /srv/data untuk file Parquet yang dibaca analitik. Jika keduanya menggunakan direktori yang sama, job pencadangan yang membuat snapshot salah satunya akan bersaing dengan engine lainnya.

Jangan arahkan dua proses ke satu file database DuckDB dalam mode baca-tulis. Hanya satu proses yang boleh menulis ke file DuckDB. Proses kedua akan gagal membukanya. Banyak pembaca dapat mengaksesnya jika masing-masing menetapkan access_mode = 'READ_ONLY'. Hal ini sering mengejutkan pengguna yang sebelumnya menggunakan SQLite, karena beberapa proses biasanya berbagi satu file SQLite. Jika analitik Anda hanya membaca file Parquet, masalah ini tidak muncul. Ini menjadi alasan tambahan untuk menyimpan state yang persisten di SQLite.

Cadangan berbeda, dan perbedaannya menimbulkan masalah

Database SQLite yang sedang berjalan terdiri atas tiga file. Menyalinnya dengan cp saat proses penulisan berlangsung menghasilkan file yang dapat dibuka, tetapi isinya tidak benar. Gunakan perintah pencadangan bawaan engine. Perintah ini mengambil snapshot yang konsisten saat aplikasi tetap melakukan penulisan:

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 mencetak ok pada salinan yang baik. Hasil lainnya berarti snapshot tersebut harus dibuang dan pencadangan harus dilakukan lagi.

File Parquet tidak pernah berubah setelah ditulis, sehingga tidak memerlukan penanganan khusus. Cadangkan direktorinya. Kirim kedua path ke luar server dengan pencadangan restic dari VPS, sehingga seluruh lapisan data terdiri atas dua direktori dalam satu tugas pencadangan.

Mode kegagalan dan string persis yang akan Anda lihat

Error: database is locked dari SQLite berarti koneksi lain menahan kunci tulis lebih lama daripada batas waktu yang Anda izinkan. Ini bukan kerusakan data. Tetapkan PRAGMA busy_timeout pada setiap koneksi, lalu cari transaksi panjang yang seharusnya terdiri dari beberapa transaksi singkat.

Error: unable to open database file setelah perubahan izin biasanya berarti proses dapat menulis file, tetapi tidak dapat menulis direktorinya. SQLite membuat app.db-wal dan app.db-shm di samping database, sehingga direktori itu sendiri harus dapat ditulisi, bukan hanya file .db.

IO Error: Could not set lock on file dari DuckDB berarti proses kedua sudah membuka database tersebut untuk penulisan. Tutup shell lain, atau buka database Anda dalam mode hanya-baca.

Out of Memory Error dari DuckDB pada VPS kecil berarti kueri memerlukan memori kerja yang lebih besar daripada yang tersedia. DuckDB menggunakan disk sebagai ruang limpahan jika memungkinkan. Sediakan lokasi limpahan dengan membuka file database di disk, bukan :memory:, lalu batasi penggunaannya dengan SET memory_limit = '2GB';. Pada server yang menjalankan layanan lain, batas tersebut mencegah kueri ad hoc menghabiskan RAM aplikasi.

Binder Error: Referenced column "amount" not found saat mengueri Parquet hampir selalu berarti skema file tidak sesuai dengan yang Anda ingat. Jalankan DESCRIBE SELECT * FROM '/srv/data/orders.parquet';, lalu periksa nama kolom sebenarnya yang dikembalikan.

Cara memilih dalam praktik

Tentukan pola penulisan data. Banyak operasi tulis kecil yang harus tetap bertahan setelah listrik terputus berarti SQLite. Tentukan pola pembacaan data. Pemindaian penuh dengan agregasi atas riwayat panjang berarti DuckDB. Sebagian besar sistem nyata menjawab ya untuk kedua pertanyaan tersebut. Respons yang tepat adalah memberikan masing-masing mesin bagian yang paling dikuasainya, bukan memaksa salah satunya menangani bagian yang lain.

Hindari memindahkan status aplikasi aktif ke DuckDB hanya karena laporan berjalan lambat. Laporan tersebut lambat karena tata letak penyimpanan. Jadi, perbaikannya adalah ekspor data, bukan menulis ulang jalur penulisan data.

FAQ

Dapatkah DuckDB menggantikan SQLite untuk database aplikasi saya?

Tidak untuk aplikasi yang sering melakukan penulisan. DuckDB mengunci seluruh file database untuk penulisan, hanya mengizinkan satu proses baca-tulis pada satu waktu, dan dioptimalkan untuk perubahan massal, bukan penyisipan satu baris. Simpan status transaksional di SQLite dan biarkan DuckDB membacanya dengan ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); saat laporan membutuhkannya.

Apakah DuckDB benar-benar lebih cepat daripada SQLite untuk analitik?

Untuk pemindaian dan agregasi pada tabel besar, ya. Alasannya adalah tata letak penyimpanan, bukan trik penyetelan. DuckDB hanya membaca kolom yang disebutkan kueri dan memproses nilai dalam batch, sedangkan SQLite harus menelusuri seluruh baris untuk mencapai satu bidang. Untuk mengambil satu baris berdasarkan kunci utama, hasilnya berbalik karena SQLite mengakses dua halaman, sedangkan DuckDB mengakses penyimpanan setiap kolom.

Apakah saya memerlukan banyak RAM untuk menjalankan DuckDB di VPS?

Tidak, tetapi berikan batas penggunaan dan ruang disk. Buka file database, bukan :memory:, agar DuckDB dapat menumpahkan hasil perantara ke disk, lalu tetapkan SET memory_limit = '2GB'; ke nilai yang dapat disediakan oleh VPS Anda. Tanpa batas, satu GROUP BY besar dapat meningkatkan Out of Memory Error atau mengeluarkan layanan lain dari RAM.

Bagaimana cara memasukkan data SQLite saya ke Parquet?

Lampirkan file SQLite dari DuckDB dan salin hasil kueri langsung dengan COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);. Jalankan proses ini sesuai jadwal untuk periode yang sudah ditutup, seperti baris dari bulan lalu, dan simpan baris terbaru di SQLite karena aplikasi masih menulisnya di sana.

Mana yang harus saya cadangkan, dan bagaimana caranya?

Keduanya, dengan cara yang berbeda. Buat snapshot SQLite dengan sqlite3 app.db ".backup '/srv/backup/app.db'", bukan cp, karena database yang sedang berjalan juga merupakan -wal dan file -shm, sehingga salinan biasa dapat menjadi tidak utuh. File Parquet tidak pernah berubah setelah ditulis, jadi menyalin direktorinya sudah cukup.

#duckdb#sqlite#database#analytics#parquet#self-hosting