SSD Nodes Learn Hosting plans →
गाइड Matt Connorलेखक: Matt Connor · अपडेट किया गया: 2026-10-28

DuckDB बनाम SQLite: सर्वर पर किसका उपयोग कब करें?

SQLite ट्रांजेक्शनल डेटा के लिए है जबकि DuckDB एनालिटिक्स के लिए। जानें कि एक ही VPS पर दोनों का उपयोग कैसे करें और Parquet या CSV फाइलों को कुशलतापूर्वक कैसे प्रोसेस करें।

DuckDB बनाम SQLite सर्वर पर: एक वाक्य में उत्तर

SQLite एक OLTP (online transaction processing) इंजन है: यह डेटा को rows के रूप में स्टोर करता है और इसे एक बार में कुछ rows को सुरक्षित और तेजी से पढ़ने और लिखने के लिए बनाया गया है। DuckDB एक OLAP (online analytical processing) इंजन है: यह डेटा को columns के रूप में स्टोर करता है और इसे लाखों rows को स्कैन करके एक aggregate परिणाम देने के लिए बनाया गया है। दोनों ही embedded libraries हैं, दोनों एक plain file खोलते हैं, और दोनों में से कोई भी ऐसा server process नहीं चलाता जिसे आपको लगातार manage करना पड़े।

इसलिए "कौन सा चुनें" का सही उत्तर लगभग हमेशा "दोनों, एक ही VPS पर" होता है। आपका application अपनी live state को SQLite में रखता है। आपकी reporting DuckDB के साथ Parquet और CSV files को पढ़ती है। वे आपस में प्रतिस्पर्धा नहीं करते क्योंकि वे एक ही काम नहीं कर रहे हैं।

Row storage और column storage उत्तर को क्यों बदलते हैं

SQLite एक row को page के एक निरंतर हिस्से के रूप में लिखता है। primary key द्वारा एक order को fetch करने पर एक index page और एक data page access होता है, जो कि दो reads हैं। एक application प्रति सेकंड हजारों बार ठीक यही करती है: इस user को पढ़ना, इस session को update करना, इस order को insert करना।

DuckDB प्रत्येक column को अलग से लिखता है और उसे compress करता है। पाँच मिलियन rows पर amount_cents का sum करने के लिए केवल amount_cents column को पढ़ा जाता है, फाइल के बाकी सभी bytes को छोड़ दिया जाता है, और values के batches पर vectorised code के माध्यम से sum चलाया जाता है। अन्य columns को disk से कभी नहीं पढ़ा जाता, और यही गति का मुख्य कारण है।

अब प्रत्येक engine को दूसरे के workload पर चलाकर देखें। एक column का sum करते समय SQLite को हर row से गुजरना पड़ता है और एक field तक पहुँचने के लिए पूरी row को page से बाहर निकालना पड़ता है, इसलिए यह आवश्यकता से कहीं अधिक disk read करता है। एक order को insert करते समय DuckDB को एक single value के लिए हर column के storage को touch करना पड़ता है, और ऐसा करने के लिए उसे पूरी database file पर write lock लेना पड़ता है। कोई भी engine खराब नहीं है। प्रत्येक उस प्रश्न का उत्तर दे रहा है जिसके लिए उसे नहीं बनाया गया था।

SQLite कहाँ बेहतर है: transactional application state

SQLite का चयन तब करें जब writes छोटे और बार-बार होने वाले हों, और उन्हें खोना नहीं चाहिए। Sessions, orders, queue rows, settings, या कोई भी ऐसी चीज़ जिसे web request बनाता है, इसके लिए उपयुक्त है।

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

Table बनाएँ और उसी चरण में write-ahead logging को चालू करें।

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

Output की पहली पंक्ति wal है। यह PRAGMA है जो उस mode की रिपोर्ट कर रहा है जिसमें वह switch हुआ है, और यह सर्वर पर सबसे उपयोगी setting है। डिफ़ॉल्ट rollback journal mode में एक writer हर reader को block कर देता है। WAL mode में readers पिछली committed state को पढ़ते रहते हैं जबकि एक writer append करता है, इसलिए एक धीमी report अब उसके पीछे चल रहे web request को नहीं रोकती है।

जाँचें कि row वापस आ गई है:

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

