Kaip pagreitinti „WordPress“ paiešką naudojant statinį JSON indeksą

No comments

24.07.2026|

No comments

Tiempo de lectura Lectura: 10 min, 54 s
Número de palabras Palabras: 2017
Número de visitas Visitas: 6
Icono de traducción

Tai, kad „WordPress“ paieškos sistema yra visiškai bevertė, niekam nėra paslaptis. Estetinės problemos ją išspręsti palyginti lengva, tačiau jos veikimas – visai kita istorija, nes ji nesiūlo jokių nustatymų galimybių, o jos našumas, be to, yra apgailėtinas.

Kai „WordPress“ svetainėje susikaupia tūkstančiai įrašų, įprasta paieškos funkcija pradeda strigti. Ji sulėtėja tiek, kad tampa praktiškai nenaudinga. Atliekant paiešką, „WordPress“ duomenų bazėje vykdo užklausą „LIKE %žodis%“, kuri nuskaito visą lentelės „wp_posts“ turinį. Tai sukuria SQL užklausas, kurios gali trukti nuo kelių sekundžių iki amžinybės ir sukelti atminties apkrovos šuolius, galiausiai užblokuojančius serverį.

Būtent tai man ir nutiko. Turėdamas 6040 paskelbtų įrašų (tik ispanų kalba), paieškų, kuriose buvo daugiausia rezultatų, atsakymo laikas buvo tikra kančia – jis trukdavo ilgiau nei 10 sekundžių ar net daugiau. Ir atėjo diena, kai reikėjo šią problemą išspręsti kartą ir visiems laikams.

Ilgai apgalvojęs, supratau, kad yra bent trys galimybės tai išspręsti. Pirmoji ir paprasčiausia buvo pasinaudoti kokiu nors mokamu įskiepiu, pavyzdžiui, „Relevanssi“ (jo nemokama versija yra nepakankama), arba išorine paslauga, pavyzdžiui, „Algolia“, tačiau šią galimybę atmečiau iš karto, nes noriu pagreitinti paiešką naudojant įdiegtas funkcijas.

Iš likusių dviejų variantų pabandžiau sukurti FULLTEXT indeksą „MySQL“ naudodamas užklausas „MATCH() AGAINST()“, tačiau man tai visiškai nepavyko. Nepaisant to, kad sureguliavau „InnoDB“ variklio parametrus ir optimizavau indeksus, „MySQL“ vis tiek strigdavo apdorodama tūkstančius sudėtingų raktinių žodžių, grąžindama nepriimtinus atsakymo laikus ir perkraudama serverį. Tuomet visiškai atmečiau mintį palikti šį sunkų darbą duomenų bazei ir pasirinkau trečiąjį variantą – sukurti statinį indeksą JSON formatu.

Šiame vadove bandoma paaiškinti, kaip išspręsti šią problemą, pakeičiant sudėtingus „MySQL“ užklausimus statiniu JSON failu, kuris veikia kaip ypač greitas paieškos indeksas.

1 žingsnis: Nustatyti ir pašalinti sudėtingus arba pasikartojančius užklausimus

Prieš pritaikant JSON indeksą, būtina „išvalyti aplinką“. Daugelis įskiepių (pavyzdžiui, katalogų, maketavimo įskiepių ar papildomų paieškos įskiepių) prisijungia prie „WordPress“ paieškos proceso naudodami kabliukus (pre_get_posts, posts_where ir pan.), dėl ko į duomenų bazę siunčiami papildomi ir nereikalingi užklausimai.

Kaip aptikti šiuos užklausimus naudojant „Query Monitor“?

  1. Įdiekite ir įjunkite nemokamą „Query Monitor“ įskiepį.
  2. Atlikite bet kokią paiešką savo svetainėje.
  3. Atidarykite „Query Monitor“ išskleidžiamąjį meniu viršutinėje juostoje ir pasirinkite „Duomenų bazės užklausos“ (Database Queries).
  4. Filtruokite pagal lėtas užklausas arba peržiūrėkite sąrašą, ieškodami užklausų, kurių vykdymas trunka ilgiausiai. Be to, užsirašykite ir pasikartojančias užklausas, o jei įmanoma, išspręskite ir šią problemą.
  5. Atkreipk dėmesį į lentelių išsamių duomenų stulpelį: ten pamatysi, kuris būtent įskiepis ar failas vykdo tą užklausą.

Mano atveju teko išspręsti keletą pasikartojančių užklausų, susijusių su „Name_Directory“ įskiepiu, ir keletą „GenerateBlocks“ blokų įrašo šablone, tačiau bet kuris įskiepis, individualus kodas ar pridėtas elementas gali generuoti užklausas, kurios sulėtina atsakymą.

Kaip išjungti šiuos „hooks“ naudojant kodą

Kai nustatysite funkcijos ar klasės, dėl kurios vyksta lėtas užklausos apdorojimas, pavadinimą, galite ją išjungti savo „functions.php“ faile arba naudodami kodų fragmentų įskiepį (pavyzdžiui, „Code Snippets“, „Code“ iš „Perfmatters“ ar panašų):

