Volver a Casos de Ingeniería
26 de enero de 2025

Búsqueda Sub-20ms en 460k+ URLs: Plataforma Headless de Acordes con Indexación Automática de Google

Latencia de búsqueda sub-20ms con indexación automática de Google para 460k+ URLs

Laravel 12 Astro 5 Meilisearch MySQL 8.4 Redis Web Audio API Docker

El Cuello de Botella

Nuestro cliente, un agregador líder de acordes y letras que gestiona un catálogo que supera las 460,000 páginas únicas de canciones, operaba sobre una pila monolítica heredada donde una única instancia de Laravel intentaba servir tanto el renderizado del frontend como la gestión de datos del backend. Esta arquitectura causaba fallos críticos de infraestructura: la generación monolítica de sitemaps XML provocaba picos de memoria fatales y timeouts del servidor, impidiendo que los rastreadores de motores de búsqueda indexaran la mayoría del catálogo; las consultas SQL relacionales LIKE sobre tablas masivas generaban cuellos de botella de CPU y bloqueos de tablas bajo carga concurrente; la ingesta masiva de catálogos JSON heredados agotaba rutinariamente los límites de memoria de PHP debido al logging de consultas de Eloquent; y los trabajos de sincronización de larga duración con las APIs de Google excedían los umbrales de timeout de la puerta de enlace de Cloudflare, colapsando los flujos de trabajo administrativos. El impacto directo en el negocio fue severo: miles de páginas de alto interés permanecieron invisibles para la búsqueda orgánica durante meses, los costos de infraestructura del servidor escalaron debido a la presión constante de CPU, y los tokens OAuth sin cifrar almacenados en texto plano introdujeron un riesgo de cumplimiento catastrófico.

Arquitectura y Automatización

Descoplamos la plataforma en una verdadera arquitectura desacoplada: un backend API de Laravel 12 (PHP 8.2+, Docker/Sail, MySQL 8.4, Redis) exponiendo endpoints optimizados a un frontend Astro 5 ejecutándose en modo SSR standalone de Node y desplegado vía PM2 en un VPS. Esta separación eliminó la contención frontend-backend y permitió el escalado independiente de las capas de cómputo.

La búsqueda fue descargada completamente de MySQL hacia Meilisearch vía Laravel Scout, entregando resultados tolerantes a errores tipográficos en memoria en menos de 20ms. Redis cachea categorías de alta cardinalidad e índices de sitemap para prevenir consultas redundantes a la base de datos durante ráfagas de rastreadores.

Para resolver el cuello de botella del presupuesto de rastreo, implementamos una arquitectura de sitemap fragmentado algorítmico. En lugar de un archivo monolítico, un índice de sitemap dinámico dirige los rastreadores hacia 232+ endpoints paginados (/sitemaps/[page].xml). Cada fragmento utiliza escaneos rápidos solo por índice sobre el índice compuesto ['is_published', 'id'] para determinar start_id y end_id, luego recupera registros vía WHERE id BETWEEN—eliminando la degradación lineal del tradicional SQL OFFSET. Todas las respuestas de sitemap llevan Cache-Control: public, max-age=3600 y un layout de respaldo si la API no está disponible.

La indexación automatizada es gestionada por una integración personalizada con la Google Search Console API y la Google Indexing API. Un flujo OAuth seguro almacena tokens de acceso y refresco cifrados en reposo usando AES-256-CBC vía el servicio Crypt de Laravel. Comandos programados diariamente (seo:sync-urls, seo:submit-indexing, seo:inspect-urls) fuerzan programáticamente a Google a indexar páginas nuevas o actualizadas dentro de la cuota de 150 URLs/día. Debido a que sincronizar 460k URLs al registro local excede los timeouts HTTP, el panel administrativo Filament 3.2 dispara estos trabajos asíncronamente vía shell_exec('... > /dev/null 2>&1 &'), liberando instantáneamente el hilo HTTP y previniendo colapsos de la puerta de enlace. Observadores de Eloquent (SongObserver, ArtistObserver) se enganchan a los ciclos de vida de los modelos para invalidar automáticamente cachés de sitemap y despachar solicitudes de re-indexación en cada creación, actualización o eliminación.

El frontend de Astro maneja todas las herramientas interactivas de música del lado del cliente para proteger los Core Web Vitals. Un detector de tono YIN de autocorrelación transmite entrada de micrófono a través de la Web Audio API, renderizando un osciloscopio en canvas en tiempo real. El motor de metrónomo y acompañamiento rítmico utiliza osciladores raw, generadores de ruido rosa y filtros bandpass/highpass—además de pistas de acompañamiento polifónicas de Tone.js—sin descargar activos de audio estáticos. La transposición de acordes, generación de diagramas (SVGs dinámicos a partir de patrones simples como "x32010"), y computaciones de voicing para piano/bajo corren completamente en TypeScript (ChordEngine.ts, ChordRenderer.ts), preservando un bajo Interaction to Next Paint (INP). Del lado del servidor, Astro inyecta schema JSON-LD MusicComposition y preconecta al dominio de la API para minimizar la latencia del handshake TCP.

