N+1 Queries: The Bug That Only Appears in Production
N+1 queries happen when a loop triggers extra database queries for each item. The code looks correct and works with small data, but slows down badly in production. ORMs won’t stop this—you must load related data upfront and avoid database calls inside loops.
আপনি কি কখনও এমন পরিস্থিতিতে পড়েছেন—
ব্যাকএন্ড কোড একদম ঠিকঠাক, সব টেস্ট পাস করছে, লোকাল মেশিনে দ্রুত চলছে…। কিন্তু প্রোডাকশনে রিয়েল ট্রাফিক আসতেই অ্যাপ হঠাৎ করে ধীর হয়ে গেল?
লোকালে সব ঠিক ছিল।
কোনো error নেই।
কোনো warning নেই।
পারফরম্যান্সের দিক থেকেও সন্দেহ করার মতো কিছু না।
তারপর আসল ডেটা এলো।
Latency বাড়তে শুরু করল।
ডাটাবেস হঠাৎ bottleneck হয়ে গেল।
আপনি ক্যাশকে দোষ দিলেন।
ডাটাবেসকে দোষ দিলেন।
ইনফ্রাস্ট্রাকচারকে দোষ দিলেন।
কিন্তু শেষে দেখা গেল আসল সমস্যাটা ছিল খুবই ছোট— একটা নিরীহ-looking loop।
এটাই N+1 query problem-কে এত ভয়ংকর করে তোলে।
কেন N+1 Query এত ধরা কঠিন
সবচেয়ে tricky ব্যাপার হলো— আপনার কোড আসলে ভুল না।
ORM ঠিক যেটা আপনি বলছেন, সেটাই করছে। ডেটা কম থাকলে সবকিছু দ্রুতই চলে। কোনো জায়গায় চোখে পড়ার মতো সমস্যা দেখা যায় না।
সমস্যাটা দেখা দেয় তখনই, যখন ডেটার পরিমাণ বাড়ে।
অনেক ডেভেলপার ধরে নেন ORM নিজে থেকেই “optimize” করে নেবে।
কিন্তু সত্যিটা হলো— ORM ডাটাবেস optimize করে না, ডেভেলপারের সময় optimize করে।
আপনি যদি loop-এর ভেতরে ডাটাবেস হিট করেন, ORM আপনাকে থামাবে না। চুপচাপ প্রতিটা item-এর জন্য আলাদা query চালিয়ে দেবে।
একটা JavaScript উদাহরণ (দেখতে একদম নিরীহ)
const users = await getUsers();
for (const user of users) {
console.log(user.profile.company.name);
}
প্রথম দেখায় এটা শুধু সাধারণ JavaScript।
কোনো SQL নেই।
কোনো ডাটাবেস কল চোখে পড়ে না।
পারফরম্যান্স ইস্যু মনে হওয়ার মতো কিছুই নেই।
কিন্তু আসল ফাঁদটা এখানেই।
যদি profile বা company আগে থেকে eager load করা না থাকে, তাহলে এই এক লাইন—
user.profile.company
আসলে ডাটাবেসে ঢুকে যাচ্ছে।
এবং সেটা হচ্ছে প্রতিটা user-এর জন্য একবার করে।
ফলাফল কী দাঁড়াল?
১টা query → users আনতে
Nটা query → profiles আনতে
Nটা query → companies আনতে
এইটাই classic N+1 problem।
কোড ভুল না।
কিন্তু আপনি যা বোঝাতে চান আর ORM যা করে—এই দুটার মাঝে mismatch আছে।
আসল Root Cause কোথায়
N+1 আসলে ডাটাবেসের সমস্যা না।
এটা database boundary-এর সমস্যা।
একবার যদি loop-এর ভেতরে সেই boundary cross করেন, তাহলে cost বাড়তে থাকে—
row সংখ্যা যত বাড়ে
network round trip
query parse + execute করার overhead
সবকিছু লুকানো থাকে পরিষ্কার, সুন্দর-looking কোডের আড়ালে।
আগেই কীভাবে ধরবেন
কিছু বাস্তব অভ্যাস কাজে দেয়—
development-এ query logging অন রাখা
APM trace দেখা
একই request-এ একই query বারবার চলছে কিনা খেয়াল করা
যদি দেখেন একটা query এক request-এর মধ্যে ৫০–১০০ বার চলছে—
ওটাই আপনার red flag।
সাধারণত কীভাবে fix করা হয়
সমাধানগুলো খুব নতুন কিছু না—
loop-এর আগে সব related data load করে নেওয়া
lazy loading-এর উপর ভরসা না করা
দরকার হলে batch query করে নিজে map করা
ORM ভেদে API আলাদা হতে পারে,
কিন্তু মূল আইডিয়া একটাই—
ডাটাবেসে একবার যান, loop-এর ভেতরে না।
শেষ কথা
N+1 query কখনও bug-এর মতো মনে হয় না।
এগুলো দেখতে একদম normal কোডের মতোই লাগে—
যতক্ষণ না scale এসে আসল চেহারা দেখায়।
যদি আপনার অ্যাপ শুধু প্রোডাকশনে গিয়ে ধীর হয়,
ডাটাবেস বা ইনফ্রা দেখার আগে
একবার loop-গুলোর দিকে তাকান।
সাধারণত সমস্যাটা সেখানেই লুকিয়ে থাকে
Category
Development