Hogyan lehet felgyorsítani a WordPress keresőjét egy statikus JSON-index használatával

No comments

24.07.2026|

No comments

Tiempo de lectura Lectura: 13 min, 31 s
Número de palabras Palabras: 2502
Número de visitas Visitas: 6
Icono de traducción

Senki számára sem titok, hogy a WordPress keresője egy katasztrófa. A megjelenését illetően viszonylag könnyű megoldani a problémát, a működése azonban már más történet, mivel egyáltalán nem kínál beállítási lehetőségeket, ráadásul a teljesítménye is siralmas.

Amikor egy WordPress-oldalon több ezer bejegyzés halmozódik fel, a beépített kereső meghibásodni kezd. Annyira lelassul, hogy gyakorlatilag használhatatlanná válik. Kereséskor a WordPress egy LIKE %szó% lekérdezést futtat az adatbázisban, amely átvizsgálja a wp_posts táblázat teljes tartalmát. Ez olyan SQL-lekérdezéseket generál, amelyek végrehajtása néhány másodperctől akár egy örökkévalóságig is eltarthat, és olyan memóriaigény-csúcsokat okozhat, amelyek végül a szerver leállásához vezetnek.

Pontosan ez történt velem is. 6040 közzétett bejegyzéssel (csak spanyol nyelven) a legtöbb találatot hozó keresések válaszideje igazi pokol volt: 10 másodpercnél is hosszabb ideig tartott. És eljött a nap, amikor egyszer és mindenkorra megoldottam a problémát.

Miután alaposan átgondoltam a dolgot, rájöttem, hogy legalább három lehetőség kínálkozik a probléma megoldására. Az első és legegyszerűbb az volt, hogy valamilyen fizetős bővítményt használjak, például a Relevanssi-t (amelynek ingyenes verziója nem elégséges), vagy egy külső szolgáltatást, mint például az Algolia-t, de ezt már az elején kizártam, mert az a célom, hogy natív módon gyorsítsam fel a keresést.

A két fennmaradó lehetőség közül megpróbáltam létrehozni egy FULLTEXT indexet a MySQL-ben MATCH() AGAINST() lekérdezések segítségével, de ez szerencsétlenül végződött. Annak ellenére, hogy beállítottam az InnoDB motor paramétereit és optimalizáltam az indexeket, a MySQL továbbra is lefagyott, amikor több ezer összetett kulcsszót kellett feldolgoznia, elfogadhatatlanul hosszú válaszidőket eredményezve és túlterhelve a szervert. Ekkor döntöttem úgy, hogy teljesen elvetem azt az ötletet, hogy a nehéz terhet az adatbázisra hárítsam, és a harmadik megoldás mellett döntöttem: statikus indexet hoztam létre JSON-ban.

Ebben az oktatóanyagban megpróbáljuk elmagyarázni, hogyan lehet megoldani ezt a problémát úgy, hogy a MySQL-ben végzett erőforrásigényes lekérdezéseket egy statikus JSON-fájllal helyettesítjük, amely rendkívül gyors keresési indexként működik.

1. lépés: A terhelő vagy felesleges lekérdezések felismerése és eltávolítása

A JSON-index alkalmazása előtt elengedhetetlenül fontos, hogy „megtisztítsuk a terepet”. Számos bővítmény (például könyvtárak, elrendezés-szerkesztők vagy kiegészítő keresőbővítmények) hookok (pre_get_posts, posts_where stb.) segítségével kapcsolódik be a WordPress keresési folyamatába, ami további, felesleges lekérdezéseket indít el az adatbázisban.

Hogyan lehet ezeket a lekérdezéseket a Query Monitor segítségével azonosítani?

  1. Telepítse és aktiválja a Query Monitor nevű ingyenes bővítményt.
  2. Végezz el bármilyen keresést a weboldaladon.
  3. Nyisd meg a Query Monitor legördülő menüjét a felső sávon, és válaszd az „Adatbázis-lekérdezések” (Database Queries) lehetőséget.
  4. Szűrj a „Lassú lekérdezések” szerint, vagy nézd át a listát, és keressd meg azokat a lekérdezéseket, amelyek több időt vesznek igénybe. Ha már itt tartasz, jegyezd fel az ismétlődő lekérdezéseket is, és ha lehet, azokat is oldd meg.
  5. Nézd meg a táblázatok részletes oszlopát: ott pontosan láthatod, hogy melyik bővítmény vagy fájl futtatja az adott lekérdezést.

Az én esetemben néhány, a Name_Directory bővítményből származó duplikált lekérdezést kellett megoldanom, valamint néhány GenerateBlocks- blokkot a bejegyzés-sablonban, de bármely bővítmény, egyéni kód vagy hozzáadott elem generálhat olyan lekérdezéseket, amelyek lassítják a válaszadást.

Hogyan lehet kóddal letiltani ezeket a hookokat?

Miután azonosítottad a lassú lekérdezést kiváltó függvény vagy osztály nevét, azt kikapcsolhatod a functions.php fájlban , vagy egy kódrészlet-bővítmény (például a Code Snippets, a PerfmattersCode vagy hasonló) segítségével:

