Πώς να επιταχύνετε τη μηχανή αναζήτησης του WordPress χρησιμοποιώντας ένα στατικό ευρετήριο JSON

No comments

24.07.2026|

No comments

Tiempo de lectura Lectura: 5 min, 20 s
Número de palabras Palabras: 988
Número de visitas Visitas: 6
Icono de traducción

Το ότι η μηχανή αναζήτησης του WordPress είναι άχρηστη δεν αποτελεί μυστικό για κανέναν. Όσον αφορά την εμφάνιση, το πρόβλημα επιλύεται σχετικά εύκολα· ωστόσο, η λειτουργία της είναι μια άλλη ιστορία, καθώς δεν προσφέρει καμία επιλογή ρύθμισης, ενώ επιπλέον η απόδοσή της είναι αξιοθρήνητη.

Όταν ένας ιστότοπος στο WordPress συγκεντρώνει χιλιάδες αναρτήσεις, η ενσωματωμένη μηχανή αναζήτησης αρχίζει να παρουσιάζει προβλήματα. Επιβραδύνεται έως ότου καταστεί πρακτικά άχρηστη. Κατά την πραγματοποίηση μιας αναζήτησης, το WordPress εκτελεί ένα ερώτημα τύπου LIKE %λέξη% στη βάση δεδομένων, το οποίο σαρώνει όλο το περιεχόμενο του πίνακα wp_posts. Αυτό δημιουργεί ερωτήματα SQL που μπορεί να διαρκέσουν από μερικά δευτερόλεπτα έως μια αιωνιότητα και να προκαλέσουν αιχμές στη χρήση μνήμης που καταλήγουν να μπλοκάρουν τον διακομιστή.

Αυτό ακριβώς μου συνέβαινε ήδη. Με 6.040 δημοσιευμένες αναρτήσεις (μόνο στα ισπανικά), οι χρόνοι απόκρισης στις αναζητήσεις με πολλά αποτελέσματα ήταν πραγματικά απαράδεκτοι, με χρόνους που ξεπερνούσαν τα 10 δευτερόλεπτα ή και περισσότερο. Και έφτασε η μέρα να το λύσω μια για πάντα.

Αφού το σκέφτηκα πολύ, διαπίστωσα ότι υπήρχαν τουλάχιστον τρεις δυνατότητες για να το διορθώσω. Η πρώτη και πιο απλή ήταν να χρησιμοποιήσω κάποιο plugin επί πληρωμή, όπως το Relevanssi (η δωρεάν έκδοσή του δεν επαρκεί) ή μια εξωτερική υπηρεσία όπως το Algolia, κάτι που απέκλεισα εξ αρχής, καθώς αυτό που επιδιώκω είναι να επιταχύνω τις αναζητήσεις με ενσωματωμένο τρόπο.

Από τις δύο εναπομένουσες επιλογές, δοκίμασα να δημιουργήσω έναν δείκτη FULLTEXT στο MySQL χρησιμοποιώντας ερωτήματα MATCH() AGAINST() και απέτυχα παταγωδώς. Παρά την προσαρμογή των παραμέτρων του κινητήρα InnoDB και τη βελτιστοποίηση των ευρετηρίων, το MySQL συνέχιζε να κολλάει κατά την επεξεργασία χιλιάδων σύνθετων λέξεων-κλειδιών, με αποτέλεσμα απαράδεκτους χρόνους απόκρισης και υπερφόρτωση του διακομιστή. Τότε ήταν που απέκλεισα εντελώς την ιδέα να αφήσω το βαρύ φορτίο στη βάση δεδομένων και επέλεξα την τρίτη λύση: τη δημιουργία ενός στατικού ευρετηρίου σε JSON.

Σε αυτό το σεμινάριο θα προσπαθήσουμε να εξηγήσουμε πώς μπορεί να επιλυθεί αυτό το πρόβλημα, αντικαθιστώντας τις χρονοβόρες ερωτήσεις της MySQL με ένα στατικό αρχείο JSON που λειτουργεί ως εξαιρετικά γρήγορος δείκτης αναζήτησης.

Βήμα 1: Εντοπισμός και εξάλειψη βαριών ή περιττών ερωτημάτων

Πριν εφαρμόσετε τον δείκτη JSON, είναι απαραίτητο να «καθαρίσετε το πεδίο». Πολλά πρόσθετα (όπως κατάλογοι, εργαλεία διαμόρφωσης ή πρόσθετα δευτερεύουσας αναζήτησης) παρεμβαίνουν στη διαδικασία αναζήτησης του WordPress μέσω hooks (pre_get_posts, posts_where κ.λπ.), προκαλώντας επιπλέον και περιττές ερωτήσεις στη βάση δεδομένων.

