php elephant sticker
Photo by RealToughCandy.com on Pexels.com
目次

【Guía práctica completa】Optimización del rendimiento y observabilidad en Laravel — Caching, diseño de BD, división de jobs, Octane, métricas y estados de carga accesibles

Qué aprenderás (puntos clave)

  • Diseño de caches (Config/Route/View/Query) y caché HTTP
  • Eliminación de N+1 de Eloquent, diseño de índices, optimización de agregación y paginación
  • División de jobs, limitación de velocidad (throttling), desduplicación y manejo idempotente
  • Octane (Swoole/RoadRunner), OPcache y entrega óptima de recursos estáticos
  • Monitorización con Horizon, Telescope, logs estructurados y métricas (latencia/tasa de error)
  • UI de carga accesible, mejora progresiva e indicaciones de progreso no dependientes del color

Lectores previstos (¿quién se beneficia?)

  • Ingenieros Laravel principiantes–intermedios: aumentar velocidad y estabilidad paso a paso
  • Tech leads/arquitectos: estandarizar un diseño listo para operaciones con observabilidad
  • SRE/QA: ejecutar pruebas de rendimiento y detección de regresiones basadas en métricas
  • Diseñadores/redactores: crear pantallas de carga y UIs esqueleto accesibles

1. Principios rectores: “Reduce el desperdicio primero”, luego “hazlo más rápido por diseño”

Si optimizas en el orden equivocado, solo crece la complejidad.

  1. Medir: identifica solicitudes/consultas/jobs lentos.
  2. Reducir: N+1, consultas innecesarias, renderizado desperdiciado.
  3. Cachear: datos, plantillas, HTTP.
  4. Asincronizar: empuja trabajo pesado a jobs.
  5. Endurecer la plataforma: OPcache, Octane, CDN.
  6. Observar: detecta regresiones y habilita rollbacks.

A continuación pasos concretos para apilar mejoras con seguridad.


2. Estrategia de caché: maximiza la superficie de aciertos, controla la invalidación

2.1 Empieza con las caches incorporadas

php artisan config:cache
php artisan route:cache
php artisan view:cache
  • Reduce sobrecarga de arranque mediante caches compilados de config/rutas/Blade.
  • En producción, OPcache es un hecho. Haz que el deploy/restart refresque caches automáticamente.

2.2 Caché de datos (ejemplo en la capa Repository)

class CategoryRepo {
  public function all(): Collection {
    return Cache::remember('categories:all', now()->addHours(6), function () {
      return Category::query()->orderBy('rank')->get(['id','name','slug']);
    });
  }
}
  • Nomenclatura de claves: piensa en recurso:condiciones:version.
  • Localidad: apunta a listas/diccionarios. Para datos específicos de usuario, usa TTL corto u otra capa.

2.3 Caché de consultas de corta vida

$top = Cache::remember("posts:top:{$page}", 60, fn() =>
  Post::with('author')
      ->published()
      ->orderByDesc('score')
      ->paginate(20)
);
  • Caché de corta vida para listas pesadas con paginación + with().
  • Invalida vía eventos (en create/update/delete llama Cache::forget).

2.4 Caché HTTP (ETag/Last-Modified)

$etag = sha1($post->updated_at.$post->id);
if (request()->header('If-None-Match') === $etag) {
  return response()->noContent(304);
}
return response()->view('post.show', compact('post'))
  ->header('ETag', $etag)
  ->header('Cache-Control', 'public, max-age=120');
  • Combina con CDN para reducir ancho de banda.
  • Para páginas autenticadas, prefiere private y vidas cortas.

3. Optimización de Eloquent: N+1, índices, agregación

3.1 Detecta y elimina N+1

// Ejemplo: traer autor y tags juntos
$posts = Post::with(['author:id,name','tags:id,name'])
  ->latest()->paginate(20);
  • Usa with() para carga ansiosa de relaciones; selecciona solo columnas necesarias.
  • Para pantallas con agregados, considera counter caches o tablas preagregadas.

3.2 Diseño de índices

  • Añade índices compuestos a columnas en WHERE/ORDER BY/JOIN.
  • Para orden created_at DESC, estabiliza con (created_at, id), etc.
  • Para búsquedas LIKE, prefiere coincidencias de prefijo (column LIKE 'abc%'); considera un almacén aparte para full-text.

3.3 Lista blanca de opciones de búsqueda/orden

$sort = $req->enum('sort', ['-created_at','created_at','-score','score']) ?? '-created_at';
$dir  = str_starts_with($sort,'-') ? 'desc' : 'asc';
$col  = ltrim($sort,'-');
$query->orderBy($col, $dir);
  • Prohíbe orderBy arbitrario; lista blanca para ceñirte a rutas optimizadas.

3.4 Agregación separada

  • Para agregados pesados de dashboards, materializa periódicamente y deja la vista solo lectura/ligera.
  • Si no es necesario en tiempo real, actualiza de forma asíncrona.

