
Það er engin leyndarmál að leitarvirkni WordPress sé drasl. Frá fagurfræðilegu sjónarhorni er tiltölulega auðvelt að laga hana; virkni hennar er hins vegar allt önnur saga, því hún býður ekki upp á neinar stillingarmöguleika – og til að gera illt verra er frammistaða hennar hörmuleg.
Þegar WordPress-síða safnar þúsundum færslna fer innbyggða leitaraðgerðin að bregðast. Hún hægist svo niður að hún verður nánast gagnslaus. Þegar þú framkvæmir leit keyrir WordPress SQL-fyrirspurnina LIKE %word% á gagnagrunninum, sem skannar allt efni í wp_posts-töflunni. Þetta skapar SQL-fyrirspurnir sem geta tekið allt frá nokkrum sekúndum upp í eilífð og valdið skyndilegum minnisálagi sem leiðir til þess að netþjónninn hrundi.
Þetta er nákvæmlega það sem var þegar að gerast hjá mér. Með 6.040 birtum færslum (aðeins á spænsku) voru svartímar fyrir leitir með mörgum niðurstöðum algjör martröð, tóku yfir 10 sekúndur eða meira. Og loksins kom dagurinn til að leysa þetta einu sinni fyrir öll.
Eftir að hafa hugleitt þetta vel áttaði ég mig á því að það væru að minnsta kosti þrjár leiðir til að leysa þetta. Sú fyrsta og einfaldasta var að nota greiddan viðbót eins og Relevanssi (ókeypis útgáfan er ekki nægilega góð) eða ytri þjónustu eins og Algolia, sem ég útilokaði strax því það sem ég er að leita að er að hraða leit innan kerfisins sjálfs.
Af þeim tveimur valkostum sem eftir voru reyndi ég að búa til FULLTEXT-vísitölu í MySQL með MATCH() AGAINST()-fyrirspurnum en mistókst hrapallega. Þrátt fyrir að stilla breytur InnoDB-vélarinnar og fínstilla vísitölurnar hélt MySQL áfram að dragast þegar unnið var með þúsundir flókinna lykilorða, skilaði óásættanlegum svartímum og ofhlaðaði netþjóninn. Þá ákvað ég að útiloka algjörlega að leggja þungan álag á gagnagrunninn og kaus þriðju leiðina: að búa til kyrrstæðan vísitölu í JSON.
Þessi kennsluleiðbeining miðar að því að útskýra hvernig á að leysa þetta vandamál með því að skipta út auðlindafrekandi MySQL-fyrirspurnum fyrir kyrrstæða JSON-skrá sem virkar sem ofurhröð leitarvísitala.
Skref 1: Finndu og fjarlægðu hægar eða óþarfa fyrirspurnir
Áður en JSON-vísitalan er innleidd er nauðsynlegt að ryðja brautina. Margir viðbætur (svo sem skráarviðbætur, skipulagsviðbætur eða aukaleitarviðbætur) tengjast WordPress-leit ferlinu í gegnum hooks (pre_get_posts, posts_where o.s.frv.), sem kalla fram auka- og óþarfa fyrirspurnir til gagnagrunnsins.
Hvernig geturðu greint þessar fyrirspurnir með Query Monitor?
- Settu upp og virkjaðu ókeypis Query Monitor-viðbótina.
- Leitaðu hvar sem er á vefsíðunni þinni.
- Opnaðu fellivalmyndina Query Monitor í efri stiku og veldu " Gagnagrunnsfyrirspurnir ".
- Síaðu eftir 'hægum fyrirspurnum' eða farðu í gegnum listann til að finna þær fyrirspurnir sem taka lengstan tíma. Á meðan skráðu allar tvíteknar fyrirspurnir og, ef þú getur, lagfærðu þær líka.
- Skoðaðu dálkinn "Nánar" í töflunum: þar sérðu nákvæmlega hvaða viðbót eða skrá keyrir þessa fyrirspurn.
Í mínu tilfelli þurfti ég að leysa úr nokkrum tvíteknum fyrirspurnum frá Name_Directory-viðbótinni og nokkrum GenerateBlocks- blokkum í færslusniðmátinu, en hvaða viðbót, sérsniðinn kóði eða bætt atriði gæti verið að búa til fyrirspurnir sem hægja á svörun.
Hvernig á að slökkva á þessum hooks í kóða
Þegar þú hefur fundið nafnið á fallinu eða klasanum sem veldur hæga fyrirspurninni, geturðu slökkt á því í functions.php- skránni þinni eða með kóðabrotaviðbót (t.d. Code Snippets, PerfmattersCode eða svipaðri):
Athugið: Í blokk 1 skaltu bæta við hengjunum sem valda hægum fyrirspurnum. Ef þetta á ekki við um þig geturðu fjarlægt þennan hluta og bætt aðeins blokk 2 við kóðabrotinu.
/**
* 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);
Athugið: Til að fjarlægja aðgerð með `remove_action` eða `remove_filter` verður þú að nota nákvæmlega sama viðburðarnafnið (`pre_get_posts`), sama fallanafnið sem þú fannst í Query Monitor og sama forgang sem var notaður við bætingu (10 sem sjálfgefið gildi).
Skref 2: Búðu til og haltu leitar-JSON-skránni uppfærðri.
Til að skipta út fyrir MySQL þurfum við að búa til statíska skrá í JSON-sniði sem inniheldur lykilgögn fyrir færslurnar okkar (auðkenni, titill og einfaldur texti). Þessi skrá verður vistuð í möppunni wp-content/uploads/ og uppfærð sjálfkrafa í hvert sinn sem þú bætir við, breytir eða eyðir færslu.
PHP
/**
* 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));
}
Upphafsvinnsla: Í fyrsta sinn sem þú setur þennan kóðabrot upp geturðu tímabundið keyrt my_site_rebuild_full_json_index(); eða einfaldlega vistað/uppfært hvaða færslu sem er til staðar svo að skráin search-es.json bjóist til í fyrsta sinn. Þegar þessu er lokið, farðu í möppuna wp-content/uploads/ til að ganga úr skugga um að skráin hafi verið búin til og til að sjá hversu stór hún er.
Til að gefa þér hugmynd um áætlaða stærð þessarar search-es.json-skrár var fyrir 6.040 færslur mínar búin til 3,41 MB skrá – nokkuð viðráðanleg stærð, þar sem ekki þarf að hafa áhyggjur fyrr en hún fer að fara yfir 15 eða 20 MB (sem samsvarar um 30,000 eða 40.000 langar færslur), er ekki þörf á að hafa áhyggjur, því kyrrstæðar JSON-skrár eru hlaðnar beint í skyndiminni netþjónsins eða í skyndiminni PHP (file_get_contents) í einu, og skrá upp á nokkur megabæti er lesin á innan við þúsundunda hluta úr sekúndu; þó að ef skráin vex óhóflega, í hvert sinn sem leitin er keyrð mun PHP neyta óþarfa hnökrans í vinnsluminni til að hlaða risastóru JSON-skjali, og þá væri vert að íhuga að skrá eingöngu titla og útdrætti fremur en allt efnið, eða færa sig yfir í sérhæfða leitarlausn sem byggir á gagnagrunns-vísitöku.
Skref 3: Aukastarfsaðgerð til að staðla leitir (án áherslumerkja)
Til að tryggja að leitir skili niðurstöðum óháð því hvernig notandinn slær inn (með eða án sérstafa, eða í stórum eða smáum stöfum), er skilgreind hreinsunarfall:
PHP
/**
* 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);
}
}
Skref 4: Stöðva aðaluppfyrirspurnina með ` posts_pre_query`
Með því að nota síuna `posts_pre_query` er WordPress-leitarspurningin stöðvuð áður en hún berst í gagnagrunninn. JSON-skráin er greind, auðkennin sem samsvara leitarorðunum eru fundin og aðeins þær færslur eru sendar aftur til WordPress, með tilgreiningu á viðeigandi síðaskiptingu.
PHP
/**
* 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);
Skref 5: Staðfesting niðurstaðna
Þegar þú hefur lokið ofangreindum skrefum skaltu framkvæma prófunarleit og opna Query Monitor:
- Heildartími gagnagrunnsins: Þú munt taka eftir að hann hefur nú farið úr nokkrum sekúndum niður í aðeins nokkrar millisekúndur (venjulega á bilinu 0,03 s til 0,2 s).
- SQL-fyrirspurnir: LIKE %term% skilyrðið mun hafa horfið alveg.
- RAM: Hámarksnotkun vinnsluminni verður áfram einstaklega lág (um 30 MB).
- Röðun: Færslur verða birtar í ströngu niðrandi tímaröð (frá nýjustu til elstu), á sama tíma og innfæddri síðaskiptingu WordPress er haldið.
Ég er meira en ánægður með niðurstöðuna. Ekki aðeins hef ég leyst vandamál sem hefur staðið yfir í mörg ár, heldur er viðbragðið nú hreint út sagt stórkostlegt. Jafnvel fyrir eina af krefjandi leitunum, eins og þessa með 4.887 niðurstöðum fyrir'viñeta', bregst leitarvélin mjög hratt við. Fyrir leitir með færri niðurstöðum má segja að þær birtast samstundis. Þú getur sannreynt þetta sjálfur með því að leita að öðru hvað sem er.





