Kuinka nopeuttaa WordPressin hakutoimintoa staattisen JSON-indeksin avulla

Ei kommentteja

24.07.2026|

Ei kommentteja

Tiempo de lectura Lectura: 10 min, 4 s
Número de palabras Palabras: 1864
Número de visitas Visitas: 58
Icono de traducción

Se, että WordPressin hakutoiminto on surkea, ei ole kenellekään salaisuus. Ulkonäön osalta ongelma on suhteellisen helppo ratkaista, mutta sen toiminta onkin sitten ganz andere Sache, sillä se ei tarjoa lainkaan asetusmahdollisuuksia, ja lisäksi sen suorituskyky on surkea.

Kun WordPress-sivustolle kertyy tuhansia artikkeleita, sen oma hakutoiminto alkaa pettää. Se hidastuu, kunnes siitä tulee käytännössä käyttökelvoton. Hakuja suoritettaessa WordPress lähettää tietokantaan LIKE %sana% -kyselyn, joka skannaa koko wp_posts-taulukon sisällön. Tämä tuottaa SQL-kyselyjä, joiden suorittaminen voi kestää muutamasta sekunnista ikuisuuteen ja aiheuttaa muistipiikkejä, jotka lopulta jumittavat palvelimen.

Juuri näin minulle oli jo käynyt. Kun julkaistuja viestejä oli 6040 (vain espanjaksi), hakutuloksia sisältävien hakujen vastausajat olivat todellinen painajainen: ne kestivät yli 10 sekuntia tai jopa kauemmin. Ja sitten koitti päivä, jolloin päätin ratkaista ongelman lopullisesti.

Harkittuani asiaa pitkään huomasin, että ongelman ratkaisemiseksi oli ainakin kolme vaihtoehtoa. Ensimmäinen ja yksinkertaisin oli käyttää jotakin maksullista laajennusta, kuten Relevanssia (sen ilmainen versio ei riitä), tai ulkoista palvelua, kuten Algoliaa, mutta hylkäsin tämän vaihtoehdon heti, koska tavoitteenani on tehostaa hakutoimintoja järjestelmän omilla keinoilla.

Kokeilin jäljellä olevista kahdesta vaihtoehdosta sitä, jossa luodaan FULLTEXT-indeksi MySQL:ssä MATCH() AGAINST() -kyselyjen avulla, mutta epäonnistuin surkeasti. Huolimatta InnoDB-moottorin parametrien säätämisestä ja hakemistojen optimoinnista MySQL jumittui edelleen käsitellessään tuhansia monimutkaisia avainsanoja, mikä johti sietämättömiin vasteaikoihin ja palvelimen ylikuormittumiseen. Silloin hylkäsin kokonaan ajatuksen jättää raskas työ tietokannan tehtäväksi ja valitsin kolmannen vaihtoehdon: rakensin staattisen indeksin JSON-muodossa.

Tässä oppaassa pyritään selittämään, miten tämä ongelma voidaan ratkaista korvaamalla raskaat MySQL-kyselyt staattisella JSON-tiedostolla, joka toimii erittäin nopeana hakuhakemistona.

Vaihe 1: Tunnista ja poista raskaat tai päällekkäiset kyselyt

Ennen JSON-indeksin käyttöönottoa on ehdottomasti puhdistettava ympäristö. Monet laajennukset (kuten hakemistot, asettelutyökalut tai toissijaiset hakulaajennukset) kytkeytyvät WordPressin hakuprosessiin koukkujen (pre_get_posts, posts_where jne.) avulla, mikä aiheuttaa ylimääräisiä ja tarpeettomia kyselyjä tietokantaan.

Miten nämä kyselyt voidaan havaita Query Monitorin avulla?

  1. Asenna ja ota käyttöön ilmainen Query Monitor-laajennus.
  2. Tee mitä tahansa hakua verkkosivustollasi.
  3. Avaa yläpalkissa Query Monitorin pudotusvalikko ja valitse ”Tietokantakyselyt” (Database Queries).
  4. Suodata hakutuloksia hitaiden kyselyjen mukaan tai selaa luetteloa etsiäksesi kyselyjä, joiden suorittaminen kestää kauemmin. Samalla voit myös merkitä muistiin päällekkäiset kyselyt ja, jos mahdollista, korjata nekin.
  5. Katso taulukoiden yksityiskohdat-saraketta: sieltä näet tarkalleen, mikä laajennus tai tiedosto suorittaa kyseisen kyselyn.

Minun tapauksessani jouduin korjaamaan muutaman Name_Directory-laajennuksen aiheuttaman päällekkäisen kyselyn sekä muutaman GenerateBlocks- lohkon blogikirjoitusmallissa, mutta mikä tahansa laajennus, mukautettu koodi tai lisätty elementti voi aiheuttaa kyselyjä, jotka hidastavat vastauksen muodostumista.

Kuinka poistaa nämä hookit käytöstä koodin avulla

Kun olet löytänyt hitaan kyselyn aiheuttavan funktion tai luokan nimen, voit poistaa sen käytöstä functions.php -tiedostossasi tai koodinpätkä-laajennuksen avulla (kuten Code Snippets, PerfmattersinCode tai vastaava):

