Indexation et mise à jour de documents avec l'API REST Elasticsearch

Maîtrisez les opérations CRUD (Créer, Lire, Mettre à jour, Supprimer) dans Elasticsearch à l'aide de l'API REST. Ce guide détaille les requêtes HTTP, les points de terminaison et les charges utiles JSON nécessaires pour indexer de nouveaux documents (avec ou sans ID spécifié) et effectuer des mises à jour partielles granulaires sur des enregistrements existants. Apprenez des exemples pratiques avec `curl` pour les mises à jour atomiques, les modifications scriptées et l'ingestion efficace de données en masse.

Indexation et mise à jour de documents avec l'API REST Elasticsearch

L'indexation et la mise à jour de documents avec l'API REST Elasticsearch semblent faciles au premier abord : envoyer du JSON à un point de terminaison et recevoir du JSON en retour. Les aspects importants en production sont plus spécifiques. Créez-vous un nouveau document ou remplacez-vous un ancien ? Voulez-vous qu'Elasticsearch génère l'ID, ou avez-vous besoin d'un ID provenant de votre système source ? Effectuez-vous une mise à jour partielle, ou écrasez-vous accidentellement des champs que vous souhaitiez conserver ?

Les exemples ci-dessous utilisent localhost:9200, un index nommé products, et curl simple. Dans un cluster réel, vous pouvez également avoir besoin de HTTPS, d'authentification et d'une option de certificat.

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

Créer un document avec un ID automatique

Utilisez POST /{index}/_doc lorsque Elasticsearch peut attribuer l'ID du document.

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"
  }'

Une réponse réussie inclut un _id et un result de created.

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

Les ID automatiques conviennent pour les données d'événements, les logs, les enregistrements de clics et le contenu en ajout uniquement. Ils ne sont pas idéaux lorsque le même élément réel peut arriver à nouveau plus tard. Si la même mise à jour de produit est indexée deux fois avec des ID automatiques, vous obtenez deux documents.

Indexer avec votre propre ID

Utilisez PUT /{index}/_doc/{id} lorsque votre système source possède déjà un identifiant stable.

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 l'ID n'existe pas, Elasticsearch le crée. S'il existe déjà, Elasticsearch remplace tout le document. Ce comportement de remplacement est utile pour les tâches de synchronisation simples, mais il peut également supprimer des champs que vous avez oublié d'envoyer.

Par exemple, si le document existant a category, brand et description, et que votre prochain PUT envoie uniquement price, le _source stocké devient uniquement les champs de cette nouvelle requête. Utilisez PUT lorsque vous envoyez le document complet souhaité.

Créer uniquement si manquant

Si la création en double serait un bug, utilisez l'opération de création :

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 l'ID existe déjà, Elasticsearch renvoie un conflit de version au lieu d'écraser le document. Cela est utile pour les tâches d'ingestion où la reprise d'un message ne doit pas modifier un enregistrement existant.

Mettre à jour partiellement des champs

Utilisez POST /{index}/_update/{id} avec un objet doc lorsque vous souhaitez uniquement modifier certains champs.

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 récupère le document existant en interne, fusionne les champs fournis et indexe à nouveau la source modifiée. Il s'agit d'une mise à jour partielle du point de vue de l'utilisateur de l'API, et non d'une mutation sur place sur le disque.

Si le document peut ne pas encore exister, utilisez 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
  }'

Cela crée le document avec le contenu doc s'il est manquant. Utilisez-le lorsque votre flux amont envoie des enregistrements "d'état actuel" et que vous ne vous souciez pas de savoir si c'est la première fois qu'Elasticsearch voit l'ID.

Mises à jour scriptées

Les scripts sont utiles lorsque la nouvelle valeur dépend de l'ancienne. Un exemple courant est l'incrémentation d'un compteur :

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
      }
    }
  }'

Gardez les scripts petits et prévisibles. Ils sont puissants, mais ils sont aussi plus faciles à mal utiliser qu'une simple mise à jour doc. Pour les compteurs à volume élevé, réfléchissez attentivement au taux d'écriture, aux besoins d'actualisation et à savoir si Elasticsearch doit être le système d'enregistrement.

