DuckDB dhidi ya SQLite: ipi utumie kwenye seva?
Elewa tofauti kati ya SQLite kwa ajili ya data ya OLTP na DuckDB kwa uchanganuzi wa OLAP. Jifunze kwa nini seva nyingi za VPS hutumia zote mbili kwa utendaji bora.
DuckDB dhidi ya SQLite kwenye seva: jibu la sentensi moja
SQLite ni injini ya OLTP (online transaction processing): huhifadhi data kama safu (rows) na imeundwa kusoma na kuandika safu chache kwa wakati mmoja, kwa usalama na haraka. DuckDB ni injini ya OLAP (online analytical processing): huhifadhi data kama safu wima (columns) na imeundwa kuchanganua mamilioni ya safu na kutoa matokeo ya jumla (aggregate). Zote mbili ni maktaba zilizopachikwa (embedded libraries), zote hufungua faili ya kawaida, na hakuna inayohitaji mchakato wa seva (server process) wa kuifuatilia kila mara.
Kwa hivyo, jibu la kweli kwa swali la "ipi nichague" karibu kila mara ni "zote mbili, kwenye VPS moja". Programu yako huweka hali yake ya sasa (live state) kwenye SQLite. Ripoti zako husoma faili za Parquet na CSV kwa kutumia DuckDB. Hazishindani kwa sababu hazifanyi kazi sawa.
Kwa nini uhifadhi wa safu mlalo na safu wima hubadilisha jibu
SQLite huandika safu mlalo (row) kama kipande kimoja kinachoendelea cha ukurasa. Kuchota oda moja kwa kutumia primary key yake hugusa ukurasa mmoja wa index na ukurasa mmoja wa data, ambayo ni usomaji wa mara mbili. Hilo ndilo hasa ambalo programu hufanya maelfu ya mara kwa sekunde: soma mtumiaji huyu, sasisha kikao hiki, ingiza oda hii.
DuckDB huandika kila safu wima (column) kivyake na kuibana. Kujumlisha amount_cents katika safu mlalo milioni tano husoma safu wima ya amount_cents pekee, huruka kila baiti nyingine kwenye faili, na kuendesha jumla kupitia msimbo wa vectorised juu ya makundi ya thamani. Safu wima nyingine hazisomwi kamwe kutoka kwenye diski, na hapo ndipo kasi inapotoka.
Sasa endesha kila injini dhidi ya mzigo wa kazi wa mwenzake. SQLite inapojumlisha safu wima, inabidi ipitie kila safu mlalo na kuvuta safu mlalo nzima kutoka kwenye ukurasa ili kufikia uwanja mmoja, kwa hivyo inasoma diski nyingi zaidi kuliko inavyohitaji. DuckDB inapoingiza oda moja, inabidi iguse hifadhi ya kila safu wima kwa ajili ya thamani moja, na inachukua write lock kwenye faili nzima ya database ili kufanya hivyo. Hakuna injini iliyoharibika. Kila moja inajibu swali ambalo haikuundwa kwa ajili yake.
Mahali ambapo SQLite inafanya vizuri: hali ya programu inayohitaji miamala (transactional application state)
Chagua SQLite wakati uandishi wa data ni mdogo, wa mara kwa mara, na haupaswi kupotea. Hii inajumuisha vikao (sessions), maagizo (orders), safu za foleni (queue rows), mipangilio, na chochote kinachotengenezwa na ombi la wavuti.
sudo apt update && sudo apt install -y sqlite3
sudo install -d -o "$USER" -g "$USER" /srv/appTengeneza jedwali na uwashe write-ahead logging katika hatua moja.
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);
SQLMstari wa kwanza wa matokeo ni wal. Hiyo ni PRAGMA inayotoa taarifa ya hali iliyobadilishiwa, na ndiyo mipangilio muhimu zaidi kwenye seva. Katika hali ya kawaida ya rollback journal, mwandishi huzuia kila msomaji. Katika hali ya WAL, wasomaji wanaendelea kusoma hali ya mwisho iliyohifadhiwa (committed state) wakati mwandishi mmoja anaongeza data, hivyo ripoti ya polepole haikwamishe tena ombi la wavuti lililo nyuma yake.
Thibitisha kuwa safu imerudi:
sqlite3 /srv/app/app.db "SELECT * FROM orders WHERE customer = 'ana';"Utapata 1|ana|2026-07-30T09:14:00Z|4200. Faili mbili zaidi zimejitokeza kando ya hifadhidata, app.db-wal na app.db-shm, na zote ni mali yake. Kunakili app.db pekee wakati programu inaendelea kufanya kazi kutakupa nakala iliyoharibika (torn backup), jambo ambalo limefafanuliwa zaidi hapa chini.
SQLite bado inaruhusu mwandishi mmoja kwa wakati mmoja. Kikomo hicho ni kufuli, si foleni, kwa hivyo mwandishi wa pili anayesubiri kwa muda mrefu atashindwa na kutoa database is locked badala ya kukwama milele. Ongeza muda wa kusubiri kwa kutumia PRAGMA busy_timeout = 5000; kwenye kila muunganisho unaofunguliwa na programu yako. Sekunde tano za uvumilivu huondoa makosa mengi haya katika mzigo wa kawaida wa kazi za wavuti.
Mahali ambapo DuckDB inashinda: uchanganuzi wa data kwenye faili ulizonazo tayari
Chagua DuckDB wakati swali linaanza na "ngapi", "kiasi gani" au "kumi bora ni zipi", na data yako ni mkusanyiko wa faili za CSV au Parquet. Sakinisha client ya mstari wa amri (command line), toleo la 1.5.5 kufikia Julai 2026:
curl https://install.duckdb.org | shHati hii inasakinisha binary kwenye ~/.duckdb/cli/latest/duckdb na kuchapisha mstari unaoiweka kwenye PATH yako. Thibitisha kuwa inafanya kazi:
~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"Tengeneza faili halisi ya kufanyia query. Hii inaandika safu milioni tano za oda kwenye Parquet, zilizobanwa kwa kutumia 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);"Sasa uliza swali la kiuchanganuzi. Fungua shell, washa kipima muda (timer), na ufanye query kwenye faili moja kwa moja bila hatua ya kuingiza data (import):
.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;Soma namba yako mwenyewe kutoka .timer badala ya kuamini namba iliyochapishwa, kwa sababu matokeo yanategemea diski yako na idadi ya core zako. Umbo la matokeo ndilo la muhimu. Hakukuwa na CREATE TABLE, hakukuwa na INSERT na hakukuwa na hatua ya kupakia data: DuckDB ilisoma footer ya Parquet, ikabaini ni sehemu zipi za safu (column chunks) ambazo query ilihitaji, na kusoma hizo pekee. Saraka nzima inafanya kazi kwa njia hiyo hiyo ukitumia glob, FROM '/srv/data/orders-*.parquet', ambayo ndiyo njia inayofanya mwezi mzima wa data za kila siku kuwa query moja.
Kasi ya diski ndiyo msingi wa haya yote, na uchanganuzi wa safu (column scan) ni usomaji mrefu wa mfululizo, kwa hivyo tofauti kati ya NVMe na hifadhi ya zamani ya SATA kwenye VPS inaonekana hapa wazi zaidi kuliko inavyoonekana kwenye usomaji mdogo wa nasibu (random reads) wa SQLite.
Kusoma database yako ya SQLite kutoka DuckDB
Injini hizi mbili hukutana kupitia extension ya sqlite ya DuckDB. Unganisha database ya programu kwa hali ya kusoma pekee (read only), ili query ya uchanganuzi isiweze kamwe kuandika kwenye hali ya sasa ya data:
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;Hii husoma safu (rows) kutoka kwenye faili la SQLite wakati wa query bila kuhitaji nakala. Ni njia rahisi lakini si ya haraka, kwa sababu data iliyo kwenye diski bado imehifadhiwa kwa mfumo wa safu na DuckDB inalazimika kuichanganua. Itumie kwa ajili ya kusafirisha data (export), si kwa ajili ya dashboard inayojipakia upya kila sekunde thelathini:
COPY (SELECT * FROM app.orders)
TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);Taarifa hiyo moja ndiyo muundo mzima. SQLite inamiliki safu za hivi karibuni. Usafirishaji uliopangwa hubadilisha vipindi vilivyofungwa kuwa Parquet. DuckDB hujibu kila swali linalohusu miezi mingi, na database ya programu hubaki ndogo, jambo linalofanya uandikaji wake uwe wa haraka.
Endesha usafirishaji kwa ratiba badala ya kuufanya kwa mkono. Jozi ya systemd service na timer ndiyo ukubwa sahihi kwa kazi hii: unit moja inayoendesha COPY, na timer moja inayoiwasha kila usiku.
Kuziendesha zote mbili kwenye VPS moja
Hakuna kinachohitaji container wala port hapa. Injini zote mbili ni maktaba (libraries), kwa hivyo usakinishaji ni wa kifurushi na njia ya faili (file path). Ikiwa sehemu nyingine ya stack yako tayari inaendeshwa chini ya Docker Compose kwenye VPS hiyo hiyo, mount saraka ya data kwenye container inayohitaji badala ya kuongeza huduma ya database, kwa sababu hakuna huduma ya kuongeza.
Sheria mbili huweka mpangilio huu mbali na matatizo.
Ipe kila injini saraka yake: /srv/app kwa faili ya SQLite ambayo programu huandika, /srv/data kwa faili za Parquet ambazo analytics husoma. Zinaposhiriki saraka moja, kazi ya backup inayochukua snapshot ya moja itashindana na nyingine.
Usielekeze michakato (processes) miwili kwenye faili moja ya database ya DuckDB katika hali ya kusoma na kuandika (read-write mode). Mchakato mmoja tu ndio unaoweza kushikilia faili ya DuckDB kwa ajili ya kuandika, na wa pili utashindwa kuifungua kabisa. Wasomaji wengi ni sawa wakati kila mmoja wao anapoweka access_mode = 'READ_ONLY'. Hili huwashangaza watu wanaotoka SQLite, ambapo michakato kadhaa hushiriki faili mara kwa mara. Ikiwa analytics yako inasoma faili za Parquet pekee, swali hili halijitokezi, ambayo ni sababu nyingine ya kuhifadhi hali ya kudumu (durable state) kwenye SQLite.
Backup hutofautiana, na tofauti hiyo huleta matatizo
Database ya SQLite inayofanya kazi inajumuisha faili tatu, na kuzinakili kwa kutumia cp wakati inaandika data husababisha faili inayofunguka lakini yenye data isiyo sahihi. Tumia amri ya backup ya injini hiyo yenyewe, ambayo huchukua snapshot thabiti wakati programu inaendelea kuandika:
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 huchapisha ok ikiwa nakala ni nzuri. Kitu kingine chochote kinamaanisha kuwa unapaswa kutupa snapshot hiyo na kuchukua nyingine.
Faili za Parquet hazibadiliki baada ya kuandikwa, kwa hivyo hazihitaji ushughulikiaji maalum: nakili saraka hiyo. Tuma njia zote mbili nje ya seva kwa kutumia restic backups kutoka kwa VPS yako, na safu nzima ya data itakuwa saraka mbili katika kazi moja ya backup.
Njia za kufeli na ujumbe kamili utakaouona
Error: database is locked kutoka SQLite inamaanisha muunganisho mwingine ulishikilia lock ya kuandika kwa muda mrefu kuliko muda ulioruhusiwa. Hii si ufisadi wa data. Weka PRAGMA busy_timeout kwenye kila muunganisho, kisha tafuta transaction ndefu ambayo ilipaswa kuwa mfululizo wa transaction fupi.
Error: unable to open database file baada ya kubadilisha ruhusa kwa kawaida inamaanisha mchakato unaweza kuandika kwenye faili lakini si kwenye folda yake. SQLite hutengeneza app.db-wal na app.db-shm kando ya database, kwa hivyo folda yenyewe lazima iwe na ruhusa ya kuandika, si faili la .db pekee.
IO Error: Could not set lock on file kutoka DuckDB inamaanisha mchakato wa pili tayari umefungua database hiyo kwa ajili ya kuandika. Funga shell nyingine, au ufungue yako kwa hali ya kusoma pekee (read only).
Out of Memory Error kutoka DuckDB kwenye VPS ndogo inamaanisha query ilihitaji kumbukumbu (RAM) zaidi ya iliyokuwepo. DuckDB huhamishia data kwenye diski inapowezekana, kwa hivyo ipe nafasi ya kuhifadhi kwa kufungua faili la database kwenye diski badala ya :memory:, na udhibiti matumizi yake kwa SET memory_limit = '2GB';. Kwenye seva inayotumia huduma nyingine, kikomo hicho ndicho kinachozuia query ya ghafla isifukuze programu yako kutoka kwenye RAM.
Binder Error: Referenced column "amount" not found wakati wa kuuliza (querying) Parquet karibu kila mara inamaanisha schema ya faili si ile unayokumbuka. Tekeleza DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; na usome majina halisi ya safuwima (columns).
Jinsi ya kuchagua katika utekelezaji
Uliza muundo wa uandishi (write pattern) ni upi. Uandishi mwingi mdogo ambao lazima uendelee kuwepo baada ya kukatika kwa umeme unamaanisha SQLite. Uliza muundo wa usomaji (read pattern) ni upi. Utafutaji wa kina (full scans) wenye jumla (aggregates) juu ya historia ndefu unamaanisha DuckDB. Mifumo mingi ya kweli hujibu ndiyo kwa maswali yote mawili, na jibu sahihi ni kuipa kila injini sehemu ambayo inaiweza vizuri badala ya kulazimisha moja kati yao kufanya kazi ya nyingine.
Uhamiaji unaopaswa kuepukwa ni kuhamishia hali ya programu (application state) inayotumika kwenye DuckDB kwa sababu ripoti ilikuwa polepole. Ripoti ilikuwa polepole kwa sababu ya mpangilio wa hifadhi (storage layout), kwa hivyo suluhisho ni kutoa data (export), si kuandika upya njia yako ya uandishi (write path).
FAQ
Je, DuckDB inaweza kuchukua nafasi ya SQLite kwa database ya programu yangu?
Hapana, ikiwa programu yako inaandika data mara kwa mara. DuckDB hufunga (lock) faili nzima ya database wakati wa kuandika, inaruhusu mchakato mmoja tu wa kusoma na kuandika kwa wakati mmoja, na imesanifiwa kwa ajili ya mabadiliko makubwa badala ya kuingiza safu moja moja (single-row inserts). Hifadhi hali ya miamala (transactional state) katika SQLite na uiruhusu DuckDB kuisoma kwa kutumia ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); wakati ripoti inapohitajika.
Je, DuckDB ni ya haraka zaidi kuliko SQLite kwa uchanganuzi wa data?
Kwa uchanganuzi (scans) na jumla (aggregates) kwenye jedwali kubwa, ndiyo, na sababu ni mpangilio wa uhifadhi badala ya mbinu ya usanidi. DuckDB inasoma safu wima (columns) zilizotajwa kwenye query pekee na kuchakata thamani kwa makundi, wakati SQLite lazima ipitie safu nzima (rows) ili kufikia uwanja mmoja. Kwa kutafuta safu moja kwa kutumia primary key, hali hubadilika, kwa sababu SQLite inagusa kurasa mbili tu wakati DuckDB inagusa uhifadhi wa kila safu wima.
Je, nahitaji RAM nyingi ili kuendesha DuckDB kwenye VPS?
Hapana, lakini iwekee kikomo na utumie diski. Fungua faili ya database badala ya :memory: ili DuckDB iweze kuhamisha matokeo ya muda kwenye diski, kisha weka SET memory_limit = '2GB'; kwa thamani ambayo VPS yako inaweza kumudu. Bila kikomo, GROUP BY moja kubwa inaweza kusababisha Out of Memory Error au kuondoa huduma nyingine kwenye RAM.
Ninawezaje kuhamisha data yangu ya SQLite kwenda Parquet?
Unganisha faili ya SQLite kutoka DuckDB na unakili matokeo ya query moja kwa moja kwa kutumia COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd);. Iendeshe kwa ratiba kwa vipindi vilivyofungwa, kama vile safu za mwezi uliopita, na uache safu za hivi karibuni katika SQLite ambapo programu bado inaandika data.
Ni ipi ninayopaswa kuhifadhi (backup), na vipi?
Zote mbili, kwa njia tofauti. Chukua snapshots za SQLite kwa kutumia sqlite3 app.db ".backup '/srv/backup/app.db'" badala ya cp, kwa sababu database inayofanya kazi pia ni -wal na faili ya -shm, hivyo nakala ya kawaida inaweza kuharibika. Faili za Parquet hazibadiliki kamwe baada ya kuandikwa, kwa hivyo kunakili saraka (directory) kunatosha.