* Huom. Lohkoon 1 tulee lisätä ne hookit, jotka aiheuttavat hitaita kyselyjä. Jos tämä ei koske sinua, voit poistaa tämän osan ja lisätä koodinpätkään vain lohkon 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);

Huomautus: Kun irrotat hookin remove_action- tai remove_filter-komennolla, sinun on käytettävä täsmälleen samaa tapahtuman nimeä (pre_get_posts), samaa funktio-nimeä, jonka olet tunnistanut Query Monitorissa, sekä samaa prioriteettia, jolla se lisättiin (oletusarvo 10).

Vaihe 2: Hakutiedoston JSON-tiedoston luominen ja päivittäminen

MySQL:n korvaamiseksi meidän on luotava staattinen JSON-muotoinen tiedosto, joka sisältää viestiemme avaintiedot (tunnus, otsikko ja teksti). Tämä tiedosto tallennetaan wp-content/uploads/ -kansioon, ja se päivittyy automaattisesti aina, kun luot, muokkaat tai poistat viestin.

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));
}

Alkuasetukset: Kun asennat tämän koodinpätkän ensimmäistä kertaa, voit suorittaa väliaikaisesti komennon mi_sitio_reconstruir_indice_json_completo(); tai yksinkertaisesti tallentaa tai päivittää minkä tahansa olemassa olevan merkinnän, jotta tiedosto search-es.json luodaan ensimmäistä kertaa. Kun olet tehnyt tämän, käy wp-content/uploads/ -kansiossa varmistamassa, että tiedosto on luotu, ja tarkista sen koko.

Jotta saat käsityksen tämän search-es.json-tiedoston arvioidusta koosta: minun 6040 tietueestani luotiin 3,41 Mb:n kokoinen tiedosto, mikä on varsin hallittavissa oleva koko, sillä niin kauan kuin tiedoston koko ei nouse yli 15 tai 20 MB:n (vastaa noin 30.000 tai 40 000 laajaa merkintää), ei ole syytä huoleen, sillä staattiset JSON-tiedostot ladataan kerralla suoraan palvelimen tai PHP:n välimuistiin (file_get_contents), ja muutaman megatavun kokoinen tiedosto luetaan alle tuhannesosassa sekunnissa; jos tiedosto kuitenkin kasvaa kohtuuttoman suureksi, joka kerta, kun haku suoritetaan, PHP kuluttaa tarpeettoman ylimääräisen RAM-muistin piikin valtavan JSON-tiedoston lataamiseksi. Tällöin kannattaisi harkita vain otsikoiden ja otteiden indeksointia koko sisällön sijaan tai siirtyä erilliseen tietokantapohjaiseen hakuratkaisuun.

Vaihe 3: Aputoiminto hakujen normalisointiin (ilman aksentteja)

Jotta hakutulokset löytyisivät riippumatta siitä, miten käyttäjä kirjoittaa (aksenttien kanssa tai ilman, isoilla tai pienillä kirjaimilla), määritellään puhdistustoiminto:

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);
    }
}

Vaihe 4: Pääkyselyn sieppaaminen posts_pre_query-funktiolla

posts_pre_query-suodattimen avulla WordPressin hakukysely siepataan ennen kuin se ehtii päästä tietokantaan. JSON-tiedosto luetaan, haetaan hakutermejä vastaavat tunnukset (ID:t) ja palautetaan WordPressille ainoastaan nämä tulokset määrittelemällä vastaava sivutus.

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);

Vaihe 5: Tulosten tarkistaminen

Kun olet suorittanut edellä mainitut vaiheet, tee hakutesti ja avaa Query Monitor:

  1. Tietokannan kokonaisaika: Huomaat, että se on nyt lyhentynyt useista sekunneista vain muutamaan millisekuntiin (yleensä 0,03–0,2 s).
  2. SQL-kyselyt: LIKE %termi% -lause katoaa kokonaan.
  3. RAM-muisti: Muistin käytön huippuarvo pysyy poikkeuksellisen alhaisena (noin 30 Mt).
  4. Järjestys: Merkinnät näytetään tiukasti kronologisessa laskevassa järjestyksessä (uusimmasta vanhimpaan) noudattaen WordPressin omaa sivutusta.

Olen enemmän kuin tyytyväinen tulokseen. En ole vain päässyt eroon ongelmasta, joka on vaivannut minua jo vuosia, vaan nyt hakutulos on suorastaan upea. Yhdessä vaativimmista hauista, kuten tässä , jossa hakusanalle”viñeta” löytyy 4887 tulosta, hakukone vastaa erittäin nopeasti. Hakujen osalta, joissa tuloksia on vähemmän, voidaan sanoa, että ne näkyvät välittömästi. Voit tarkistaa tämän hakemalla mitä tahansa muuta.

Kuinka nopeuttaa WordPressin hakutoimintoa staattisen JSON-indeksin avulla 1

Jätä kommentti

Onko mitään sanottavaa?

Este blog se aloja en LucusHost

LucusHost, el mejor hosting