Πώς μπορεί κανείς να εντοπίσει αυτές τις ερωτήσεις με το Query Monitor;

  1. Εγκαταστήστε και ενεργοποιήστε το δωρεάν πρόσθετο Query Monitor.
  2. Πραγματοποιήστε οποιαδήποτε αναζήτηση στον ιστότοπό σας.
  3. Άνοιξε το αναπτυσσόμενο μενού του Query Monitor στην επάνω γραμμή και επέλεξε «Ερωτήματα βάσης δεδομένων » (Database Queries).
  4. Φιλτράρετε τα αποτελέ σματα ανά «αργές ερωτήσεις» ή εξετάστε τη λίστα αναζητώντας τις ερωτήσεις που χρειάζονται περισσότερο χρόνο. Παρεμπιπτόντως, σημειώστε επίσης τις διπλές ερωτήσεις και, αν μπορείτε, επιλύστε και αυτό το πρόβλημα.
  5. Κοίτα τη στήλη με τις λεπτομέρειες στους πίνακες: εκεί θα δεις ακριβώς ποιο πρόσθετο ή ποιο αρχείο εκτελεί αυτή την ερώτηση.

Στην περίπτωσή μου, έπρεπε να επιλύσω μερικές διπλές ερωτήσεις του πρόσθετου Name_Directory, καθώς και ορισμένα μπλοκ του GenerateBlocks στο πρότυπο της ανάρτησης, αλλά οποιοδήποτε πρόσθετο, προσαρμοσμένος κώδικας ή πρόσθετο στοιχείο μπορεί να δημιουργεί ερωτήσεις που επιβραδύνουν την απόκριση.

Πώς να απενεργοποιήσετε αυτά τα hooks μέσω κώδικα

Μόλις εντοπίσεις το όνομα της συνάρτησης ή της κλάσης που προκαλεί την αργή ερώτηση, μπορείς να την απενεργοποιήσεις στο αρχείο functions.php ή μέσω ενός πρόσθετου για αποσπάσματα κώδικα (όπως το Code Snippets, το Code της Perfmatters ή κάτι παρόμοιο):

* Σημείωση. Στο μπλοκ 1 πρέπει να προσθέσεις τα hooks σου που προκαλούν αργές ερωτήσεις. Αν δεν ισχύει αυτό για σένα, μπορείς να διαγράψεις αυτό το τμήμα και να προσθέσεις μόνο το μπλοκ 2 στο snippet.

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

Σημείωση: Για να αποσυνδέσεις ένα hook με τις εντολές remove_action ή remove_filter, πρέπει να χρησιμοποιήσεις ακριβώς το ίδιο όνομα συμβάντος (pre_get_posts), το ίδιο όνομα της συνάρτησης που εντόπισες στο Query Monitor και την ίδια προτεραιότητα με την οποία προστέθηκε (εξ ορισμού 10).

Βήμα 2: Δημιουργία και ενημέρωση του αρχείου JSON αναζήτησης

Για να αντικαταστήσουμε το MySQL, πρέπει να δημιουργήσουμε ένα στατικό αρχείο σε μορφή JSON που θα περιέχει τα βασικά δεδομένα των αναρτήσεών μας (ID, τίτλος και απλό κείμενο). Αυτό το αρχείο θα αποθηκευτεί στον φάκελο wp-content/uploads/ και θα ενημερώνεται αυτόματα κάθε φορά που δημιουργείτε, επεξεργάζεστε ή διαγράφετε μια ανάρτηση.

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

Αρχική δημιουργία: Την πρώτη φορά που θα εγκαταστήσεις αυτό το snippet, μπορείς να εκτελέσεις προσωρινά τη συνάρτηση my_site_rebuild_full_json_index(); ή απλά να αποθηκεύσεις/ενημερώσεις οποιαδήποτε υπάρχουσα καταχώριση, ώστε να δημιουργηθεί για πρώτη φορά το αρχείο search-es.json. Μόλις το κάνετε αυτό, επισκεφθείτε τη διαδρομή wp-content/uploads/ για να βεβαιωθείτε ότι έχει δημιουργηθεί και να δείτε το μέγεθός του.

