N+1 query সমস্যা ও সমাধান
তালিকার জন্য একটি query, তারপর প্রতিটি row-র জন্য আরও একটি। N+1 হলো API পেজ ধীর হওয়ার সবচেয়ে সাধারণ কারণ। কীভাবে ধরবেন ও ঠিক করবেন, জানুন।

N+1 সমস্যা হয় যখন কোড তালিকা আনতে ১টি query চালায়, তারপর প্রতিটি item-এর সম্পর্কিত data আনতে আরও N টি query চালায়।
একটি উদাহরণ
৫০টি article তাদের author-এর নামসহ দেখান:
const articles = await db.query("SELECT * FROM articles LIMIT 50"); // ১টি query
for (const a of articles) {
a.author = await db.query("SELECT name FROM authors WHERE id = ?", a.author_id); // ৫০টি query
}
এক পেজের জন্য ৫১টি query। ৫০০ article হলে ৫০১টি। প্রতিটি query দ্রুত, কিন্তু network round trip ও overhead জমে যায়, আর database অনেক পুনরাবৃত্ত কাজ করে।
কেন লুকিয়ে থাকে
ORM এটি সহজ করে দেয়। Loop-এ article.author ধরা property পড়ার মতো দেখায়, কিন্তু প্রতিবার lazily একটি query চালু করে। ডেভেলপারের ল্যাপটপে ৫টি row-এ এটি অদৃশ্য। প্রোডাকশনে হাজারো row-এ পেজ হামাগুড়ি দেয়।
কীভাবে ধরবেন
- Query log: শুধু ID আলাদা অনেক একই রকম query।
- প্রতি request-এ query গোনা টুল (Django Debug Toolbar, Rails Bullet, APM tracing)।
- যে পেজের সময় row সংখ্যার সাথে বাড়ে।
- মূল endpoint-এর test-এ query সংখ্যা assert করুন।
সমাধান
১. এক query-তে JOIN
SELECT a.*, au.name AS author_name
FROM articles a JOIN authors au ON au.id = a.author_id
LIMIT 50;
২. IN দিয়ে batch: মোট ২টি query
SELECT * FROM articles LIMIT 50;
SELECT * FROM authors WHERE id IN (3, 7, 9, 12); -- দরকারি সব id একসাথে
তারপর কোডে মেলান। বেশিরভাগ ORM এটি আপনার হয়ে করে:
- Django:
select_related(join) ওprefetch_related(IN query) - Rails:
includes - Sequelize / Prisma / TypeORM:
include/relations - SQLAlchemy:
joinedload,selectinload
৩. DataLoader (GraphQL): এক tick-এ চাওয়া সব ID একসাথে জমিয়ে একবারে লোড করে, প্রতি request-এ caching সহ।
৪. Cache করুন কম বদলানো সম্পর্কিত data (যেমন author-এর নাম)।
উল্টো দিকও দেখুন
অনেক "এক-থেকে-অনেক" table এক query-তে join করলে row গুণ হয়ে বিশাল ফল আসতে পারে। কখনো বেছে নেওয়া দুই-তিনটি query একটি দৈত্যাকার join-এর চেয়ে ভালো।
মোটামুটি নিয়ম
Loop-এর ভেতরে database call থাকলে থামুন এবং ভাবুন সব item-এর জন্য এটি loop-এর আগে একবারে করা যায় কি না।
মূল কথা: প্রতি request-এ query গুনুন, সম্পর্কিত data বাল্কে (join বা IN) আনুন, এবং আপনার ORM-এর eager loading সচেতনভাবে ব্যবহার করুন।
Backend ও Database সিরিজ, পর্ব ২ (performance ও operations): 2 নম্বর (মোট 6)। আগের লেখা: EXPLAIN: query plan পড়ে ধীর query খোঁজা.