Indexation et mises à jour en masse

Pour plus de quelques documents, utilisez _bulk. Le corps de la requête est du JSON délimité par des sauts de ligne, et le saut de ligne final est important.

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"}}

Envoyez-le comme ceci :

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

Utilisez --data-binary, pas -d simple, pour que curl conserve les sauts de ligne. Ensuite, inspectez la réponse. Une requête en masse peut renvoyer HTTP 200 alors que des éléments individuels ont échoué.

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)'

Actualisation et visibilité

Un document indexé n'est pas toujours consultable immédiatement. Elasticsearch actualise les indices périodiquement par défaut. Si vous devez lire le document par ID, GET /products/_doc/sku-mouse-x1 peut le trouver immédiatement. Si vous avez besoin qu'il apparaisse dans la recherche avant de continuer un test, utilisez 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}'

N'ajoutez pas d'actualisations forcées à chaque écriture en production sans mesurer le coût. Elles peuvent réduire le débit d'indexation.

Une règle empirique pratique

Utilisez POST /_doc pour les données en ajout uniquement où les événements en double sont acceptables ou traités ailleurs. Utilisez PUT /_doc/{id} lorsque la requête contient le document actuel complet. Utilisez _update lorsque vous souhaitez uniquement modifier certains champs. Utilisez _create lorsque l'écrasement masquerait un problème de données. Utilisez _bulk lorsque le travail concerne plus de quelques documents.

La plupart des bugs d'indexation Elasticsearch proviennent du choix d'une forme d'écriture incorrecte. Le point de terminaison doit correspondre à vos données source, à votre comportement de reprise et au fait qu'Elasticsearch stocke un enregistrement complet ou une copie consultable de l'enregistrement d'un autre système.

Vérifiez les mappings avant de blâmer la requête d'écriture

Parfois, l'écriture réussit et le résultat de la recherche semble toujours incorrect. C'est souvent un problème de mapping, pas un problème de point de terminaison d'indexation.

Vérifiez le mapping :

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

Si price a été indexé pour la première fois en tant que texte parce qu'un document de test précoce a envoyé "25.99" comme chaîne, les requêtes de plage peuvent se comporter mal. Si sku nécessite une correspondance exacte et des agrégations, il doit généralement être un champ keyword ou avoir un sous-champ mot-clé. Si les horodatages arrivent dans différents formats, certains documents peuvent être rejetés lors de l'indexation tandis que d'autres réussissent.

Pour des systèmes prévisibles, créez le mapping d'index avant la première écriture :

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" }
      }
    }
  }'

Le mapping dynamique est pratique pour l'exploration. Pour l'ingestion en production, les mappings explicites empêchent un mauvais premier document de façonner l'index.

Gérez les conflits et les reprises délibérément

Les mises à jour simultanées peuvent entrer en conflit. Si deux workers mettent à jour le même document presque en même temps, l'un peut être basé sur une version plus ancienne. Pour les reprises de mise à jour simples, Elasticsearch prend en charge 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
    }
  }'

Cela est utile pour les mises à jour à faible risque, mais ce n'est pas un remplacement pour une propriété claire. Si un système possède les noms de produits et un autre possède l'inventaire, envisagez de mettre à jour différents champs via des pipelines bien définis. Si l'ordre des événements est important, incluez les horodatages source et rejetez les mises à jour plus anciennes dans votre couche d'ingestion ou avec un script prudent.

Lire après écriture lors du débogage

Lors du test d'un flux d'indexation, récupérez le document par ID immédiatement :

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

Ensuite, recherchez-le :

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

Si GET par ID fonctionne mais pas la recherche, pensez à l'actualisation, au mapping, à l'analyseur ou au type de requête. Si les deux échouent, pensez à une erreur d'indexation, un mauvais index, un mauvais ID, une authentification ou un routage.

Pour les applications, enregistrez l'index cible, l'ID du document, le statut de la réponse et les erreurs en masse au niveau de l'élément. Vous n'avez pas besoin de journaliser chaque document complet pour toujours, mais pendant le déploiement, ces champs font gagner des heures.