आपको 1|ana|2026-07-30T09:14:00Z|4200 प्राप्त होगा। Database के बगल में दो और फाइलें दिखाई दीं, app.db-wal और app.db-shm, और दोनों उसी से संबंधित हैं। Application के चलने के दौरान केवल app.db को copy करने से एक अधूरा (torn) backup मिलता है, जिसे आगे विस्तार से समझाया गया है।

SQLite अभी भी एक समय में एक ही writer की अनुमति देता है। वह सीमा एक lock है, queue नहीं, इसलिए दूसरा writer जो बहुत देर तक प्रतीक्षा करता है, वह हमेशा के लिए block होने के बजाय database is locked के साथ fail हो जाता है। अपने application द्वारा खोले जाने वाले प्रत्येक connection पर PRAGMA busy_timeout = 5000; के साथ प्रतीक्षा समय बढ़ाएँ। पाँच सेकंड का धैर्य सामान्य web workload पर इन त्रुटियों में से अधिकांश को हटा देता है।

जहाँ DuckDB उपयोगी है: आपकी मौजूदा files पर analytics

जब सवाल "how many", "how much" या "which top ten" से शुरू हो और input CSV या Parquet files का संग्रह हो, तब DuckDB चुनें। command line client, version 2.0.0 (27 October 2026 तक LTS release), install करें:

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

जब तक आप कोई version pin नहीं करते, script latest stable release install करती है। उदाहरण के लिए, पिछले release पर बने रहने के लिए curl https://install.duckdb.org | DUCKDB_VERSION=1.5.6 sh का उपयोग करें। यह binary को ~/.duckdb/cli/latest/duckdb के अंतर्गत install करती है और वह line print करती है जो इसे आपके PATH पर रखती है। पुष्टि करें कि यह चल रहा है:

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

Query करने के लिए एक वास्तविक file बनाएँ। यह zstd से compressed Parquet में orders की five million rows लिखता है:

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

अब analytical question पूछें। shell खोलें, timer चालू करें और बिना किसी import step के file को सीधे query करें:

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

प्रकाशित number पर भरोसा करने के बजाय अपना number .timer से पढ़ें, क्योंकि result आपके disk और core count पर निर्भर करता है। महत्वपूर्ण result का overall shape है। इसमें कोई CREATE TABLE, कोई INSERT और कोई load step नहीं था: DuckDB ने Parquet footer पढ़ा, पता लगाया कि query को कौन-से column chunks चाहिए और केवल उन्हीं chunks को पढ़ा। glob, FROM '/srv/data/orders-*.parquet', के साथ पूरी directory इसी तरह काम करती है। इसी कारण daily exports का एक महीना एक query बन जाता है।

Disk speed इस पूरी प्रक्रिया की आधारभूत सीमा है और column scan एक लंबा sequential read होता है। इसलिए VPS पर NVMe और पुराने SATA storage के बीच का अंतर SQLite के छोटे random reads की तुलना में यहाँ अधिक स्पष्ट दिखाई देता है।

DuckDB से अपनी SQLite database को पढ़ना

ये दोनों engines DuckDB के sqlite extension के माध्यम से जुड़ते हैं। application database को read-only मोड में attach करें, ताकि कोई analytics query live state में बदलाव न कर सके:

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;

यह query के समय बिना किसी copy के SQLite file से rows पढ़ता है। यह सुविधाजनक है लेकिन तेज नहीं है, क्योंकि disk पर data अभी भी row storage के रूप में है और DuckDB को उसे traverse करना पड़ता है। इसका उपयोग export के लिए करें, न कि ऐसे dashboard के लिए जो हर तीस सेकंड में reload होता है:

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

यह एक statement ही पूरा pattern है। SQLite के पास हाल की live rows रहती हैं। एक scheduled export बंद हो चुकी अवधि के data को Parquet में बदल देता है। DuckDB उन सभी सवालों के जवाब देता है जो महीनों के data पर आधारित होते हैं, और application database का आकार छोटा रहता है, जिससे उसकी write speed बनी रहती है।

Export को मैन्युअल रूप से चलाने के बजाय schedule पर चलाएं। इसके लिए एक systemd service और timer pair सही विकल्प है: एक unit जो COPY को चलाती है, और एक timer जो इसे हर रात trigger करता है।

एक ही VPS पर दोनों को चलाना

