SSD Nodes Learn 8GB RAM — $66/বছর
নির্দেশিকা Matt Connorদ্বারা Matt Connor · আপডেট করা হয়েছে 2026-08-01

সার্ভারে DuckDB বনাম SQLite: কোনটি ব্যবহার করবেন

SQLite-এ transactional application state রাখুন, আর DuckDB দিয়ে Parquet ও CSV-তে analytics চালান। একটি VPS-এ দুটিই কেন দরকার, কাজের উদাহরণসহ জানুন।

সার্ভারে DuckDB বনাম SQLite: এক বাক্যের উত্তর

SQLite একটি OLTP ইঞ্জিন (online transaction processing): এটি ডেটা সারি হিসেবে সংরক্ষণ করে এবং একবারে অল্প কয়েকটি সারি নিরাপদে ও দ্রুত পড়া এবং লেখার জন্য তৈরি। DuckDB একটি OLAP ইঞ্জিন (online analytical processing): এটি ডেটা কলাম হিসেবে সংরক্ষণ করে এবং লক্ষ লক্ষ সারি স্ক্যান করে একটি aggregate ফল ফেরত দেওয়ার জন্য তৈরি। দুটিই embedded library, দুটিই একটি সাধারণ ফাইল খোলে, এবং কোনোটিই এমন server process চালায় না যেটির নিয়মিত তদারকি করতে হবে।

তাই “কোনটি” প্রশ্নের সৎ উত্তর প্রায় সব সময়ই “একই VPS-এ দুটিই”। আপনার application-এর live state SQLite-এ থাকে। আপনার reporting DuckDB দিয়ে Parquet এবং CSV ফাইল পড়ে। তারা প্রতিদ্বন্দ্বী নয়, কারণ তারা একই কাজ করে না।

সারি-ভিত্তিক স্টোরেজ এবং কলাম-ভিত্তিক স্টোরেজ কেন ফলাফল বদলে দেয়

SQLite একটি পৃষ্ঠায় একটি সারিকে একটানা অংশ হিসেবে লেখে। প্রাথমিক কী ব্যবহার করে একটি অর্ডার আনতে একটি ইনডেক্স পৃষ্ঠা এবং একটি ডেটা পৃষ্ঠা অ্যাক্সেস করতে হয়, অর্থাৎ 2টি রিড। একটি অ্যাপ্লিকেশন প্রতি সেকেন্ডে হাজার হাজার বার ঠিক এই কাজই করে: এই ব্যবহারকারীকে পড়া, এই সেশন আপডেট করা, এই অর্ডার সন্নিবেশ করা।

DuckDB প্রতিটি কলাম আলাদাভাবে লেখে এবং সেটি কমপ্রেস করে। পাঁচ মিলিয়ন সারি জুড়ে amount_cents-এর যোগফল বের করার সময় এটি শুধু amount_cents কলাম পড়ে, ফাইলের অন্য প্রতিটি বাইট এড়িয়ে যায় এবং মানগুলোর ব্যাচের ওপর ভেক্টরাইজড কোড ব্যবহার করে যোগফল গণনা করে। অন্য কলামগুলো কখনোই ডিস্ক থেকে পড়া হয় না। গতি মূলত এখান থেকেই আসে।

এখন প্রতিটি ইঞ্জিনকে অন্য ইঞ্জিনের কাজের ধরন দিয়ে চালান। SQLite-তে একটি কলামের যোগফল বের করতে হলে প্রতিটি সারি অতিক্রম করতে হয় এবং একটি ক্ষেত্র পর্যন্ত পৌঁছানোর জন্য পৃষ্ঠা থেকে পুরো সারিটি আনতে হয়। ফলে প্রয়োজনের তুলনায় অনেক বেশি ডিস্ক পড়তে হয়। DuckDB-তে একটি অর্ডার সন্নিবেশ করতে একটি মানের জন্য প্রতিটি কলামের স্টোরেজে অ্যাক্সেস করতে হয় এবং কাজটি সম্পন্ন করতে পুরো ডেটাবেস ফাইলে write lock নিতে হয়। কোনো ইঞ্জিনই ত্রুটিপূর্ণ নয়। প্রতিটি ইঞ্জিন এমন একটি প্রশ্নের উত্তর দিচ্ছে, যার জন্য সেটিকে তৈরি করা হয়নি।

