Single vs Split Queries in .Net
ধারণা (Concept)
EF Core (Entity Framework Core) যখন কোনো entity (যেমন Blog) থেকে related entities (যেমন Posts বা Contributors) নিয়ে আসে, তখন এটি সাধারণত একটি SQL প্রশ্ন (query) ব্যবহার করে সবকিছু JOIN করার চেষ্টা করে। এটাকেই “single query” বলা হয়।
কিন্তু একটা সমস্যা হয় — যখন তোমার parent entity-র অনেকগুলো collection navigation আছে (যেমন অনেক Post, অনেক Contributor), তখন JOIN-এর কারণে Cartesian explosion ঘটে — অর্থাৎ, অনেক বেশি অপ্রয়োজনীয় রো (rows) রিটার্ন হয়। পাশাপাশি, parent entity-এর বড় কিছু column (যেমন বড় টেক্সট, বাইনারি ডেটা) বারবার ডুপ্লিকেট হতে পারে যেসব রো-তে join হয়েছে — যা নেটওয়ার্ক ও মেমরি উভয়ের জন্যই খারাপ।
এক্ষেত্রে, EF Core একটি বিকল্প দেয় — split queries — যেখানে একটি parent query (প্রধান টেবিল) করা হবে, তারপর প্রতি collection navigation-এর জন্য একটি আলাদা query চালানো হবে। অর্থাৎ, আমরা query-বিভাজন (splitting) করি।
উদাহরণসহ ব্যাখ্যা
ধরো তোমার কাছে Blogs entity আছে, এবং প্রতিটি ব্লগের সঙ্গে দুইটি collection আছে:
-
Posts(ক’টি পোস্ট) -
Contributors(ক’জন অবদানকারী)
var blogs = await ctx.Blogs .Include(b => b.Posts) .Include(b => b.Contributors) .ToListAsync();
যদি EF Core single query ব্যবহার করে:
তাহলে SQL হবে প্রায় এমন:
SELECT b.*, p.*, c.* FROM Blogs AS b LEFT JOIN Posts AS p ON b.Id = p.BlogId LEFT JOIN Contributors AS c ON b.Id = c.BlogId ORDER BY b.Id, p.Id
এখানে যদি একটি ব্লগে ১০টা পোস্ট এবং ১০ জন contributor থাকে, তাহলে ওই ব্লগের জন্য রিটার্ন হবে ১০ × ১০ = ১০০ রো — এই “Cartesian explosion”। EF রিটার্ন করা তথ্যকে পরে ভেঙে (reconstruct) সব entities বানায়, কিন্তু ডেটা ট্রান্সফার অনেক বেশি হয়েছে।
আর একটি সমস্যা: যদি Blogs টেবিলে বড় কোনো কলাম থাকে (যেমন একটা বড় টেক্সট বা ইমেজ ডেটা), তাহলে সেই কলাম সেই ব্লগের জন্য ১০০ রোতে একাধিক বার যাবে — অর্থাৎ নেটে ও মেমরিতে বেশি ডেটা যাবে ও রাখা হবে।
split query ব্যবহার করলে:
var blogs = await ctx.Blogs .Include(b => b.Posts) .AsSplitQuery() .ToListAsync();
এভাবে EF Core প্রথমে একটা query চালাবে Blogs টেবিল থেকে:
SELECT [b].* FROM Blogs AS b ORDER BY [b].Id
তারপর অন্য একটি query চালাবে Posts টেবিল থেকে (যেখানে BlogId match হবে):
SELECT [p].* FROM Blogs AS b INNER JOIN Posts AS p ON b.Id = p.BlogId ORDER BY b.Id
(যদি আরও collection থাকে, তাহলে আরও আলাদা query হবে।)
এভাবে:
-
“Cartesian explosion” এড়ানো যায় — কারণ অন্যান্য collections একসাথে join হচ্ছে না।
-
parent-entity-এর বড় কলাম বারবার রিটার্ন হচ্ছে না।
-
ডেটা duplication কম হয়।
কেমন কী অসুবিধা আছে split query-র?
যদিও split queries অনেক ক্ষেত্রে ভালো, কিছু trade-offs (পার্শ্বপ্রভাব) আছে:
-
Consistency (ডেটা সামঞ্জস্যতা)
একাধিক SQL query চালালে, যদি database সেই মুহূর্তে concurrent update হয়, তাহলে প্রথম ও দ্বিতীয় query-র ফলাফল সামঞ্জস্য নাও থাকতে পারে।
এই সমস্যা কমাতে পারো — যেমন, সেই operations একটি transaction-এ (যেমন serializable বা snapshot isolation) চালিয়ে — কিন্তু সেটা performance-এ প্রভাব ফেলতে পারে। Microsoft Learn -
Network roundtrips (নেটওয়ার্ক কল বাড়ে)
একাধিক query মানে একাধিক রাউন্ড ট্রিপ (client ↔ DB) – latency বেশি হলে পারফর্মেন্স খারাপ হতে পারে। Microsoft Learn -
Memory / buffering
বেশিরভাগ ডাটাবেস একই সময়ে একাধিক query execute করার সুযোগ দেয় না, তাই প্রথম query-এর ফলাফল পুরোটা আগে buffer করতে হবে, তারপর পরবর্তী query চালানো হবে — এতে memory চাহিদা বাড়তে পারে। Microsoft Learn -
Reference navigations + collection navigations
যদি তোমার entity-তে reference properties (যেমন one-to-one বা many-to-one) ও collection navigation দুইই থাকে, split queries সেই সব reference navigations-উপরে join করবে প্রতিটি split query-তে — এতে performance degrade হতে পারে। Microsoft Learn
কিভাবে ব্যবহার করবে / কনফিগার করবে
-
তুমি নির্দিষ্ট কোন query-এ
.AsSplitQuery()বা.AsSingleQuery()ব্যবহার করতে পারো, যাতে ঐ query split অথবা single behaviour নেবে। Microsoft Learn -
তুমি পুরো DbContext-র জন্য default behaviour নির্ধারণ করতে পারো, যেমন:
optionsBuilder .UseSqlServer(connectionString, o => o.UseQuerySplittingBehavior(QuerySplittingBehavior.SplitQuery));
-
এভাবে, সব query by default split query হবে। Microsoft Learn
-
যদি default behaviour split রাখা থাকে, তবুও তুমি একটা নির্দিষ্ট query-তে
.AsSingleQuery()দিয়ে override করতে পারো। Microsoft Learn
কখন কোনটা ব্যবহার করবে?
সাধারণভাবে:
-
যদি তোমার কম number of collections থাকে, এবং join এ ওভারহেড কম হয়, তাহলে single query ভালো কাজ করতে পারে।
-
কিন্তু যদি অনেক collection navigation একসাথে load করো, এবং join-এর কারণে Cartesian explosion বা data duplication সমস্যা হয়, তাহলে split query ব্যবহার করা ভালো।
-
অবশ্যই performance পরীক্ষণ (profiling) করা উচিত — সব ক্ষেত্রে split query ভালো হবে এমন কথা নেই।
-
যদি latency বেশি হয় বা network cost গুরুত্ব রাখে, তখন split query-এর extra roundtrips সমস্যা হতে পারে।
-
যদি ডেটাবেস consistency খুব বেশি জরুরি হয়, তাহলে transaction বা isolation বেছে নিতে হবে।