यहाँ किसी भी चीज़ के लिए container या port की आवश्यकता नहीं है। दोनों engines libraries हैं, इसलिए इनका इंस्टॉलेशन केवल एक package और file path का मामला है। यदि आपका बाकी stack पहले से ही उसी VPS पर Docker Compose के अंतर्गत चल रहा है, तो database service जोड़ने के बजाय data directory को उस container में mount करें जिसे इसकी आवश्यकता है, क्योंकि यहाँ जोड़ने के लिए कोई service नहीं है।

दो नियम इस व्यवस्था को समस्याओं से दूर रखते हैं।

प्रत्येक engine को अपनी directory दें: application द्वारा लिखे जाने वाले SQLite file के लिए /srv/app, और analytics द्वारा पढ़े जाने वाले Parquet files के लिए /srv/data। जब वे एक ही directory साझा करते हैं, तो एक का snapshot लेने वाला backup job दूसरे के साथ conflict कर सकता है।

दो processes को एक DuckDB database file पर read-write mode में point न करें। केवल एक process ही DuckDB file को writing के लिए hold कर सकती है, और दूसरी process उसे open करने में विफल हो जाएगी। जब हर reader access_mode = 'READ_ONLY' set करता है, तो कई readers का होना ठीक है। यह उन लोगों को हैरान करता है जो SQLite से आते हैं, जहाँ कई processes नियमित रूप से एक file साझा करती हैं। यदि आपकी analytics केवल Parquet files को पढ़ती है, तो यह सवाल कभी नहीं उठता, जो कि durable state को SQLite में रखने का एक और कारण है।

Backups में अंतर होता है, और यह अंतर समस्या पैदा करता है

चलते हुए SQLite database में तीन files होती हैं, और उन्हें cp से copy करने पर आपको ऐसी file मिलेगी जो open तो हो जाएगी लेकिन गलत होगी। engine के अपने backup command का उपयोग करें, जो application के लिखते रहने के दौरान एक consistent snapshot ले लेता है:

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 सफल copy होने पर ok print करता है। इसके अलावा कुछ भी दिखने का मतलब है कि उस snapshot को हटा दें और दूसरा लें।

Parquet files लिखे जाने के बाद कभी नहीं बदलती हैं, इसलिए उन्हें किसी विशेष handling की आवश्यकता नहीं होती: directory का backup लें। दोनों paths को restic backups from your VPS का उपयोग करके server से बाहर भेजें, और पूरी data layer एक backup job में दो directories बन जाएगी।

Failure modes और आपको दिखाई देने वाले exact strings

SQLite से आने वाला database is locked, चाहे वह आपके application के driver से आए या sqlite3 shell से, इसका अर्थ है कि किसी अन्य connection ने write lock को आपके timeout की अनुमति से अधिक समय तक पकड़े रखा। यह corruption नहीं है। प्रत्येक connection पर PRAGMA busy_timeout सेट करें। फिर ऐसी लंबी transaction खोजें जिसे कई छोटी transactions में होना चाहिए था।

Permission बदलने के बाद unable to open database file का अर्थ आम तौर पर यह होता है कि process file में लिख सकता है, लेकिन उसकी directory में नहीं। SQLite database के पास app.db-wal और app.db-shm बनाता है। इसलिए केवल .db file नहीं, बल्कि directory स्वयं भी writable होनी चाहिए।

DuckDB से आने वाला IO Error: Could not set lock on file बताता है कि दूसरा process पहले से उस database को writing के लिए open किए हुए है। दूसरा shell बंद करें या अपना shell read only खोलें।

छोटे VPS पर DuckDB से आने वाला Out of Memory Error बताता है कि query को उपलब्ध working memory से अधिक memory चाहिए थी। DuckDB intermediate results को default रूप से disk पर spill करता है। इसमें :memory: session भी शामिल है। इसलिए database file खोलना spilling को सक्षम नहीं बनाता। यह temporary files database file के पास मौजूद .tmp directory में लिखता है। In-memory session के लिए यह उन्हें उस स्थान के सापेक्ष .tmp directory में लिखता है जहाँ से आपने इसे शुरू किया था। इसलिए इसे free disk space वाली writable जगह से शुरू करें या SET temp_directory = '/var/tmp/duckdb'; के साथ कोई दूसरी जगह दें। महत्वपूर्ण setting cap है: SET memory_limit = '2GB';। Default machine की RAM का 80% है। इसलिए अन्य services चलाने वाले box पर यही limit किसी ad hoc query को आपकी application को RAM से बाहर धकेलने से रोकती है। कुछ aggregates, जैसे string_agg(), spill नहीं कर सकते। कुछ sorting-based aggregates भी spill नहीं कर सकते। इसलिए उन पर आधारित query फिर भी out of memory हो सकती है।