যেখানে SQLite কার্যকর: লেনদেনভিত্তিক অ্যাপ্লিকেশন স্টেট

যখন write অপারেশন ছোট, ঘন ঘন হয় এবং হারানো যাবে না, তখন SQLite নির্বাচন করুন। Sessions, orders, queue rows, settings—ওয়েব 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

আউটপুটের প্রথম লাইন হলো wal। এটি PRAGMA, যা mode পরিবর্তনের পরের অবস্থা জানায়, এবং server-এ এটিই সবচেয়ে কার্যকর setting। Default rollback journal mode-এ একজন writer সব reader-কে block করে। WAL mode-এ একজন writer append করার সময় reader-রা সর্বশেষ committed state পড়তে থাকে। তাই ধীর report-এর কারণে তার পেছনে থাকা web request আর আটকে থাকে না।

Row-টি ফেরত এসেছে কি না যাচাই করুন:

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

আপনি 1|ana|2026-07-30T09:14:00Z|4200 পাবেন। Database-এর পাশে আরও দুটি file তৈরি হয়েছে: app.db-wal এবং app.db-shm। দুটিই database-এর অংশ। Application চলার সময় শুধু app.db copy করলে backup অসম্পূর্ণ হবে। এ বিষয়ে নিচে আরও বলা হয়েছে।

SQLite-তে এক সময়ে এখনও মাত্র একজন writer কাজ করতে পারে। এই সীমাটি একটি lock, queue নয়। তাই দ্বিতীয় writer খুব বেশি সময় অপেক্ষা করলে অনন্তকাল block না হয়ে database is locked দিয়ে ব্যর্থ হয়। Application যে প্রতিটি connection খোলে, তাতে PRAGMA busy_timeout = 5000; দিয়ে অপেক্ষার সময় বাড়ান। স্বাভাবিক web workload-এ পাঁচ সেকেন্ড অপেক্ষা করলে এই error-গুলোর বেশিরভাগ দূর হয়।

আপনার কাছে থাকা ফাইলের ওপর বিশ্লেষণে DuckDB যেখানে এগিয়ে

প্রশ্নটি যদি "কতগুলি", "কত" বা "শীর্ষ দশটির কোনগুলি" দিয়ে শুরু হয় এবং ইনপুটে একগুচ্ছ CSV বা Parquet ফাইল থাকে, তাহলে DuckDB বেছে নিন। কমান্ড-লাইন ক্লায়েন্ট ইনস্টল করুন। July 2026 অনুযায়ী version 1.5.5:

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

স্ক্রিপ্টটি binary-টি ~/.duckdb/cli/latest/duckdb-এর অধীনে ইনস্টল করে এবং সেটিকে আপনার PATH-এ যুক্ত করার লাইনটি প্রিন্ট করে। এটি চলছে কি না নিশ্চিত করুন:

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

জিজ্ঞাসা করার জন্য একটি বাস্তবসম্মত ফাইল তৈরি করুন। এটি zstd দিয়ে compressed করে Parquet-এ orders-এর পাঁচ মিলিয়ন row লেখে:

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 ধাপ ছাড়াই সরাসরি ফাইলটি 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;

প্রকাশিত কোনো সংখ্যার ওপর নির্ভর না করে .timer থেকে নিজের ফলাফল পড়ুন, কারণ ফলাফল আপনার disk এবং core-এর সংখ্যার ওপর নির্ভর করে। ফলাফলের গঠনই গুরুত্বপূর্ণ। কোনো CREATE TABLE, কোনো INSERT এবং কোনো load ধাপ ছিল না: DuckDB Parquet footer পড়েছে, query-টির কোন column chunk প্রয়োজন তা নির্ধারণ করেছে এবং শুধু সেগুলিই পড়েছে। glob, FROM '/srv/data/orders-*.parquet' ব্যবহার করলে একটি সম্পূর্ণ directory-ও একইভাবে কাজ করে। এভাবেই এক মাসের দৈনিক export একটি query-তে পরিণত হয়।

