So beschleunigen Sie die WordPress-Suchfunktion mithilfe eines statischen JSON-Indexes

Keine Kommentare

24.07.2026|

Keine Kommentare

Tiempo de lectura Lectura: 11 min, 14 s
Número de palabras Palabras: 2080
Número de visitas Visitas: 6
Icono de traducción

Dass die Suchfunktion von WordPress ein Reinfall ist, ist kein Geheimnis. Was das Design angeht, lässt sich das relativ leicht beheben, doch die Funktionsweise ist eine ganz andere Geschichte, da sie keinerlei Konfigurationsmöglichkeiten bietet – und obendrein ist die Leistung erbärmlich.

Wenn eine WordPress-Website Tausende von Beiträgen umfasst, versagt die integrierte Suchfunktion. Sie wird immer langsamer, bis sie praktisch unbrauchbar ist. Bei einer Suche führt WordPress eine LIKE %Wort%-Abfrage in der Datenbank durch, die den gesamten Inhalt der Tabelle wp_posts durchsucht. Dies erzeugt SQL-Abfragen, die von einigen Sekunden bis zu einer Ewigkeit dauern können und zu Speicherauslastungsspitzen führen, die letztendlich den Server zum Absturz bringen.

Genau das ist mir bereits passiert. Bei 6040 veröffentlichten Beiträgen (allein auf Spanisch) waren die Ladezeiten bei Suchanfragen mit vielen Ergebnissen die reinste Hölle – oft dauerte es mehr als 10 Sekunden oder sogar noch länger. Und schließlich war der Tag gekommen, an dem ich das Problem ein für alle Mal lösen musste.

Nachdem ich lange darüber nachgedacht hatte, stellte ich fest, dass es mindestens drei Möglichkeiten gab, das Problem zu beheben. Die erste und einfachste bestand darin, auf ein kostenpflichtiges Plugin wie Relevanssi (dessen kostenlose Version nicht ausreicht) oder einen externen Dienst wie Algolia zurückzugreifen, was ich jedoch von vornherein ausschloss, da ich die Suche auf native Weise beschleunigen möchte.

Von den beiden verbleibenden Optionen habe ich versucht, einen FULLTEXT-Index in MySQL mithilfe von MATCH() AGAINST()-Abfragen zu erstellen – und bin kläglich gescheitert. Trotz der Anpassung der InnoDB-Einstellungen und der Optimierung der Indizes kam MySQL bei der Verarbeitung tausender komplexer Schlüsselwörter immer wieder ins Stocken, lieferte inakzeptable Antwortzeiten und überlastete den Server. Da habe ich die Idee, die Datenbank mit dieser hohen Last zu belasten, komplett verworfen und mich für den dritten Weg entschieden: den Aufbau eines statischen Indexes in JSON.

In diesem Tutorial soll erläutert werden, wie man dieses Problem lösen kann, indem man ressourcenintensive MySQL-Abfragen durch eine statische JSON-Datei ersetzt, die als ultraschneller Suchindex fungiert.

Schritt 1: Schwerfällige oder redundante Abfragen erkennen und beseitigen

Bevor man den JSON-Index einsetzt, ist es unerlässlich, die Voraussetzungen zu schaffen. Viele Plugins (wie Verzeichnisse, Layout-Plugins oder Plugins für sekundäre Suchfunktionen) greifen über Hooks (pre_get_posts, posts_where usw.) in den Suchprozess von WordPress ein und lösen dadurch zusätzliche und unnötige Abfragen an die Datenbank aus.

Wie lassen sich diese Abfragen mit dem Query Monitor erkennen?

  1. Installiere und aktiviere das kostenlose Plugin „Query Monitor“.
  2. Führen Sie eine beliebige Suche auf Ihrer Website durch.
  3. Öffne das Dropdown-Menü des Query Monitors in der oberen Leiste und wähle „Datenbankabfragen“ (Database Queries) aus.
  4. Filtere nach langsamen Abfragen oder durchsuche die Liste nach den Abfragen, die am längsten dauern. Notiere dir dabei auch die doppelten Abfragen und behebe diese, wenn möglich, ebenfalls.
  5. Schau dir die Detailspalte der Tabellen an: Dort siehst du genau, welches Plugin oder welche Datei diese Abfrage ausführt.

In meinem Fall musste ich eine Handvoll doppelter Abfragen des Plugins „Name_Directory“ sowie einige GenerateBlocks-Blöcke in der Beitragsvorlage beheben, aber jedes Plugin, jeder benutzerdefinierte Code oder jedes hinzugefügte Element kann Abfragen erzeugen, die die Antwort verlangsamen.

So deaktiviert man diese Hooks per Code

Sobald du den Namen der Funktion oder Klasse gefunden hast, die die langsame Abfrage auslöst, kannst du sie in deiner `functions.php` -Datei oder mithilfe eines Plugins für Code-Schnipsel (wie „Code Snippets“, „Code“ von Perfmatters oder ähnliches) deaktivieren:

* Hinweis: In Block 1 musst du deine Hooks einfügen, die langsame Abfragen verursachen. Wenn dies bei dir nicht der Fall ist, kannst du diesen Teil weglassen und nur Block 2 in das Snippet einfügen.

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

