Introducción: Más allá del caché superficial
En el ecosistema WordPress, la mayoría de los artículos sobre rendimiento se limitan a recomendar plugins de caché o CDN. Pero cuando gestionas sitios con tráfico alto, bases de datos complejas o arquitecturas distribuidas, necesitas una estrategia técnica profunda, no soluciones de “clic y olvida”. Este manual, escrito desde la experiencia en seguridad, sockets y PHP de bajo nivel, te guiará a través de los verdaderos puntos de fricción que afectan el rendimiento real de WordPress: la base de datos, el servidor web, las políticas CORS y la gestión eficiente de medios. Además, presentamos un plugin construido desde cero para resolver problemas específicos que ningún otro aborda de forma integrada.
---
## 1. Optimización de la Base de Datos: El Corazón del Rendimiento
WordPress depende de MySQL (o MariaDB) como motor de persistencia. Si la base de datos está mal configurada o fragmentada, ningún caché salvará tu sitio.
### 1.1. Índices inteligentes y consultas lentas
El primer paso es identificar consultas lentas. Activa el *slow query log*:
```ini
# En my.cnf
slow_query_log = 1
slow_query_log_file = /var/log/mysql/slow.log
long_query_time = 1
log_queries_not_using_indexes = 1
```
Luego, analiza con `mysqldumpslow` o `pt-query-digest`. En WordPress, las tablas más problemáticas suelen ser:
- `wp_options`: Si contiene cientos de transients sin limpiar.
- `wp_postmeta`: Cuando se abusa de campos personalizados sin índices.
- `wp_comments`: En sitios con foros o blogs muy activos.
**Solución:** Añade índices compuestos donde sea necesario. Por ejemplo, si filtras posts por meta_key y meta_value:
```sql
ALTER TABLE wp_postmeta ADD INDEX idx_meta_key_value (meta_key, meta_value(191));
```
> ⚠️ **Nota de seguridad**: Nunca indexar valores completos de `meta_value` si contienen datos sensibles. Usa prefijos (`(191)`) para evitar exponer información.
### 1.2. Limpieza proactiva
WordPress no elimina transients expirados de forma automática si no se accede a ellos. Usa un cron real (no el pseudo-cron de WordPress) para ejecutar limpiezas:
```bash
# En crontab
0 2 * * * wp --path=/var/www/site transient delete --expired
```
También considera particionar tablas grandes (como `wp_posts`) por año si superas los 100k posts.
### 1.3. Motor de almacenamiento: InnoDB sí, MyISAM no
Asegúrate de que todas tus tablas usen **InnoDB**. MyISAM carece de transacciones, bloqueos de fila y recuperación ante fallos. Convierte con:
```sql
ALTER TABLE wp_options ENGINE=InnoDB;
```
---
## 2. Tuning del Servidor Web: Apache vs Nginx
La elección entre Apache y Nginx no es solo filosófica; tiene implicaciones reales en rendimiento bajo carga.
### 2.1. Apache: MPM y módulos innecesarios
Si usas Apache, evita el MPM `prefork` en entornos modernos. Usa `event`:
```apache
# En apache2.conf
LoadModule mpm_event_module modules/mod_mpm_event.so
```
Desactiva módulos que no uses (`mod_php` si usas PHP-FPM, `mod_autoindex`, `mod_status` en producción). Cada módulo consume memoria y CPU.
Configura adecuadamente:
```apache
<IfModule mpm_event_module>
StartServers 2
MinSpareThreads 25
MaxSpareThreads 75
ThreadLimit 64
ThreadsPerChild 25
MaxRequestWorkers 400
MaxConnectionsPerChild 10000
</IfModule>
```
### 2.2. Nginx: Buffers, keepalive y gzip
Nginx brilla en servir contenido estático y manejar conexiones concurrentes. Configuración crítica:
```nginx
http {
sendfile on;
tcp_nopush on;
tcp_nodelay on;
keepalive_timeout 65;
keepalive_requests 100;
gzip on;
gzip_vary on;
gzip_min_length 1024;
gzip_types text/plain text/css application/json application/javascript;
# Buffers para evitar escritura lenta al cliente
client_body_buffer_size 128k;
client_max_body_size 10M;
client_header_buffer_size 1k;
large_client_header_buffers 4 4k;
}
```
**Importante**: Usa `fastcgi_cache` en lugar de plugins de caché cuando sea posible. Es más rápido y consume menos memoria.
---
## 3. CORS: No es solo un error de consola
Las políticas CORS mal configuradas no solo rompen APIs o embeds; también generan solicitudes OPTIONS innecesarias que consumen recursos del servidor.
### 3.1. Configuración mínima y segura
En **Nginx**:
```nginx
location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
add_header 'Access-Control-Allow-Origin' 'https://tudominio.com';
add_header 'Access-Control-Allow-Methods' 'GET, OPTIONS';
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
}
```
En **Apache**:
```apache
<FilesMatch "\.(js|css|png|jpg|jpeg|gif|ico|svg)$">
Header set Access-Control-Allow-Origin "https://tudominio.com"
Header set Access-Control-Allow-Methods "GET, OPTIONS"
</FilesMatch>
```
> 🔒 **Práctica segura**: Nunca uses `Access-Control-Allow-Origin: *` en contenido que incluya cookies o autenticación. Siempre especifica dominios explícitos.
---
## 4. Gestión de Imágenes en la Media Library: El peso invisible
La biblioteca de medios es el mayor consumidor de ancho de banda y disco en WordPress. Pero el problema no es solo el tamaño; es la *multiplicidad* de formatos generados.
### 4.1. Limitar tamaños de imagen innecesarios
Desactiva tamaños que no usas en tu tema:
```php
function custom_image_sizes($sizes) {
unset($sizes['medium_large']); // 768px, rara vez usado
unset($sizes['1536x1536']); // Full HD+ (solo si no lo usas)
unset($sizes['2048x2048']);
return $sizes;
}
add_filter('intermediate_image_sizes_advanced', 'custom_image_sizes');
```
### 4.2. WebP + Lazy Load nativo
Desde PHP 7.4+, puedes generar WebP directamente con GD o Imagick. Pero WordPress no lo hace por defecto. Solución: intercepta el proceso de subida.
Además, usa el atributo `loading="lazy"` en imágenes:
```php
function add_lazy_loading($html, $id) {
return str_replace('<img ', '<img loading="lazy" ', $html);
}
add_filter('get_image_tag', 'add_lazy_loading', 10, 2);
```
### 4.3. Servir imágenes desde un subdominio sin cookies
Crea un subdominio estático (ej. `static.tudominio.com`) y configúralo en Nginx/Apache para **no enviar cookies ni sesiones**. Esto reduce el overhead en cada solicitud de imagen.
---
## 5. El Plugin Construido: **WP-PerfCore**
Después de años de depurar sitios lentos, decidí construir un plugin que aborde estos problemas de forma coherente, sin sobrecargar el sistema. **WP-PerfCore** no es otro caché; es un *tuner de bajo nivel*.
### 5.1. Arquitectura
- **Sin interfaz de usuario**: Se configura vía constantes en `wp-config.php`.
- **Zero overhead en frontend**: Solo actúa durante subidas, cron real o inicialización de admin.
- **PHP 8.0+ nativo**: Usa typed properties, JIT-friendly code y evita globals.
### 5.2. Funcionalidades clave
#### a) **Smart Transient Cleaner**
Ejecuta limpieza de transients usando un socket Unix para evitar HTTP overhead. Se integra con systemd timers.
#### b) **Image Optimizer Pipeline**
Al subir una imagen:
1. Genera solo los tamaños definidos por el tema.
2. Convierte a WebP usando Imagick (si está disponible).
3. Añade metadatos EXIF mínimos (mejora SEO y privacidad).
4. Registra hashes para evitar duplicados.
#### c) **DB Index Advisor**
Analiza las consultas más frecuentes (usando `performance_schema` si está activo) y sugiere índices vía WP-CLI:
```bash
wp perfcore db suggest-indexes
```
#### d) **CORS Policy Enforcer**
Aplica reglas CORS basadas en dominios permitidos definidos en `WP_PERFCORE_CORS_DOMAINS`.
### 5.3. Código de ejemplo: Conversión WebP
```php
class WebP_Converter {
public static function maybe_convert( $metadata, $attachment_id ) {
if ( ! self::should_convert( $metadata ) ) return $metadata;
$file = get_attached_file( $attachment_id );
if ( ! $file || ! file_exists( $file ) ) return $metadata;
$webp_path = trailingslashit( dirname( $file ) ) . pathinfo( $file, PATHINFO_FILENAME ) . '.webp';
if ( extension_loaded( 'imagick' ) ) {
$image = new Imagick( $file );
$image->setImageFormat( 'webp' );
$image->setImageCompressionQuality( 82 );
$image->writeImage( $webp_path );
$image->destroy();
} elseif ( function_exists( 'imagewebp' ) ) {
// Fallback a GD
$source = self::get_gd_resource( $file );
imagewebp( $source, $webp_path, 82 );
imagedestroy( $source );
}
if ( file_exists( $webp_path ) ) {
$metadata['sizes']['webp'] = [
'file' => basename( $webp_path ),
'width' => $metadata['width'],
'height' => $metadata['height'],
];
}
return $metadata;
}
}
add_filter( 'wp_generate_attachment_metadata', [ WebP_Converter::class, 'maybe_convert' ], 10, 2 );
```
> ✅ **Ventaja**: No requiere procesos externos ni llamadas a APIs. Todo en PHP puro, con fallbacks seguros.
---
## Conclusión: Rendimiento es disciplina, no magia
Mejorar el rendimiento de WordPress no se trata de instalar un plugin mágico. Se trata de entender cómo interactúan la base de datos, el servidor web, las políticas de red y el manejo de recursos estáticos. Con una configuración sólida, limpieza proactiva y herramientas construidas con precisión (como **WP-PerfCore**), puedes lograr tiempos de carga consistentes incluso bajo picos de tráfico.
En *Hooked*, creemos que el rendimiento es una extensión de la seguridad: un sitio lento es un sitio vulnerable. Porque cuando el sistema está al límite, los errores se multiplican, los logs se saturan y los atacantes encuentran grietas.
Optimiza con inteligencia. Mide con rigor. Y nunca confíes en soluciones que no entiendes.
HTTP://www.pekstore.cl
ResponderBorrar