Parquet query करते समय Binder Error: Referenced column "amount" not found का अर्थ लगभग हमेशा यह होता है कि file का schema वह नहीं है जो आपको याद है। DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; चलाएँ और वास्तविक column names पढ़ें।

व्यावहारिक रूप से चुनाव कैसे करें

यह पूछें कि write pattern क्या है। यदि बिजली कटने के बाद भी कई छोटे-छोटे writes सुरक्षित रहने चाहिए, तो SQLite का उपयोग करें। यह पूछें कि read pattern क्या है। यदि लंबे इतिहास पर aggregates के साथ full scans करने हैं, तो DuckDB का उपयोग करें। अधिकांश वास्तविक सिस्टम इन दोनों सवालों का जवाब 'हाँ' में देते हैं, और सही प्रतिक्रिया यह है कि प्रत्येक engine को वह काम सौंपें जिसमें वह बेहतर है, न कि एक को दूसरे का काम करने के लिए मजबूर करें।

जिस migration से बचना चाहिए, वह है live application state को DuckDB में ले जाना, केवल इसलिए क्योंकि कोई report धीमी थी। report धीमी होने का कारण storage layout था, इसलिए इसका समाधान data export करना है, न कि अपने write path को फिर से लिखना।

FAQ

क्या DuckDB मेरे application database के लिए SQLite की जगह ले सकता है?

उन applications के लिए नहीं जिनमें बार-बार write operations होते हैं। DuckDB पूरी database file पर write lock लगा देता है, एक समय में केवल एक ही read-write process की अनुमति देता है, और इसे single-row inserts के बजाय bulk changes के लिए ट्यून किया गया है। Transactional state को SQLite में रखें और जब किसी report के लिए आवश्यकता हो, तो DuckDB को ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); के साथ उसे read करने दें।

क्या analytics के लिए DuckDB वास्तव में SQLite से तेज़ है?

बड़ी table पर scans और aggregates के लिए, हाँ, और इसका कारण कोई tuning trick नहीं बल्कि storage layout है। DuckDB केवल उन columns को read करता है जिनके नाम query में दिए गए हैं और values को batches में process करता है, जबकि SQLite को एक field तक पहुँचने के लिए पूरी rows को scan करना पड़ता है। Primary key द्वारा एक single row fetch करने के लिए स्थिति उलट जाती है, क्योंकि SQLite केवल दो pages को touch करता है जबकि DuckDB हर column के storage को touch करता है।

क्या VPS पर DuckDB चलाने के लिए मुझे बहुत अधिक RAM की आवश्यकता है?

नहीं, लेकिन इसे एक limit दें और disk पर spill करने के लिए जगह रखें। DuckDB डिफ़ॉल्ट रूप से intermediate results को disk पर spill करता है, यहाँ तक कि :memory: session में भी, बशर्ते इसकी temporary directory writable हो और उसमें खाली जगह हो। फिर SET memory_limit = '2GB'; को उस मान पर सेट करें जिसे आपका VPS वहन कर सके। यदि इसे RAM के 80% के डिफ़ॉल्ट मान पर छोड़ दिया जाए, तो एक बड़ी GROUP BY के कारण Out of Memory Error बढ़ सकता है या अन्य services RAM से बाहर हो सकती हैं।

मैं अपना SQLite data Parquet में कैसे लाऊँ?

DuckDB से SQLite file को attach करें और COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd); के साथ सीधे query को copy करें। इसे बंद अवधियों (जैसे पिछले महीने की rows) के लिए schedule पर चलाएँ, और हाल की rows को SQLite में ही रहने दें जहाँ application अभी भी उन्हें लिख रहा है।

मुझे किसका backup लेना चाहिए, और कैसे?

दोनों का, अलग-अलग तरीकों से। cp के बजाय sqlite3 app.db ".backup '/srv/backup/app.db'" का उपयोग करके SQLite snapshots लें, क्योंकि एक चलती हुई database एक -wal और एक -shm file भी होती है और साधारण copy करने से data खराब हो सकता है। Parquet files एक बार लिखे जाने के बाद कभी नहीं बदलतीं, इसलिए directory को copy करना ही पर्याप्त है।

क्या यह लेख उपयोगी था?