Για να πάρεις μια ιδέα για το κατά προσέγγιση μέγεθος αυτού του αρχείου search-es.json, για τις 6.040 εγγραφές μου δημιουργήθηκε ένα αρχείο 3,41 Mb, ένα μέγεθος αρκετά εύχρηστο, καθώς δεν χρειάζεται να ανησυχείς μέχρι να ξεπεράσει τα 15 ή 20 MB (το ισοδύναμο περίπου 30.000 ή 40.000 εκτενείς εγγραφές) δεν υπάρχει λόγος ανησυχίας, καθώς τα στατικά αρχεία JSON φορτώνονται άμεσα στην κρυφή μνήμη του διακομιστή ή του PHP (file_get_contents) με μία κίνηση, και ένα μέγεθος λίγων megas διαβάζεται σε λιγότερο από ένα χιλιοστό του δευτερολέπτου· ωστόσο, αν το αρχείο μεγαλώσει υπερβολικά, κάθε φορά που εκτελείται μια αναζήτηση, η PHP θα καταναλώνει μια περιττή επιπλέον αιχμή μνήμης RAM για να φορτώσει ένα τεράστιο JSON, οπότε θα ήταν σκόπιμο να εξεταστεί η δυνατότητα ευρετηρίασης μόνο των τίτλων και των αποσπασμάτων αντί για ολόκληρο το περιεχόμενο, ή η μετάβαση σε μια λύση αναζήτησης με ευρετηρίαση σε αποκλειστική βάση δεδομένων.

Βήμα 3: Βοηθητική συνάρτηση για την ομαλοποίηση των αναζητήσεων (χωρίς διακριτικά)

Για να διασφαλιστεί ότι οι αναζητήσεις θα επιστρέφουν αποτελέσματα ανεξάρτητα από τον τρόπο πληκτρολόγησης του χρήστη (με ή χωρίς διακριτικά, με κεφαλαία ή μικρά γράμματα), ορίζεται μια συνάρτηση καθαρισμού:

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: Παρεμπόδιση της κύριας ερώτησης με τη συνάρτηση posts_pre_query

Μέσω του φίλτρου posts_pre_query, η αναζήτηση του WordPress παρεμποδίζεται πριν φτάσει στη βάση δεδομένων. Διαβάζεται το JSON, εντοπίζονται τα ID που αντιστοιχούν στους όρους αναζήτησης και επιστρέφονται στο WordPress μόνο αυτές οι εγγραφές, καθορίζοντας την αντίστοιχη σελιδοποίηση.

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: Επαλήθευση αποτελεσμάτων

Αφού ολοκληρώσετε τα παραπάνω βήματα, πραγματοποιήστε μια δοκιμαστική αναζήτηση και ανοίξτε το Query Monitor:

  1. Συνολικός χρόνος BD: Θα διαπιστώσεις ότι τώρα μειώνεται από αρκετά δευτερόλεπτα σε μόλις μερικά χιλιοστά του δευτερολέπτου (συνήθως μεταξύ 0,03s και 0,2s).
  2. Ερωτήματα SQL: Η ρήτρα LIKE %όρος% θα έχει εξαφανιστεί εντελώς.
  3. Μνήμη RAM: Η μέγιστη χρήση μνήμης θα παραμείνει εξαιρετικά χαμηλή (περίπου 30 MB).
  4. Ταξινόμηση: Οι καταχωρήσεις θα εμφανίζονται σε αυστηρά φθίνουσα χρονολογική σειρά (από την πιο πρόσφατη προς την παλαιότερη), σύμφωνα με την εγγενή σελιδοποίηση του WordPress.

Είμαι περισσότερο από ικανοποιημένος με το αποτέλεσμα. Όχι μόνο έλυσα ένα πρόβλημα που με ταλαιπωρούσε εδώ και χρόνια, αλλά τώρα η απόκριση είναι σχεδόν θεαματική. Σε μία από τις πιο απαιτητικές αναζητήσεις, όπως αυτή με 4. 887 αποτελέσματα γιατη λέξη «viñeta», η μηχανή αναζήτησης ανταποκρίνεται πολύ γρήγορα. Για αναζητήσεις με λιγότερα αποτελέσματα, μπορεί να πει κανείς ότι εμφανίζονται αμέσως. Μπορείτε να το διαπιστώσετε αναζητώντας οτιδήποτε άλλο.

Πώς να επιταχύνετε τη μηχανή αναζήτησης του WordPress χρησιμοποιώντας ένα στατικό ευρετήριο JSON 1

Σχολιάστε

Έχεις τίποτα να πεις;

Este blog se aloja en LucusHost

LucusHost, el mejor hosting