En este articulos vamos a hablar de que es Elasticsearch , que es lucene , su comparacion con Solr y Opensearch y una explicacion detallada del proceso de busqueda de una query asi como tambien sobre el algoritmo de busqueda utilizado y algunos tipos para mejorarlo.
Qué es Elasticsearch?
Elasticsearch es un motor de búsqueda y análisis distribuido, altamente escalable, diseñado para trabajar con grandes volúmenes de datos estructurados, semiestructurados y no estructurados. Se construye sobre Apache Lucene y lo expone de forma mucho más accesible y robusta a través de APIs RESTful, índices distribuidos y un ecosistema completo de ingestión, análisis y visualización.
Esto lo convierte en un estándar de facto en el mundo del procesamiento de logs, observabilidad, motores de búsqueda internos de aplicaciones, e-commerce, analítica en tiempo casi real y sistemas de inteligencia operacional.
En esencia, Elasticsearch es una capa distribuida que toma a Lucene —una librería escrita en Java que implementa los algoritmos fundamentales de indexación, búsqueda de texto completo, relevancia y scoring— y le añade capacidades que Lucene por sí solo no posee:
Clustering
Replicación
Particionamiento mediante shards
Tolerancia a fallos
Balanceo de carga
APIs de administración
Tipado flexible de documentos JSON
Control de ciclo de vida de índices
Seguridad y monitoreo
De esta manera, se convierte en una herramienta industrial lista para producción y altamente extensible.
Elasticsearch vs. Solr
Esta idea de “envolver” a Lucene con una arquitectura distribuida no es exclusiva de Elasticsearch. Apache Solr, otro de los grandes motores de búsqueda construidos también sobre Lucene, persigue un objetivo semejante, pero lo hace desde una perspectiva distinta.
Mientras Solr históricamente estuvo más orientado a configuraciones manuales mediante XML, a una personalización fina del análisis lingüístico y a escenarios donde la relevancia y la semántica de las búsquedas eran críticas, Elasticsearch adoptó desde sus inicios un enfoque de usabilidad más simple: configuración vía JSON, APIs REST y una curva de adopción más amigable para desarrolladores web y de sistemas distribuidos.
Esto explica en gran parte su enorme popularidad en escenarios de logging y analítica masiva.
Aunque ambos comparten el mismo “corazón” —Lucene y su índice invertido, postings lists, segmentos inmutables, garbage collection interno de segmentos viejos y el modelo de consulta booleano—, difieren en filosofía y experiencia de usuario.
Elasticsearch vs. OpenSearch
La historia más reciente introduce un tercer jugador: OpenSearch.
Este surge como un fork comunitario de Elasticsearch y Kibana impulsado por AWS tras el cambio de licencia de Elastic hacia SSPL/Elastic License 2.0, que limitó ciertos usos gratuitos en entornos SaaS.
OpenSearch hereda directamente el código de Elasticsearch 7.x y mantiene la compatibilidad de APIs. En esencia, es “otro Elasticsearch” pero con licencia Apache 2.0, gobernado por una comunidad abierta y con fuerte apoyo de Amazon para integrarlo en sus servicios administrados como Amazon OpenSearch Service.
A nivel técnico, OpenSearch y Elasticsearch son extremadamente parecidos:
Ambos continúan usando Lucene en el backend.
Ambos mantienen el concepto de índices, shards, réplicas, mappings y analyzers.
Ambos gestionan translogs y buffers en memoria antes de persistir segmentos en disco.
Ambos exponen los documentos tras un refresh.
Ambos implementan algoritmos de scoring como TF-IDF o BM25 gracias a Lucene.
Ambos resuelven queries distribuidas mediante nodos coordinadores.
La diferencia real no está en el motor base, sino en el modelo de licenciamiento, la velocidad de evolución de features y la estrategia de gobernanza.
Lucene como núcleo común
Para entender mejor esta relación, hay que visualizar a Lucene como el “micro-núcleo” de indexación y búsqueda:
Un motor de propósito general.
Extremadamente eficiente.
Capaz de organizar texto y datos en índices invertidos.
Responsable de permitir búsquedas rápidas y precisas.
Sin embargo, Lucene no es distribuido ni ofrece clustering ni APIs listas para producción.
Por eso, Solr, Elasticsearch y OpenSearch pueden verse como “distribuciones de Lucene”, cada una con un ecosistema distinto:
Solr → Gobernado por Apache Software Foundation, modular y tradicional.
Elasticsearch → Gobernado por Elastic NV, con un stack integrado (ELK/Elastic Stack).
OpenSearch → Gobernado por AWS y la comunidad, con licencia Apache 2.0.
Flujo de búsqueda en Elasticsearch
Nota: esta seccion describe el flujo completo de una búsqueda de texto en Elasticsearch y, al final, profundiza en el scoring (BM25/TF-IDF), su tunning, implicaciones distribuidas y técnicas para depuración.
1) Recepción de la petición (cliente → coordinador)
Entrada: el cliente envía una petición HTTP/REST (
GET /index/_search
oPOST /index/_search
con cuerpo JSON).Nodo receptor = coordinating node: cualquier nodo puede recibir la petición y actuar como coordinador. El coordinating node valida la sintaxis JSON y la estructura del Query DSL y aplica parámetros globales (timeout, preference, routing si se envía).
Operaciones comunes en el coordinador: interpretar headers, gestionar autenticación/autorización, aplicar shard preference (ej.
_local
,_primary
), y decidir si se usaquery_then_fetch
odfs_query_then_fetch
.query_then_fetch
(default): cada shard calcula puntuaciones con sus estadísticas locales.dfs_query_then_fetch
: fase DFS (Distributed Frequency Search) recoge estadísticas globales (docFreq/termFreq) antes de la búsqueda para que los scores sean comparables entre shards.
2) Análisis y parsing del Query DSL (coordinador)
Parseo del DSL → estructura interna: Elasticsearch traduce el JSON DSL a objetos Query internos.
Rewrite y normalización: consultas multi-term (wildcard, prefix, regexp) se rewriten a formas explícitas (expansión en múltiples term queries o autómatas).
Análisis de texto (match/query_string): el texto se pasa por el analyzer del campo objetivo (tokenizer + token filters: lowercasing, stopwords, stemming, synonyms, asciifolding, etc.).
Comprobación de DFS: si se eligió
dfs_query_then_fetch
, el coordinador planifica la fase DFS previa.
3) Shard routing
Determinación de shards: el coordinador decide qué shards deben participar: todos los shards relevantes o un subconjunto si se usó
?routing=...
opreference
.Balancing primario/replica: el coordinador puede seleccionar réplicas para distribuir carga de lectura.
4) Fase DFS — recolección de estadísticas globales
Objetivo: sincronizar estadísticas (docFreq, totalTermFreq) entre shards para que el cálculo de IDF (y por ende los scores) sea comparable globalmente.
Coste/beneficio: mejora consistencia en ranking, incrementa latencia por una ronda extra de network I/O.
5) Scatter: envío de la query a los shards (query phase)
Fan-out: el coordinador envía la query final a cada shard seleccionado.
Per-shard parsing: el shard convierte la query en objetos Lucene
Query
y realiza optimizaciones (conversión debool
, filtros en bitsets, etc.).Ejecución por segmentos: la búsqueda recorre cada segmento (inverted index / postings) y usa collectors (TopK), técnicas WAND/Block-Max, skip pointers y caches.
6) Combinar por shard: top-K local y retorno de resultados parciales
TopK local: cada shard devuelve sus resultados locales (docID local,
_score
, sort values).Eficiencia: no se transfiere
_source
en esta fase; se devuelve metainformación suficiente para el fetch posterior.
7) Map/Reduce,merge y orden global
Merge sort: el coordinador combina los topN locales en el ranking global.
Aggs map/reduce: para agregaciones, cada shard calcula buckets/metrics y el coordinador reduce combinando buckets por key.
8) Fetch phase
Fetch deferred: solo los documentos finales son solicitados con
_source
/stored_fields; se evitan lecturas innecesarias.Fuentes:
_source
, stored fields,doc_values
(columnar para sorting/agg), fielddata (heap, evitarlo).
9) Highlighting / suggesters / scripted fields
Highlighting: puede re-analizar o requerir term vectors/offsets.
Suggesters y script_score: estructuras adicionales (FSTs para suggesters) y ejecución de scripts por hit aumentan coste CPU/I/O.
10) Resultado final
Construcción JSON:
hits.total
,hits.hits
(con_id
,_score
,_source
),aggregations
,took
,_shards
.Errores/timeout:
timed_out
,_shards.failed
.Profiling:
took
yprofile
API para desglose por fases.
11) Consideraciones de rendimiento
Preferir
search_after
para deep pagination.Usar
doc_values
ykeyword
para sorting/aggs.Evitar
_source
completo en listados voluminosos.Usar filter context para clausulas no-scoring.
Reducir uso de
wildcards
/regex costosos.
12) Herramientas para inspección y profiling
Profile API para desglose por shard/fase.
Explain API para entender el cálculo de
_score
en un documento._analyze para ver tokens generados por un analyzer.
search_type=dfs_query_then_fetch para scores comparables.
13) Casos complejos: nested, parent/child, NRT
Nested: cada nested doc indexado como documento interno; búsquedas usan inner_hits.
Parent/Child: block join collectors.
NRT: Near-Realtime: visibilidad tras
refresh
(default ~1s).
14) Resumen conceptual
Coordinador recibe y parsea la query.
Determina shards objetivo y (opcional) ejecuta DFS.
Scatter: cada shard ejecuta búsqueda en segmentos Lucene.
Reduce: coordina y ordena resultados, reduce aggregations.
Fetch: recuperar
_source
solo para los hits finales.Entrega JSON al cliente.
Scoring (relevancia)
1) Concepto: ¿qué es el score y para qué sirve?
El _score
es un número relativo que refleja cuán relevante es un documento ante una consulta. No es una probabilidad absoluta: es una métrica comparativa usada para ordenar resultados. En Lucene/Elasticsearch el scoring combina factores como la frecuencia del término en el documento (TF), la rareza global del término (IDF), la longitud del campo (normalización), coincidencia de campos con boosts y otros factores (por ejemplo queryBoost
, fieldboost
, function_score).
2) Evolución histórica: TF-IDF → BM25
TF-IDF clásico (Lucene ClassicSimilarity): TF (term frequency) * IDF (inverse document frequency) * fieldNorm. TF podía usar la raíz cuadrada o logaritmo; IDF se calculaba como
log(numDocs / docFreq)
. ClassicSimilarity incluía además coordinación (número de términos coincidentes).BM25 (Okapi BM25): actualmente es el default en Lucene/Elasticsearch porque ofrece mejor comportamiento práctico en búsquedas de texto. BM25 introduce saturación en TF y una normalización por longitud más controlada mediante parámetros
k1
(saturación del TF) yb
(grado de normalización por longitud).
3) Fórmula BM25 (versión canónica)
Para un término t
en documento d
y colección D
:
score(d, q) = sum_{t in q} IDF(t) * ((tf_{t,d} * (k1+1)) / (tf_{t,d} + k1 * (1 - b + b * (len_d / avgLen))))
Donde:
tf_{t,d}
= número de ocurrencias del términot
en el documentod
(term frequency).len_d
= longitud del campo (p. ej. número de tokens del campobody
end
).avgLen
= longitud promedio del campo en la colección.k1
= parámetro de saturación de TF (comúnmente ~1.2–1.5). Sik1 = 0
entonces TF queda anulado; si muy alto, TF escala casi linealmente.b
= parámetro de normalización por longitud entre 0 y 1 (0 = no normalizar por longitud, 1 = normalización completa). Valores típicos:b ≈ 0.75
.IDF(t)
= función de rareza; una forma común:
IDF(t) = log( (N - df_t + 0.5) / (df_t + 0.5) )
donde
N
= número total de documentos en la colección;df_t
= número de documentos que contienent
(document frequency).
Interpretación:
Si
tf_{t,d}
crece, el numerador aumenta pero el denominador también; la fórmula aplica saturación: grandes cuentas detf
no hacen crecer el score indefinidamente (evita que documentos muy largos/duplicados dominen).b
ajusta penalización por longitud: documentos más largos tienen menor peso por token cuandob
alto.IDF
penaliza términos comunes (df alto) y favorece términos raros.
4) Parámetros BM25: k1 y b — efectos y tuning
k1 (TF saturation): controla cuán fuertemente aumenta el score con TF.
k1 ≈ 0
: TF tiene poco efecto → casi como boolean scoring.k1 ≈ 1.2–1.5
: valores por defecto en muchas implementaciones (buen balance).k1
alto: favorece docs con alta frecuencia del término (útil si repetición es significado de importancia).
b (length normalization): controla corrección por longitud del campo.
b = 0
: sin normalización por longitud; un token tiene mismo peso sin importar longitud del documento.b = 1
: normalización completa por longitud.b ≈ 0.75
: valor típicamente recomendado.
Tuning práctico:
En colecciones con documentos muy heterogéneos en longitud (p. ej. títulos vs artículos largos), ajustar
b
reduce sesgo hacia documentos largos.Si la repetición de keywords dentro de un doc es signo fuerte de relevancia (p. ej. logs con many occurrences), aumentar
k1
puede ayudar.
Cómo cambiar BM25 params en un índice:
PUT /mi-indice
{
"settings": {
"index": {
"similarity": {
"mi_bm25": {
"type": "BM25",
"k1": 1.2,
"b": 0.75
}
}
}
},
"mappings": {
"properties": {
"body": {
"type": "text",
"similarity": "mi_bm25"
}
}
}
}
5) Otros factores que afectan el score en Lucene/ES
fieldNorms (normas de campo)
En Lucene, cada campo almacenaba una norm (antes en PermGen/now metaspace/norms) que codifica un factor de normalización por documento para ese campo (principalmente debido a la longitud del campo y boosts de campo en index time).
Puedes omitir norms (
"norms": false
) para ahorrar espacio si no necesitas length normalization (pero pierdes ajuste por longitud).
Boosts (index-time y query-time)
Index-time boosting: histórico, se aplicaba boost al indexar un documento/campo. Hoy es menos usado (se prefiere normalizar datos o usar function_score).
Query-time boosting:
^2.0
por ejemplo enmatch
omulti_match
para aumentar peso de un campo.
{
"multi_match": {
"query": "error crítico",
"fields": ["title^3", "body"]
}
}
Coordination factor (coord)
En modelos booleanos puros, la coordinación penaliza documentos que coinciden en menos términos del query; en BM25 moderno esto no siempre se aplica por defecto, pero en queries multi-term puedes emplear
minimum_should_match
.
Document length (len_d) y avgLen
Importantes para normalización; Lucene guarda estadísticas necesarias y BM25 usa
avgLen
calculado por campo.
Doc values, stored fields y fielddata
doc_values no afectan scoring directo, pero si se usan para sorting/aggs permiten lecturas eficientes.
fielddata (cargar text a heap) impacta memory usage y puede afectar performance si abusas de sorting/agg sobre fields no keyword.
6) Scoring distribuido: implicaciones y DFS
Problema: IDF y field norms dependen de estadísticas globales (N, df_t, avgLen). Sin coordinación previa, cada shard calcula IDF local (basado en docs dentro del shard) lo que produce scores que no son inmediatamente comparables entre shards.
Solución parcial:
dfs_query_then_fetch
(DFS) recopila docFreq de cada shard antes de la query principal; el coordinador calcula estadística agregada y la reenvía, permitiendo que cada shard calcule scores con IDF global.Costo: DFS añade una ronda de comunicación extra (latency). En la práctica,
query_then_fetch
es suficiente para la mayoría de casos; usar DFS cuando el ranking es crítico y la mejora justifica la latencia.
7) Optimización práctica del scoring
Evitar mezclar demasiados campos sin ponderación: usar
multi_match
confields
y boosts controlados.Usar
minimum_should_match
para queries de múltiples términos para evitar matches débiles.Tener cuidado con
function_score
: sumar factores (recency, popularity) puede desplazar el ranking; normaliza outputs.Usar rescoring: para queries que necesitan re-ranking costoso (p. ej. re-ranking semántico con embeddings), ejecutar una primera búsqueda barata (
query_then_fetch
) y luego re-scorear top100 con modelos más costosos usando_rescore
o a nivel de aplicación.Ejemplo
rescore
:
{
"query": { "match": { "body": "error crítico" } },
"rescore": {
"window_size": 100,
"query": {
"rescore_query": {
"match_phrase": { "body": "error crítico" }
},
"query_weight": 0.7,
"rescore_query_weight": 2.0
}
}
}
8) Function score: mezclar relevancia con señales no-textuales
function_score
permite combinar el score textual con funciones (decays, scripts, random). Ejemplos prácticos:
Decay function (recency):
{
"query": {
"function_score": {
"query": { "match": { "body": "error" } },
"functions": [
{
"gauss": {
"publish_date": {
"origin": "now",
"scale": "7d",
"decay": 0.5
}
}
}
],
"score_mode": "sum",
"boost_mode": "multiply"
}
}
}
Favorece documentos recientes combinando score textual con decay por fecha.
Scripted score (custom):
{
"query": {
"function_score": {
"script_score": {
"script": {
"source": "Math.log(2 + doc['views'].value) * _score"
}
}
}
}
}
Multiplica el
_score
por una función deviews
.
Precaución: script_score
corre en cada hit evaluado; si se ejecuta en shards con mucha carga puede subir CPU y GC.
9) Rescoring semántico / reranking con modelos externos
Flujo común: buscar topK con ES (BM25), luego re-rank con inferencia de embedding o modelo ML (puede ser en proceso separado o usando
rank_features
/script_score).Ventaja: reduce coste de inferencia al aplicarla solo sobre candidatos.
10) Depuración y explicación de scores en ES
Explain API
Proporciona un desglose por término que muestra cómo se calculó el score para un documento:
GET /mi-indice/_explain/ID
{
"query": { "match": { "body": "error crítico" } }
}
Resultado: árbol de explicación con contributions, TF, IDF y boosts. Útil para ver por qué un doc obtuvo mayor score que otro.
Profile API
Da tiempos por fase y por shard; muestra cómo se ejecutaron collectors, si se usaron WAND, etc. No es el desglose del cálculo de BM25 pero sí permite ver hotspots de CPU y latencia por shard/segment.
Interpretación práctica
Si un documento con muchas repeticiones domina el topN: revisar
k1
.Si documentos largos puntúan demasiado alto: revisar
b
o desactivarnorms
donde sea apropiado.Si ranking difiere entre shards notablemente: probar
dfs_query_then_fetch
y comparar.
11) Configuraciones a nivel índice que impactan scoring
Similarity en mappings: asignar
BM25
custom por campo (ver ejemplo arriba).Omit norms:
"norms": false
quita normalización por longitud; puede ahorrar memoria pero altera scoring.index_options: definir si almacenar
positions
,offsets
,term frequencies
(affects highlighting and phrase scoring).Por ejemplo,
index_options: positions
necesario para phrase queries y highlighting preciso.
Field types:
text
vskeyword
:text
se analiza y es relevante para scoring;keyword
no se analiza y normalmente no participa en BM25.
12) Rendimiento: técnicas internas que afectan scoring
WAND / Block-Max: optimizan queries disyuntivas acelerando el topK; afectan el orden en casos frontera si no se usan estadísticos completos.
Caching: query cache y request cache ayudan si las queries son repetidas y la segmentación es estable; el cache almacena resultados de filtros o de requests completos para índices inmutables.
13) Ejemplos rápidos (comandos prácticos)
Ver tokens (análisis):
POST /mi-indice/_analyze
{ "field": "body", "text": "Error crítico en servidor" }
Profilear una búsqueda:
GET /mi-indice/_search
{
"profile": true,
"query": { "match": { "body": "error crítico" } }
}
Explain para un doc:
GET /mi-indice/_explain/123
{
"query": { "match": { "body": "error crítico" } }
}
Cambiar BM25 params:
(Ver ejemplo de mapping arriba)
14) Resumen práctico y recomendaciones
BM25 es el punto de partida: entender
k1
yb
es crucial.DFS corrige inconsistencias de scoring en entornos distribuidos, pero tiene coste en latencia; úsalo cuando el ranking debe ser muy consistente.
Usa explain/profile para depurar casos extremos.
Evita realizar funciones costosas por documento en la fase fetch o en scripts sin limitarlas a topK.
Rescoring es la estrategia adecuada cuando quieres aplicar modelos costosos (semánticos) sin afectar latencia global.
Conclusión
El scoring en Elasticsearch/Lucene no es magia: es un conjunto de fórmulas y estadísticas (TF, DF, norms, avgLen) que se combinan con parámetros y transformaciones. Dominar BM25 y las herramientas de depuración (explain/profile) es la forma más directa de controlar resultados relevantes en producción. Además, comprender el tradeoff entre consistencia de scores (usar DFS) y latencia es clave en sistemas distribuidos donde la comparación entre shards puede alterar el ordenamiento final.
En la siguiente parte hablaremos del flujo de escritura, replicacion e indexado, asi de trucos para optimizar dicho proceso .
Fuentes recomendadas :
https://lucene.apache.org/core/
https://solr.apache.org/guide/solr/latest/getting-started/introduction.html
https://www.elastic.co/docs
https://www.oreilly.com/library/view/elasticsearch-7-quick/9781789803327/