CRUD-операции над документами
В статье Kibana Dev Tools и работа через REST API мы добавили первые документы и убедились, что кластер отвечает. Здесь разберём все четыре базовые операции полностью: создание, чтение, обновление и удаление — с нюансами, которые на практике важны.
Эндпоинт _doc и структура адреса
Каждый документ в Elasticsearch доступен по URL:
/<index>/_doc/<id>Здесь _doc — фиксированный суффикс (начиная с ES 7.0 поддерживается только один тип документа на индекс). HTTP-метод определяет, что именно произойдёт: чтение, запись или удаление.
Создание документа
Явный ID. Когда идентификатор задаёт бизнес-логика — артикул, slug, UUID из вашей БД — используйте PUT:
PUT /products/_doc/42
{
"name": "Клавиатура механическая",
"price": 7500,
"in_stock": true
}Если документ с ID 42 уже есть — он будет полностью заменён новым. Не дополнен, а именно целиком перезаписан.
Автоматический ID. Если идентификатор не важен — используйте POST без ID:
POST /products/_doc
{
"name": "Коврик для мыши",
"price": 800,
"in_stock": true
}ES сам сгенерирует уникальный Base64-идентификатор и вернёт его в поле _id ответа — что-то вроде aB3cDe-xYz1234.
Защита от перезаписи. Хотите убедиться, что документ создаётся только один раз? Замените _doc на _create:
PUT /products/_create/42
{
"name": "Клавиатура механическая",
"price": 7500,
"in_stock": true
}Если документ с ID 42 уже существует, ES вернёт 409 Conflict — операция не выполнится.
Чтение документа
GET /products/_doc/42Ответ:
{
"_index": "products",
"_id": "42",
"_version": 1,
"_seq_no": 0,
"_primary_term": 1,
"found": true,
"_source": {
"name": "Клавиатура механическая",
"price": 7500,
"in_stock": true
}
}Поле _source — это дословно тот JSON, что вы положили. Всё остальное — служебные метаданные ES. Если документ не найден, "found" будет false и статус ответа — 404.
Версионирование: _version, _seq_no и _primary_term
ES отслеживает каждое изменение документа тремя числами:
_version— монотонно растущий счётчик. Начинается с1при создании, увеличивается при каждой записи. После удаления и повторного создания счётчик не сбрасывается._seq_no— порядковый номер операции на шарде. Уникален в пределах шарда._primary_term— номер «эпохи» primary-шарда; увеличивается, когда шард переизбирает лидера.
Пара _seq_no + _primary_term используется для оптимистичной блокировки: вы читаете документ, запоминаете эти значения, а при обновлении передаёте их как параметры запроса. ES сверит их с текущими. Если за это время кто-то изменил документ — значения не совпадут, и вы получите 409 Conflict:
PUT /products/_doc/42?if_seq_no=0&if_primary_term=1
{
"name": "Клавиатура механическая",
"price": 8000,
"in_stock": true
}Это стандартный способ избежать «гонки» при конкурентных записях без пессимистичных локов.
flowchart LR
Client([Клиент])
Client -->|"PUT /_doc/id"| n1["Создать / Заменить"]
Client -->|"POST /_doc"| n2["Создать с авто-ID"]
Client -->|"PUT /_create/id"| n3["Только создать"]
Client -->|"GET /_doc/id"| n4["Прочитать"]
Client -->|"POST /_update/id"| n5["Частично обновить / Upsert"]
Client -->|"DELETE /_doc/id"| n6["Удалить"]
n1 --> Store[(_source в индексе)]
n2 --> Store
n3 --> Store
n4 --> Store
n5 --> Store
n6 -->|"помечает удалённым"| StoreЧастичное обновление через _update
PUT на _doc полностью заменяет документ. Но чаще нужно изменить одно-два поля, не трогая остальные. Для этого есть _update:
POST /products/_update/42
{
"doc": {
"price": 8500
}
}ES достанет существующий документ, применит изменения из doc и сохранит обратно. Поля name и in_stock останутся нетронутыми. Под капотом — атомарная операция «прочитать → смержить → записать» на primary-шарде.
Если в doc указать поле, которого раньше не было — оно добавится. Ошибок не будет.
Upsert: обновить или создать
Классический сценарий: нужно обновить документ, а если его нет — создать с нуля. _update поддерживает ключ upsert:
POST /products/_update/99
{
"doc": {
"price": 3000
},
"upsert": {
"name": "USB-хаб",
"price": 3000,
"in_stock": true
}
}Логика проста:
- Документ
99существует → применяетсяdoc(частичное обновление). - Документа
99нет → создаётся документ изupsert.
Это удобно для счётчиков, кешей или логики «инициализировать при первом обращении».
Удаление документа
DELETE /products/_doc/42В ответе придёт "result": "deleted". Если документа не было — "result": "not_found" и статус 404.
Удалённый документ не исчезает с диска мгновенно: Lucene помечает его как удалённый, а физически удаляет при следующем слиянии сегментов (merge). Место освобождается постепенно — это нормально.
Near real-time: документ появляется в поиске с задержкой
Прямой запрос по ID (GET /_doc/id) работает без задержки — ES читает данные напрямую из шарда. Но поисковый запрос (_search) увидит новый или изменённый документ только после refresh — по умолчанию раз в секунду. Это и называется near real-time (NRT).
Принудительный refresh для экспериментов:
POST /products/_refreshВ продакшне не злоупотребляйте: частые принудительные refresh-ы заметно снижают скорость индексации.