Tu búsqueda en el sitio funciona “bien” hasta que deja de hacerlo: un lanzamiento de producto, un enlace viral o un cliente entusiasta que escribe
“wireless noise cancelling…” y la CPU de tu base de datos alcanza el máximo. Entonces llegan las páginas que agotan el tiempo, la relevancia que se vuelve
aleatoria y alguien sugiere cachearlo todo “hasta que lo arreglemos”. Así es como los incidentes terminan teniendo tazas conmemorativas.
Esta es la línea práctica entre “MariaDB es suficiente” y “necesitas un clúster de búsqueda”. No como ideología. Como operaciones:
latencia en la cola, reindexado, control de ranking, dominios de fallo y cómo diagnosticar qué es lo que realmente está lento.
La verdadera pregunta: ¿qué estás optimizando?
“MariaDB vs Elasticsearch” es un encuadre engañoso. En producción no eliges solo una tecnología; eliges qué modos de fallo estás dispuesto a asumir.
MariaDB es excelente cuando la búsqueda es un problema de consulta filtrada. Elasticsearch es excelente cuando la búsqueda es un problema de lenguaje y
relevancia. El momento en que necesitas ambos a escala —filtros rápidos, tolerancia a errores tipográficos, sinónimos, autocompletado, “quisiste decir”,
ajuste de ranking, puntuación multi-campo— has pasado de “consultar registros” a “ejecutar un sistema de recuperación”.
Los sistemas de recuperación quieren su propia infraestructura, porque el trabajo que hacen es distinto.
Regla práctica orientada a producción: usa MariaDB para búsqueda cuando la consulta es predecible, acotada y se puede indexar con alta selectividad.
Usa Elasticsearch cuando necesitas servir intención, no coincidencias exactas, y necesitas controlar la relevancia sin reescribir tu SQL cada semana.
La trampa es pensar que Elasticsearch es “para escala” y MariaDB es “para pequeño”. Eso está al revés. Ambos pueden escalar.
Elasticsearch es para comportamientos de búsqueda que los motores relacionales no están diseñados para hacer de forma económica o consistente.
Hechos interesantes y contexto histórico (breve, útil)
- Lucene existe antes que Elasticsearch. La biblioteca central de indexación/búsqueda (Lucene) empezó en 1999; Elasticsearch (construido sobre Lucene) llegó alrededor de 2010 y lo empaquetó para uso distribuido.
- BM25 se convirtió en la línea base de relevancia. Lucene cambió de TF/IDF clásico a BM25 como un modelo de puntuación más moderno y ajustable. Esto importa porque la “relevancia” es matemáticas, no intuición.
- FULLTEXT de MySQL/MariaDB fue diseñado para documentos, pero con limitaciones. Es útil, pero el análisis de lenguaje, el control de scoring y la composición de consultas son limitados frente a motores basados en Lucene.
- FULLTEXT en InnoDB llegó más tarde de lo que muchos recuerdan. Históricamente, FULLTEXT se asociaba con MyISAM; el soporte en InnoDB apareció después y las expectativas operativas tardaron en alinearse con la realidad.
- Elasticsearch popularizó el indexado “casi en tiempo real”. El concepto de intervalo de refresh (segmentos que se vuelven buscables tras el refresh) es un tradeoff deliberado entre rendimiento y frescura.
- La búsqueda distribuida es un problema de coordinación. Lo difícil no es indexar; son shards, réplicas, enrutamiento, merges y la muy humana tendencia a cambiar mappings después de lanzar.
- SQL LIKE se volvió el pecado original de la búsqueda en sitio. La gente todavía lo publica porque funciona el primer día. El día treinta viene con una factura de CPU.
- Los appliances de búsqueda existían antes de los “servicios gestionados” en la nube. Las empresas usaban cajas de búsqueda propietarias mucho antes de las opciones alojadas actuales; el dolor operativo no es nuevo, solo está reempaquetado.
Qué puede hacer MariaDB para la búsqueda en sitio (y dónde se quiebra)
MariaDB es ideal cuando realmente es una consulta por índice
MariaDB brilla cuando tu “búsqueda” es principalmente:
- Coincidencias exactas (SKU, ID, correo, nombre de usuario).
- Coincidencias por prefijo que pueden usar un índice (p. ej., tabla de palabras clave normalizada).
- Filtrado/ordenación sobre columnas estructuradas (categoría, precio, estado, permisos).
- Conjuntos de datos relativamente pequeños donde los escaneos completos siguen siendo baratos y predecibles.
La base de datos te da consistencia fuerte, joins sencillos, transacciones y una única superficie operacional.
Si tu producto necesita “encontrar productos con color=azul y precio < 50 y en stock”, MariaDB es el adulto en la sala.
FULLTEXT: útil, pero no finjas que es un motor de búsqueda
FULLTEXT de MariaDB puede soportar una búsqueda básica en sitio. Suele ser la opción correcta cuando:
- Tienes un solo idioma, necesidades morfológicas limitadas y coincidencias mayormente literales.
- Puedes aceptar relevancia tosca y no estás ajustando el ranking semanalmente.
- El corpus no es enorme y la concurrencia de consultas es moderada.
- No necesitas analizadores sofisticados (sinónimos, reglas de stemming por campo, n-grams para autocompletado).
Pero FULLTEXT es donde el optimismo busca aprobación presupuestaria y luego muere en producción.
Las grietas aparecen en cuatro lugares:
- Control de ranking: tienes menos palancas y son menos composables. Si necesitas “coincidencias en título ganan a menos que el cuerpo coincida fuertemente y la recencia importe”, terminarás construyendo lógica de scoring personalizada fuera del SQL.
- Análisis de lenguaje: tokenización, stopwords, stemming, sinónimos—esto no es el foco de los motores relacionales. Puedes parchearlo, pero estarás parcheando para siempre.
- Aislamiento operativo: carga pesada de búsqueda compite con escrituras OLTP. Cuando la búsqueda sube, tu tráfico de checkout no debería enterarse.
- Flexibilidad de consulta: fuzziness, tolerancia a errores tipográficos, consultas de frase, proximidad y boosting multi-campo son posibles en alguna forma pero dolorosos de evolucionar sin reescribir.
El gran olor de producción: tu BD se convierte en la caja de búsqueda
Si ejecutas la búsqueda dentro de MariaDB, estás atando el comportamiento de tipeo del usuario a tu almacén primario. Eso significa:
- Cada pulsación en un endpoint de autocompletado puede convertirse en una consulta a la BD.
- La búsqueda puede disparar búsquedas fulltext que no se cachean bien.
- Consultas lentas roban buffer pool y CPU de las transacciones.
Aquí es donde los equipos terminan “resolviendo” la búsqueda añadiendo réplicas de solo lectura. Funciona hasta que no; y desplaza
el fallo a la latencia de replicación y resultados inconsistentes. No es una estrategia; es una táctica de demora.
Broma #1: Si usas LIKE '%term%' en producción, la base de datos eventualmente te emparejará con una llamada de incidente.
Lo que realmente te aporta Elasticsearch
Es un motor de recuperación diseñado con tradeoffs opinativos
Elasticsearch no es “un reemplazo de base de datos”. Es un sistema de indexación y recuperación optimizado para:
- Consultas full-text rápidas sobre grandes corpus.
- DSL de consulta flexible: lógica booleana, frase/proximidad, fuzziness, boosting, ponderaciones por campo.
- Pipelines de análisis de texto: tokenizers, filtros, sinónimos, stemming, n-grams, analizadores por campo.
- Escala horizontal con shards y réplicas.
- Actualizaciones casi en tiempo real (ciclos de refresh) en lugar de consistencia transaccional estricta.
Operativamente, compras aislamiento y distintos controles
La razón de producción para ejecutar Elasticsearch es el aislamiento: la carga de búsqueda pertenece a un clúster separado con su propio escalado,
su propio envelope de rendimiento y su propio radio de explosión de fallos.
Elasticsearch te da perillas que MariaDB no tiene:
- Paralelismo a nivel de shard: fan-out de consultas por shards y luego combinación de resultados. Pagas sobrecarga de coordinación; ganas rendimiento.
- Gestión del ciclo de vida del índice: rollover, retención, merges de segmentos y tiering que se pueden planificar, no adivinar.
- Reindexado como operación de primera clase: los cambios de esquema son dolorosos, pero el ecosistema espera reindexados y provee flujos de trabajo.
- Velocidad de iteración en relevancia: cambiar analizadores, boosting, sinónimos y lógica de consulta puede hacerse sin rediseñar el esquema relacional.
Lo que también compras: nuevos modos de fallo
Elasticsearch con gusto te permitirá crear un clúster que parece sano y rinde de forma terrible. Culpables típicos:
- Explosión de shards: demasiados shards pequeños, demasiada sobrecarga.
- Presión de merges de segmentos: el rendimiento de indexación se hunde bajo merges.
- Presión de heap y pausas de GC: la latencia de cola se vuelve una gráfica de sierra.
- Errores de mapping: campos indexados incorrectamente, llevando a consultas costosas o relevancia rota.
- Intervalo de refresh demasiado bajo: “¿por qué el indexado es tan lento?” porque hiciste una commit cada segundo.
Y sí, es otro clúster. Otro conjunto de snapshots/backups, upgrades y sorpresas en el on-call. Pero si la búsqueda es
una característica de producto, no una casilla por marcar, el clúster no es opcional. Es el costo de hacer negocios.
Cuándo el clúster de búsqueda es obligatorio (disparadores de decisión)
“Obligatorio” es una palabra fuerte. Así que aquí tienes disparadores operativos fuertes. Si tienes dos o más, deja de debatir y
empieza a planificar el clúster de búsqueda.
1) Autocompletado y typeahead a niveles de tráfico reales
El autocompletado cambia el perfil de consulta: en lugar de una búsqueda por sesión, recibes varias por interacción de usuario.
Las consultas son cortas, de baja selectividad y difíciles de cachear. MariaDB puede hacerlo, pero lo hará ruidosamente.
2) La relevancia es un KPI de producto, no algo bonito de tener
Si los stakeholders hablan de “calidad de resultados”, “merchandising”, “boostear esta categoría”, “degradar fuera de stock”,
“sinónimos” o “tolerancia a errores tipográficos”, estás en territorio de motores de búsqueda. Los controles de relevancia de FULLTEXT no son donde quieres
pasar tu carrera profesional.
3) Multilenguaje, morfología o lenguaje de dominio
Cuando necesitas analizadores por idioma, tokenización personalizada (números de pieza, nombres químicos, citas legales)
o sinónimos que cambian sin despliegues, Elasticsearch se convierte en la opción práctica.
4) Necesitas señales de ranking híbridas
Los resultados de búsqueda dependen cada vez más de señales como popularidad, recencia, click-through, personalización, disponibilidad
y reglas de negocio. Elasticsearch puede mezclar esto con function scores y filtros estructurados sin convertir SQL en una pieza de performance art.
5) Tu base de datos OLTP es un recurso sagrado
Si MariaDB maneja dinero, pedidos, auth, inventario o cualquier cosa que despierte a los ejecutivos, quieres la búsqueda fuera de ella.
Los picos de búsqueda no deberían poder asfixiar el throughput transaccional.
6) Requieres frescura “suficientemente buena”, no consistencia estricta
Elasticsearch es casi en tiempo real por diseño. Si “los ítems son buscables en ~1–30 segundos” es aceptable, puedes desacoplar el indexado de las escrituras.
Si realmente necesitas consistencia read-after-write para resultados de búsqueda, debes diseñarlo explícitamente (ruta de lectura híbrida, indicios en UI o indexado síncrono)
o quedarte en la base de datos y aceptar las limitaciones.
7) Necesitas evolución de esquema de búsqueda sin downtime
Vas a cambiar mappings/analyzers. Si tu organización no tolera tiempo de inactividad en búsqueda mientras migras índices,
quieres un motor con flujos de reindexado y corte de alias integrados.
Broma #2: Un clúster de búsqueda es como una segunda máquina de espresso—nadie quiere mantenerla, pero todos notan cuando falta.
La opción sensata: arquitectura híbrida
Patrón: MariaDB como fuente de verdad, Elasticsearch como índice optimizado para lectura
En la mayoría de compañías reales, la mejor respuesta es ambas:
- MariaDB permanece como fuente de verdad para entidades, permisos, transacciones e integridad referencial.
- Elasticsearch almacena un documento desnormalizado y optimizado para consultas por entidad (producto, artículo, ticket, listado).
- Las peticiones de búsqueda van a Elasticsearch primero; los resultados devuelven IDs; la aplicación opcionalmente hidrata detalles desde MariaDB.
Esta arquitectura tiene un nombre en la práctica: acepta la consistencia eventual, pero diseña la brecha.
Construyes una canalización de indexación, monitorizas el retraso y decides cómo se comporta la UI cuando el índice está atrasado.
Pipeline de indexación: tu fiabilidad está aquí, no en la consulta
El pipeline puede ser:
- CDC (change data capture) desde binlogs de MariaDB → stream → indexador → Elasticsearch.
- Patrón outbox: la aplicación escribe la entidad + evento outbox en la misma transacción DB; un worker consume e indexa.
- Reindexado batch periódico para sistemas más simples, con actualizaciones incrementales cuando sea posible.
El patrón outbox es aburrido. Por eso funciona. Convierte “¿indexamos esto?” en un trabajo reintentable con un libro mayor durable.
Decisiones de diseño clave que previenen dolor futuro
- Versiones de índice inmutables: crea
products_v42, luego cambia un aliasproducts_current. Nunca “edites en sitio” cuando cambian analizadores. - Almacena campos fuente necesarios para renderizar: evita “tormentas de hidratación” donde cada resultado de búsqueda se convierte en N consultas a la BD.
- Separa tipos de consulta: índice de autocompletado vs índice de búsqueda completa vs índice de navegación por categoría. Diferentes analizadores, distintas estrategias de shard.
- SLA de frescura explícito: “99% de actualizaciones buscables dentro de 30s.” Luego mídelo.
Una cita, porque operaciones es filosofía con pager duty
“La esperanza no es una estrategia.”
— Gen. Gordon R. Sullivan
Tareas de producción: comandos, salidas, decisiones (12+)
Estas son las comprobaciones que ejecutas cuando alguien dice “la búsqueda está lenta” y todos miran a la base de datos.
Cada tarea incluye un comando ejecutable, un fragmento de salida realista, qué significa y la decisión que tomas.
Task 1: Identificar las consultas top de MariaDB por tiempo (Performance Schema)
cr0x@server:~$ mysql -e "SELECT DIGEST_TEXT, COUNT_STAR, ROUND(SUM_TIMER_WAIT/1e12,2) AS total_s, ROUND(AVG_TIMER_WAIT/1e12,4) AS avg_s FROM performance_schema.events_statements_summary_by_digest ORDER BY SUM_TIMER_WAIT DESC LIMIT 3\G"
*************************** 1. row ***************************
DIGEST_TEXT: SELECT * FROM products WHERE name LIKE ? ORDER BY updated_at DESC LIMIT ?
COUNT_STAR: 248901
total_s: 9123.55
avg_s: 0.0367
*************************** 2. row ***************************
DIGEST_TEXT: SELECT id FROM products WHERE MATCH(title,body) AGAINST (? IN BOOLEAN MODE) LIMIT ?
COUNT_STAR: 55432
total_s: 2101.22
avg_s: 0.0379
Qué significa: Estás gastando horas de CPU en consultas LIKE y FULLTEXT. La latencia promedio parece tolerable, pero el volumen lo domina.
Decisión: Si estos son endpoints de búsqueda visibles para usuarios, aíslalos. Como mínimo: deja de usar LIKE con comodines; considera Elasticsearch si se requieren relevancia/autocompletado.
Task 2: Confirmar si LIKE usa un índice (EXPLAIN)
cr0x@server:~$ mysql -e "EXPLAIN SELECT * FROM products WHERE name LIKE '%headphones%' LIMIT 20\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: products
type: ALL
possible_keys: idx_products_name
key: NULL
key_len: NULL
rows: 1843921
Extra: Using where
Qué significa: Escaneo completo de tabla. Tu índice en name es inútil con un comodín al inicio.
Decisión: Reemplázalo por FULLTEXT cuando sea aceptable, o muévete a Elasticsearch para coincidencias por substring/tolerancia a errores tipográficos.
Task 3: Comprobar buffer pool de MariaDB y presión de lectura
cr0x@server:~$ mysql -e "SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_reads'; SHOW GLOBAL STATUS LIKE 'Innodb_buffer_pool_read_requests';"
+-------------------------+----------+
| Variable_name | Value |
+-------------------------+----------+
| Innodb_buffer_pool_reads| 18499231 |
+-------------------------+----------+
+----------------------------------+-------------+
| Variable_name | Value |
+----------------------------------+-------------+
| Innodb_buffer_pool_read_requests | 83199231121 |
+----------------------------------+-------------+
Qué significa: La ratio es pasable, pero las lecturas desde disco son grandes en términos absolutos. Las consultas de búsqueda pueden estar rompiendo la localidad de caché.
Decisión: Si OLTP se ve afectado, aísla la búsqueda. Aumentar buffer pool ayuda hasta que deja de hacerlo; es mejor mover los patrones de IO de búsqueda fuera de la BD primaria.
Task 4: Detectar lag de replicación si empujaste búsqueda a réplicas
cr0x@server:~$ mysql -e "SHOW SLAVE STATUS\G" | egrep "Seconds_Behind_Master|Slave_IO_Running|Slave_SQL_Running"
Slave_IO_Running: Yes
Slave_SQL_Running: Yes
Seconds_Behind_Master: 47
Qué significa: La réplica está retrasada ~47 segundos. Tus resultados de búsqueda pueden estar desactualizados y las lecturas pueden acumularse.
Decisión: O aceptas la desactualización explícitamente (indicio en UX, modelo eventual) o dejas de usar réplicas como recurso de búsqueda. Esto es una fuerte señal a favor de un índice de búsqueda.
Task 5: Medir comportamiento de FULLTEXT en MariaDB (y si es selectivo)
cr0x@server:~$ mysql -e "EXPLAIN SELECT id FROM products WHERE MATCH(title,body) AGAINST ('wireless headphones' IN NATURAL LANGUAGE MODE) LIMIT 20\G"
*************************** 1. row ***************************
id: 1
select_type: SIMPLE
table: products
type: fulltext
possible_keys: ft_title_body
key: ft_title_body
key_len: 0
rows: 120000
Extra: Using where
Qué significa: FULLTEXT se usa, pero espera examinar ~120k filas para esa consulta. No es terrible una vez; terrible a escala.
Decisión: Si necesitas alta QPS, autocompletado o múltiples campos con boosting, cambia a Elasticsearch y diseña el dimensionado de shards acorde.
Task 6: Comprobar salud del clúster Elasticsearch rápidamente
cr0x@server:~$ curl -s http://localhost:9200/_cluster/health?pretty
{
"cluster_name" : "search-prod",
"status" : "yellow",
"timed_out" : false,
"number_of_nodes" : 6,
"number_of_data_nodes" : 4,
"active_primary_shards" : 48,
"active_shards" : 48,
"unassigned_shards" : 48
}
Qué significa: Amarillo con réplicas no asignadas. Puedes servir lecturas, pero no tienes redundancia para muchos shards.
Decisión: Trátalo como un incidente de fiabilidad. Arregla la asignación (umbrales de disco, pérdida de nodos, conteo de réplicas) antes de afinar rendimiento; de lo contrario estás afinando un clúster cojo.
Task 7: Identificar explosión de shards (conteo y tamaños)
cr0x@server:~$ curl -s http://localhost:9200/_cat/shards?v | head
index shard prirep state docs store ip node
products_v42 0 p STARTED 812341 3.2gb 10.0.1.21 es-data-1
products_v42 0 r UNASSIGNED
products_v42 1 p STARTED 799002 3.1gb 10.0.1.22 es-data-2
products_v42 1 r UNASSIGNED
Qué significa: Tienes réplicas no asignadas y los tamaños de shard son relativamente pequeños. Si hay cientos/miles de shards así, la sobrecarga dominará.
Decisión: Consolida shards (menos primarias), arregla la asignación de réplicas y deja de crear un índice por tenant a menos que disfrutes el dolor.
Task 8: Comprobar distribución de latencia de búsqueda (no promedios)
cr0x@server:~$ curl -s http://localhost:9200/_nodes/stats/indices/search?pretty | egrep -A3 "query_total|query_time_in_millis"
"query_total" : 9012331,
"query_time_in_millis" : 81233122,
"query_current" : 37
Qué significa: Las estadísticas agregadas muestran carga, no la latencia de cola. Pero un query_current alto bajo carga sugiere saturación.
Decisión: Correlaciona con p95/p99 de la aplicación y métricas de rechazos del threadpool a continuación. No declares victoria por una media.
Task 9: Comprobar rechazos del threadpool (síntoma clásico “está lento”)
cr0x@server:~$ curl -s http://localhost:9200/_nodes/stats/thread_pool/search?pretty | egrep -A6 "\"search\"|rejected"
"search" : {
"threads" : 13,
"queue" : 1000,
"active" : 13,
"rejected" : 21844,
"largest" : 13,
"completed" : 99123312
}
Qué significa: Las peticiones de búsqueda están siendo rechazadas. Los usuarios ven timeouts y fallos intermitentes.
Decisión: Escala horizontalmente, reduce el coste de las consultas (filtros, fielddata, paginación profunda), añade caching o aplica throttling. Los rechazos significan que estás fuera de capacidad ahora mismo.
Task 10: Detectar consultas lentas en Elasticsearch vía slowlog
cr0x@server:~$ sudo tail -n 5 /var/log/elasticsearch/search-prod_index_search_slowlog.log
[2025-12-30T10:21:41,902][WARN ][index.search.slowlog.query] [es-data-2] [products_v42][1] took[2.8s], took_millis[2803], total_hits[200000], search_type[QUERY_THEN_FETCH], source[{"query":{"match":{"body":{"query":"wireless headphones","operator":"and"}}},"sort":["_score",{"updated_at":"desc"}],"from":0,"size":10000}]
Qué significa: Paginación profunda (size:10000) más ordenación es costoso y a menudo innecesario.
Decisión: Implementa search_after o limita size, devuelve menos resultados o usa un endpoint de navegación separado con ordenaciones precomputadas.
Task 11: Comprobar conteo de segmentos y presión de merges
cr0x@server:~$ curl -s http://localhost:9200/_cat/segments/products_v42?v | head
index shard prirep segment generation docs.count size committed searchable version compound
products_v42 0 p _0 0 5123 9.8mb true true 9.11.1 true
products_v42 0 p _1 1 4981 9.5mb true true 9.11.1 true
Qué significa: Muchos segmentos pequeños pueden implicar refreshes frecuentes y merges intensos después.
Decisión: Para indexación pesada, aumenta el intervalo de refresh, agrupa actualizaciones por lotes o usa indexado bulk. Deja que los merges respiren.
Task 12: Validar intervalo de refresh del índice (frescura vs throughput)
cr0x@server:~$ curl -s http://localhost:9200/products_v42/_settings?pretty | egrep -A3 "refresh_interval"
"refresh_interval" : "1s",
"number_of_shards" : "12",
"number_of_replicas" : "1"
Qué significa: 1s de refresh es agresivo. Genial para frescura, malo para throughput de indexación y churn de segmentos.
Decisión: Si no necesitas frescura de 1 segundo, muévete a 5–30s y mide mejoras en ingestión y latencia de consulta.
Task 13: Confirmar problemas de mapping que causan consultas costosas
cr0x@server:~$ curl -s http://localhost:9200/products_v42/_mapping?pretty | egrep -n "\"name\"|\"type\"|\"keyword\"" | head
34: "name" : {
35: "type" : "text"
36: },
78: "category" : {
79: "type" : "text"
80: }
Qué significa: category es solo text; filtrar/agregar sobre ella será lento o requerirá fielddata (mala noticia).
Decisión: Añade subcampos keyword para filtros/aggregations exactos. Planea un reindex con corte de alias.
Task 14: Medir retraso de indexación (salud del pipeline)
cr0x@server:~$ mysql -e "SELECT MAX(updated_at) AS db_latest, (SELECT MAX(indexed_at) FROM search_index_audit) AS indexed_latest\G"
*************************** 1. row ***************************
db_latest: 2025-12-30 10:27:12
indexed_latest: 2025-12-30 10:26:02
Qué significa: El índice está ~70 segundos detrás de las escrituras en la BD (asumiendo que indexed_at registra actualizaciones de índice exitosas).
Decisión: Si tu SLO de frescura es 30s, esto es un incidente. Investiga backlog del indexador, fallos bulk o throttling de ingestión en ES.
Task 15: Comprobar sistema de ficheros y latencia de disco en nodos de datos
cr0x@server:~$ iostat -x 1 3
Device r/s w/s rkB/s wkB/s await svctm %util
nvme0n1 210.3 98.2 8123.4 4312.8 18.42 0.62 97.8
Qué significa: El disco está casi saturado, con await elevado. Búsqueda y merges demandan mucho IO.
Decisión: Aumenta throughput de almacenamiento (más nodos, discos más rápidos), reduce el conteo de shards y evita indexación pesada durante ventanas pico de consulta.
Guion de diagnóstico rápido
Cuando “la búsqueda está lenta”, quieres un triaje en tres pasadas: síntomas visibles para el usuario, saturación del backend y luego diseño de consulta/índice.
No empieces adivinando. Empieza por acotar.
Primero: confirma dónde se consume el tiempo
- Métricas de la aplicación: latencia p50/p95/p99 para el endpoint de búsqueda y tasas de error/timeouts.
- Desglose de dependencias: tiempo en BD vs tiempo en ES vs tiempo de rendering en la app. Si no tienes tracing, añádelo. Tus suposiciones son caras.
- Concurrencia: ¿cuántas peticiones en vuelo se están acumulando? El encolamiento es el asesino silencioso.
Si p50 está bien y p99 es horrible, tienes saturación, pausas de GC, contención de locks o un pequeño porcentaje de consultas patológicas.
Si todo está uniformemente lento, tienes restricciones sistémicas: disco, CPU o red.
Segundo: comprueba señales obvias de saturación
- MariaDB: slow query log, CPU, misses del InnoDB buffer pool, esperas de locks, lag de replicación (si usas réplicas para búsqueda).
- Elasticsearch: rechazos de threadpool, GC, uso de heap, utilización/latencia de disco, shards no asignados, nodos calientes.
- Infraestructura: IO de nodo (
iostat), CPU steal (virtualizado), drops/latencia de red entre app y nodos de búsqueda.
Tercero: aísla las formas de consulta costosas
- Paginación profunda (from/size) y ordenación sobre campos no analizados.
- Consultas con comodines y comodines al inicio en SQL.
- Aggregaciones de alta cardinalidad (ES) o predicados poco selectivos (SQL).
- Mappings incorrectos que fuerzan fielddata o scripting.
- Frescura demasiado agresiva (intervalo de refresh de ES muy bajo) mientras se indexa con intensidad.
Cuarto: decide si es un problema de capacidad o de diseño
Si hay rechazos del threadpool, saturación de disco o lag de réplica, tienes un problema de capacidad ahora.
Si las consultas son fundamentalmente caras (comodines, paginación profunda, baja selectividad), tienes un problema de diseño que la capacidad solo enmascarará.
Tres mini-historias corporativas (realistas, anonimizadas)
Mini-historia 1: el incidente provocado por una suposición errónea
Un marketplace mediano lanzó “sugerencias de búsqueda” porque producto quería que la UI se sintiera rápida. Ingeniería tomó el atajo obvio: una consulta MariaDB sobre la tabla products, WHERE name LIKE 'term%', con un índice en name.
Funcionó en staging. También funcionó en producción—hasta las 9 a.m.
La suposición errónea fue que “búsqueda por prefijo usa índice, así que es barata”. Eso es solo medio cierto. En producción,
los usuarios tecleaban prefijos cortos: “a”, “b”, “s”. Esos prefijos coincidían con grandes rangos. La consulta usó índice, sí, pero aún así
recorrió porciones masivas, hizo sorting y devolvió top-N. La latencia subió. La CPU subió. Las conexiones se acumulaban.
El checkout comenzó a agotar tiempo porque compartía la misma base de datos.
La respuesta al incidente fue clásica: escalar la BD verticalmente, añadir una réplica de lectura, añadir caching. El caching ayudó para los prefijos más populares, hasta que las promociones cambiaron lo que los usuarios tecleaban y el cache falló de nuevo. La réplica se retrasó, las sugerencias se volvieron inconsistentes y soporte recibió tickets sobre “productos desaparecidos.”
La solución no fue heroica. Separaron el caso de uso de búsqueda. El autocompletado pasó a Elasticsearch con un analizador n-gram.
Pusieron rate-limit a las sugerencias por usuario, debounced en frontend y un tope estricto en cantidad de resultados. MariaDB volvió
a ser buena en transacciones. La lección no fue “Elasticsearch es más rápido.” Fue “la forma de entrada del usuario importa más que la definición de tu índice.”
Mini-historia 2: la optimización que salió mal
Un portal interno de documentación usaba Elasticsearch y tenía latencias decentes—hasta que un ingeniero “optimizó la relevancia”. El plan:
aumentar campos en la consulta (título, encabezados, cuerpo, tags), añadir fuzziness, sinónimos y boostear documentos recientes.
Buenas metas. El DSL de consulta creció hasta una pequeña novela.
Lo lanzaron justo antes de una ola de onboarding en la compañía. El tráfico de búsqueda subió. De pronto las latencias p99 se volvieron segundos,
no milisegundos. Los nodos estaban “sanos”, pero los rechazos del threadpool de búsqueda subieron. Empezaron a aparecer pausas de GC. El clúster
no se cayó; simplemente se volvió educadamente inútil.
El fallo vino de unos pocos costes compuestos: fuzziness en múltiples campos, un mínimo de should-match bajo y un size grande porque la UI quería mostrar “muchos resultados” sin paginación. Cada consulta se expandía en muchas combinaciones de términos y sacaba ventanas de resultados grandes. CPU y heap subieron. Las caches de consulta fueron ineficaces porque cada consulta de usuario era única.
La recuperación fue tratar las features de relevancia como un presupuesto. Restringieron fuzziness a campos cortos, ajustaron minimum_should_match, redujeron size, eliminaron sorts caros e introdujeron un enfoque en dos fases: primero una recuperación barata, luego un re-ranking opcional para el top-N. La relevancia mejoró y el clúster sobrevivió al onboarding.
La moraleja: “mejor relevancia” puede ser un ataque de denegación de servicio que te autoescribes. Hazlo medible y acotado.
Mini-historia 3: la práctica aburrida pero correcta que salvó el día
Un sitio retail ejecutaba búsqueda híbrida: MariaDB para la verdad, Elasticsearch para búsqueda. Nada extravagante. Lo que sí tenían era
una tabla outbox en MariaDB, procesada por una pequeña flota de workers. Cada actualización de índice era un job. Cada job tenía reintentos.
Cada fallo acababa en un bucket de dead-letter con contexto suficiente para reproducir.
Una tarde, un despliegue introdujo un cambio de mapping que rechazó documentos para un subconjunto de productos—solo aquellos con
un atributo raro. El worker empezó a fallar esos jobs repetidamente. Sin cortafuegos, esto habría sido un backlog sin fin, luego un incidente de “falta de productos en búsqueda” y luego una noche larga.
En cambio, los controles aburridos entraron en acción. El worker tenía un circuit breaker: tras N fallos para un job, lo ponía en cuarentena.
Las alertas de retraso de indexado saltaron porque el SLO de frescura se rompió. On-call pudo ver inmediatamente qué documentos fallaron y por qué,
porque la entrada outbox almacenaba el ID de entidad y el tipo de operación.
Revertieron el cambio de mapping, reprodujeron los jobs en cuarentena y volvieron al estado estable sin adivinar.
Sin heroicidades, sin arqueología SQL manual. Solo un ledger y un bucle de reintento.
La lección: tu pipeline de indexación es un sistema distribuido. Trátalo como tal, incluso si es “solo búsqueda.”
Errores comunes: síntoma → causa raíz → solución
1) Síntoma: la búsqueda se queda intermitentemente sin respuesta durante picos de tráfico
Causa raíz: La búsqueda comparte recursos de MariaDB con OLTP; los pools de conexiones se saturan; las búsquedas lentas se ponen detrás de las escrituras.
Solución: Aísla la carga de búsqueda (Elasticsearch o clúster DB separado), aplica timeouts a las consultas e implementa límites de tasa para autocompletado.
2) Síntoma: “añadimos una réplica y empeoró”
Causa raíz: El lag de réplica causa resultados inconsistentes; los reintentos de la app amplifican la carga; las expectativas read-after-write se rompen.
Solución: Deja de usar réplicas como motores de búsqueda. Si debes hacerlo, acepta explícitamente la desactualización y muéstrala en la UX; de lo contrario construye un índice.
3) Síntoma: Elasticsearch está “sano” pero las consultas son lentas
Causa raíz: Saturación del threadpool, shards calientes o pausas de GC; la salud del clúster no captura rendimiento.
Solución: Revisa rechazos del thread_pool, presión de heap, IO de disco y distribución de shards; escala o reduce el coste de consulta.
4) Síntoma: filtros/aggregaciones son dolorosamente lentos en Elasticsearch
Causa raíz: Filtrado sobre campos text, ausencia de mapping keyword; habilitar fielddata para “arreglar”lo, explotando el heap.
Solución: Añade subcampos keyword, reindexa y elimina hacks basados en fielddata.
5) Síntoma: el indexado se ralentiza justo cuando el tráfico alcanza su pico
Causa raíz: Intervalo de refresh demasiado bajo, merges pesados y carga de consulta simultánea; el IO se contiende.
Solución: Aumenta el intervalo de refresh, usa bulk/lot-indexing, programa reindexados pesados fuera de horas pico y asegúrate de que los nodos datos tengan margen de IO.
6) Síntoma: “faltan elementos nuevos en los resultados de búsqueda”
Causa raíz: Retraso en el pipeline de índices, jobs fallidos o rechazos silenciosos por mapping.
Solución: Monitorea explícitamente el retraso de frescura, implementa dead-letter/quarantine, alerta sobre documentos rechazados y proporciona herramientas de reintento.
7) Síntoma: los cambios de relevancia rompen inesperadamente tras un deploy
Causa raíz: Cambios de analyzer/mapping requieren reindex; cambios parciales generan comportamiento mixto entre segmentos y campos.
Solución: Usa índices versionados + corte de alias. Nunca “modifiques las ruedas mientras conduces” cambiando analizadores in situ.
8) Síntoma: CPU de MariaDB se dispara por búsqueda, incluso con FULLTEXT
Causa raíz: Consultas de baja selectividad, alta concurrencia o carga mezclada; FULLTEXT todavía consume CPU y memoria.
Solución: Mueve la búsqueda afuera, o restringe la búsqueda (longitud mínima, filtros), añade caching para consultas frecuentes y limita la concurrencia.
Listas de verificación / plan paso a paso
Paso a paso: decidir si Elasticsearch es obligatorio
- Lista los comportamientos de búsqueda requeridos: autocompletado, tolerancia a errores tipográficos, sinónimos, multilenguaje, consultas de frase, boosting, señales de ranking personalizadas.
- Mide el perfil de consultas: QPS, p95/p99, concurrencia y si los usuarios generan múltiples consultas por interacción.
- Revisa el impacto en BD: ¿aparecen las consultas de búsqueda entre los principales consumidores de tiempo? ¿Se ven afectados endpoints OLTP durante picos de búsqueda?
- Define requisito de frescura: ¿1–30 segundos es aceptable? Si sí, desacopla el indexado. Si no, planifica una ruta de lectura híbrida o reevalúa el alcance.
- Decide el aislamiento: si OLTP es crítico para el negocio, aisla la búsqueda por defecto.
- Estima el presupuesto operacional: on-call, cadencia de upgrades, estrategia de snapshots y planificación de capacidad para shards/heap/disk.
Paso a paso: implementar arquitectura híbrida sin volverla frágil
- Define el modelo de documento: desnormaliza los campos necesarios para búsqueda y para renderizar la página de resultados.
- Elige un mecanismo de indexación: outbox o CDC. Prefiere outbox si quieres manejo de fallos controlable.
- Construye idempotencia: indexar la misma actualización de entidad dos veces debe ser seguro.
- Añade tablas/ métricas de auditoría: rastrea timestamp más reciente indexado y conteos de fallos.
- Usa índices versionados:
items_v1,items_v2con un aliasitems_current. - Plan de reindexado: construcción en background, dual-write (opcional), luego corte de alias y desmantelamiento del índice antiguo.
- Guardarraíles: limita tamaños de resultado, throttling en autocompletado, bloquea paginación profunda y establece timeouts de consulta.
- UX de fallo: si ES está caído, decide si mostrar fallback (búsqueda limitada en BD) o un error amigable. Elige uno y pruébalo.
Checklist operativo: evita que Elasticsearch se convierta en una casa encantada
- Política de dimensionado de shards (evita shards minúsculos; evita demasiados).
- Dashboards para: rechazos de threadpool, uso de heap, GC, latencia de disco, tasa de indexación, tiempo de refresh/merge.
- Estrategia de snapshots y calendario de pruebas de restauración.
- SLOs explícitos: query p95/p99 y retraso de frescura en indexado.
- Runbooks para: shards no asignados, nodos calientes, documentos rechazados, corte de reindex.
Preguntas frecuentes (FAQ)
1) ¿Puede MariaDB FULLTEXT reemplazar a Elasticsearch para un sitio pequeño?
Sí, si la “búsqueda” es monoidioma, de baja QPS con expectativas de relevancia modestas y sin autocompletado.
El momento en que producto pide sinónimos, fuzziness o boosting multi-campo, planifica la migración.
2) ¿Elasticsearch es solo para “big data”?
No. Elasticsearch es para la complejidad del comportamiento de búsqueda y el aislamiento de la carga. Conjuntos de datos pequeños aún pueden justificarlo si la UX depende de relevancia y typeahead.
3) ¿Cuál es la diferencia operacional más grande entre búsqueda en MariaDB y Elasticsearch?
La búsqueda en MariaDB compite con escrituras y transacciones; la búsqueda en Elasticsearch compite con indexación y merges.
En ambos casos gestionas contención—pero en Elasticsearch puedes aislarla del OLTP.
4) ¿Por qué no simplemente añadir más réplicas de MariaDB para búsqueda?
Las réplicas intercambian CPU por lag de replicación y complejidad operativa. Está bien para escalar lecturas de consultas predecibles.
Las consultas de búsqueda suelen ser impredecibles y de baja selectividad, y las réplicas no arreglan las limitaciones de relevancia.
5) ¿Cómo manejo permisos (ACLs) en Elasticsearch?
O bien filtras por campos de permiso en la consulta (el documento contiene tenant/org/visibility), o usas índices por tenant si el número de tenants es pequeño.
Evita índices por usuario a menos que disfrutes fines de semana largos. Para ACLs complejas, considera devolver IDs candidatos y luego aplicar permisos en la capa de aplicación.
6) ¿Cómo reindexo sin downtime?
Usa índices versionados más un alias. Construye items_v2, backfill, luego mueve atómicamente el alias items_current de v1 a v2.
Mantén v1 para rollback hasta tener confianza.
7) ¿Cuál es el error de escalado más común en Elasticsearch?
Demasiados shards. Aumenta el coste de coordinación, la sobrecarga de memoria y el tiempo de recuperación. Menos shards bien dimensionados ganan a muchos shards pequeños en la mayoría de entornos productivos.
8) ¿Los resultados de búsqueda deben hidratarse desde MariaDB o servirse completamente desde Elasticsearch?
Si hidratas cada fila de resultado desde MariaDB, corres el riesgo de convertir una búsqueda en N+1 queries bajo carga.
Almacena suficientes campos en Elasticsearch para renderizar la página de resultados. Hidrata solo cuando el usuario accede a la página de detalle.
9) ¿Puedo usar Elasticsearch como sistema de registro (system of record)?
Puedes, pero generalmente no deberías. Elasticsearch no está optimizado para integridad relacional y flujos transaccionales.
Úsalo como índice derivado y mantiene la verdad en MariaDB (u otro almacén transaccional).
10) ¿Qué frescura puedo esperar realísticamente con Elasticsearch?
Típicamente segundos a decenas de segundos, dependiendo del intervalo de refresh, carga de indexación y diseño del pipeline.
Si necesitas “buscable inmediatamente”, aceptarás mayor coste de indexación o implementarás una ruta de lectura híbrida.
Conclusión: próximos pasos que puedes ejecutar
Si tu búsqueda en sitio es esencialmente filtrado estructurado con coincidencias por palabra ocasionales, MariaDB puede soportarla—especialmente
si mantienes las consultas selectivas y evitas fantasías de comodines. Pero si la búsqueda es una característica de producto con autocompletado, tolerancia a errores tipográficos,
ajuste de relevancia y ranking multi-campo, el clúster de búsqueda no es un lujo. Es el límite correcto.
Pasos prácticos:
- Ejecuta los diagnósticos: identifica si MariaDB está pagando la factura de búsqueda hoy (consultas lentas, CPU, presión de buffer).
- Decide el aislamiento: si OLTP y búsqueda comparten recursos, sepáralos antes de que el próximo pico de tráfico lo haga por ti.
- Diseña el pipeline: elige outbox o CDC, define SLO de frescura y construye reintentos + cuarentena.
- Implementa índices versionados: los cortes de alias son la forma de evitar que el drama de reindexado se convierta en downtime.
- Establece guardarraíles: limita ventanas de resultado, aplica throttling en autocompletado y prohíbe la paginación profunda en tus contratos API.
Construye la búsqueda como si esperases que la usen humanos a escala: impacientes, creativos y todos a la vez. Eso no es pesimismo. Eso es operaciones.