4. Optimización de vistas: adelgazar Blade y front-end

4.1 Condicionales y componentes

  • Reduce @include dentro de bucles grandes; renderiza en lotes de colecciones.
  • Preformatea helpers pesados mediante ViewModels o Accessors.

4.2 Imágenes y estáticos

  • Define width/height en <img> para suprimir CLS.
  • Usa <picture>, srcset y loading="lazy" para recortar bytes.
  • Con HTTP/2, empaqueta con criterio; tree-shake CSS/JS no usado en build.

4.3 Estados de carga accesibles

<div role="status" aria-live="polite" class="mb-2">
  Loading data…
</div>
<ul aria-busy="true" aria-describedby="loading-desc">
  <li class="skeleton h-6 w-full"></li>
  <li class="skeleton h-6 w-5/6 mt-2"></li>
</ul>
<p id="loading-desc" class="sr-only">The list will appear once loading completes.</p>
  • Las UIs esqueleto deben incluir descripciones de texto.
  • Tras completar, pon aria-busy="false" y mueve el foco al inicio de la lista para restaurar contexto.

5. Asincronía: división de jobs, desduplicación, throttling

5.1 Descarga trabajo pesado a jobs

class ExportOrders implements ShouldQueue {
  public $tries = 3;
  public $timeout = 1800; // 30 min
  public function handle(ExportService $svc) { $svc->run(); }
}
  • Garantiza idempotencia (mismo input, mismo output) para que los reintentos no rompan el estado.

5.2 Desduplicación (jobs únicos)

$lock = Cache::lock("export:{$userId}", 600);
if ($lock->get()) {
  ExportOrders::dispatch($userId)->onQueue('exports');
  // Release $lock->release() cuando el job termine
}
  • Serializa trabajo del mismo tipo por usuario para proteger recursos.

5.3 Middleware de rate-limit

public function middleware(): array {
  return [ new \Illuminate\Queue\Middleware\RateLimited('exports') ];
}
  • Aplica throttling para APIs externas y envíos de email.

5.4 Fallback en front-end

  • Si no hay push en tiempo real, usa polling.
  • Mientras carga usa role="status", y en fallo proporciona causa breve + próximos pasos.

6. Octane/OPcache/ajuste de procesos

6.1 Fundamentos de OPcache

  • En producción: opcache.enable=1, opcache.validate_timestamps=0 (para builds inmutables).
  • Reinicia procesos en el deploy para refrescar caches.

6.2 Dónde ayuda Laravel Octane

  • Reduce sobrecarga de E/S síncrona.
  • Comparte sesión/caché/colas vía almacenes externos.
  • Evita singletons con estado y contaminación entre requests; reinicializa periódicamente (p. ej., Octane::tick()).

6.3 Servir imágenes/estáticos vía CDN

  • Si la app no tiene que servirlos, sirve vía CDN.
  • Usa URLs firmadas para acceso con tiempo limitado.

7. Observabilidad: logs, trazas, métricas

7.1 Logs estructurados

Log::info('order.created', [
  'order_id' => $order->id,
  'user_id' => $order->user_id,
  'amount' => $order->amount,
  'request_id' => request()->header('X-Request-Id')
]);
  • Registra siempre claves buscables (user ID, order ID, request ID).
  • Enmascara PII, nunca registres tokens/secretos.

7.2 Latencia, tasa de error, throughput

  • Rastrea percentiles (p50/p95/p99) para reflejar experiencia de usuario.
  • “Lento ≠ malo” — define SLOs y detecta desviaciones.
  • Compara métricas por deploy; haz rollback ante regresiones.

7.3 Telescope/Horizon

  • Telescope: visibilidad de requests/queries/exceptions/jobs.
  • Horizon: backlogs de colas, jobs fallidos, paneles de tiempos de proceso.
  • Restringe en producción; habilítalo brevemente con fines forenses.

8. Paginación y carga incremental

8.1 simplePaginate e infinite scroll

$items = Item::orderByDesc('id')->simplePaginate(50);
  • Evita el coste de consultas de total-count.
  • Para scroll infinito, provee también un control manual (botón “Cargar más”).

8.2 Carga accesible

<button id="more" class="btn" aria-controls="list" aria-describedby="load-hint">
  Load more
</button>
<p id="load-hint" class="sr-only">Additional results will be appended at the bottom.</p>
<div id="alist" role="feed" aria-busy="false"></div>
  • Asegura que sea operable incluso sin auto-carga.

9. Haz que los fallos sean inocuos: timeouts, reintentos, circuit breakers

  • Para clientes HTTP, define timeouts explícitos de conexión/respuesta.
  • Reintenta errores transitorios con backoff exponencial.
  • Ante fallos persistentes, abre un circuito temporal y muestra copia de fallback en la UI.
  • Limita el radio de impacto con scopes (transacciones/actualizaciones parciales).