┌──────────────────────────────────────────────────────────────────────┐
│                         NAVEGADOR CLIENTE                            │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │ Detector YIN │  │ Transposición│  │ Metrónomo Web Audio /    │  │
│  │ de Tono      │  │ de Acordes   │  │ Sintetizador de Acomp.   │  │
│  │ (Web Audio)  │  │ (TypeScript) │  │ (Tone.js / Oscillators)  │  │
│  └──────────────┘  └──────────────┘  └──────────────────────────┘  │
└──────────────────────────────┬─────────────────────────────────────┘
                               │
                               ▼
┌──────────────────────────────────────────────────────────────────────┐
│                       ASTRO 5 SSR (NODE/PM2)                         │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │ Inyección    │  │ Índice       │  │ Preconnect / Cache       │  │
│  │ Schema       │  │ Sitemap      │  │ Fallbacks                │  │
│  │ SSR JSON-LD  │  │ (232+)       │  │                          │  │
│  └──────────────┘  └──────────────┘  └──────────────────────────┘  │
└──────────────────────────────┬─────────────────────────────────────┘
                               │ API REST
                               ▼
┌──────────────────────────────────────────────────────────────────────┐
│                     LARAVEL 12 API (DOCKER/SAIL)                     │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │ Meilisearch  │  │ Redis Cache  │  │ Observadores Eloquent    │  │
│  │ Scout Driver │  │ (Categorías,│  │ (Auto-limpieza sitemap / │  │
│  │ (<20ms)      │  │  Sitemaps)   │  │  Disparar re-index)      │  │
│  └──────────────┘  └──────────────┘  └──────────────────────────┘  │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────────────────┐  │
│  │ Filament 3.2 │  │ Almacenamiento│  │ Trabajos Async en      │  │
│  │ Panel Admin  │  │ OAuth Cifrado │  │ Segundo Plano            │  │
│  └──────────────┘  └──────────────┘  │ (shell_exec & detach)    │  │
└──────────────┬───────────────┬─────────────────────────────────────┘
               │               │
               ▼               ▼
        ┌──────────┐    ┌──────────────────┐
        │ MySQL 8.4│    │ APIs de Google   │
        │ (PDO)    │    │ (Indexación + GSC)│
        └──────────┘    └──────────────────┘

ROI Medible

Fiabilidad del Sitemap

Generación monolítica de XML causando picos de memoria fatales y timeouts del servidor, bloqueando el acceso de rastreadores al catálogo.

  • Antes: Sitemap único que agotaba memoria y fallaba bajo carga.
  • DESPUÉS: 232+ sitemaps fragmentados algorítmicamente usando paginación por rango de índice compuesto (WHERE id BETWEEN), cero timeouts y cobertura completa de rastreadores.

Latencia de Búsqueda

Consultas SQL LIKE sobre 460k filas disparando CPU y bloqueando tablas bajo tráfico concurrente.

  • Antes: Bloqueos de tablas y picos de CPU por búsquedas SQL.
  • DESPUÉS: Búsqueda en memoria tolerante a errores tipográficos vía Meilisearch entregando tiempos de respuesta sub-20ms con carga MySQL cero.

Velocidad de Indexación

Descubrimiento orgánico dejando miles de páginas de alto interés sin indexar durante meses.

  • Antes: Páginas invisibles durante meses; indexación puramente orgánica.
  • DESPUÉS: Pipeline automatizado de Google Indexing API enviando 150 URLs/día con credenciales OAuth cifradas AES-256-CBC, forzando indexación en días en lugar de meses.

Ingesta Masiva

Importaciones JSON heredadas agotando memoria de PHP vía logs de consultas de Eloquent.

  • Antes: Fallos por agotamiento de memoria en importaciones masivas.
  • DESPUÉS: Parser de streaming RecursiveDirectoryIterator con logging de consultas desactivado y sync diferido de Scout, logrando ingesta sin agotamiento de memoria.

Estabilidad de Trabajos Administrativos

Sincronización de 460k URLs al registro de Search Console excediendo timeouts de puerta de enlace de Cloudflare de 100 segundos, colapsando paneles administrativos.

  • Antes: Timeouts de puerta de enlace y caídas del panel admin.
  • DESPUÉS: Ejecución en segundo plano vía shell disparada desde Filament, liberando hilos HTTP instantáneamente y eliminando errores de puerta de enlace.

Rendimiento de Audio Interactivo

Descargas pesadas de archivos de audio estáticos degradando LCP e INP en herramientas musicales.

  • Antes: LCP lento por descarga de activos de audio.
  • DESPUÉS: 100% DSP Web Audio procedural y motores sintetizados de Tone.js, eliminando latencia de activos externos y preservando puntajes bajos de Interaction to Next Paint (INP).

Escrito por

Miguel Ortiz

Growth Engineer & Technical SEO

Hablemos de un Desafío Similar