Indexación y Actualización de Documentos con la API REST de Elasticsearch

Domina las operaciones básicas Crear, Leer, Actualizar, Eliminar (CRUD) en Elasticsearch usando la API REST. Esta guía detalla las solicitudes HTTP precisas, endpoints y cargas JSON necesarias para indexar nuevos documentos (con o sin IDs especificados) y realizar actualizaciones parciales y granulares en registros existentes. Aprende ejemplos prácticos con `curl` para actualizaciones atómicas, modificaciones mediante scripts e ingesta eficiente de datos por lotes.

Indexación y Actualización de Documentos con la API REST de Elasticsearch

Indexar y actualizar documentos con la API REST de Elasticsearch parece fácil al principio: envía JSON a un endpoint y recibe JSON de vuelta. Las partes que importan en producción son más específicas. ¿Estás creando un documento nuevo o reemplazando uno antiguo? ¿Quieres que Elasticsearch genere el ID, o necesitas un ID de tu sistema de origen? ¿Estás haciendo una actualización parcial, o estás sobrescribiendo accidentalmente campos que querías conservar?

Los ejemplos a continuación usan localhost:9200, un índice llamado products y curl simple. En un clúster real, también puedes necesitar HTTPS, autenticación y una opción de certificado.

curl -u elastic:password --cacert http_ca.crt https://es.example.com:9200/

Crear un documento con un ID automático

Usa POST /{index}/_doc cuando Elasticsearch pueda asignar el ID del documento.

curl -X POST "localhost:9200/products/_doc" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "mouse-x1",
    "name": "Wireless Mouse X1",
    "price": 25.99,
    "in_stock": true,
    "updated_at": "2026-05-24T09:30:00Z"
  }'

Una respuesta exitosa incluye un _id y un result de created.

{
  "_index": "products",
  "_id": "wI4aQZkB8xExample",
  "_version": 1,
  "result": "created"
}

Los IDs automáticos son adecuados para datos de eventos, registros, registros de clics y contenido de solo adición. No son ideales cuando el mismo elemento del mundo real puede llegar nuevamente más tarde. Si la misma actualización de producto se indexa dos veces con IDs automáticos, obtienes dos documentos.

Indexar con tu propio ID

Usa PUT /{index}/_doc/{id} cuando tu sistema de origen ya tenga un identificador estable.

curl -X PUT "localhost:9200/products/_doc/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "mouse-x1",
    "name": "Wireless Mouse X1",
    "price": 25.99,
    "in_stock": true,
    "updated_at": "2026-05-24T09:30:00Z"
  }'

Si el ID no existe, Elasticsearch lo crea. Si ya existe, Elasticsearch reemplaza todo el documento. Ese comportamiento de reemplazo es útil para trabajos de sincronización simples, pero también puede eliminar campos que olvidaste enviar.

Por ejemplo, si el documento existente tiene category, brand y description, y tu siguiente PUT solo envía price, el _source almacenado se convierte solo en los campos de esa nueva solicitud. Usa PUT cuando estés enviando el documento completo deseado.

Crear solo si falta

Si la creación duplicada sería un error, usa la operación create:

curl -X PUT "localhost:9200/products/_create/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "sku": "mouse-x1",
    "name": "Wireless Mouse X1",
    "price": 25.99
  }'

Si el ID ya existe, Elasticsearch devuelve un conflicto de versión en lugar de sobrescribir el documento. Esto es útil para trabajos de ingesta donde reintentar un mensaje no debería cambiar un registro existente.

Actualizar campos parcialmente

Usa POST /{index}/_update/{id} con un objeto doc cuando solo quieras cambiar algunos campos.

curl -X POST "localhost:9200/products/_update/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": {
      "price": 22.49,
      "in_stock": false,
      "updated_at": "2026-05-24T10:15:00Z"
    }
  }'

Elasticsearch obtiene el documento existente internamente, fusiona los campos proporcionados e indexa la fuente modificada nuevamente. Es una actualización parcial desde el punto de vista del usuario de la API, no una mutación en el lugar en el disco.

Si es posible que el documento aún no exista, usa doc_as_upsert:

curl -X POST "localhost:9200/products/_update/sku-keyboard-k90" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": {
      "sku": "keyboard-k90",
      "name": "Mechanical Keyboard K90",
      "price": 129.99,
      "in_stock": true
    },
    "doc_as_upsert": true
  }'

Esto crea el documento con el contenido de doc si falta. Úsalo cuando tu feed ascendente envíe registros de "estado actual" y no te importe si esta es la primera vez que Elasticsearch ve el ID.

Actualizaciones con scripts

Los scripts son útiles cuando el nuevo valor depende del valor antiguo. Un ejemplo común es incrementar un contador:

curl -X POST "localhost:9200/products/_update/sku-mouse-x1" \
  -H "Content-Type: application/json" \
  -d '{
    "script": {
      "source": "ctx._source.views = (ctx._source.views ?: 0) + params.count",
      "params": {
        "count": 1
      }
    }
  }'

Mantén los scripts pequeños y predecibles. Son potentes, pero también son más fáciles de usar incorrectamente que una actualización simple con doc. Para contadores de alto volumen, piensa cuidadosamente en la tasa de escritura, las necesidades de actualización y si Elasticsearch debería ser el sistema de registro.

Indexación y actualizaciones por lotes

Para más de un puñado de documentos, usa _bulk. El cuerpo de la solicitud es JSON delimitado por nuevas líneas, y la nueva línea final es importante.