* Megjegyzés: Az 1. blokkba be kell illesztened azokat a hookokat, amelyek lassú lekérdezéseket generálnak. Ha ez nem vonatkozik rád, akkor ezt a részt kihagyhatod, és csak a 2. blokkot illesztheted be a kódrészletbe.

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

Megjegyzés: Ha egy hookot a `remove_action` vagy a `remove_filter` paranccsal szeretnél eltávolítani, pontosan ugyanazt az eseménynevet (pre_get_posts), ugyanazt a függvénynevet, amelyet a Query Monitorban azonosítottál, és ugyanazt a prioritást (alapértelmezés szerint 10) kell használnod, amellyel a hookot hozzáadtad.

2. lépés: A keresési JSON-fájl létrehozása és naprakészen tartása

A MySQL helyettesítéséhez létre kell hoznunk egy JSON formátumú statikus fájlt, amely tartalmazza a bejegyzéseink legfontosabb adatait (azonosító, cím és egyszerű szöveg). Ezt a fájlt a wp-content/uploads/ mappában kell elmenteni, és automatikusan frissülni fog minden alkalommal, amikor bejegyzést hozol létre, szerkesztesz vagy törölsz.

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

Kezdeti létrehozás: Amikor először telepíted ezt a kódrészletet, ideiglenesen futtathatod a mi_sitio_reconstruir_indice_json_completo(); parancsot, vagy egyszerűen menthetsz/frissíthetsz bármely meglévő bejegyzést, hogy a search-es.json fájl először létrejöjjön. Miután ezt elvégezte, látogasson el a wp-content/uploads/ könyvtárba, hogy megbizonyosodjon a fájl létrehozásáról, és megismerje annak méretét.

Hogy képet kapj a search-es.json fájl hozzávetőleges méretéről: az én 6040 bejegyzésem esetén egy 3,41 Mb-os fájl jött létre, ami elég kezelhető méret, mivel addig nem kell aggódni, amíg a mérete nem haladja meg a 15–20 MB-ot (ami körülbelül 30.000 vagy 40 000 terjedelmes bejegyzésnek felel meg), nem kell aggódni, mert a statikus JSON-fájlokat egy csapásra közvetlenül a szerver vagy a PHP (file_get_contents) gyorsítótárába töltik be, és néhány megabájt méretű fájlt kevesebb, mint egy ezredmásodperc alatt beolvasnak; azonban, ha a fájl túlzottan megnő, akkor minden keresés végrehajtásakor a PHP feleslegesen fogyaszt majd extra RAM-memóriát egy hatalmas JSON-fájl betöltéséhez; ilyenkor érdemes megfontolni, hogy a teljes tartalom helyett csak a címeket és kivonatokat indexeljük, vagy áttérjünk egy dedikált adatbázis-alapú keresési megoldásra.

3. lépés: Segédfüggvény a keresések normalizálásához (ékezet nélkül)

Annak biztosítására, hogy a keresések eredményeket találjanak függetlenül attól, hogy a felhasználó hogyan írja be a keresett kifejezést (akár ékezetekkel, akár anélkül, nagy- vagy kisbetűkkel), definiálunk egy tisztító függvényt:

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

4. lépés: A fő lekérdezés elfogása a `posts_pre_query` segítségével

A posts_pre_query szűrő segítségével a WordPress keresési lekérdezése még az adatbázis elérését megelőzően le van fogva. A rendszer beolvassa a JSON-t, megkeresi a keresett kifejezéseknek megfelelő azonosítókat, és kizárólag ezeket a bejegyzéseket adja vissza a WordPressnek, megadva a megfelelő oldalszámozást.

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

5. lépés: Az eredmények ellenőrzése

Miután elvégezte a fenti lépéseket, hajtson végre egy keresési tesztet, majd nyissa meg a Query Monitor alkalmazást:

  1. A BD teljes futási ideje: Látni fogod, hogy ez most már nem több másodperc, hanem csupán néhány milliszekundum (általában 0,03 és 0,2 másodperc között).
  2. SQL-lekérdezések: A LIKE %kifejezés% záradék teljesen eltűnik.
  3. RAM-memória: A memóriahasználat csúcsértéke rendkívül alacsony szinten marad (körülbelül 30 MB).
  4. Rendezés: A bejegyzések szigorúan csökkenő időrendi sorrendben (a legújabbtól a legrégebbiig) jelennek meg, a WordPress natív oldalszámozását figyelembe véve.

Több mint elégedett vagyok az eredménnyel. Nemcsak egy évek óta fennálló problémát oldottam meg, hanem a keresési eredmények is egyszerűen látványosak. Az egyik legigényesebb keresési lekérdezésnél is – mint például ez a 4887 találatot adó„viñeta” keresés – a kereső nagyon gyorsan válaszol. A kevesebb találatot adó keresések esetében szinte azonnal megjelennek az eredmények. Bármilyen más kifejezés re keresve ezt magad is ellenőrizheted.

Hogyan lehet felgyorsítani a WordPress keresőjét egy statikus JSON-index használatával 1

Szólj hozzá!

Van valami mondanivalója?

Este blog se aloja en LucusHost

LucusHost, el mejor hosting