WordOps 3.22 : j’ai testé le gestionnaire LEMP WordPress automatisé
J'avais toujours repoussé WordOps. Quand on gère déjà nginx à la main, l'idée d'un outil…
Les performances d’un site WordPress dépendent majoritairement de deux facteurs critiques : l’optimisation de la base de données et l’implémentation d’un système de cache efficace. En 2025, avec l’augmentation des exigences en matière de Core Web Vitals et l’importance croissante du temps de chargement pour le SEO, l’optimisation des performances n’est plus optionnelle. Ce guide complet vous accompagnera dans l’optimisation avancée de votre base de données et la mise en place de stratégies de caching performantes.
WordPress utilise plusieurs tables principales :
wpposts : Articles, pages, CPT, révisionswppostmeta : Métadonnées des postswpusers : Utilisateurswpusermeta : Métadonnées utilisateurswpoptions : Configuration et options (autoload)wpterms, wptermtaxonomy, wptermrelationships : Taxonomieswpcomments, wpcommentmeta : CommentairesLe problème d’autoload est l’un des plus critiques pour les performances. WordPress charge automatiquement toutes les options avec autoload = 'yes' à chaque requête.
Analyser et optimiser les options autoload
/
class AutoloadOptimizer {
/
Analyser les options autoload
/
public static function analyzeautoload() {
global $wpdb;
$autoloaddata = $wpdb->getresults(
"SELECT optionname, LENGTH(optionvalue) as optionsize
FROM $wpdb->options
WHERE autoload = 'yes'
ORDER BY optionsize DESC
LIMIT 50"
);
$totalsize = 0;
$report = array();
foreach ( $autoloaddata as $option ) {
$totalsize += $option->optionsize;
$report[] = array(
'optionname' => $option->optionname,
'size' => self::formatbytes( $option->optionsize ),
'sizebytes' => $option->optionsize,
);
}
errorlog( sprintf(
'Total autoload size: %s (%d bytes)',
self::formatbytes( $totalsize ),
$totalsize
) );
return array(
'totalsize' => $totalsize,
'totalhuman' => self::formatbytes( $totalsize ),
'options' => $report,
);
}
/
Désactiver l'autoload pour des options spécifiques
/
public static function disableautoload( $optionnames ) {
global $wpdb;
if ( ! isarray( $optionnames ) ) {
$optionnames = array( $optionnames );
}
foreach ( $optionnames as $optionname ) {
$wpdb->update(
$wpdb->options,
array( 'autoload' => 'no' ),
array( 'optionname' => $optionname ),
array( '%s' ),
array( '%s' )
);
errorlog( "Disabled autoload for: $optionname" );
}
// Nettoyer le cache
wpcacheflush();
}
/
Nettoyer les options orphelines
/
public static function cleanorphanedoptions() {
global $wpdb;
// Supprimer les transients expirés
$deleted = $wpdb->query(
"DELETE FROM $wpdb->options
WHERE optionname LIKE 'transienttimeout%'
AND optionvalue < UNIXTIMESTAMP()"
);
errorlog( "Deleted $deleted expired transient timeouts" );
// Supprimer les transients correspondants
$wpdb->query(
"DELETE FROM $wpdb->options
WHERE optionname LIKE 'transient%'
AND optionname NOT LIKE 'transienttimeout%'
AND optionname NOT IN (
SELECT REPLACE(optionname, 'timeout', '')
FROM $wpdb->options
WHERE optionname LIKE 'transienttimeout%'
)"
);
// Supprimer les options de plugins désactivés
$deletedpluginoptions = $wpdb->query(
"DELETE FROM $wpdb->options
WHERE optionname LIKE '%plugin%'
AND autoload = 'yes'
AND optionname NOT IN (
SELECT DISTINCT metakey
FROM $wpdb->postmeta
)"
);
errorlog( "Deleted $deletedpluginoptions orphaned plugin options" );
return $deleted + $deletedpluginoptions;
}
/
Formater les octets en format lisible
/
private static function formatbytes( $bytes ) {
$units = array( 'B', 'KB', 'MB', 'GB' );
for ( $i = 0; $bytes > 1024 && $i < count( $units ) - 1; $i++ ) {
$bytes /= 1024;
}
return round( $bytes, 2 ) . ' ' . $units[ $i ];
}
}
// Commande WP-CLI pour analyser l'autoload
if ( defined( 'WPCLI' ) && WPCLI ) {
WPCLI::addcommand( 'autoload analyze', array( 'AutoloadOptimizer', 'analyzeautoload' ) );
WPCLI::addcommand( 'autoload clean', array( 'AutoloadOptimizer', 'cleanorphanedoptions' ) );
}
Les index de base de données accélèrent considérablement les requêtes :
Gestionnaire d'index de base de données
/
class DatabaseIndexManager {
/
Ajouter des index pour optimiser les requêtes courantes
/
public static function addperformanceindexes() {
global $wpdb;
$indexes = array();
// Index sur wppostmeta pour les requêtes méta fréquentes
$indexes[] = array(
'table' => $wpdb->postmeta,
'name' => 'metakeyvalue',
'sql' => "CREATE INDEX metakeyvalue ON {$wpdb->postmeta} (metakey(191), metavalue(100))",
);
// Index composite pour posttype et poststatus
$indexes[] = array(
'table' => $wpdb->posts,
'name' => 'typestatusdate',
'sql' => "CREATE INDEX typestatusdate ON {$wpdb->posts} (posttype, poststatus, postdate)",
);
// Index pour les recherches de commentaires
$indexes[] = array(
'table' => $wpdb->comments,
'name' => 'commentapproveddate',
'sql' => "CREATE INDEX commentapproveddate ON {$wpdb->comments} (commentapproved, commentdategmt)",
);
// Index pour les relations de termes
$indexes[] = array(
'table' => $wpdb->termrelationships,
'name' => 'objectidtermid',
'sql' => "CREATE INDEX objectidtermid ON {$wpdb->termrelationships} (objectid, termtaxonomyid)",
);
foreach ( $indexes as $index ) {
if ( ! self::indexexists( $index['table'], $index['name'] ) ) {
$result = $wpdb->query( $index['sql'] );
if ( $result !== false ) {
errorlog( "Successfully created index: {$index['name']} on {$index['table']}" );
} else {
errorlog( "Failed to create index: {$index['name']} on {$index['table']}" );
}
} else {
errorlog( "Index already exists: {$index['name']} on {$index['table']}" );
}
}
}
/
Vérifier si un index existe
/
private static function indexexists( $table, $indexname ) {
global $wpdb;
$result = $wpdb->getvar(
$wpdb->prepare(
"SHOW INDEX FROM $table WHERE Keyname = %s",
$indexname
)
);
return ! isnull( $result );
}
/
Analyser et recommander des index
/
public static function analyzeslowqueries() {
global $wpdb;
// Activer le slow query log (nécessite des privilèges MySQL)
$wpdb->query( "SET GLOBAL slowquerylog = 'ON'" );
$wpdb->query( "SET GLOBAL longquerytime = 1" );
// Analyser les requêtes lentes
$slowqueries = $wpdb->getresults( "SHOW PROCESSLIST" );
$recommendations = array();
foreach ( $slowqueries as $query ) {
if ( $query->Time > 1 ) {
$recommendations[] = array(
'query' => $query->Info,
'duration' => $query->Time,
'state' => $query->State,
);
}
}
return $recommendations;
}
/
Optimiser toutes les tables
/
public static function optimizetables() {
global $wpdb;
$tables = $wpdb->getcol( "SHOW TABLES" );
$results = array();
foreach ( $tables as $table ) {
$result = $wpdb->query( "OPTIMIZE TABLE $table" );
$results[ $table ] = $result !== false;
errorlog( "Optimized table: $table - " . ( $results[ $table ] ? 'Success' : 'Failed' ) );
}
return $results;
}
/
Nettoyer les révisions de posts
/
public static function cleanpostrevisions( $keep = 5 ) {
global $wpdb;
// Trouver et supprimer les vieilles révisions
$deleted = $wpdb->query(
$wpdb->prepare(
"DELETE FROM $wpdb->posts
WHERE posttype = 'revision'
AND postparent IN (
SELECT p.ID FROM (
SELECT ID FROM $wpdb->posts
WHERE posttype NOT IN ('revision', 'auto-draft')
) p
)
AND ID NOT IN (
SELECT ID FROM (
SELECT r.ID
FROM $wpdb->posts r
WHERE r.posttype = 'revision'
ORDER BY r.postmodified DESC
LIMIT %d
) keeprevisions
)",
$keep
)
);
errorlog( "Deleted $deleted old revisions (keeping $keep per post)" );
return $deleted;
}
/
Nettoyer les auto-drafts
/
public static function cleanautodrafts() {
global $wpdb;
$deleted = $wpdb->query(
"DELETE FROM $wpdb->posts
WHERE poststatus = 'auto-draft'
AND postmodified < DATESUB(NOW(), INTERVAL 7 DAY)"
);
errorlog( "Deleted $deleted auto-drafts older than 7 days" );
return $deleted;
}
}
// Exécuter au switch de thème ou activation de plugin
addaction( 'afterswitchtheme', array( 'DatabaseIndexManager', 'addperformanceindexes' ) );
// Tâche cron pour maintenance
addaction( 'monthemeweeklymaintenance', function() {
DatabaseIndexManager::cleanpostrevisions( 5 );
DatabaseIndexManager::cleanautodrafts();
AutoloadOptimizer::cleanorphanedoptions();
DatabaseIndexManager::optimizetables();
} );
// Enregistrer la tâche cron
if ( ! wpnextscheduled( 'monthemeweeklymaintenance' ) ) {
wpscheduleevent( time(), 'weekly', 'monthemeweeklymaintenance' );
}
Optimisations de WPQuery
/
class QueryOptimizer {
/
Exemple de requête optimisée pour l'affichage
/
public static function getoptimizedposts( $args = array() ) {
$defaults = array(
'posttype' => 'post',
'postsperpage' => 10,
'nofoundrows' => true, // Désactiver le comptage total
'updatepostmetacache' => false, // Désactiver si pas de meta
'updateposttermcache' => false, // Désactiver si pas de termes
'fields' => 'ids', // Récupérer seulement les IDs
);
$args = wpparseargs( $args, $defaults );
return new WPQuery( $args );
}
/
Requête avec cache personnalisé
/
public static function getcachedposts( $args = array(), $cacheduration = 3600 ) {
$cachekey = 'query' . md5( serialize( $args ) );
$cached = wpcacheget( $cachekey, 'monthemequeries' );
if ( false !== $cached ) {
return $cached;
}
$query = new WPQuery( $args );
$posts = $query->posts;
wpcacheset( $cachekey, $posts, 'monthemequeries', $cacheduration );
return $posts;
}
/
Pré-charger les métadonnées pour éviter les requêtes multiples
/
public static function primepostcaches( $postids ) {
if ( empty( $postids ) ) {
return;
}
// Pré-charger les posts
primepostcaches( $postids, true, true );
// Pré-charger les termes
updateobjecttermcache( $postids, 'post' );
// Pré-charger les métadonnées
updatemetacache( 'post', $postids );
}
/
Requête SQL directe pour de meilleures performances
/
public static function getpostsraw( $posttype = 'post', $limit = 10 ) {
global $wpdb;
$cachekey = "rawposts{$posttype}{$limit}";
$posts = wpcacheget( $cachekey, 'monthemequeries' );
if ( false === $posts ) {
$posts = $wpdb->getresults(
$wpdb->prepare(
"SELECT ID, posttitle, postname, postdate
FROM $wpdb->posts
WHERE posttype = %s
AND poststatus = 'publish'
ORDER BY postdate DESC
LIMIT %d",
$posttype,
$limit
)
);
wpcacheset( $cachekey, $posts, 'monthemequeries', 3600 );
}
return $posts;
}
/
Désactiver les requêtes inutiles sur certaines pages
/
public static function disableunnecessaryqueries() {
// Désactiver les emojis (économise 2 requêtes)
removeaction( 'wphead', 'printemojidetectionscript', 7 );
removeaction( 'adminprintscripts', 'printemojidetectionscript' );
removeaction( 'wpprintstyles', 'printemojistyles' );
removeaction( 'adminprintstyles', 'printemojistyles' );
// Désactiver les embeds (économise plusieurs requêtes)
removeaction( 'wphead', 'wpoembedadddiscoverylinks' );
removeaction( 'wphead', 'wpoembedaddhostjs' );
// Désactiver XML-RPC si non utilisé
addfilter( 'xmlrpcenabled', 'returnfalse' );
// Limiter les révisions
if ( ! defined( 'WPPOSTREVISIONS' ) ) {
define( 'WPPOSTREVISIONS', 3 );
}
// Augmenter l'intervalle d'autosave
if ( ! defined( 'AUTOSAVEINTERVAL' ) ) {
define( 'AUTOSAVEINTERVAL', 300 ); // 5 minutes
}
}
}
addaction( 'init', array( 'QueryOptimizer', 'disableunnecessaryqueries' ) );
Implémentation du cache objet avec Redis
/
class RedisObjectCache {
private $redis;
private $prefix;
private $defaultexpiration = 3600;
/
Constructeur
/
public function construct() {
$this->prefix = 'wp' . DBNAME . ':';
if ( classexists( 'Redis' ) ) {
$this->redis = new Redis();
try {
$this->redis->connect(
defined( 'WPREDISHOST' ) ? WPREDISHOST : '127.0.0.1',
defined( 'WPREDISPORT' ) ? WPREDISPORT : 6379
);
if ( defined( 'WPREDISPASSWORD' ) && WPREDISPASSWORD ) {
$this->redis->auth( WPREDISPASSWORD );
}
$this->redis->setOption( Redis::OPTSERIALIZER, Redis::SERIALIZERPHP );
$this->redis->setOption( Redis::OPTPREFIX, $this->prefix );
} catch ( Exception $e ) {
errorlog( 'Redis connection failed: ' . $e->getMessage() );
$this->redis = null;
}
}
}
/
Récupérer une valeur du cache
/
public function get( $key, $group = 'default' ) {
if ( ! $this->redis ) {
return wpcacheget( $key, $group );
}
$cachekey = $this->buildkey( $key, $group );
try {
$value = $this->redis->get( $cachekey );
return $value !== false ? $value : false;
} catch ( Exception $e ) {
errorlog( 'Redis get failed: ' . $e->getMessage() );
return false;
}
}
/
Définir une valeur dans le cache
/
public function set( $key, $value, $group = 'default', $expiration = 0 ) {
if ( ! $this->redis ) {
return wpcacheset( $key, $value, $group, $expiration );
}
$cachekey = $this->buildkey( $key, $group );
$expiration = $expiration ?: $this->defaultexpiration;
try {
return $this->redis->setex( $cachekey, $expiration, $value );
} catch ( Exception $e ) {
errorlog( 'Redis set failed: ' . $e->getMessage() );
return false;
}
}
/
Supprimer une valeur du cache
/
public function delete( $key, $group = 'default' ) {
if ( ! $this->redis ) {
return wpcachedelete( $key, $group );
}
$cachekey = $this->buildkey( $key, $group );
try {
return $this->redis->del( $cachekey ) > 0;
} catch ( Exception $e ) {
errorlog( 'Redis delete failed: ' . $e->getMessage() );
return false;
}
}
/
Vider un groupe de cache
/
public function flushgroup( $group ) {
if ( ! $this->redis ) {
return wpcacheflushgroup( $group );
}
try {
$pattern = $this->prefix . $group . ':';
$keys = $this->redis->keys( $pattern );
if ( ! empty( $keys ) ) {
return $this->redis->del( $keys ) > 0;
}
return true;
} catch ( Exception $e ) {
errorlog( 'Redis flush group failed: ' . $e->getMessage() );
return false;
}
}
/
Récupérer ou générer avec callback
/
public function remember( $key, $group, $callback, $expiration = 0 ) {
$value = $this->get( $key, $group );
if ( false === $value ) {
$value = $callback();
$this->set( $key, $value, $group, $expiration );
}
return $value;
}
/
Construire la clé de cache
/
private function buildkey( $key, $group ) {
return $group . ':' . $key;
}
/
Obtenir les statistiques Redis
/
public function getstats() {
if ( ! $this->redis ) {
return array();
}
try {
$info = $this->redis->info();
return array(
'connected' => true,
'memoryused' => $info['usedmemoryhuman'] ?? 'N/A',
'totalkeys' => $this->redis->dbSize(),
'hits' => $info['keyspacehits'] ?? 0,
'misses' => $info['keyspacemisses'] ?? 0,
'hitrate' => $this->calculatehitrate( $info ),
'evictedkeys' => $info['evictedkeys'] ?? 0,
'connectedclients' => $info['connectedclients'] ?? 0,
);
} catch ( Exception $e ) {
errorlog( 'Redis stats failed: ' . $e->getMessage() );
return array( 'connected' => false );
}
}
/
Calculer le taux de hits
/
private function calculatehitrate( $info ) {
$hits = $info['keyspacehits'] ?? 0;
$misses = $info['keyspacemisses'] ?? 0;
$total = $hits + $misses;
if ( $total === 0 ) {
return 0;
}
return round( ( $hits / $total ) 100, 2 );
}
}
// Initialiser le cache Redis
global $monthemerediscache;
$monthemerediscache = new RedisObjectCache();
Système de page cache avancé
/
class AdvancedPageCache {
private $cachedir;
private $cacheduration = 3600;
private $mobilecache = true;
private $usercache = false;
/
Constructeur
/
public function construct() {
$this->cachedir = WPCONTENTDIR . '/cache/montheme-page-cache/';
addaction( 'init', array( $this, 'servecachedpage' ), 1 );
addaction( 'shutdown', array( $this, 'cachepage' ) );
addaction( 'savepost', array( $this, 'clearcacheforpost' ) );
addaction( 'commentpost', array( $this, 'clearcacheforpost' ) );
addaction( 'switchtheme', array( $this, 'clearallcache' ) );
}
/
Servir une page en cache
/
public function servecachedpage() {
// Ne pas mettre en cache si :
if ( isadmin() ||
isuserloggedin() ||
( defined( 'DOINGAJAX' ) && DOINGAJAX ) ||
( defined( 'DOINGCRON' ) && DOINGCRON ) ||
( defined( 'WPCLI' ) && WPCLI ) ||
'GET' !== $SERVER['REQUESTMETHOD'] ) {
return;
}
$cachefile = $this->getcachefilepath();
if ( ! $cachefile || ! fileexists( $cachefile ) ) {
return;
}
// Vérifier si le cache est expiré
if ( time() - filemtime( $cachefile ) > $this->cacheduration ) {
@unlink( $cachefile );
return;
}
// Servir le cache
$cachedcontent = filegetcontents( $cachefile );
if ( $cachedcontent ) {
// Ajouter des en-têtes
header( 'X-Cache-Status: HIT' );
header( 'X-Cache-Time: ' . date( 'Y-m-d H:i:s', filemtime( $cachefile ) ) );
echo $cachedcontent;
exit;
}
}
/
Mettre une page en cache
/
public function cachepage() {
// Conditions pour ne pas mettre en cache
if ( isadmin() ||
isuserloggedin() ||
is404() ||
issearch() ||
isfeed() ||
( defined( 'DOINGAJAX' ) && DOINGAJAX ) ||
'GET' !== $SERVER['REQUESTMETHOD'] ) {
return;
}
// Récupérer le contenu de la page
$content = obgetcontents();
if ( empty( $content ) || strlen( $content ) < 500 ) {
return;
}
$cachefile = $this->getcachefilepath();
if ( ! $cachefile ) {
return;
}
// Créer le répertoire si nécessaire
$cachedir = dirname( $cachefile );
if ( ! fileexists( $cachedir ) ) {
wpmkdirp( $cachedir );
}
// Ajouter un commentaire avec la date de mise en cache
$content .= "n";
// Écrire le fichier de cache
fileputcontents( $cachefile, $content, LOCKEX );
// Ajouter un en-tête indiquant que c'est un MISS
header( 'X-Cache-Status: MISS' );
}
/
Obtenir le chemin du fichier de cache
/
private function getcachefilepath() {
$requesturi = $SERVER['REQUESTURI'] ?? '/';
// Nettoyer l'URI
$requesturi = trim( $requesturi, '/' );
$requesturi = strreplace( array( '../', './', '//' ), '', $requesturi );
if ( empty( $requesturi ) ) {
$requesturi = 'index';
}
// Sous-répertoire pour mobile si activé
$subdir = '';
if ( $this->mobilecache && wpismobile() ) {
$subdir = 'mobile/';
}
// Hash pour la sécurité
$hash = md5( $requesturi );
return $this->cachedir . $subdir . $hash . '.html';
}
/
Nettoyer le cache pour un post spécifique
/
public function clearcacheforpost( $postid ) {
$post = getpost( $postid );
if ( ! $post ) {
return;
}
// Nettoyer la page du post
$posturl = getpermalink( $postid );
$this->clearurlcache( $posturl );
// Nettoyer la page d'accueil
$this->clearurlcache( homeurl( '/' ) );
// Nettoyer les archives liées
if ( $post->posttype === 'post' ) {
$this->clearurlcache( getposttypearchivelink( 'post' ) );
// Nettoyer les archives de catégories
$categories = getthecategory( $postid );
foreach ( $categories as $category ) {
$this->clearurlcache( getcategorylink( $category->termid ) );
}
}
}
/
Nettoyer le cache d'une URL
/
private function clearurlcache( $url ) {
$requesturi = strreplace( homeurl(), '', $url );
$requesturi = trim( $requesturi, '/' );
if ( empty( $requesturi ) ) {
$requesturi = 'index';
}
$hash = md5( $requesturi );
// Desktop
$cachefile = $this->cachedir . $hash . '.html';
if ( fileexists( $cachefile ) ) {
@unlink( $cachefile );
}
// Mobile
if ( $this->mobilecache ) {
$mobilecachefile = $this->cachedir . 'mobile/' . $hash . '.html';
if ( fileexists( $mobilecachefile ) ) {
@unlink( $mobilecachefile );
}
}
}
/
Vider tout le cache
/
public function clearallcache() {
$this->deletedirectorycontents( $this->cachedir );
errorlog( 'Page cache cleared' );
}
/
Supprimer le contenu d'un répertoire
/
private function deletedirectorycontents( $dir ) {
if ( ! isdir( $dir ) ) {
return;
}
$files = glob( $dir . '', GLOBMARK );
foreach ( $files as $file ) {
if ( isdir( $file ) ) {
$this->deletedirectorycontents( $file );
@rmdir( $file );
} else {
@unlink( $file );
}
}
}
/
Obtenir les statistiques de cache
/
public function getstats() {
if ( ! isdir( $this->cachedir ) ) {
return array(
'totalfiles' => 0,
'totalsize' => 0,
'oldest' => null,
'newest' => null,
);
}
$files = glob( $this->cachedir . '.html' );
$totalsize = 0;
$oldest = PHPINTMAX;
$newest = 0;
foreach ( $files as $file ) {
$totalsize += filesize( $file );
$mtime = filemtime( $file );
if ( $mtime < $oldest ) {
$oldest = $mtime;
}
if ( $mtime > $newest ) {
$newest = $mtime;
}
}
return array(
'totalfiles' => count( $files ),
'totalsize' => $this->formatbytes( $totalsize ),
'oldest' => $oldest !== PHPINTMAX ? date( 'Y-m-d H:i:s', $oldest ) : null,
'newest' => $newest !== 0 ? date( 'Y-m-d H:i:s', $newest ) : null,
);
}
/
Formater les octets
/
private function formatbytes( $bytes ) {
$units = array( 'B', 'KB', 'MB', 'GB' );
for ( $i = 0; $bytes > 1024 && $i < count( $units ) - 1; $i++ ) {
$bytes /= 1024;
}
return round( $bytes, 2 ) . ' ' . $units[ $i ];
}
}
// Initialiser le page cache
new AdvancedPageCache();
Helper pour le fragment caching
/
class FragmentCache {
/
Récupérer ou générer un fragment
/
public static function get( $key, $callback, $expiration = 3600 ) {
$cachekey = 'fragment' . $key;
$output = gettransient( $cachekey );
if ( false === $output ) {
obstart();
$callback();
$output = obgetclean();
settransient( $cachekey, $output, $expiration );
}
return $output;
}
/
Invalider un fragment
/
public static function delete( $key ) {
$cachekey = 'fragment' . $key;
deletetransient( $cachekey );
}
/
Invalider tous les fragments
/
public static function flush() {
global $wpdb;
$wpdb->query(
"DELETE FROM $wpdb->options
WHERE optionname LIKE 'transientfragment%'
OR optionname LIKE 'transienttimeoutfragment%'"
);
}
}
// Exemple d'utilisation dans un template
function monthemedisplaypopularposts() {
echo FragmentCache::get( 'popularposts', function() {
$popular = new WPQuery( array(
'posttype' => 'post',
'postsperpage' => 5,
'metakey' => 'postviewscount',
'orderby' => 'metavaluenum',
'order' => 'DESC',
) );
if ( $popular->haveposts() ) {
echo '';
while ( $popular->haveposts() ) {
$popular->thepost();
?>
### title(); ?>
postmeta( gettheID(), 'postviewscount', true ); ?> vues
';
}
wpresetpostdata();
}, 1800 ); // Cache pour 30 minutes
}
Configuration MySQL optimale
Fichier my.cnf recommandé
[mysqld]
# Configuration optimisée pour WordPress
# InnoDB Buffer Pool (70-80% de la RAM disponible)
innodb
bufferpoolsize = 2G
innodbbufferpoolinstances = 2
# Logs
innodblogfilesize = 256M
innodblogbuffersize = 16M
innodbflushlogattrxcommit = 2
innodbflushmethod = ODIRECT
# Connexions
maxconnections = 200
maxallowedpacket = 64M
waittimeout = 300
interactivetimeout = 300
# Cache de requêtes (désactivé pour MySQL 8.0+)
querycachetype = 0
querycachesize = 0
# Tables temporaires
tmptablesize = 64M
maxheaptablesize = 64M
# Performances
innodbfilepertable = 1
innodbstatsonmetadata = 0
# Monitoring
slowquerylog = 1
slowquerylogfile = /var/log/mysql/mysql-slow.log
longquerytime = 2
# Character set
character-set-server = utf8mb4
collation-server = utf8mb4unicodeci
Monitoring et debugging
Dashboard de performances
Dashboard de monitoring des performances
/
class PerformanceDashboard {
/
Ajouter le menu admin
/
public function construct() {
addaction( 'adminmenu', array( $this, 'addmenu' ) );
}
/
Ajouter le menu
/
public function addmenu() {
addmanagementpage(
( 'Performances', 'mon-theme' ),
( 'Performances', 'mon-theme' ),
'manageoptions',
'montheme-performance',
array( $this, 'renderdashboard' )
);
}
/
Afficher le dashboard
/
public function renderdashboard() {
global $monthemerediscache;
?>
# htmle( 'Dashboard de Performances', 'mon-theme' ); ?>
## htmle( 'Autoload', 'mon-theme' ); ?>
data = AutoloadOptimizer::analyzeautoload();
?>
htmle( 'Taille totale:', 'mon-theme' ); ?> html( $autoloaddata['totalhuman'] ); ?>
data['totalsize'] > 800000 ) : ?>
html
e( '⚠️ Autoload trop élevé (recommandé < 800KB)', 'mon-theme' ); ?>
htmle( '✓ Autoload dans les normes', 'mon-theme' ); ?>
## htmle( 'Base de données', 'mon-theme' ); ?>
status = $wpdb->getresults( "SHOW TABLE STATUS" );
$totalsize = 0;
foreach ( $tablestatus as $table ) {
$totalsize += $table->Datalength + $table->Indexlength;
}
?>
htmle( 'Taille totale:', 'mon-theme' ); ?> format( $totalsize ); ?>
htmle( 'Tables:', 'mon-theme' ); ?> status ); ?>
rediscache ) : ?>
## htmle( 'Redis Cache', 'mon-theme' ); ?>
stats = $monthemerediscache->getstats();
if ( $redisstats['connected'] ) :
?>
htmle( 'Mémoire utilisée:', 'mon-theme' ); ?> html( $redisstats['memoryused'] ); ?>
html
e( 'Clés totales:', 'mon-theme' ); ?> html( $redisstats['totalkeys'] ); ?>
htmle( 'Taux de hits:', 'mon-theme' ); ?> html( $redisstats['hitrate'] ); ?>%
html
e( '✗ Redis non connecté', 'mon-theme' ); ?>
## htmle( 'Page Cache', 'mon-theme' ); ?>
cache = new AdvancedPageCache();
$cachestats = $pagecache->getstats();
?>
htmle( 'Fichiers en cache:', 'mon-theme' ); ?> html( $cachestats['totalfiles'] ); ?>
htmle( 'Taille totale:', 'mon-theme' ); ?> html( $cachestats['totalsize'] ); ?>
Conclusion
L'optimisation des performances WordPress en 2025 repose sur trois piliers : une base de données bien indexée et maintenue, un système de caching multi-niveaux (object cache, page cache, fragment cache), et une configuration MySQL optimale. En suivant ces pratiques, vous pouvez réduire les temps de chargement jusqu'à 80% et améliorer significativement l'expérience utilisateur.
Points clés :
Maintenir l'autoload sous 800 KB
Indexer les colonnes fréquemment requêtées
Implémenter Redis pour l'object cache
Utiliser le fragment caching pour les widgets
Optimiser les requêtes WPQuery
Nettoyer régulièrement la base de données
Monitorer les performances en continu
Ressources supplémentaires
WordPress Performance Optimization
MySQL Performance Tuning
Redis Documentation
Query Monitor Plugin
Mots-clés:** optimisation WordPress, performances WordPress, cache WordPress, Redis WordPress, MySQL optimisation, WPQuery performance, autoload WordPress, base de données WordPress, object cache, page cache WordPress 2025
Cet article est vivant — corrections, contre-arguments et retours de production sont les bienvenus. Trois canaux, choisissez celui qui vous convient.