10. Rendimiento de i18n y zonas horarias

  • Configura la localización de Carbon una vez en el boot.
  • Proporciona traducciones vía JSON o claves nombradas para mejorar ratios de acierto de caché.
  • Consolida formato de moneda/fecha en el servidor; minimiza cómputo en cliente.

11. Equilibrio seguridad vs. velocidad

  • Aplica límites de tasa más estrictos a operaciones sensibles.
  • Aplica CSP/HSTS/Referrer-Policy vía headers, y si usas nonces dinámicos, minimiza la sobrecarga en plantillas.
  • Para URLs firmadas y checks de auth, considera rutas que deben evitar caches.

12. UX: guía para estados lentos, fallidos y offline

12.1 Guía ante respuesta lenta

<div id="status" role="status" aria-live="polite">
  No response for over 3 seconds. Please check your connection.
</div>
  • A medida que pasa el tiempo, muestra guía corta y próximos pasos (recargar, modo lite, etc.).

12.2 Guía ante fallos

  • Pon un botón “Intentar de nuevo” en primera línea.
  • Muestra un ID de solicitud utilizable por soporte.
  • No dependas solo del color—usa icono + texto para estados.

13. Muestras de implementación: de medir cuellos de botella a arreglos

13.1 Mide tiempo de respuesta vía middleware

class RequestTimer {
  public function handle($req, Closure $next) {
    $start = microtime(true);
    $res = $next($req);
    $ms = (int)((microtime(true) - $start) * 1000);
    Log::info('http.timing', [
      'path' => $req->path(),
      'status' => $res->getStatusCode(),
      'ms' => $ms,
      'request_id' => $req->headers->get('X-Request-Id'),
    ]);
    return $res->headers->set('Server-Timing', "app;dur={$ms}");
  }
}
  • En el panel Network del navegador, inspecciona Server-Timing para detectar vistas lentas.

13.2 Visualiza N+1 (solo dev)

DB::listen(function ($query) {
  if (str_contains($query->sql, 'select') && $query->time > 30) {
    logger()->debug('slow.query', ['sql' => $query->sql, 'ms' => $query->time]);
  }
});
  • Registra consultas por encima de un umbral.

13.3 Mejoras fundamentales de consulta

  • Usa withCount y selectRaw para empujar agregación al DB.
  • Acumula histórico/estadísticas en tablas separadas para evitar hot paths intensivos en escritura.

14. Checklist (para distribución)

Medición

  • [ ] p50/p95/p99, tasa de errores, throughput
  • [ ] Logs de consultas/solicitudes lentas
  • [ ] Dashboard comparando con el último deploy

Reducción

  • [ ] N+1 eliminado (with() / minimización de columnas)
  • [ ] Índices adecuados
  • [ ] Poda de recálculos / ramificación en plantillas

Caché

  • [ ] Config/Route/View/OPcache
  • [ ] Caché de datos de corta vida + estrategia de invalidación
  • [ ] HTTP ETag/Last-Modified/CDN

Asincronía

  • [ ] Jobify + reintentos + timeouts
  • [ ] Jobs únicos / rate limiting
  • [ ] Fallbacks (polling / modo lite)

Plataforma

  • [ ] Considerar cobertura de Octane
  • [ ] CDN para estáticos
  • [ ] srcset de imágenes / lazy-load / width & height

Observabilidad

  • [ ] Logs estructurados e IDs de solicitud
  • [ ] Operación segura de Horizon/Telescope
  • [ ] Umbrales de alerta y procedimientos de rollback

Accesibilidad

  • [ ] Texto de carga + role="status"
  • [ ] Indicaciones de estado más allá del color; restauración de foco
  • [ ] Próximas acciones ante fallo

15. Errores comunes y cómo evitarlos

  • Cachear mientras siguen N+1 → Elimina primero, cachea después.
  • Caches de larga vida sin invalidación → invalidación dirigida por eventos.
  • Pasar orden/búsqueda arbitrarios tal cual → lista blanca para rutas optimizadas.
  • Sentirse seguro tras “solo async” → Sin reintentos/idempotencia, los fallos se acumulan.
  • Estados de carga silenciosos → Proporciona guía breve y formas de actuar.
  • Optimizar sin medir → No sabrás el impacto; ejecuta medir → mejorar → volver a medir.

16. Resumen

  • Avanza en orden: medir → reducir → cachear → async → endurecer plataforma → observar.
  • Para Eloquent, usa with() + índices y externaliza agregados para aligerar lo fundamental.
  • Haz los jobs resilientes con desduplicación, límites de tasa, idempotencia.
  • Impulsa la velocidad base con OPcache/Octane/CDN.
  • Mantén accesibles los flujos de carga/fallo/reintento.
  • Pon métricas en dashboards para detectar y revertir regresiones pronto.

El rendimiento no es un arreglo único: es un hábito de medir y mejorar. Usa esta plantilla para hacer crecer un Laravel de equipo rápido y comprensible.


Referencias

por greeden

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

日本語が含まれない投稿は無視されますのでご注意ください。(スパム対策)