cat bulk-products.ndjson
{"index":{"_index":"products","_id":"sku-mouse-x1"}}
{"sku":"mouse-x1","name":"Wireless Mouse X1","price":25.99,"in_stock":true}
{"update":{"_index":"products","_id":"sku-keyboard-k90"}}
{"doc":{"price":119.99,"in_stock":true},"doc_as_upsert":true}
{"delete":{"_index":"products","_id":"sku-old-cable"}}

Envíalo así:

curl -X POST "localhost:9200/_bulk" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @bulk-products.ndjson

Usa --data-binary, no -d simple, para que curl conserve las nuevas líneas. Luego inspecciona la respuesta. Una solicitud por lotes puede devolver HTTP 200 mientras elementos individuales fallaron.

curl -s -X POST "localhost:9200/_bulk" \
  -H "Content-Type: application/x-ndjson" \
  --data-binary @bulk-products.ndjson | jq '.errors, .items[] | select(.index.error or .update.error or .delete.error)'

Actualización y visibilidad

Un documento indexado no siempre se puede buscar de inmediato. Elasticsearch actualiza los índices periódicamente de forma predeterminada. Si necesitas leer el documento por ID, GET /products/_doc/sku-mouse-x1 puede encontrarlo de inmediato. Si necesitas que aparezca en la búsqueda antes de continuar una prueba, usa refresh=wait_for:

curl -X PUT "localhost:9200/products/_doc/sku-mouse-x1?refresh=wait_for" \
  -H "Content-Type: application/json" \
  -d '{"sku":"mouse-x1","name":"Wireless Mouse X1","price":25.99}'

No agregues actualizaciones forzadas a cada escritura de producción sin medir el costo. Pueden reducir el rendimiento de indexación.

Una regla práctica

Usa POST /_doc para datos de solo adición donde los eventos duplicados sean aceptables o se manejen en otro lugar. Usa PUT /_doc/{id} cuando la solicitud contenga el documento completo actual. Usa _update cuando solo quieras cambiar ciertos campos. Usa _create cuando sobrescribir ocultaría un problema de datos. Usa _bulk cuando el trabajo sea más de unos pocos documentos.

La mayoría de los errores de indexación de Elasticsearch provienen de elegir la forma de escritura incorrecta. El endpoint debe coincidir con tus datos de origen, tu comportamiento de reintento y si Elasticsearch está almacenando un registro completo o una copia buscable del registro de otro sistema.

Verifica los mapeos antes de culpar a la solicitud de escritura

A veces la escritura tiene éxito y el resultado de la búsqueda aún parece incorrecto. Eso suele ser un problema de mapeo, no un problema del endpoint de indexación.

Verifica el mapeo:

curl -s "localhost:9200/products/_mapping?pretty"

Si price se indexó primero como texto porque un documento de prueba temprano envió "25.99" como cadena, las consultas de rango pueden comportarse mal. Si sku necesita coincidencia exacta y agregaciones, generalmente debería ser un campo keyword o tener un subcampo keyword. Si las marcas de tiempo llegan en diferentes formatos, algunos documentos pueden rechazarse durante la indexación mientras que otros tienen éxito.

Para sistemas predecibles, crea el mapeo del índice antes de la primera escritura:

curl -X PUT "localhost:9200/products" \
  -H "Content-Type: application/json" \
  -d '{
    "mappings": {
      "properties": {
        "sku": { "type": "keyword" },
        "name": { "type": "text" },
        "price": { "type": "double" },
        "in_stock": { "type": "boolean" },
        "updated_at": { "type": "date" }
      }
    }
  }'

El mapeo dinámico es conveniente para la exploración. Para la ingesta en producción, los mapeos explícitos evitan que un primer documento defectuoso dé forma al índice.

Maneja conflictos y reintentos deliberadamente

Las actualizaciones concurrentes pueden entrar en conflicto. Si dos trabajadores actualizan el mismo documento casi al mismo tiempo, uno puede basarse en una versión anterior. Para reintentos de actualización simples, Elasticsearch admite retry_on_conflict:

curl -X POST "localhost:9200/products/_update/sku-mouse-x1?retry_on_conflict=3" \
  -H "Content-Type: application/json" \
  -d '{
    "doc": {
      "price": 21.99
    }
  }'

Eso es útil para actualizaciones de bajo riesgo, pero no es un reemplazo para una propiedad clara. Si un sistema posee los nombres de los productos y otro posee el inventario, considera actualizar diferentes campos a través de tuberías bien definidas. Si el orden de los eventos importa, incluye marcas de tiempo de origen y rechaza actualizaciones más antiguas en tu capa de ingesta o con un script cuidadoso.

Leer después de escribir durante la depuración

Al probar un flujo de indexación, obtén el documento por ID de inmediato:

curl -s "localhost:9200/products/_doc/sku-mouse-x1?pretty"

Luego búscalo:

curl -s "localhost:9200/products/_search?pretty" \
  -H "Content-Type: application/json" \
  -d '{
    "query": {
      "term": {
        "sku": "mouse-x1"
      }
    }
  }'

Si GET por ID funciona pero la búsqueda no, piensa en actualización, mapeo, analizador o tipo de consulta. Si ambos fallan, piensa en error de indexación, índice incorrecto, ID incorrecto, autenticación o enrutamiento.

Para aplicaciones, registra el índice de destino, el ID del documento, el estado de la respuesta y los errores a nivel de elemento en lotes. No necesitas registrar cada documento completo para siempre, pero durante la implementación, esos campos ahorran horas.