এই পুরো প্রক্রিয়ার ভিত্তি হলো disk speed। Column scan দীর্ঘ sequential read হওয়ায় VPS-এ NVMe এবং পুরোনো SATA storage-এর পার্থক্য এখানে SQLite-এর ছোট random read-এর তুলনায় আরও স্পষ্টভাবে দেখা যায়।

DuckDB থেকে আপনার SQLite ডেটাবেস পড়া

দুটি ইঞ্জিন 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;

এটি কোনো copy তৈরি না করে query চলার সময় SQLite file থেকে rows পড়ে। এটি সুবিধাজনক, তবে দ্রুত নয়, কারণ disk-এ থাকা data এখনও row storage হিসেবে সংরক্ষিত এবং DuckDB-কে তা একে একে পড়তে হয়। এটি export-এর জন্য ব্যবহার করুন, প্রতি ত্রিশ সেকেন্ডে reload হওয়া dashboard-এর জন্য নয়:

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

এই একটি statement-ই সম্পূর্ণ pattern। SQLite সাম্প্রতিক live rows-এর মালিক থাকে। একটি scheduled export বন্ধ হওয়া period-গুলোকে Parquet-এ রূপান্তর করে। DuckDB মাসজুড়ে থাকা data নিয়ে প্রতিটি প্রশ্নের উত্তর দেয়, আর application database ছোট থাকে, ফলে এর writes দ্রুত হয়।

Export-টি হাতে না চালিয়ে schedule অনুযায়ী চালান। একটি systemd service এবং timer জোড়া এই কাজের জন্য উপযুক্ত: একটি unit COPY চালাবে, আর একটি timer প্রতি রাতে সেটি চালাবে।

একই VPS-এ উভয়টি চালানো

এখানে কোনো container বা port প্রয়োজন নেই। উভয় engine library, তাই install বলতে package এবং file path বোঝায়। আপনার stack-এর বাকি অংশ যদি ইতিমধ্যে একই VPS-এ Docker Compose-এর অধীনে চলে, database service যোগ না করে যে container-এর এটি প্রয়োজন, তার মধ্যে data directory mount করুন। কারণ যোগ করার মতো কোনো service নেই।

এই বিন্যাসে সমস্যা এড়াতে দুটি নিয়ম অনুসরণ করুন।

প্রতিটি engine-এর জন্য আলাদা directory দিন: application যে SQLite file-এ লেখে তার জন্য /srv/app, analytics যে Parquet files পড়ে তার জন্য /srv/data। তারা একই directory ব্যবহার করলে, একটি snapshot নেওয়ার সময় backup job অন্যটির সঙ্গে প্রতিযোগিতায় জড়িয়ে পড়ে।

দুটি process-কে একই DuckDB database file read-write mode-এ নির্দেশ করবেন না। DuckDB file-এ লেখার জন্য এক সময়ে কেবল একটি process থাকতে পারে। দ্বিতীয়টি file-টি একেবারেই open করতে ব্যর্থ হয়। প্রতিটি process access_mode = 'READ_ONLY' সেট করলে অনেক reader ব্যবহার করা যায়। SQLite থেকে আসা ব্যবহারকারীদের কাছে এটি আশ্চর্যজনক হতে পারে, কারণ SQLite-এ একাধিক process নিয়মিতভাবে একই file ব্যবহার করে। আপনার analytics যদি শুধু Parquet files পড়ে, তাহলে এই সমস্যা ওঠে না। Durable state SQLite-এ রাখার এটি আরও একটি কারণ।

ব্যাকআপের মধ্যে পার্থক্য থাকে, এবং সেই পার্থক্য সমস্যা তৈরি করে

