बैच लोडर से N+1 हल करना
@BatchMapping और DataLoader-शैली के बैच्ड निर्धारण से N+1 समस्या समाप्त कीजिए।
बैच लोडर से N+1 हल करना, CoddyKit पर Spring Boot 4 की संपूर्ण मार्गदर्शिका का एक निःशुल्क पाठ है। यह 4 में से 3वाँ पाठ है। इस अध्ययन पथ के 3 तक कोई भी पाठ पूरा पढ़ना निःशुल्क है — इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ व्यावहारिक अभ्यास भी उपलब्ध कराता है। यह Spring Boot 4 की संपूर्ण मार्गदर्शिका सीखने के मार्ग का हिस्सा है और आपकी प्रगति वेब तथा CoddyKit ऐप पर सिंक होती रहती है। Spring Boot 4 की संपूर्ण मार्गदर्शिका पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
N+1 समस्या कैसी दिखती है
GraphQL में फ़ील्ड का समाधान आवश्यकता पड़ने पर धीरे-धीरे किया जाता है। जब कोई क्वेरी ऑब्जेक्ट की सूची और फिर हर ऑब्जेक्ट का कोई नेस्टेड फ़ील्ड माँगती है, तो Spring for GraphQL नेस्टेड रिज़ॉल्वर को हर पैरेंट के लिए एक बार चलाता है।
- लेखकों को प्राप्त करने के लिए 1 क्वेरी
- हर लेखक के
booksको प्राप्त करने के लिएNअतिरिक्त क्वेरी
इस तरह डेटाबेस तक N+1 बार आना-जाना पड़ता है। 100 लेखकों के साथ आप 101 क्वेरी चलाते हैं। सरल GraphQL API में प्रदर्शन को सबसे अधिक प्रभावित करने वाली यही समस्या है, और बैचिंग इसका समाधान है।
सरल @SchemaMapping रिज़ॉल्वर
यहाँ हर पैरेंट के लिए चलने वाला वह रिज़ॉल्वर है जो N+1 समस्या पैदा करता है। परिणामों में मौजूद हर Author के लिए Spring books को अलग-अलग चलाता है और हर बार अपनी SQL क्वेरी भेजता है।
यह सही है, लेकिन बड़े स्तर पर प्रभावी नहीं रहता। ध्यान दें कि इसमें बैचिंग बिल्कुल नहीं है: एक लेखक इनपुट में और डेटाबेस की एक कॉल आउटपुट में।
@Controller
public class AuthorController {
private final BookRepository books;
public AuthorController(BookRepository books) {
this.books = books;
}
// Called once PER author -> N+1
@SchemaMapping(typeName = "Author")
public List<Book> books(Author author) {
return books.findByAuthorId(author.id());
}
}बैचिंग का विचार
समाधान यह है कि पहले सभी पैरेंट कुंजियाँ एकत्र करें, फिर उन्हें एक ही बैच कॉल में हल करें।
- सभी 100 लेखकों की आईडी एकत्र करें
- एक क्वेरी चलाएँ:
SELECT * FROM book WHERE author_id IN (...) - परिणामों को फिर से हर लेखक के अनुसार समूहित करें
इससे N+1 घटकर ठीक 2 क्वेरी रह जाती हैं। Spring for GraphQL इसे व्यक्त करने के दो तरीके देता है: उच्च-स्तरीय @BatchMapping एनोटेशन और निम्न-स्तरीय DataLoader API।
@BatchMapping से Map लौटाना
@BatchMapping सबसे आसान समाधान है। एक पैरेंट के बजाय, आपके मेथड को पैरेंट की List मिलती है और वह पैरेंट के आधार पर कुंजीबद्ध Map<Parent, Value> लौटाता है।
Spring फ़ील्ड के लिए आवश्यक हर Author को एकत्र करता है, इस मेथड को एक बार चलाता है, फिर हर प्रविष्टि को अपने सही स्थान पर अपने-आप पहुँचा देता है।
@Controller
public class AuthorController {
private final BookRepository books;
public AuthorController(BookRepository books) {
this.books = books;
}
@BatchMapping(typeName = "Author", field = "books")
public Map<Author, List<Book>> books(List<Author> authors) {
Set<Long> ids = authors.stream()
.map(Author::id)
.collect(Collectors.toSet());
Map<Long, List<Book>> byAuthorId = books.findByAuthorIdIn(ids).stream()
.collect(Collectors.groupingBy(Book::authorId));
return authors.stream().collect(Collectors.toMap(
a -> a,
a -> byAuthorId.getOrDefault(a.id(), List.of())
));
}
}@BatchMapping से List लौटाना
एक दूसरा, और भी छोटा रूप है: ऐसी List लौटाएँ जो इनपुट सूची के साथ क्रमानुसार संरेखित हो। परिणाम का i तत्व इनपुट के i लेखक से संबंधित होना चाहिए।
जब क्रम बनाए रखना कठिन हो, तब Map वाला रूप इस्तेमाल करें; और जब आप इनपुट के समान क्रम में हर इनपुट के लिए एक परिणाम स्थान सुनिश्चित कर सकें, तब List वाला रूप इस्तेमाल करें।
@BatchMapping(typeName = "Author", field = "books")
public List<List<Book>> books(List<Author> authors) {
Map<Long, List<Book>> byAuthorId = books.findByAuthorIdIn(
authors.stream().map(Author::id).toList())
.stream()
.collect(Collectors.groupingBy(Book::authorId));
// Same order as the input list
return authors.stream()
.map(a -> byAuthorId.getOrDefault(a.id(), List.of()))
.toList();
}फ़ील्ड का नाम अपने-आप निर्धारित करना
यदि आप field एट्रिब्यूट छोड़ देते हैं, तो Spring इसे मेथड के नाम से निर्धारित करता है। Author प्रकार पर books नाम वाला मेथड Author.books से मैप होता है।
आपको typeName की आवश्यकता केवल तब होती है जब कंट्रोलर पहले से किसी प्रकार से बँधा न हो, और field की आवश्यकता केवल तब होती है जब मेथड का नाम स्कीमा के फ़ील्ड से अलग हो। नीचे का उदाहरण पूरी तरह अपने-आप निर्धारित किए जाने पर निर्भर है।
@Controller
public class AuthorController {
// typeName "Author" inferred is NOT automatic here, so set it;
// field "books" IS inferred from the method name.
@BatchMapping(typeName = "Author")
public Map<Author, List<Book>> books(List<Author> authors) {
// ... batched lookup ...
return Map.of();
}
}साधारण Java में Map का समूह बनाना कैसे काम करता है
हर बैच लोडर का मूल तरीका एक जैसा होता है: चाइल्ड ऑब्जेक्ट की सपाट सूची लें, फिर उनकी विदेशी कुंजी के आधार पर groupingBy करें। इस स्निपेट में Spring का बिल्कुल उपयोग नहीं है, ताकि आप प्रक्रिया को स्पष्ट रूप से देख सकें।
इसे चलाएँ: पुस्तकों पर एक बार चलने से लेखक की आईडी से उसके पुस्तकों के संग्रह तक का मैप बन जाता है—ठीक वही, जो बैच रिज़ॉल्वर लौटाता है।
import java.util.*;
import java.util.stream.*;
public class Main {
record Book(long authorId, String title) {}
public static void main(String[] args) {
List<Book> all = List.of(
new Book(1, "Dune"),
new Book(2, "1984"),
new Book(1, "Messiah"),
new Book(3, "It")
);
Map<Long, List<Book>> byAuthor = all.stream()
.collect(Collectors.groupingBy(Book::authorId));
for (long id : List.of(1L, 2L, 3L)) {
List<Book> books = byAuthor.getOrDefault(id, List.of());
System.out.println("author " + id + " -> " + books.size() + " book(s)");
}
}
}निम्न-स्तरीय DataLoader
अंदरूनी रूप से @BatchMapping, java-dataloader लाइब्रेरी के DataLoader का उपयोग करता है। पूरे नियंत्रण के लिए आप स्वयं भी एक लोडर पंजीकृत कर सकते हैं, जैसे कई फ़ील्ड के बीच लोडर साझा करने या हर अनुरोध के लिए कैशिंग जोड़ने के लिए।
इसे BatchLoaderRegistry के माध्यम से पंजीकृत करें, जिसे Spring अपने-आप कॉन्फ़िगर करके इंजेक्ट करता है।
@Configuration
public class DataLoaderConfig {
public DataLoaderConfig(BatchLoaderRegistry registry, BookRepository books) {
registry.forTypePair(Long.class, List.class)
.registerMappedBatchLoader((authorIds, env) -> {
Map<Long, List<Book>> grouped = books.findByAuthorIdIn(authorIds)
.stream()
.collect(Collectors.groupingBy(Book::authorId));
return Mono.just(authorIds.stream().collect(
Collectors.toMap(id -> id, id -> grouped.getOrDefault(id, List.of()))
));
});
}
}रिज़ॉल्वर में DataLoader का उपयोग करना
पंजीकरण के बाद, @SchemaMapping के साथ लोडर को रिज़ॉल्वर में इंजेक्ट करें। आप load(key) से एक CompletableFuture लौटाते हैं। Spring अनुरोध के दौरान की गई हर load कॉल को आपके बैच फ़ंक्शन के एक ही आह्वान में समूहित करता है।
यह @BatchMapping का मैन्युअल समकक्ष है और तब उपयोगी है जब एक लोडर कई फ़ील्ड को डेटा देता हो।
@SchemaMapping(typeName = "Author")
public CompletableFuture<List<Book>> books(
Author author,
DataLoader<Long, List<Book>> loader) {
return loader.load(author.id());
}बैच के भीतर N+1 समस्या फिर से न लाएँ
एक सूक्ष्म जाल यह है: बैच मेथड भले ही एक बार चले, लेकिन यदि आप पैरेंट पर लूप चलाकर उस लूप के भीतर रिपॉज़िटरी को कॉल करते हैं, तो आपने छिपे हुए रूप में N+1 समस्या फिर बना दी है।
- गलत:
authors.forEach(a -> books.findByAuthorId(a.id())) - सही: एक
findByAuthorIdIn(allIds)कॉल करें, फिर मेमोरी में समूह बनाएँ
मुख्य उद्देश्य एक ही थोक क्वेरी चलाना है, इसलिए हमेशा कुंजियों का पूरा समूह एक ही रिपॉज़िटरी मेथड को दें।
// ANTI-PATTERN: batched signature, but N queries inside
@BatchMapping(typeName = "Author")
public Map<Author, List<Book>> books(List<Author> authors) {
return authors.stream().collect(Collectors.toMap(
a -> a,
a -> books.findByAuthorId(a.id()) // <-- one query each = N+1 again!
));
}IN क्वेरी परिभाषित करना
बैचिंग तभी काम करती है जब आपकी डेटा परत एक साथ कई कुंजियाँ प्राप्त कर सके। Spring Data JPA के साथ आप ऐसी व्युत्पन्न क्वेरी उपलब्ध कराते हैं जो एक संग्रह स्वीकार करती है और SQL की IN शर्त में बदल जाती है।
यही एक रिपॉज़िटरी मेथड ऊपर दिए गए हर बैच लोडर को शक्ति देता है। संग्रह का आकार सीमित रखें; बहुत बड़ी IN सूचियाँ धीमी हो सकती हैं, इसलिए अत्यधिक बड़े बैच को छोटे हिस्सों में बाँटना पड़ सकता है।
public interface BookRepository extends JpaRepository<Book, Long> {
// SELECT * FROM book WHERE author_id IN (:ids)
List<Book> findByAuthorIdIn(Collection<Long> ids);
}त्वरित जाँच
आपने Author.books के लिए @BatchMapping जोड़ा है, लेकिन प्रोफ़ाइलिंग में अब भी N+1 क्वेरी दिखाई दे रही हैं। इसकी सबसे संभावित वजह क्या है?
पुनरावलोकन
आपने Spring for GraphQL में N+1 समस्या समाप्त कर दी है:
- हर पैरेंट के लिए
@SchemaMappingरिज़ॉल्वर, हर पैरेंट पर एक क्वेरी चलाते हैं: N+1। @BatchMappingको पैरेंट कीListमिलती है और वहMap<Parent, Value>या क्रम से संरेखितListलौटाता है, जिससे यह घटकर 2 क्वेरी रह जाती हैं।- मूल प्रक्रिया एक ही
findByAuthorIdIn(ids)थोक क्वेरी औरCollectors.groupingByका संयोजन है। - पूरे नियंत्रण के लिए
BatchLoaderRegistryके माध्यम सेDataLoaderपंजीकृत करें और अपने रिज़ॉल्वर सेCompletableFutureलौटाएँ। - बैच मेथड के भीतर हर पैरेंट पर लूप चलाकर क्वेरी न करें, वरना N+1 समस्या फिर आ जाएगी।
एआई शिक्षक के साथ Java सीखें — निःशुल्क
अपने ब्राउज़र में वास्तविक कोड लिखें और चलाएँ, चौबीसों घंटे एआई शिक्षक से तुरंत सहायता पाएँ, और वेब या ऐप पर वहीं से शुरू करें जहाँ आपने छोड़ा था।
- पाठ्यक्रम
- 21
- पाठ
- 84
अक्सर पूछे जाने वाले प्रश्न
क्या “बैच लोडर से N+1 हल करना” पाठ निःशुल्क है?
हाँ — Spring Boot 4 की संपूर्ण मार्गदर्शिका अध्ययन पथ के 3 तक कोई भी पाठ, जिसमें “बैच लोडर से N+1 हल करना” भी शामिल है, यहाँ वेब पर पूरा पढ़ना निःशुल्क है। इसके बाद CoddyKit PRO हर पाठ अनलॉक करता है, साथ ही अंतर्निर्मित कोड संपादक और चौबीसों घंटे एआई शिक्षक के साथ इंटरैक्टिव अभ्यास भी उपलब्ध कराता है। Spring Boot 4 की संपूर्ण मार्गदर्शिका पाठ्यक्रम में कुल 4 पाठ शामिल हैं।
“बैच लोडर से N+1 हल करना” में मैं क्या सीखूँगा?
@BatchMapping और DataLoader-शैली के बैच्ड निर्धारण से N+1 समस्या समाप्त कीजिए। आप ब्राउज़र में सीधे चलाए जाने वाले व्यावहारिक कोड के साथ Spring Boot 4 की संपूर्ण मार्गदर्शिका का अभ्यास करते हैं, और पाठ पूरा करते समय 24/7 एआई ट्यूटर आपके प्रश्नों के उत्तर देता है।
क्या Spring Boot 4 की संपूर्ण मार्गदर्शिका शुरू करने के लिए मुझे किसी अनुभव की आवश्यकता है?
पहले के अनुभव की आवश्यकता नहीं है। CoddyKit पर Spring Boot 4 की संपूर्ण मार्गदर्शिका शुरुआती से लेकर उन्नत शिक्षार्थियों तक सभी के लिए व्यवस्थित किया गया है, इसलिए आप यहीं से या शुरुआत से सीखना शुरू कर सकते हैं और अपनी गति से आगे बढ़ सकते हैं। यह 4 में से 3वाँ पाठ है।
“बैच लोडर से N+1 हल करना” पाठ पूरा करने में कितना समय लगता है?
CoddyKit का अधिकांश पाठ लगभग 5–10 मिनट में पूरा हो जाता है। हर पाठ छोटा और संवादात्मक है, इसलिए आप लगातार प्रगति करते हैं और वेब या ऐप पर वहीं से सीखना जारी रख सकते हैं जहाँ आपने छोड़ा था।
क्या मैं इस Spring Boot 4 की संपूर्ण मार्गदर्शिका पाठ में कोड लिख और चला सकता हूँ?
हाँ। हर Spring Boot 4 की संपूर्ण मार्गदर्शिका पाठ में एक अंतर्निर्मित कोड संपादक शामिल है, जिससे आप सीधे अपने ब्राउज़र में वास्तविक कोड लिख और चला सकते हैं और तुरंत एआई प्रतिक्रिया पा सकते हैं—स्थानीय सेटअप की आवश्यकता नहीं है।
इस पाठ्यक्रम के सभी पाठ
- स्कीमा-प्रथम डिज़ाइन और प्रकार मैपिंग
- डेटा फ़ेचर और तर्क बाइंडिंग
- बैच लोडर से N+1 हल करना
- सब्सक्रिप्शन, त्रुटियाँ और स्कीमा सुरक्षा