* Pastaba. 1-ajame bloke turite įtraukti savo „hooks“, kurie sukelia lėtas užklausas. Jei tai ne jūsų atvejis, galite ištrinti šią dalį ir į fragmentą įtraukti tik 2-ąjį bloką.

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

Pastaba: Norėdami atjungti „hook“ naudodami „remove_action“ arba „remove_filter“, turite naudoti tiksliai tokį patį įvykio pavadinimą („pre_get_posts“), tą pačią funkcijos pavadinimą, kurį nustatėte „Query Monitor“, ir tą patį prioritetą, su kuriuo jis buvo pridėtas (pagal numatytąjį nustatymą – 10).

2 žingsnis: Sukurti paieškos JSON failą ir jį nuolat atnaujinti

Norėdami pakeisti „MySQL“, turime sukurti statinį JSON formato failą, kuriame būtų pateikti pagrindiniai įrašų duomenys (ID, pavadinimas ir paprastas tekstas). Šis failas bus išsaugotas aplanke „wp-content/uploads/“ ir bus automatiškai atnaujinamas kiekvieną kartą, kai sukursite, redaguosite ar ištrinsite įrašą.

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

Pradinis sukūrimas: Pirmą kartą įdiegus šį kodą, galite laikinai paleisti funkciją „mi_sitio_reconstruir_indice_json_completo();“ arba tiesiog išsaugoti/atnaujinti bet kurį esamą įrašą, kad failas „search-es.json“ būtų sukurtas pirmą kartą. Tai padarę, apsilankykite kataloge „wp-content/uploads/“, kad įsitikintumėte, jog failas buvo sukurtas, ir sužinotumėte jo dydį.

Kad galėtum susidaryti įspūdį apie apytikslį šio „search-es.json“ failo dydį, galiu pasakyti, kad iš mano 6040 įrašų buvo sukurtas 3,41 Mb dydžio failas – tai gana priimtinas dydis, nes kol jis nepradės sverti daugiau nei 15 ar 20 MB (tai atitinka maždaug 30.000 arba 40 000 išsamių įrašų), nerimauti nereikia, nes statiniai JSON failai iš karto įkeliami tiesiai į serverio arba PHP (file_get_contents) talpyklą, o kelių megabaitų dydžio failas perskaitomas per mažiau nei tūkstantąją sekundės dalį; tačiau jei failas pernelyg išauga, kiekvieną kartą atliekant paiešką PHP sunaudos nereikalingą papildomą RAM atminties srautą, kad įkeltų milžinišką JSON failą; tokiu atveju vertėtų apsvarstyti galimybę indeksuoti tik pavadinimus ir ištraukas, o ne visą turinį, arba pereiti prie paieškos sprendimo, indeksuojamo specialioje duomenų bazėje.

3 žingsnis: Pagalbinė funkcija paieškos normalizavimui (be diakritinių ženklų)

Siekiant užtikrinti, kad paieškos duotų rezultatų nepriklausomai nuo to, kaip vartotojas rašo (su diakritiniais ženklais ar be jų, didžiosiomis ar mažosiomis raidėmis), apibrėžiama valymo funkcija:

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 žingsnis: Pagrindinio užklausos perėmimas naudojant „posts_pre_query“

Naudojant filtrą „posts_pre_query“, WordPress paieškos užklausa yra perimama dar prieš jai pasiekiant duomenų bazę. Perskaitomas JSON failas, surandami ID, atitinkantys ieškomus terminus, ir WordPress grąžinami tik tie įrašai, nurodant atitinkamą puslapių numeraciją.

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 žingsnis: Rezultatų patikrinimas

Atlikę ankstesnius veiksmus, atlikite paieškos bandymą ir atidarykite „Query Monitor“:

  1. Bendras BD laikas: Pastebėsi, kad dabarjis sutrumpėjo nuo kelių sekundžių iki vos kelių milisekundžių (paprastai nuo 0,03 s iki 0,2 s).
  2. SQL užklausos: Sąlyga „LIKE %terminas%“ bus visiškai pašalinta.
  3. RAM atmintis: Maksimalus atminties naudojimas išliks išskirtinai mažas (apie 30 MB).
  4. Rūšiavimas: Įrašai bus rodomi griežtai chronologine tvarka nuo naujausių iki seniausių, laikantis „WordPress“ numatytos puslapių numeracijos.

Esu daugiau nei patenkintas rezultatu. Ne tik atsikratiau problemos, kuri mane kamavo jau daugelį metų, bet ir dabar paieškos rezultatas yra tiesiog įspūdingas. Net ir vienoje iš sudėtingiausių paieškų, pavyzdžiui, šioje , kurioje užklausai„viñeta“ rasta 4887 rezultatų, paieškos sistema atsako labai greitai. Kalbant apie paieškas, kurių rezultatų skaičius mažesnis, galima sakyti, kad jie rodomi akimirksniu. Tai galite patikrinti paieškodami bet ko kito.

Kaip pagreitinti „WordPress“ paiešką naudojant statinį JSON indeksą 1
Susiję straipsniai

Parašykite komentarą

Ar norite ką nors pasakyti?

Este blog se aloja en LucusHost

LucusHost, el mejor hosting