
यह कोई रहस्य नहीं है कि वर्डप्रेस की खोज सुविधा बेकार है। सौंदर्य की दृष्टि से इसे ठीक करना अपेक्षाकृत आसान है; हालांकि, इसकी कार्यक्षमता एक बिल्कुल अलग मामला है, क्योंकि इसमें कोई कॉन्फ़िगरेशन विकल्प नहीं हैं – और इससे भी बुरी बात यह है कि इसका प्रदर्शन बेहद खराब है।
जब कोई वर्डप्रेस साइट हजारों पोस्ट जमा कर लेती है, तो अंतर्निहित खोज फ़ंक्शन विफल होने लगता है। यह इतनी धीमी हो जाती है कि यह व्यावहारिक रूप से बेकार हो जाती है। जब आप खोज करते हैं, तो वर्डप्रेस डेटाबेस पर LIKE %word% क्वेरी चलाता है, जो wp_posts टेबल की पूरी सामग्री को स्कैन करती है। इससे ऐसी SQL क्वेरीज़ उत्पन्न होती हैं जो कुछ सेकंड से लेकर अनंतकाल तक चल सकती हैं, और मेमोरी में उछाल आ जाता है जिससे सर्वर क्रैश हो जाता है।
यह बिल्कुल वही था जो पहले से ही मेरे साथ हो रहा था। 6,040 पोस्ट प्रकाशित होने के साथ (केवल स्पेनिश में), बहुत सारे परिणामों वाली खोजों के लिए प्रतिक्रिया समय एक असली दुःस्वप्न था, जिसमें 10 सेकंड या उससे अधिक समय लग जाता था। और अंततः वह दिन आ ही गया जब इसे एक बार और हमेशा के लिए सुलझाया जा सके।
बहुत सोच-विचार करने के बाद मुझे एहसास हुआ कि इसे सुलझाने के कम से कम तीन तरीके हैं। पहला और सबसे सरल तरीका था Relevanssi जैसे पेड प्लगइन (मुफ्त संस्करण पर्याप्त नहीं है) या Algolia जैसी बाहरी सेवा का उपयोग करना, जिसे मैंने तुरंत ही खारिज कर दिया क्योंकि मैं मूल रूप से खोजों को तेज करना चाहता था।
बाकी बचे दो विकल्पों में से, मैंने MATCH() AGAINST() क्वेरीज़ का उपयोग करके MySQL में FULLTEXT इंडेक्स बनाने की कोशिश की, लेकिन बुरी तरह असफल रहा। InnoDB इंजन पैरामीटर समायोजित करने और इंडेक्स को अनुकूलित करने के बावजूद, MySQL हजारों जटिल कीवर्ड्स को प्रोसेस करते समय बार-बार धीमा पड़ जाता था, अस्वीकार्य प्रतिक्रिया समय देता था और सर्वर पर अत्यधिक लोड डाल देता था। तब मैंने डेटाबेस पर भारी लोड डालने के विकल्प को पूरी तरह से खारिज कर दिया और तीसरे दृष्टिकोण को अपनाया: JSON में एक स्थिर इंडेक्स बनाना।
यह ट्यूटोरियल इस समस्या को हल करने के लिए संसाधन-गहन MySQL क्वेरीज़ को एक स्थिर JSON फ़ाइल से बदलने के तरीके को समझाने का उद्देश्य रखता है, जो एक अल्ट्रा-फास्ट खोज अनुक्रमणिका के रूप में कार्य करती है।
चरण 1: धीमी या अनावश्यक क्वेरीज़ की पहचान करें और उन्हें हटाएँ
JSON इंडेक्स को लागू करने से पहले रास्ता साफ़ करना आवश्यक है। कई प्लगइन्स (जैसे डायरेक्टरी प्लगइन्स, लेआउट प्लगइन्स या सेकेंडरी सर्च प्लगइन्स) हुक्स (pre_get_posts, posts_where आदि) के माध्यम से वर्डप्रेस सर्च प्रक्रिया में हस्तक्षेप करते हैं, जिससे डेटाबेस पर अतिरिक्त और अनावश्यक क्वेरीज़ चलती हैं।
आप क्वेरी मॉनिटर का उपयोग करके इन क्वेरीज़ की पहचान कैसे कर सकते हैं?
- नि:शुल्क क्वेरी मॉनिटर प्लगइन इंस्टॉल करें और सक्रिय करें।
- अपनी वेबसाइट पर कहीं भी खोजें।
- शीर्ष बार में क्वेरी मॉनिटर ड्रॉप-डाउन मेनू खोलें और ' डेटा बेस क्वेरीज़ ' चुनें।
- 'Slow queries' के आधार पर फ़िल्टर करें या सूची में जाकर उन क्वेरीज़ को खोजें जिन्हें चलने में सबसे अधिक समय लगता है। साथ ही, किसी भी डुप्लिकेट क्वेरीज़ का नोट रखें और यदि संभव हो तो उन्हें भी ठीक कर लें।
- टेबलों में 'Details' कॉलम पर एक नज़र डालें: वहाँ आप ठीक-ठीक देख पाएँगे कि कौन सा प्लगइन या फ़ाइल वह क्वेरी चला रही है।
मेरे मामले में, मुझे Name_Directory प्लगइन से कुछ डुप्लिकेट क्वेरीज़ और पोस्ट टेम्पलेट में कुछ GenerateBlocks ब्लॉक्स को ठीक करना पड़ा, लेकिन कोई भी प्लगइन, कस्टम कोड या जोड़ा गया कोई भी तत्व ऐसी क्वेरीज़ उत्पन्न कर सकता है जो प्रतिक्रिया को धीमा कर रही हैं।
कोड के माध्यम से उन हुक्स को कैसे अक्षम करें
एक बार जब आप उस फ़ंक्शन या क्लास का नाम पहचान लेते हैं जो धीमी क्वेरी का कारण बन रहा है, तो आप इसे अपनी functions.php फ़ाइल में या कोड स्निपेट प्लगइन (जैसे Code Snippets, PerfmattersCode या समान) का उपयोग करके अक्षम कर सकते हैं:
* नोट: ब्लॉक 1 में, आपको अपने हुक्स जोड़ने चाहिए जो धीमी क्वेरीज़ का कारण बनते हैं। यदि यह आप पर लागू नहीं होता है, तो आप इस खंड को हटा सकते हैं और स्निपेट में केवल ब्लॉक 2 जोड़ सकते हैं।
/**
* 1. Desactivar funciones invasivas o redundantes en las búsquedas globales de WP
*/
add_action('init', function() {
// Ejemplo: Si Query Monitor muestra que un plugin de directorio se ejecuta en pre_get_posts:
// remove_action('hook_de_wordpress', 'nombre_de_la_funcion_del_plugin', prioridad);
remove_action('pre_get_posts', 'name_directory_search', 10);
remove_filter('posts_where', 'name_directory_insert_sitewide_search_results', 10);
}, 20);
/**
* 2. Bloquear la búsqueda SQL nativa tipo LIKE %palabra% en MySQL
*/
add_filter('posts_search', function($search, $wp_query) {
if (!is_admin() && is_search()) {
return ''; // Neutraliza el LIKE %palabra% en MySQL globalmente en páginas de búsqueda
}
return $search;
}, 10, 2);
नोट: `remove_action` या `remove_filter` का उपयोग करके किसी क्रिया को अनहुक करने के लिए, आपको बिल्कुल उसी इवेंट नाम (`pre_get_posts`), उसी फ़ंक्शन नाम का उपयोग करना होगा जिसे आपने Query Monitor में पहचाना था, और उसी प्राथमिकता (डिफ़ॉल्ट रूप से 10) का उपयोग करना होगा।
चरण 2: सर्च JSON फ़ाइल जेनरेट करें और उसे अपडेटेड रखें
MySQL को प्रतिस्थापित करने के लिए, हमें JSON प्रारूप में एक स्थिर फ़ाइल बनानी होगी जिसमें हमारी पोस्ट्स के मुख्य डेटा (ID, शीर्षक और सादा पाठ) शामिल हों। यह फ़ाइल wp-content/uploads/ फ़ोल्डर में सहेजी जाएगी और हर बार जब आप कोई पोस्ट बनाएँ, संपादित करें या हटाएँ, तो यह स्वचालित रूप से अपडेट हो जाएगी।
पीएचपी
/**
* Actualizar el archivo search-es.json automáticamente al guardar o borrar un post
*/
add_action('save_post', 'mi_sitio_actualizar_indice_busqueda_json', 10, 3);
add_action('deleted_post', 'mi_sitio_actualizar_indice_busqueda_json', 10, 1);
function mi_sitio_actualizar_indice_busqueda_json($post_id) {
if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
if (wp_is_post_revision($post_id)) return;
$post = get_post($post_id);
if (!$post || $post->post_type !== 'post') return;
// Opcional: Filtrar por idioma si usas un plugin multilingüe como Polylang
if (function_exists('pll_get_post_language') && pll_get_post_language($post_id) !== 'es') {
return;
}
mi_sitio_reconstruir_indice_json_completo();
}
/**
* Función que recorre todas las entradas publicadas y crea el JSON
*/
function mi_sitio_reconstruir_indice_json_completo() {
$args = array(
'post_type' => 'post',
'post_status' => 'publish',
'posts_per_page' => -1,
'orderby' => 'date',
'order' => 'ASC',
'fields' => 'ids', // Solo IDs para optimizar memoria
);
if (function_exists('pll_current_language')) {
$args['lang'] = 'es';
}
$all_post_ids = get_posts($args);
$indice = array();
foreach ($all_post_ids as $id) {
$titulo = get_the_title($id);
$contenido = get_post_field('post_content', $id);
$indice[] = array(
'i' => (int)$id,
't' => (string)$titulo,
'k' => (string)wp_strip_all_tags($contenido),
);
}
$upload_dir = wp_upload_dir();
$json_path = $upload_dir['basedir'] . '/search-es.json';
file_put_contents($json_path, json_encode($indice, JSON_UNESCAPED_UNICODE));
}
प्रारंभिक सेटअप: जब आप इस स्निपेट को पहली बार इंस्टॉल करते हैं, तो आप अस्थायी रूप से my_site_rebuild_full_json_index(); चला सकते हैं या किसी मौजूदा पोस्ट को बस सेव/अपडेट कर सकते हैं ताकि search-es.json फ़ाइल पहली बार बन जाए। एक बार यह हो जाने के बाद, यह जांचने के लिए कि फ़ाइल बन गई है और उसका आकार जानने के लिए wp-content/uploads/ निर्देशिका में जाएँ।
आपको इस search-es.json फ़ाइल के लगभग आकार का अंदाज़ा देने के लिए, मेरी 6,040 प्रविष्टियों के लिए, 3.41 एमबी की एक फ़ाइल बनाई गई थी – यह एक काफी प्रबंधनीय आकार है, क्योंकि तब तक चिंता करने की कोई आवश्यकता नहीं है जब तक यह 15 या 20 एमबी से अधिक नहीं हो जाती (जो लगभग 30 के बराबर है,000 या 40,000 लंबी प्रविष्टियों के बराबर होने पर ही चिंता करने की आवश्यकता होती है, क्योंकि स्थिर JSON फ़ाइलें सीधे सर्वर के कैश में या PHP के `file_get_contents` फ़ंक्शन के माध्यम से एक साथ लोड होती हैं, और कुछ मेगाबाइट की फ़ाइल एक सेकंड के एक हजारवें हिस्से से भी कम समय में पढ़ी जाती है; हालाँकि, यदि फ़ाइल अत्यधिक बढ़ जाती है, हर बार जब खोज चलायी जाती है, PHP एक विशाल JSON फ़ाइल लोड करने के लिए RAM में अनावश्यक वृद्धि करेगा। उस स्थिति में, केवल शीर्षकों और अंशों को ही इंडेक्स करने पर विचार करना या समर्पित डेटाबेस-इंडेक्स्ड खोज समाधान में माइग्रेट करना उचित होगा।
चरण 3: खोजों को मानकीकृत करने के लिए सहायक फ़ंक्शन (विराम चिह्नों के बिना)
यह सुनिश्चित करने के लिए कि खोजें परिणाम लौटाएँ, चाहे उपयोगकर्ता कैसे भी टाइप करे (उच्चारण चिह्नों के साथ या बिना, या बड़े अक्षरों में या छोटे अक्षरों में), एक सफाई फ़ंक्शन परिभाषित किया गया है:
पीएचपी
/**
* Función auxiliar para eliminar acentos y pasar a minúsculas
*/
if (!function_exists('mi_sitio_limpiar_acentos')) {
function mi_sitio_limpiar_acentos($cadena) {
$cadena = mb_strtolower($cadena, 'UTF-8');
$string = array(
'á'=>'a', 'à'=>'a', 'ä'=>'a', 'â'=>'a', 'ª'=>'a', 'Á'=>'a', 'À'=>'a', 'Ä'=>'a', 'Â'=>'a',
'é'=>'e', 'è'=>'e', 'ë'=>'e', 'ê'=>'e', 'É'=>'e', 'È'=>'e', 'Ë'=>'e', 'Ê'=>'e',
'í'=>'i', 'ì'=>'i', 'ï'=>'i', 'î'=>'i', 'Í'=>'i', 'Ì'=>'i', 'Ï'=>'i', 'Î'=>'i',
'ó'=>'o', 'ò'=>'o', 'ö'=>'o', 'ô'=>'o', 'Ó'=>'o', 'Ò'=>'o', 'Ö'=>'o', 'Ô'=>'o',
'ú'=>'u', 'ù'=>'u', 'ü'=>'u', 'û'=>'u', 'Ú'=>'u', 'Ù'=>'u', 'Ü'=>'u', 'Û'=>'u',
'ñ'=>'n', 'Ñ'=>'n'
);
return strtr($cadena, $string);
}
}
चरण 4: ` posts_pre_query` का उपयोग करके मुख्य क्वेरी को इंटरसेप्ट करें
`posts_pre_query` फ़िल्टर का उपयोग करके, वर्डप्रेस खोज क्वेरी डेटाबेस तक पहुँचने से पहले इंटरसेप्ट की जाती है। JSON को पार्स किया जाता है, खोज शब्दों से मेल खाने वाली आईडी की पहचान की जाती है, और केवल वही पोस्ट्स वर्डप्रेस को लौटाई जाती हैं, संबंधित पेजिनेशन निर्दिष्ट करते हुए।
पीएचपी
/**
* Interceptación de la búsqueda nativa leyendo el archivo JSON
*/
add_filter('posts_pre_query', function($posts, $wp_query) {
if (!is_admin() && $wp_query->is_search() && $wp_query->is_main_query()) {
if (function_exists('pll_current_language')) {
if (pll_current_language() !== 'es') return $posts;
}
$s = $wp_query->get('s');
if (empty($s)) return array();
$upload_dir = wp_upload_dir();
$json_path = $upload_dir['basedir'] . '/search-es.json';
if (!file_exists($json_path)) return $posts;
$indice = json_decode(file_get_contents($json_path), true);
if (!is_array($indice)) return $posts;
$s_clean = mi_sitio_limpiar_acentos(wp_strip_all_tags($s));
$palabras_buscadas = array_filter(explode(' ', $s_clean));
if (empty($palabras_buscadas)) return array();
$matched_ids = array();
// Recorrer el índice acumulando coincidencias
foreach ($indice as $item) {
$titulo_clean = mi_sitio_limpiar_acentos($item['t']);
$texto_clean = mi_sitio_limpiar_acentos($item['k']);
$coinciden = true;
foreach ($palabras_buscadas as $palabra) {
if (mb_strlen($palabra, 'UTF-8') < 2) continue; // Ignorar palabras de 1 letra
if (mb_strpos($titulo_clean, $palabra) === false && mb_strpos($texto_clean, $palabra) === false) {
$coinciden = false;
break;
}
}
if ($coinciden) {
$matched_ids[] = (int)$item['i'];
}
}
// Si no hay resultados, devolver array vacío
if (empty($matched_ids)) {
$wp_query->found_posts = 0;
$wp_query->max_num_pages = 0;
return array();
}
// Paginación
$posts_per_page = (int)get_option('posts_per_page', 10);
$paged = max(1, (int)$wp_query->get('paged'));
$total_encontrados = count($matched_ids);
$wp_query->found_posts = $total_encontrados;
$wp_query->max_num_pages = ceil($total_encontrados / $posts_per_page);
// Recuperar solo los objetos de los posts encontrados, ordenados por fecha
return get_posts(array(
'post__in' => $matched_ids,
'orderby' => 'date',
'order' => 'DESC',
'posts_per_page' => $posts_per_page,
'paged' => $paged,
'post_type' => 'post',
'no_found_rows' => true,
'cache_results' => true,
'update_post_meta_cache' => false,
'update_post_term_cache' => false,
'suppress_filters' => true,
));
}
return $posts;
}, 10, 2);
चरण 5: परिणामों का सत्यापन
एक बार जब आप उपरोक्त चरणों को पूरा कर लें, तो एक परीक्षण खोज चलाएँ और क्वेरी मॉनिटर खोलें:
- कुल डेटाबेस समय: आप देखेंगे कि यह अब कई सेकंड से घटकर केवल कुछ मिलीसेकंड (आमतौर पर 0.03 से 0.2 सेकंड के बीच) हो गया है।
- SQL क्वेरीज़: LIKE %term% क्लॉज पूरी तरह से गायब हो चुका होगा।
- RAM: पीक मेमोरी उपयोग असाधारण रूप से कम रहेगा (लगभग 30 एमबी)।
- सॉर्टिंग: पोस्ट्स को सख्त उतरोत्तर कालानुक्रमिक क्रम (नवीनतम से पुराने तक) में प्रदर्शित किया जाएगा, साथ ही वर्डप्रेस की मूल पेजिनेशन बरकरार रखी जाएगी।
मैं परिणाम से पूरी तरह संतुष्ट हूँ। न केवल मैंने वर्षों से चल रही एक समस्या का समाधान कर लिया है, बल्कि अब प्रतिक्रिया वाकई शानदार है। यहाँ तक कि सबसे चुनौतीपूर्ण खोजों में से एक के लिए भी, जैसे कि 'viñeta' के लिए 4,887 परिणामों वाली यह खोज , सर्च इंजन बहुत तेजी से प्रतिक्रिया देता है। कम परिणामों वाली खोजों के लिए, आप कह सकते हैं कि वे तुरंत ही दिख जाते हैं। आप किसी भी अन्य चीज़ की खोज करके इसे स्वयं देख सकते हैं।