চলমান SQLite database তিনটি file নিয়ে গঠিত। লেখার মাঝখানে cp দিয়ে সেগুলো copy করলে এমন একটি file পাওয়া যায়, যা খোলে কিন্তু সঠিক নয়। Engine-এর নিজস্ব backup command ব্যবহার করুন। এটি application লিখতে থাকা অবস্থায় একটি সামঞ্জস্যপূর্ণ 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 প্রিন্ট করে। অন্য যেকোনো ফলাফলের অর্থ হলো সেই snapshot বাতিল করে আরেকটি নিতে হবে।

Parquet file লেখার পর আর পরিবর্তিত হয় না। তাই এগুলোর জন্য বিশেষ ব্যবস্থাপনার প্রয়োজন নেই। Directory-টি backup করুন। আপনার VPS থেকে restic backup ব্যবহার করে দুটি path-ই server-এর বাইরে পাঠান। এতে পুরো data layer একটি backup job-এ দুটি directory হিসেবে সংরক্ষিত হবে।

ত্রুটির ধরন এবং আপনি যে নির্দিষ্ট স্ট্রিংগুলি দেখতে পাবেন

SQLite থেকে Error: database is locked এর অর্থ হলো, অন্য একটি সংযোগ আপনার নির্ধারিত timeout-এর চেয়ে বেশি সময় ধরে write lock ধরে রেখেছিল। এটি corruption নয়। প্রতিটি সংযোগে PRAGMA busy_timeout সেট করুন। এরপর এমন কোনো দীর্ঘ transaction খুঁজুন, যেটি কয়েকটি ছোট transaction হওয়া উচিত ছিল।

অনুমতি পরিবর্তনের পর Error: unable to open database file দেখা গেলে সাধারণত এর অর্থ হলো, process ফাইলে লিখতে পারে, কিন্তু তার directory-তে লিখতে পারে না। SQLite database-এর পাশে app.db-wal এবং app.db-shm তৈরি করে। তাই শুধু .db ফাইল নয়, directory-টিও writable হতে হবে।

DuckDB থেকে IO Error: Could not set lock on file এর অর্থ হলো, দ্বিতীয় কোনো process ইতিমধ্যে database-টি writing-এর জন্য খুলে রেখেছে। অন্য shell-টি বন্ধ করুন, অথবা আপনারটি read only হিসেবে খুলুন।

ছোট VPS-এ DuckDB থেকে Out of Memory Error দেখা গেলে এর অর্থ হলো, একটি query-এর যত working memory প্রয়োজন ছিল, তা উপলভ্য ছিল না। সম্ভব হলে DuckDB disk-এ spill করে। তাই :memory:-এর পরিবর্তে disk-এ একটি database file খুলে spill করার জায়গা দিন এবং SET memory_limit = '2GB'; দিয়ে এর memory ব্যবহার সীমিত করুন। অন্য service চলা server-এ এই সীমাই একটি ad hoc query-কে আপনার application-কে RAM-এর বাইরে ঠেলে দেওয়া থেকে আটকায়।

Parquet query করার সময় Binder Error: Referenced column "amount" not found দেখা গেলে প্রায় সবসময় এর অর্থ হলো, ফাইলটির schema আপনার মনে থাকা schema নয়। DESCRIBE SELECT * FROM '/srv/data/orders.parquet'; চালান এবং প্রকৃত column name-গুলি দেখুন।

বাস্তবে কীভাবে নির্বাচন করবেন

রাইট প্যাটার্ন কী, তা নির্ধারণ করুন। বিদ্যুৎ বিচ্ছিন্ন হলেও টিকে থাকতে হবে—এমন অনেক ছোট রাইট থাকলে SQLite ব্যবহার করুন। রিড প্যাটার্ন কী, তা নির্ধারণ করুন। দীর্ঘ সময়ের ইতিহাসের ওপর aggregate-সহ full scan প্রয়োজন হলে DuckDB ব্যবহার করুন। অধিকাংশ বাস্তব সিস্টেমে উভয় প্রশ্নের উত্তরই হ্যাঁ হয়। সেক্ষেত্রে একটি engine-কে অন্যটির কাজ করাতে বাধ্য না করে, প্রতিটি engine-কে তার উপযোগী অর্ধেক কাজ দিন।

