
یہ کوئی راز نہیں کہ ورڈپریس کی سرچ فنکشن بالکل بیکار ہے۔ جمالیاتی اعتبار سے اسے ٹھیک کرنا نسبتاً آسان ہے؛ تاہم اس کی فعالیت بالکل مختلف معاملہ ہے، کیونکہ اس میں کسی بھی قسم کے کنفیگریشن آپشنز موجود نہیں ہیں – اور بدقسمتی سے اس کی کارکردگی بھی انتہائی ناقص ہے۔
جب ورڈپریس سائٹ پر ہزاروں پوسٹس جمع ہو جاتی ہیں تو بلٹ ان سرچ فنکشن ناکام ہونے لگتا ہے۔ یہ اتنی سست ہو جاتی ہے کہ عملی طور پر بے کار ہو جاتی ہے۔ جب آپ تلاش کرتے ہیں تو ورڈپریس ڈیٹا بیس پر LIKE %word% کا سوال چلاتا ہے، جو wp_posts ٹیبل کے پورے مواد کو اسکین کرتا ہے۔ اس سے ایس کیو ایل سوالات پیدا ہوتے ہیں جو چند سیکنڈ سے لے کر لامتناہی وقت تک لے سکتے ہیں، اور میموری میں اتار چڑھاؤ پیدا کر کے سرور کو کریش کر سکتے ہیں۔
یہ بالکل وہی تھا جو پہلے ہی میرے ساتھ ہو رہا تھا۔ 6,040 پوسٹس شائع ہونے کے بعد (صرف ہسپانوی میں)، بہت سے نتائج والی تلاشوں کے جوابات دینے میں واقعی ڈراؤنا خواب تھا، جو 10 سیکنڈ یا اس سے زیادہ وقت لے لیتا تھا۔ اور آخر کار وہ دن آ ہی گیا جب اسے ایک بار اور ہمیشہ کے لیے حل کرنا تھا۔
اس پر کافی غور کرنے کے بعد مجھے احساس ہوا کہ اسے حل کرنے کے کم از کم تین طریقے ہیں۔ پہلا اور سب سے آسان طریقہ Relevanssi جیسے ادائیگی شدہ پلگ ان (اس کا مفت ورژن معیار کے مطابق نہیں) یا Algolia جیسی بیرونی سروس استعمال کرنا تھا، جسے میں نے فوراً خارج کر دیا کیونکہ میں اندرونی طور پر تلاشوں کو تیز کرنا چاہتا ہوں۔
باقی دو اختیارات میں سے، میں نے MySQL میں MATCH() AGAINST() کوئریز استعمال کرتے ہوئے FULLTEXT انڈیکس بنانے کی کوشش کی لیکن بدقسمتی سے ناکام رہا۔ انو ڈی بی انجن کے پیرامیٹرز کو ایڈجسٹ کرنے اور انڈیکسز کو بہتر بنانے کے باوجود، MySQL ہزاروں پیچیدہ کلیدی الفاظ پر کارروائی کرتے وقت سست پڑ جاتا تھا، ناقابلِ قبول ردِ عمل کے اوقات دیتا اور سرور پر زیادہ بوجھ ڈال دیتا تھا۔ تب میں نے ڈیٹا بیس پر بھاری بوجھ ڈالنے کے خیال کو بالکل خارج کر دیا اور تیسرا طریقہ اختیار کیا: JSON میں ایک جامد انڈیکس بنانا۔
اس ٹیوٹوریل کا مقصد یہ بتانا ہے کہ وسائل طلب MySQL سوالات کو ایک جامد JSON فائل سے تبدیل کرکے اس مسئلے کو کیسے حل کیا جائے، جو ایک انتہائی تیز تلاش انڈیکس کے طور پر کام کرتی ہے۔
مرحلہ 1: سست یا غیر ضروری کوئریاں شناخت کریں اور ہٹا دیں۔
JSON انڈیکس کو نافذ کرنے سے پہلے راستہ صاف کرنا ضروری ہے۔ بہت سے پلگ انز (جیسے ڈائریکٹری پلگ انز، لے آؤٹ پلگ انز یا ثانوی تلاش کے پلگ انز) pre_get_posts، posts_where وغیرہ جیسے ہوکس کے ذریعے ورڈپریس کی تلاش کے عمل میں شامل ہوتے ہیں، جس سے ڈیٹا بیس پر اضافی اور غیر ضروری استفسارات چلتے ہیں۔
آپ Query Monitor استعمال کرتے ہوئے ان استفسارات کی شناخت کیسے کر سکتے ہیں؟
- مفت Query Monitor پلگ ان انسٹال کریں اور فعال کریں۔
- اپنی ویب سائٹ پر کہیں بھی تلاش کریں۔
- اوپری بار میں Query Monitor کے ڈراپ ڈاؤن مینو کو کھولیں اور 'Database Quer ies ' منتخب کریں۔
- 'سست استفسارات' کے لحاظ سے فلٹر کریں یا فہرست میں جا کر ان استفسارات کی نشاندہی کریں جو چلنے میں سب سے زیادہ وقت لیتے ہیں۔ اس دوران کسی بھی دہرائے گئے استفسار کو نوٹ کریں اور اگر ممکن ہو تو انہیں بھی ترتیب دے دیں۔
- جدولوں میں 'تفصیلات' کالم پر نظر ڈالیں: وہاں آپ بالکل دیکھ سکیں گے کہ کون سا پلگ ان یا فائل اس کوئری کو چلا رہا ہے۔
میرے معاملے میں مجھے 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: نتائج کی تصدیق
جب آپ نے اوپر دیے گئے مراحل مکمل کر لیے ہوں، تو ایک ٹیسٹ سرچ کریں اور Query Monitor کھولیں:
- کل ڈیٹا بیس کا وقت: آپ نوٹ کریں گے کہ یہ اب چند سیکنڈز سے گھٹ کر صرف چند ملی سیکنڈز (عموماً 0.03 سیکنڈ اور 0.2 سیکنڈ کے درمیان) رہ گیا ہے۔
- SQL سوالات: LIKE %term% شق مکمل طور پر غائب ہو چکی ہوگی۔
- RAM: میموری کے استعمال کا عروج غیر معمولی طور پر کم رہے گا (تقریباً 30 میگا بائٹس)۔
- ترتیب: پوسٹس کو سخت نزولی زمانی ترتیب (سب سے حالیہ سے سب سے پرانے) میں دکھایا جائے گا، جبکہ ورڈپریس کی مقامی صفحہ بندی برقرار رکھی جائے گی۔
میں نتیجے سے نہ صرف پوری طرح مطمئن ہوں بلکہ اس سے بھی زیادہ خوش ہوں۔ نہ صرف میں نے ایک ایسے مسئلے کو حل کر لیا ہے جو برسوں سے چل رہا تھا، بلکہ اب جواب بے مثال ہے۔ یہاں تک کہ سب سے زیادہ مطالبہ کرنے والی تلاشوں کے لیے بھی، جیسے کہ 'viñeta' کے لیے 4,887 نتائج والی یہ تلاش ، سرچ انجن بہت تیزی سے جواب دیتا ہے۔ کم نتائج والی تلاشوں کے لیے تو آپ کہہ سکتے ہیں کہ وہ فوراً ظاہر ہو جاتے ہیں۔ آپ خود کسی بھی دوسری چیز کی تلاش کر کے اس کی تصدیق کر سکتے ہیں۔







