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

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

SQLite transactional डेटा के लिए है जबकि DuckDB Parquet और CSV पर analytics के लिए। जानें कि एक ही VPS पर दोनों का उपयोग कैसे करें और अपने डेटाबेस प्रदर्शन को कैसे बढ़ाएं।

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 कहाँ बेहतर है: आपके पास मौजूद फाइलों पर एनालिटिक्स

जब प्रश्न "कितने", "कितना" या "शीर्ष दस कौन से हैं" से शुरू हो और इनपुट CSV या Parquet फाइलों का ढेर हो, तो DuckDB चुनें। कमांड लाइन क्लाइंट इंस्टॉल करें, जुलाई 2026 के अनुसार संस्करण 1.5.5:

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

यह स्क्रिप्ट बाइनरी को ~/.duckdb/cli/latest/duckdb के अंतर्गत इंस्टॉल करती है और वह लाइन प्रिंट करती है जो इसे आपके PATH पर डालती है। पुष्टि करें कि यह चलता है:

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

क्वेरी करने के लिए एक यथार्थवादी फाइल बनाएं। यह zstd के साथ कंप्रेस की गई Parquet में ऑर्डर्स की पचास लाख पंक्तियाँ लिखता है:

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

अब एनालिटिकल प्रश्न पूछें। शेल खोलें, टाइमर चालू करें, और बिना किसी इम्पोर्ट स्टेप के सीधे फाइल को क्वेरी करें:

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

प्रकाशित आंकड़ों पर भरोसा करने के बजाय .timer से अपना नंबर पढ़ें, क्योंकि परिणाम आपकी डिस्क और कोर काउंट पर निर्भर करता है। इसका स्वरूप ही मायने रखता है। इसमें कोई CREATE TABLE, कोई INSERT और कोई लोड स्टेप नहीं था: DuckDB ने Parquet फुटर को पढ़ा, यह पता लगाया कि क्वेरी को किन कॉलम चंक्स की आवश्यकता है, और केवल उन्हें ही पढ़ा। एक पूरी डायरेक्टरी भी glob, FROM '/srv/data/orders-*.parquet' के साथ इसी तरह काम करती है, जो कि दैनिक एक्सपोर्ट्स के एक महीने के डेटा को एक क्वेरी में बदलने का तरीका है।

डिस्क की गति इन सबके नीचे का आधार है, और कॉलम स्कैन एक लंबा सीक्वेंशियल रीड है, इसलिए VPS पर NVMe और पुराने SATA स्टोरेज के बीच का अंतर यहाँ SQLite के छोटे रैंडम रीड्स की तुलना में अधिक स्पष्ट रूप से दिखाई देता है।

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 बन जाएगी।

विफलता के प्रकार और वे सटीक स्ट्रिंग्स जो आपको दिखाई देंगी

Error: database is locked का SQLite से आने का अर्थ है कि किसी अन्य कनेक्शन ने आपके timeout द्वारा अनुमत समय से अधिक समय तक write lock को होल्ड करके रखा है। यह डेटाबेस के खराब (corruption) होने का संकेत नहीं है। हर कनेक्शन पर PRAGMA busy_timeout सेट करें, और फिर उन लंबी transaction की तलाश करें जिन्हें कई छोटी transaction में विभाजित किया जाना चाहिए था।

अनुमति (permission) बदलने के बाद Error: unable to open database file का अर्थ आमतौर पर यह होता है कि process फाइल में तो लिख सकती है, लेकिन उसकी directory में नहीं। SQLite डेटाबेस के बगल में app.db-wal और app.db-shm बनाता है, इसलिए केवल .db फाइल ही नहीं, बल्कि पूरी directory का writable होना आवश्यक है।

DuckDB से IO Error: Could not set lock on file का अर्थ है कि किसी अन्य process ने उस डेटाबेस को पहले से ही write mode में open कर रखा है। दूसरी shell को बंद करें, या अपनी shell को read-only mode में open करें।

एक छोटे VPS पर DuckDB से Out of Memory Error का अर्थ है कि query को उपलब्ध memory से अधिक working memory की आवश्यकता थी। DuckDB संभव होने पर disk पर spill करता है, इसलिए :memory: के बजाय disk पर डेटाबेस फाइल open करके इसे spill करने के लिए जगह दें, और SET memory_limit = '2GB'; के साथ इसकी memory खपत को सीमित करें। अन्य services चलाने वाले सर्वर पर, यह सीमा ही वह चीज है जो किसी ad hoc query को आपकी application को RAM से बाहर करने से रोकती है।

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

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

यह पूछें कि 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 को access करता है।

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

नहीं, लेकिन इसे एक limit और disk space दें। :memory: के बजाय database file को open करें ताकि DuckDB intermediate results को disk पर spill कर सके, फिर SET memory_limit = '2GB'; को उस value पर set करें जिसे आपका VPS वहन कर सके। बिना limit के, एक बड़ी GROUP BY query Out of Memory Error को trigger कर सकती है या अन्य 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 अभी भी उन्हें write कर रहा है।

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

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