Индексация и обновление документов с помощью REST API Elasticsearch
Освойте основные операции создания, чтения, обновления и удаления (CRUD) в Elasticsearch с помощью REST API. Это руководство подробно описывает точные HTTP-запросы, конечные точки и полезные нагрузки JSON, необходимые для индексации новых документов (с указанными ID или без них) и выполнения детальных, частичных обновлений существующих записей. Изучите практические примеры curl для атомарных обновлений, модификаций с помощью скриптов и эффективной пакетной загрузки данных.
Индексация и обновление документов с помощью REST API Elasticsearch
Индексация и обновление документов с помощью REST API Elasticsearch на первый взгляд кажутся простыми: отправьте JSON на конечную точку и получите JSON в ответ. Однако важные для продакшена детали более специфичны. Создаёте ли вы новый документ или заменяете старый? Хотите ли вы, чтобы Elasticsearch сгенерировал ID, или вам нужен ID из вашей исходной системы? Выполняете ли вы частичное обновление или случайно перезаписываете поля, которые хотели сохранить?
Примеры ниже используют localhost:9200, индекс с именем products и обычный curl. В реальном кластере вам также могут понадобиться HTTPS, аутентификация и опция сертификата.
curl -u elastic:password --cacert http_ca.crt https://es.example.com:9200/
Создание документа с автоматическим ID
Используйте POST /{index}/_doc, когда Elasticsearch может назначить ID документа.
curl -X POST "localhost:9200/products/_doc" \
-H "Content-Type: application/json" \
-d '{
"sku": "mouse-x1",
"name": "Беспроводная мышь X1",
"price": 25.99,
"in_stock": true,
"updated_at": "2026-05-24T09:30:00Z"
}'
Успешный ответ включает _id и result со значением created.
{
"_index": "products",
"_id": "wI4aQZkB8xExample",
"_version": 1,
"result": "created"
}
Автоматические ID подходят для данных событий, логов, записей кликов и контента, предназначенного только для добавления. Они не идеальны, когда один и тот же реальный объект может появиться снова позже. Если одно и то же обновление продукта индексируется дважды с автоматическими ID, вы получите два документа.
Индексация с собственным ID
Используйте PUT /{index}/_doc/{id}, когда ваша исходная система уже имеет стабильный идентификатор.
curl -X PUT "localhost:9200/products/_doc/sku-mouse-x1" \
-H "Content-Type: application/json" \
-d '{
"sku": "mouse-x1",
"name": "Беспроводная мышь X1",
"price": 25.99,
"in_stock": true,
"updated_at": "2026-05-24T09:30:00Z"
}'
Если ID не существует, Elasticsearch создаёт его. Если он уже существует, Elasticsearch заменяет весь документ. Такое поведение замены полезно для простых задач синхронизации, но оно также может удалить поля, которые вы забыли отправить.
Например, если существующий документ содержит category, brand и description, а ваш следующий PUT отправляет только price, сохранённый _source будет содержать только поля из этого нового запроса. Используйте PUT, когда вы отправляете полный желаемый документ.
Создание только при отсутствии
Если дублирование создания было бы ошибкой, используйте операцию create:
curl -X PUT "localhost:9200/products/_create/sku-mouse-x1" \
-H "Content-Type: application/json" \
-d '{
"sku": "mouse-x1",
"name": "Беспроводная мышь X1",
"price": 25.99
}'
Если ID уже существует, Elasticsearch возвращает конфликт версий вместо перезаписи документа. Это полезно для задач приёма данных, где повторная попытка обработки сообщения не должна изменять существующую запись.
Частичное обновление полей
Используйте POST /{index}/_update/{id} с объектом doc, когда вы хотите изменить только некоторые поля.
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 внутренне извлекает существующий документ, объединяет предоставленные поля и снова индексирует изменённый источник. Это частичное обновление с точки зрения пользователя API, а не изменение на месте на диске.
Если документ может ещё не существовать, используйте doc_as_upsert:
curl -X POST "localhost:9200/products/_update/sku-keyboard-k90" \
-H "Content-Type: application/json" \
-d '{
"doc": {
"sku": "keyboard-k90",
"name": "Механическая клавиатура K90",
"price": 129.99,
"in_stock": true
},
"doc_as_upsert": true
}'
Это создаёт документ с содержимым doc, если он отсутствует. Используйте это, когда ваш вышестоящий канал отправляет записи "текущего состояния" и вам не важно, видит ли Elasticsearch этот ID впервые.
Обновления с помощью скриптов
Скрипты полезны, когда новое значение зависит от старого. Распространённый пример — увеличение счётчика:
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
}
}
}'
Делайте скрипты небольшими и предсказуемыми. Они мощные, но их также легче использовать неправильно, чем простое обновление doc. Для высоконагруженных счётчиков тщательно продумайте скорость записи, потребности в обновлении и то, должна ли Elasticsearch быть системой записи.
Пакетная индексация и обновления
Для более чем нескольких документов используйте _bulk. Тело запроса представляет собой JSON, разделённый новыми строками, и последняя новая строка важна.
cat bulk-products.ndjson
{"index":{"_index":"products","_id":"sku-mouse-x1"}}
{"sku":"mouse-x1","name":"Беспроводная мышь 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"}}
Отправьте это так:
curl -X POST "localhost:9200/_bulk" \
-H "Content-Type: application/x-ndjson" \
--data-binary @bulk-products.ndjson
Используйте --data-binary, а не обычный -d, чтобы curl сохранял новые строки. Затем проверьте ответ. Пакетный запрос может вернуть HTTP 200, даже если отдельные элементы завершились ошибкой.
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)'
Обновление и видимость
Проиндексированный документ не всегда сразу доступен для поиска. Elasticsearch по умолчанию периодически обновляет индексы. Если вам нужно прочитать документ по ID, GET /products/_doc/sku-mouse-x1 может найти его сразу. Если вам нужно, чтобы он появился в результатах поиска перед продолжением теста, используйте 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":"Беспроводная мышь X1","price":25.99}'
Не добавляйте принудительные обновления к каждой записи в продакшене без оценки затрат. Они могут снизить пропускную способность индексации.
Практическое эмпирическое правило
Используйте POST /_doc для данных, предназначенных только для добавления, где дублирование событий допустимо или обрабатывается в другом месте. Используйте PUT /_doc/{id}, когда запрос содержит полный текущий документ. Используйте _update, когда вы хотите изменить только определённые поля. Используйте _create, когда перезапись скрыла бы проблему с данными. Используйте _bulk, когда объём работы превышает несколько документов.
Большинство ошибок индексации Elasticsearch возникают из-за выбора неправильной формы записи. Конечная точка должна соответствовать вашим исходным данным, вашему поведению при повторных попытках и тому, хранит ли Elasticsearch полную запись или копию записи из другой системы для поиска.
Проверяйте маппинги, прежде чем винить запрос на запись
Иногда запись выполняется успешно, но результат поиска всё равно выглядит неправильно. Это часто проблема маппинга, а не проблема конечной точки индексации.
Проверьте маппинг:
curl -s "localhost:9200/products/_mapping?pretty"
Если price был сначала проиндексирован как текст, потому что ранний тестовый документ отправил "25.99" в виде строки, запросы диапазона могут работать некорректно. Если sku требует точного совпадения и агрегаций, он обычно должен быть полем типа keyword или иметь подполе keyword. Если метки времени поступают в разных форматах, некоторые документы могут быть отклонены во время индексации, в то время как другие будут успешно обработаны.
Для предсказуемых систем создайте маппинг индекса перед первой записью:
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" }
}
}
}'
Динамический маппинг удобен для исследования. Для приёма данных в продакшене явные маппинги предотвращают формирование индекса неправильным первым документом.
Обрабатывайте конфликты и повторные попытки осознанно
Конкурентные обновления могут конфликтовать. Если два воркера обновляют один и тот же документ почти одновременно, один из них может основываться на более старой версии. Для простых повторных попыток обновления Elasticsearch поддерживает 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
}
}'
Это полезно для обновлений с низким риском, но не заменяет чёткое владение. Если одна система владеет названиями продуктов, а другая — запасами, рассмотрите возможность обновления разных полей через чётко определённые конвейеры. Если порядок событий важен, включайте исходные метки времени и отклоняйте более старые обновления в вашем уровне приёма данных или с помощью тщательно написанного скрипта.
Чтение после записи во время отладки
При тестировании потока индексации немедленно получите документ по ID:
curl -s "localhost:9200/products/_doc/sku-mouse-x1?pretty"
Затем выполните поиск:
curl -s "localhost:9200/products/_search?pretty" \
-H "Content-Type: application/json" \
-d '{
"query": {
"term": {
"sku": "mouse-x1"
}
}
}'
Если GET по ID работает, а поиск — нет, подумайте об обновлении, маппинге, анализаторе или типе запроса. Если оба не работают, подумайте об ошибке индексации, неправильном индексе, неправильном ID, аутентификации или маршрутизации.
Для приложений логируйте целевой индекс, ID документа, статус ответа и ошибки на уровне элементов пакета. Вам не нужно вечно логировать каждый полный документ, но во время развёртывания эти поля сэкономят часы.