DuckDB vs SQLite sa isang server: alin ang gamit?
SQLite para sa transactional state, DuckDB para sa analytics sa Parquet at CSV. Alamin kung bakit karaniwang parehong tumatakbo sa iisang VPS, may halimbawa ng bawat isa.
DuckDB kumpara sa SQLite sa isang server: ang sagot sa isang pangungusap
Ang SQLite ay isang OLTP engine (online transaction processing): iniimbak nito ang data bilang mga row at ginawa itong ligtas at mabilis na magbasa at magsulat ng ilang row nang sabay-sabay. Ang DuckDB ay isang OLAP engine (online analytical processing): iniimbak nito ang data bilang mga column at ginawa itong mag-scan ng milyun-milyong row at magbalik ng isang aggregate. Pareho silang embedded library, parehong nagbubukas ng plain file, at wala sa kanila ang nagpapatakbo ng server process na kailangan mong bantayan.
Kaya ang tapat na sagot sa tanong na “alin” ay halos palaging “pareho, sa iisang VPS”. Itinatago ng application mo ang live state nito sa SQLite. Binabasa ng reporting mo ang mga Parquet at CSV file gamit ang DuckDB. Hindi sila nagkakumpitensya dahil magkaiba ang ginagawa nilang trabaho.
Bakit nagbabago ang sagot batay sa row storage at column storage
Isinusulat ng SQLite ang isang row bilang isang magkakadugtong na bahagi ng isang page. Kapag kumukuha ng isang order gamit ang primary key nito, isang index page at isang data page ang kailangang ma-access, kaya dalawang read ito. Ito mismo ang ginagawa ng isang application nang libo-libong beses bawat segundo: basahin ang user na ito, i-update ang session na ito, at mag-insert ng order na ito.
Isinusulat ng DuckDB ang bawat column nang hiwalay at kino-compress ito. Kapag sina-sum ang amount_cents sa limang milyong row, amount_cents column lamang ang binabasa nito. Nilalaktawan nito ang lahat ng iba pang byte sa file at ipinapadaan ang sum sa vectorised code na nagpoproseso ng mga batch ng value. Hindi kailanman binabasa mula sa disk ang ibang mga column. Dito nagmumula ang bilis nito.
Ngayon, patakbuhin ang bawat engine sa workload ng isa pa. Kapag nagsu-sum ng isang column ang SQLite, kailangan nitong daanan ang bawat row at kunin ang buong row mula sa page upang maabot ang isang field. Dahil dito, mas marami itong binabasang disk kaysa sa kinakailangan. Kapag nag-i-insert naman ang DuckDB ng isang order, kailangan nitong i-access ang storage ng bawat column para sa iisang value. Kumukuha rin ito ng write lock sa buong database file habang ginagawa ito. Walang sirang engine sa mga ito. Ang bawat isa ay sumasagot sa tanong na hindi nito idinisenyong pangasiwaan.
Saan mahusay ang SQLite: transactional application state
Piliin ang SQLite kapag maliit at madalas ang writes, at hindi dapat mawala ang mga ito. Kasama rito ang sessions, orders, queue rows, settings, at anumang ginagawa ng web request.
sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/appGawin ang table at i-enable ang write-ahead logging sa parehong step.
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);
SQLAng unang linya ng output ay wal. Ito ang PRAGMA na nag-uulat ng mode na pinagpalitan nito, at ito ang pinakamahalagang setting sa isang server. Sa default na rollback journal mode, bina-block ng writer ang lahat ng reader. Sa WAL mode, patuloy na nagbabasa ang mga reader ng huling committed state habang nag-a-append ang isang writer. Dahil dito, hindi na pinatitigil ng mabagal na report ang web request na nakapila sa likod nito.
Tingnan kung bumalik ang row:
sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"Makukuha mo ang 1|ana|2026-07-30T09:14:00Z|4200. May dalawa pang file na lumitaw sa tabi ng database, app.db-wal at app.db-shm, at parehong bahagi ng database ang mga ito. Kapag kinopya mo lamang ang app.db habang tumatakbo ang application, magkakaroon ka ng hindi kumpletong backup. Tatalakayin ito sa ibaba.
Isang writer lang ang pinapayagan ng SQLite sa bawat pagkakataon. Ang limitasyong ito ay isang lock, hindi isang queue. Kaya kapag masyadong matagal maghintay ang ikalawang writer, mabibigo ito gamit ang database is locked sa halip na tuluyang ma-block. Taasan ang wait gamit ang PRAGMA busy_timeout = 5000; sa bawat connection na binubuksan ng application mo. Sa karaniwang web workload, sapat na ang limang segundong paghihintay para maalis ang karamihan sa mga error na ito.
Saan nangingibabaw ang DuckDB: analytics sa mga file na mayroon ka na
Piliin ang DuckDB kapag nagsisimula ang tanong sa “ilan”, “magkano”, o “ano ang nangungunang sampu”, at ang input ay koleksiyon ng mga CSV o Parquet file. I-install ang command line client, na bersyon 1.5.5 noong July 2026:
curl https://install.duckdb.org | shIni-install ng script ang binary sa ilalim ng ~/.duckdb/cli/latest/duckdb at pini-print ang linya para idagdag ito sa iyong PATH. Tiyaking gumagana ito:
~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"Gumawa ng makatotohanang file na ite-query. Nagsusulat ito ng limang milyong row ng orders sa Parquet, na naka-compress gamit ang 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);"Itanong ngayon ang analytical question. Buksan ang shell, i-on ang timer, at direktang i-query ang file nang walang import step:
.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;Basahin ang sarili mong numero mula sa .timer sa halip na magtiwala sa isang published na numero, dahil nakadepende ang resulta sa iyong disk at bilang ng cores. Ang hugis ng resulta ang mahalaga. Walang CREATE TABLE, walang INSERT, at walang load step: binasa ng DuckDB ang Parquet footer, tinukoy kung aling column chunks ang kailangan ng query, at ang mga iyon lamang ang binasa nito. Pareho ang paraan para sa isang buong directory gamit ang glob, FROM '/srv/data/orders-*.parquet', kaya nagiging isang query ang isang buwang daily exports.
Ang disk speed ang nagtatakda ng pinakamababang performance sa lahat ng ito, at ang column scan ay isang mahabang sequential read. Kaya mas malinaw dito ang agwat sa pagitan ng NVMe at mas lumang SATA storage sa isang VPS kaysa sa maliliit na random read ng SQLite.
Pagbasa ng iyong SQLite database mula sa DuckDB
Nagkakaugnay ang dalawang engine sa pamamagitan ng sqlite extension ng DuckDB. I-attach ang application database bilang read-only upang hindi kailanman makapagsulat sa live state ang isang analytics query:
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;Binabasa nito ang mga row mula sa SQLite file sa oras ng query nang walang pagkopya. Maginhawa ito, pero hindi ito mabilis dahil row storage pa rin ang data sa disk at kailangan itong i-scan ng DuckDB. Gamitin ito para sa export, hindi para sa dashboard na nagre-reload bawat tatlumpung segundo:
COPY (SELECT * FROM app.orders)
TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);Ang statement na iyon ang buong pattern. Ang SQLite ang namamahala sa mga pinakabagong live row. Ginagawang Parquet ng isang naka-schedule na export ang mga saradong period. Sinasagot ng DuckDB ang bawat tanong na sumasaklaw sa maraming buwan, habang nananatiling maliit ang application database kaya mabilis ang mga write nito.
Patakbuhin ang export ayon sa schedule sa halip na mano-mano. Ang isang systemd service at timer pair ang tamang laki para rito: isang unit na nagpapatakbo ng COPY, at isang timer na nagpapaandar nito gabi-gabi.
Pagpapatakbo ng dalawa sa iisang VPS
Walang kailangang container o port para sa alinman dito. Mga library ang dalawang engine, kaya package at file path ang kailangan sa pag-install. Kung tumatakbo na ang iba pang bahagi ng stack mo sa Docker Compose sa iisang VPS, i-mount ang data directory sa container na nangangailangan nito sa halip na magdagdag ng database service, dahil walang service na kailangang idagdag.
Dalawang panuntunan ang makatutulong para maiwasan ang mga problema sa setup na ito.
Bigyan ng sariling directory ang bawat engine: /srv/app para sa SQLite file na sinusulatan ng application, at /srv/data para sa mga Parquet file na binabasa ng analytics. Kapag iisang directory ang ginagamit nila, maaaring magsabay ang backup job na nagso-snapshot sa isa at ang operasyon ng isa pa.
Huwag ituro ang dalawang proseso sa iisang DuckDB database file habang nasa read-write mode. Isang proseso lamang ang maaaring magsulat sa DuckDB file, at tuluyang mabibigong buksan ito ng ikalawang proseso. Ayos lang ang maraming reader kapag itinatakda ng bawat isa ang access_mode = 'READ_ONLY'. Nakalilito ito para sa mga galing sa SQLite, kung saan karaniwan nang nagsasaluhan ang ilang proseso sa isang file. Kung Parquet files lamang ang binabasa ng analytics mo, hindi lilitaw ang isyung ito. Isa pa itong dahilan para panatilihin sa SQLite ang durable state.
Magkakaiba ang mga backup, at may epekto ang pagkakaibang ito
Ang tumatakbong SQLite database ay binubuo ng tatlong file. Kapag kinopya ang mga ito gamit ang cp habang may isinusulat, makakakuha ka ng file na nagbubukas pero mali ang laman. Gamitin ang sariling backup command ng engine. Gumagawa ito ng consistent snapshot habang patuloy na nagsusulat ang application:
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;"Ang integrity_check ay nagpi-print ng ok kapag maayos ang kopya. Kapag iba ang resulta, itapon ang snapshot na iyon at gumawa ng panibago.
Hindi nagbabago ang mga Parquet file matapos maisulat, kaya hindi kailangan ng espesyal na handling. I-back up ang directory. Ipadala ang dalawang path palabas ng server gamit ang mga restic backup mula sa iyong VPS. Sa ganitong paraan, dalawang directory lang ang buong data layer sa iisang backup job.
Mga failure mode at ang eksaktong strings na makikita mo
Ang Error: database is locked mula sa SQLite ay nangangahulugang mas matagal na hinawakan ng isa pang connection ang write lock kaysa sa pinahintulutan ng iyong timeout. Hindi ito corruption. I-set ang PRAGMA busy_timeout sa bawat connection, pagkatapos ay hanapin ang mahabang transaction na dapat sana ay hinati sa ilang maiikling transaction.
Ang Error: unable to open database file pagkatapos ng pagbabago sa permission ay karaniwang nangangahulugang maaaring magsulat ang process sa file ngunit hindi sa directory nito. Gumagawa ang SQLite ng app.db-wal at app.db-shm sa tabi ng database, kaya dapat writable ang directory mismo, hindi lamang ang .db file.
Ang IO Error: Could not set lock on file mula sa DuckDB ay nangangahulugang may isa nang process na nakabukas sa database para sa pagsusulat. Isara ang kabilang shell, o buksan ang iyo bilang read-only.
Ang Out of Memory Error mula sa DuckDB sa isang maliit na VPS ay nangangahulugang mas maraming working memory ang kailangan ng query kaysa sa available. Nag-spill ang DuckDB sa disk kapag maaari, kaya bigyan ito ng mapag-i-spill sa pamamagitan ng pagbubukas ng database file sa disk sa halip na :memory:, at limitahan ang paggamit nito gamit ang SET memory_limit = '2GB';. Sa isang box na nagpapatakbo ng iba pang serbisyo, ang limitasyong ito ang pumipigil sa isang ad hoc query na magpaubos sa RAM ng iyong application.
Ang Binder Error: Referenced column "amount" not found kapag nag-query ng Parquet ay halos palaging nangangahulugang hindi ang schema ng file ang iyong natatandaan. Patakbuhin ang DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; at basahin ang aktuwal na mga pangalan ng column.
Paano pumili sa aktuwal na paggamit
Alamin kung ano ang write pattern. Kung maraming maliliit na write na kailangang mapanatili kahit magkaroon ng power cut, SQLite ang piliin. Alamin din kung ano ang read pattern. Kung full scan na may mga aggregate sa mahabang history ang kailangan, DuckDB ang angkop. Sa karamihan ng totoong system, parehong “oo” ang sagot sa dalawang tanong. Ang tamang hakbang ay gamitin ang bawat engine sa bahagi kung saan ito mahusay, sa halip na piliting punan ng isa ang kakayahan ng isa pa.
Ang migration na dapat iwasan ay ang paglipat ng live application state sa DuckDB dahil mabagal ang isang report. Mabagal ang report dahil sa storage layout. Kaya export ang tamang fix, hindi rewrite ng write path.
FAQ
Maaari bang ipalit ang DuckDB sa SQLite para sa database ng application ko?
Hindi para sa application na madalas magsulat. Nagla-lock ang DuckDB sa buong database file kapag may write operation, isang read-write process lang ang pinapayagan nito sa bawat pagkakataon, at nakatuon ito sa maramihang pagbabago sa halip na single-row inserts. Itago ang transactional state sa SQLite at ipabasa ito sa DuckDB gamit ang ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); kapag kailangan ito ng isang report.
Mas mabilis ba talaga ang DuckDB kaysa SQLite para sa analytics?
Oo, para sa mga scan at aggregate sa malaking table. Ang dahilan ay ang storage layout, hindi isang tuning trick. Binabasa lamang ng DuckDB ang mga column na tinutukoy ng query at pinoproseso ang mga value nang batch-batch, samantalang kailangang daanan ng SQLite ang buong row para maabot ang isang field. Sa pagkuha ng isang row gamit ang primary key, kabaligtaran ang resulta dahil dalawang page lang ang ina-access ng SQLite, habang ina-access ng DuckDB ang storage ng bawat column.
Kailangan ko ba ng malaking RAM para patakbuhin ang DuckDB sa isang VPS?
Hindi, pero magtakda ng limit at maglaan ng disk. Magbukas ng database file sa halip na :memory: upang mai-spill ng DuckDB sa disk ang mga intermediate result, pagkatapos ay itakda ang SET memory_limit = '2GB'; sa halagang kaya ng iyong VPS na ilaan. Kung walang limit, maaaring magdulot ang isang malaking GROUP BY ng Out of Memory Error o magpaalis sa ibang serbisyo sa RAM.
Paano ko ililipat ang data ko mula SQLite papuntang Parquet?
I-attach ang SQLite file mula sa DuckDB at direktang i-copy ang query output gamit ang COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);. Patakbuhin ito ayon sa schedule para sa mga saradong period, gaya ng mga row mula noong nakaraang buwan, at panatilihin ang mga kamakailang row sa SQLite kung saan patuloy na nagsusulat ang application.
Alin ang dapat kong i-back up, at paano?
Pareho, pero magkaiba ang paraan. Gumawa ng SQLite snapshot gamit ang sqlite3 app.db ".backup '/srv/backup/app.db'" sa halip na cp dahil ang tumatakbong database ay isa ring -wal at -shm file, at maaaring maputol o maging inconsistent ang plain copy. Hindi nagbabago ang mga Parquet file kapag naisulat na, kaya sapat nang kopyahin ang directory.