যে migration এড়ানো উচিত, তা হলো কোনো report ধীর হওয়ায় live application state DuckDB-তে স্থানান্তর করা। Report ধীর ছিল storage layout-এর কারণে। তাই সমাধান হলো export, আপনার write path নতুন করে লেখার কাজ নয়।

FAQ

DuckDB কি আমার অ্যাপ্লিকেশন ডেটাবেস হিসেবে SQLite-এর বিকল্প হতে পারে?

যে ডেটাবেসে ঘন ঘন লেখা হয়, তার জন্য নয়। DuckDB পুরো ডেটাবেস ফাইলের ওপর write lock নেয়, একই সময়ে মাত্র একটি read-write process অনুমোদন করে এবং single-row insert-এর বদলে bulk change-এর জন্য অপ্টিমাইজ করা। Transactional state SQLite-এ রাখুন। কোনো রিপোর্টে প্রয়োজন হলে DuckDB-কে ATTACH '/srv/app/app.db' AS app (TYPE sqlite, READ_ONLY); দিয়ে সেই ডেটা পড়তে দিন।

বিশ্লেষণের ক্ষেত্রে DuckDB কি সত্যিই SQLite-এর চেয়ে দ্রুত?

বড় টেবিলের ওপর scan এবং aggregate-এর ক্ষেত্রে দ্রুত। এর কারণ tuning-এর কোনো কৌশল নয়, বরং storage layout। DuckDB query-তে উল্লেখ করা column-গুলোই পড়ে এবং value-গুলো batch আকারে process করে। অন্যদিকে, একটি field-এ পৌঁছাতে SQLite-কে পুরো row অতিক্রম করতে হয়। Primary key দিয়ে একটি row আনলে ফল উল্টো হয়। কারণ SQLite দুটি page স্পর্শ করে, কিন্তু DuckDB-কে প্রতিটি column-এর storage স্পর্শ করতে হয়।

VPS-এ DuckDB চালাতে কি অনেক RAM দরকার?

না। তবে DuckDB-কে একটি limit এবং disk দিন। :memory:-এর পরিবর্তে একটি database file খুলুন, যাতে DuckDB intermediate result disk-এ spill করতে পারে। এরপর SET memory_limit = '2GB';-কে এমন একটি value-তে সেট করুন, যা আপনার VPS বরাদ্দ করতে পারে। Limit না থাকলে একটি বড় GROUP BY Out of Memory Error ঘটাতে পারে অথবা অন্যান্য service-কে RAM থেকে সরিয়ে দিতে পারে।

আমার SQLite data কীভাবে Parquet-এ নেব?

DuckDB থেকে SQLite file attach করুন এবং COPY (SELECT * FROM app.orders) TO '/srv/data/orders.parquet' (FORMAT parquet, COMPRESSION zstd); দিয়ে সরাসরি একটি query-এর ফল copy করুন। বন্ধ হয়ে যাওয়া সময়সীমার জন্য, যেমন গত মাসের row-গুলোর জন্য, এটি schedule অনুযায়ী চালান। সাম্প্রতিক row-গুলো SQLite-এ রাখুন, যেখানে অ্যাপ্লিকেশন এখনও সেগুলোতে write করে।

কোনটি এবং কীভাবে backup করব?

দুটিই, তবে আলাদাভাবে। cp-এর পরিবর্তে sqlite3 app.db ".backup '/srv/backup/app.db'" দিয়ে SQLite snapshot নিন। কারণ চলমান database একই সঙ্গে একটি -wal এবং একটি -shm file, তাই সাধারণ copy অসম্পূর্ণ হতে পারে। Parquet file একবার লেখা হলে আর পরিবর্তিত হয় না। তাই directory copy করাই যথেষ্ট।

#duckdb#sqlite#database#analytics#parquet#self-hosting