Hinweis: Um einen Hook mit `remove_action` oder `remove_filter` zu entfernen, musst du genau denselben Ereignisnamen (pre_get_posts), denselben Funktionsnamen, den du im Query Monitor identifiziert hast, und dieselbe Priorität verwenden, mit der er hinzugefügt wurde (standardmäßig 10).

Schritt 2: Die JSON-Datei für die Suche erstellen und auf dem neuesten Stand halten

Um MySQL zu ersetzen, müssen wir eine statische Datei im JSON-Format erstellen, die die wichtigsten Daten unserer Beiträge (ID, Titel und Klartext) enthält. Diese Datei wird im Ordner „wp-content/uploads/“ gespeichert und jedes Mal automatisch aktualisiert, wenn du einen Beitrag erstellst, bearbeitest oder löschst.

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

Erstinstallation: Wenn du dieses Snippet zum ersten Mal installierst, kannst du vorübergehend die Funktion „meine_seite_vollständigen_json-Index_neu_erstellen(); “ ausführen oder einfach einen beliebigen bestehenden Eintrag speichern bzw. aktualisieren, damit die Datei „search-es.json“ zum ersten Mal erstellt wird. Besuchen Sie anschließend den Pfad wp-content/uploads/, um sicherzustellen, dass die Datei erstellt wurde, und um ihre Größe zu überprüfen.

Um dir eine Vorstellung vom ungefähren Umfang dieser search-es.json-Datei zu geben: Für meine 6.040 Einträge wurde eine Datei mit einer Größe von 3,41 MB generiert – ein durchaus überschaubarer Umfang, da man sich erst dann Sorgen machen muss, wenn die Datei mehr als 15 oder 20 MB (das entspricht etwa 30.000 oder 40.000 umfangreiche Einträge) jedoch nicht über 15 oder 20 MB hinausgeht, besteht kein Grund zur Sorge, da statische JSON-Dateien auf einen Schlag direkt in den Cache des Servers oder von PHP (file_get_contents) geladen werden und eine Größe von wenigen Megabyte in weniger als einer Millisekunde gelesen wird; sollte die Datei jedoch übermäßig anwachsen, verbraucht PHP bei jeder Suchanfrage einen unnötigen zusätzlichen RAM-Spitzenbedarf, um ein riesiges JSON-Datenobjekt auszulesen. In diesem Fall sollte man in Erwägung ziehen, nur die Titel und Auszüge statt des gesamten Inhalts zu indizieren oder auf eine dedizierte, datenbankbasierte Suchlösung umzusteigen.

Schritt 3: Hilfsfunktion zur Normalisierung von Suchanfragen (ohne Akzente)

Um sicherzustellen, dass die Suche unabhängig davon, wie der Benutzer den Suchbegriff eingibt (mit oder ohne Akzente, in Groß- oder Kleinbuchstaben), Ergebnisse liefert, wird eine Bereinigungsfunktion definiert:

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

Schritt 4: Die Hauptabfrage mit ` posts_pre_query` abfangen

Mithilfe des Filters „posts_pre_query“ wird die Suchabfrage von WordPress abgefangen, bevor sie die Datenbank erreicht. Das JSON wird gelesen, die IDs, die mit den Suchbegriffen übereinstimmen, werden ermittelt, und nur diese Einträge werden unter Angabe der entsprechenden Paginierung an WordPress zurückgegeben.

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

Schritt 5: Überprüfung der Ergebnisse

Nachdem Sie die oben genannten Schritte ausgeführt haben, führen Sie einen Suchtest durch und öffnen Sie den Query Monitor:

  1. Gesamtdauer der Datenbankabfrage: Du wirst feststellen, dass sie sich nun von mehreren Sekunden auf nur noch wenige Millisekunden verkürzt hat (normalerweise zwischen 0,03 s und 0,2 s).
  2. SQL-Abfragen: Die Klausel „LIKE %Begriff%“ wird vollständig verschwunden sein.
  3. RAM: Die maximale Speicherauslastung bleibt außergewöhnlich gering (etwa 30 MB).
  4. Reihenfolge: Die Einträge werden in streng absteigender chronologischer Reihenfolge (vom neuesten zum ältesten) angezeigt, wobei die native Paginierung von WordPress beibehalten wird.

Ich bin mehr als zufrieden mit dem Ergebnis. Ich habe nicht nur ein Problem beseitigt, das mich schon seit Jahren begleitet hat, sondern die Antwort ist nun auch geradezu spektakulär. Selbst bei einer der anspruchsvollsten Suchanfragen, wie dieser mit 4. 887 Ergebnissen für „Vignette“, liefert die Suchmaschine sehr schnelle Ergebnisse. Bei Suchanfragen mit weniger Ergebnissen werden diese praktisch sofort angezeigt. Du kannst das selbst überprüfen, indem du nach etwas anderem suchst.

So beschleunigen Sie die WordPress-Suchfunktion mithilfe eines statischen JSON-Indexes 1

Schreibe einen Kommentar

Haben Sie etwas zu sagen?

Este blog se aloja en LucusHost

LucusHost, el mejor hosting