سرور پر DuckDB بمقابلہ SQLite: کون سا بہتر ہے؟
SQLite transactional application state کے لیے اور DuckDB Parquet و CSV analytics کے لیے ہے۔ جانیں ایک ہی VPS پر دونوں کیوں چلتے ہیں، worked example کے ساتھ۔
سرور پر DuckDB بمقابلہ SQLite: ایک جملے میں جواب
SQLite ایک OLTP engine (آن لائن transaction processing) ہے: یہ data کو rows کی صورت میں محفوظ کرتا ہے اور ایک وقت میں چند rows کو محفوظ اور تیزی سے read اور write کرنے کے لیے بنایا گیا ہے۔ DuckDB ایک OLAP engine (آن لائن analytical processing) ہے: یہ data کو columns کی صورت میں محفوظ کرتا ہے اور لاکھوں rows کو scan کرکے ایک aggregate واپس کرنے کے لیے بنایا گیا ہے۔ دونوں embedded libraries ہیں، دونوں ایک سادہ file کھولتی ہیں، اور کسی میں بھی ایسا server process نہیں چلتا جس کی مسلسل نگرانی کرنا ضروری ہو۔
اس لیے "کون سا؟" کا دیانت دار جواب تقریباً ہمیشہ "دونوں، ایک ہی VPS پر" ہوتا ہے۔ آپ کی application اپنی live state SQLite میں محفوظ رکھتی ہے۔ آپ کی reporting، DuckDB کے ذریعے Parquet اور CSV files سے data پڑھتی ہے۔ دونوں ایک دوسرے کے حریف نہیں ہیں، کیونکہ دونوں ایک ہی کام نہیں کر رہے۔
قطار پر مبنی storage اور column-based storage نتیجہ کیوں بدلتے ہیں
SQLite ایک row کو page کے اندر ایک مسلسل حصے کے طور پر لکھتا ہے۔ primary key کے ذریعے ایک order حاصل کرنے کے لیے ایک index page اور ایک data page تک رسائی درکار ہوتی ہے، یعنی 2 reads۔ ایپلیکیشن فی سیکنڈ ہزاروں مرتبہ یہی کام کرتی ہے: اس user کو پڑھنا، اس session کو update کرنا، اور یہ order insert کرنا۔
DuckDB ہر column کو الگ لکھتا اور اسے compress کرتا ہے۔ 5 million rows پر amount_cents کا مجموعہ نکالتے وقت صرف amount_cents column پڑھا جاتا ہے، file کے ہر دوسرے byte کو چھوڑ دیا جاتا ہے، اور values کے batches پر vectorised code کے ذریعے sum چلایا جاتا ہے۔ باقی columns disk سے کبھی نہیں پڑھے جاتے۔ رفتار کی اصل وجہ یہی ہے۔
اب ہر engine کو دوسرے engine کے workload پر چلائیں۔ SQLite میں کسی column کا مجموعہ نکالتے وقت ہر row پر جانا پڑتا ہے اور ایک field تک پہنچنے کے لیے page سے پوری row پڑھنی پڑتی ہے۔ اس لیے ضرورت سے کہیں زیادہ disk data پڑھا جاتا ہے۔ DuckDB میں ایک order insert کرتے وقت ایک value کے لیے ہر column کے storage کو چھونا پڑتا ہے، اور اس عمل کے لیے پوری database file پر write lock لیا جاتا ہے۔ کوئی بھی engine خراب نہیں ہے۔ ہر 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);
SQLOutput کی پہلی لائن wal ہے۔ یہ PRAGMA ہے جو تبدیل کیے گئے mode کی اطلاع دیتا ہے، اور server پر سب سے زیادہ مفید setting ہے۔ default rollback journal mode میں ایک writer ہر reader کو روک دیتا ہے۔ 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 کے ساتھ مزید دو files، app.db-wal اور app.db-shm، ظاہر ہوں گی، اور دونوں اسی database سے متعلق ہیں۔ application چلتے ہوئے صرف app.db copy کرنے سے backup نامکمل بنے گا۔ اس کی وضاحت آگے کی گئی ہے۔
SQLite اب بھی ایک وقت میں صرف ایک writer کی اجازت دیتا ہے۔ یہ حد lock ہے، queue نہیں۔ اس لیے جو دوسرا writer بہت دیر تک wait کرے، وہ ہمیشہ کے لیے block ہونے کے بجائے database is locked کے ساتھ fail ہو جاتا ہے۔ application کی جانب سے کھولے جانے والے ہر connection پر PRAGMA busy_timeout = 5000; کے ذریعے wait time بڑھائیں۔ معمول کے web workload میں پانچ seconds انتظار کرنے سے ان میں سے زیادہ تر errors ختم ہو جاتے ہیں۔
جہاں DuckDB بہتر ہے: پہلے سے موجود فائلوں پر تجزیہ
جب سوال "کتنے"، "کتنی مقدار" یا "ٹاپ 10 میں کون سے" سے شروع ہو اور input میں CSV یا Parquet فائلوں کا مجموعہ ہو، تو DuckDB منتخب کریں۔ command line client انسٹال کریں۔ جولائی 2026 تک version 1.5.5 دستیاب ہے:
curl https://install.duckdb.org | shیہ script binary کو ~/.duckdb/cli/latest/duckdb میں انسٹال کرتی ہے اور وہ لائن دکھاتی ہے جو اسے آپ کے PATH میں شامل کرتی ہے۔ تصدیق کریں کہ یہ چل رہا ہے:
~/.duckdb/cli/latest/duckdb :memory: "SELECT version();"query کرنے کے لیے ایک حقیقی نوعیت کی فائل بنائیں۔ یہ script orders کی 5 million rows Parquet میں لکھتی ہے اور zstd سے compress کرتی ہے:
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);"اب تجزیاتی سوال چلائیں۔ shell کھولیں، timer فعال کریں، اور کسی import step کے بغیر فائل کو براہ راست 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 سے پڑھیں، کیونکہ نتیجہ آپ کی disk اور core count پر منحصر ہوتا ہے۔ اہم چیز اس کی عمومی شکل ہے۔ یہاں کوئی CREATE TABLE، کوئی INSERT اور کوئی load step نہیں تھا۔ DuckDB نے Parquet footer پڑھا، معلوم کیا کہ query کو کون سے column chunks درکار ہیں، اور صرف وہی chunks پڑھے۔ glob، FROM '/srv/data/orders-*.parquet'، کے ذریعے پوری directory بھی اسی طرح کام کرتی ہے۔ اسی طرح روزانہ کی exports کا ایک ماہ ایک query بن جاتا ہے۔
ان سب کے لیے disk speed بنیادی حد ہے، اور column scan ایک طویل sequential read ہوتا ہے۔ اسی لیے VPS پر NVMe اور پرانی SATA storage کے درمیان فرق یہاں SQLite کی چھوٹی random reads کے مقابلے میں زیادہ واضح نظر آتا ہے۔
DuckDB سے اپنے SQLite database کو پڑھنا
دونوں engines کی باہمی رسائی DuckDB کی sqlite extension کے ذریعے ہوتی ہے۔ Application database کو صرف پڑھنے کے لیے 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 کے وقت SQLite file سے rows پڑھتا ہے اور کوئی copy نہیں بناتا۔ یہ آسان ہے، لیکن تیز نہیں، کیونکہ disk پر data اب بھی row storage کی شکل میں ہے اور DuckDB کو اسے مکمل طور پر پڑھنا پڑتا ہے۔ اسے export کے لیے استعمال کریں، ایسے dashboard کے لیے نہیں جو ہر تیس سیکنڈ بعد reload ہوتا ہو:
COPY (SELECT * FROM app.orders)
TO '/srv/data/orders-2026-07.parquet' (FORMAT parquet, COMPRESSION zstd);یہی پورا pattern ہے۔ SQLite حالیہ live rows کا مالک رہتا ہے۔ Scheduled export بند periods کو Parquet میں تبدیل کرتا ہے۔ DuckDB ہر وہ سوال حل کرتا ہے جو کئی مہینوں پر محیط ہو، جبکہ application database چھوٹا رہتا ہے، جس سے اس کی writes تیز رہتی ہیں۔
Export کو ہاتھ سے چلانے کے بجائے schedule کے مطابق چلائیں۔ ایک systemd service اور timer کا جوڑا اس کام کے لیے مناسب ہے: ایک unit جو COPY چلاتی ہے، اور ایک timer جو اسے ہر رات چلاتا ہے۔
ایک ہی VPS پر دونوں چلانا
یہاں کسی container کی ضرورت نہیں، اور کسی port کی بھی ضرورت نہیں۔ دونوں engines libraries ہیں، اس لیے installation ایک package اور file path پر مشتمل ہوتی ہے۔ اگر آپ کے stack کا باقی حصہ پہلے ہی اسی VPS پر Docker Compose کے تحت چل رہا ہے تو database service شامل کرنے کے بجائے data directory اس container میں mount کریں جسے اس کی ضرورت ہے، کیونکہ شامل کرنے کے لیے کوئی service موجود نہیں۔
دو اصول اس انتظام میں مسائل سے بچاتے ہیں۔
ہر engine کے لیے الگ directory رکھیں: /srv/app اس SQLite file کے لیے جس میں application لکھتی ہے، اور /srv/data ان Parquet files کے لیے جنہیں analytics پڑھتا ہے۔ جب دونوں ایک ہی directory استعمال کرتے ہیں تو ایک کا snapshot لینے والا backup job دوسرے کے ساتھ بیک وقت چلنے لگتا ہے۔
دو processes کو ایک ہی DuckDB database file پر read-write mode میں مت چلائیں۔ صرف ایک process DuckDB file کو writing کے لیے کھول سکتا ہے، اور دوسرا اسے بالکل کھول نہیں پاتا۔ متعدد readers درست طور پر کام کرتے ہیں، بشرطیکہ ہر reader access_mode = 'READ_ONLY' set کرے۔ SQLite سے آنے والے صارفین کے لیے یہ بات غیر متوقع ہو سکتی ہے، کیونکہ وہاں کئی processes معمول کے مطابق ایک ہی file share کرتے ہیں۔ اگر آپ کا analytics صرف Parquet files پڑھتا ہے تو یہ مسئلہ پیدا ہی نہیں ہوتا۔ یہ durable state کو SQLite میں رکھنے کی ایک اور وجہ ہے۔
Backups مختلف ہوتے ہیں، اور یہ فرق نقصان دہ ثابت ہو سکتا ہے
فعال SQLite database تین files پر مشتمل ہوتا ہے۔ اسے write کے دوران cp کے ذریعے copy کرنے سے ایسی file بنتی ہے جو کھل تو جاتی ہے، لیکن درست نہیں ہوتی۔ اس کے بجائے engine کا اپنا backup command استعمال کریں۔ یہ application کے write جاری رکھنے کے دوران 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;"درست copy پر integrity_check، ok دکھاتا ہے۔ اس کے علاوہ کوئی بھی output ہو تو اس snapshot کو حذف کریں اور دوبارہ backup لیں۔
Parquet files لکھے جانے کے بعد تبدیل نہیں ہوتیں، اس لیے ان کے لیے کسی خاص طریقہ کار کی ضرورت نہیں ہے۔ Directory کا backup لیں۔ دونوں paths کو server سے باہر اپنے VPS سے restic backups کے ذریعے بھیجیں۔ اس طرح پوری data layer ایک ہی backup job میں دو directories پر مشتمل ہوگی۔
ناکامی کی صورتیں اور آپ کو نظر آنے والے عین strings
Error: database is locked from SQLite کا مطلب ہے کہ کسی دوسرے connection نے write lock آپ کے مقررہ timeout سے زیادہ دیر تک برقرار رکھا۔ یہ data corruption نہیں ہے۔ ہر connection پر PRAGMA busy_timeout set کریں، پھر ایسی طویل transaction تلاش کریں جسے کئی مختصر transactions میں ہونا چاہیے تھا۔
اجازت تبدیل کرنے کے بعد Error: unable to open database file عموماً اس بات کا مطلب ہے کہ process فائل میں write کر سکتا ہے، لیکن اس کی directory میں نہیں۔ SQLite database کے ساتھ app.db-wal اور app.db-shm بناتا ہے، اس لیے صرف .db فائل نہیں بلکہ directory خود بھی writable ہونی چاہیے۔
IO Error: Could not set lock on file from DuckDB کا مطلب ہے کہ کوئی دوسرا process پہلے ہی اس database کو writing کے لیے کھولے ہوئے ہے۔ دوسری shell بند کریں، یا اپنی shell کو read only mode میں کھولیں۔
چھوٹے VPS پر Out of Memory Error from DuckDB کا مطلب ہے کہ query کو دستیاب working memory سے زیادہ memory درکار تھی۔ DuckDB جہاں ممکن ہو، data کو disk پر spill کرتا ہے۔ اس لیے :memory: کے بجائے disk پر database file کھول کر اسے spill کرنے کی جگہ دیں، اور SET memory_limit = '2GB'; کے ذریعے اس کے استعمال کی حد مقرر کریں۔ دوسرے services چلانے والے server پر یہی حد کسی ad hoc query کو آپ کی application کو RAM سے باہر نکالنے سے روکتی ہے۔
Parquet query کرتے وقت Binder Error: Referenced column "amount" not found تقریباً ہمیشہ اس بات کا مطلب ہے کہ فائل کا schema وہ نہیں جسے آپ یاد سمجھ رہے ہیں۔ DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; چلائیں اور اصل column names پڑھیں۔
عملی طور پر انتخاب کیسے کریں
دیکھیں کہ write pattern کیا ہے۔ اگر بہت سی چھوٹی writes بجلی بند ہونے کے بعد بھی محفوظ رہنی چاہییں تو SQLite استعمال کریں۔ یہ بھی دیکھیں کہ read pattern کیا ہے۔ اگر طویل مدت کی history پر aggregates کے ساتھ full scans درکار ہوں تو DuckDB استعمال کریں۔ زیادہ تر حقیقی systems میں دونوں سوالات کا جواب ہاں ہوتا ہے۔ ایسی صورت میں درست طریقہ یہ ہے کہ ہر engine کو وہ کام دیا جائے جس میں وہ بہتر ہے، بجائے اس کے کہ ایک engine سے دوسرے کے کام کی توقع کی جائے۔
جس migration سے گریز کرنا چاہیے وہ یہ ہے کہ کسی report کے سست ہونے کی وجہ سے live application state کو DuckDB میں منتقل کر دیا جائے۔ Report storage layout کی وجہ سے سست تھی، اس لیے حل export ہے، write path کو دوبارہ لکھنا نہیں۔
FAQ
کیا DuckDB میری application database کے لیے SQLite کی جگہ لے سکتا ہے؟
اگر database میں بار بار 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); کے ذریعے اسے پڑھنے دیں۔
کیا analytics کے لیے DuckDB واقعی SQLite سے تیز ہے؟
بڑی table پر scans اور aggregates کے لیے ہاں، اور اس کی وجہ کسی tuning trick کے بجائے storage layout ہے۔ DuckDB صرف وہ columns پڑھتا ہے جنہیں query میں نامزد کیا گیا ہو اور values کو batches میں process کرتا ہے، جبکہ SQLite کو ایک field تک پہنچنے کے لیے پوری rows پڑھنی پڑتی ہیں۔ Primary key کے ذریعے ایک row حاصل کرنے میں نتیجہ الٹ جاتا ہے، کیونکہ SQLite دو pages کو access کرتا ہے جبکہ DuckDB ہر column کی storage کو access کرتا ہے۔
کیا VPS پر DuckDB چلانے کے لیے بہت زیادہ RAM درکار ہے؟
نہیں، لیکن اسے ایک limit اور disk space دیں۔ :memory: کے بجائے database file کھولیں تاکہ DuckDB intermediate results کو disk پر spill کر سکے، پھر SET memory_limit = '2GB'; کو اتنی value پر set کریں جتنی RAM آپ کا VPS دے سکتا ہے۔ Limit کے بغیر ایک بڑا 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 کا output براہِ راست copy کریں۔ بند periods، مثلاً گزشتہ ماہ کی rows، کے لیے اسے schedule کے مطابق چلائیں، اور حالیہ rows SQLite میں رہنے دیں جہاں application اب بھی انہیں write کرتی ہے۔
مجھے کس کا backup لینا چاہیے، اور کیسے؟
دونوں کا، لیکن مختلف طریقوں سے۔ SQLite snapshots کے لیے sqlite3 app.db ".backup '/srv/backup/app.db'" استعمال کریں، cp کے بجائے، کیونکہ running database ایک -wal اور -shm file بھی ہوتی ہے اور سادہ copy خراب یا نامکمل ہو سکتی ہے۔ Parquet files لکھے جانے کے بعد کبھی تبدیل نہیں ہوتیں، اس لیے directory کو copy کرنا کافی ہے۔