Индексация документов: PUT, POST и bulk
Учимся загружать данные в Elasticsearch по одному документу и массово, тысячами, через bulk.
Индексация — это процесс добавления документа в индекс, при котором ES разбирает его поля и обновляет инвертированный индекс.
Одиночная индексация
Самый прямой способ — положить один документ. С известным id используем PUT:
curl -X PUT http://localhost:9200/products/_doc/1 \
-H 'Content-Type: application/json' \
-d '{"title": "Чайник", "price": 1990}'Без id — POST, и ES вернёт сгенерированный _id:
curl -X POST http://localhost:9200/products/_doc \
-H 'Content-Type: application/json' \
-d '{"title": "Тостер", "price": 3490}'Обновление и версии
Повторный PUT по тому же id заменяет документ целиком. Чтобы поменять только одно поле, есть частичное обновление через _update:
curl -X POST http://localhost:9200/products/_update/1 \
-H 'Content-Type: application/json' \
-d '{"doc": {"price": 1790}}'Каждое изменение увеличивает поле _version документа — ES так отслеживает актуальность и помогает избегать конфликтов одновременной записи.
Массовая загрузка: _bulk
Грузить миллион документов по одному HTTP-запросу — это миллион round-trip'ов, очень медленно. Эндпоинт _bulk принимает много операций в одном запросе. Формат особенный: две строки на операцию — строка-команда и строка-данные, разделённые переводом строки (это NDJSON, а не обычный JSON-массив).
curl -X POST http://localhost:9200/_bulk \
-H 'Content-Type: application/x-ndjson' \
--data-binary '
{ "index": { "_index": "products", "_id": "10" } }
{ "title": "Блендер", "price": 4990 }
{ "index": { "_index": "products", "_id": "11" } }
{ "title": "Миксер", "price": 2990 }
'Важно: каждая строка — отдельный компактный JSON без переносов внутри, и файл должен заканчиваться переводом строки. Поддерживаются действия index, create, update, delete.
Как работает под капотом
Когда документ приходит, ES определяет, в какой шард он попадёт (по хешу от _id), и пишет его в этот шард. Сначала запись попадает в буфер в памяти и в журнал (translog), а потом периодически (по умолчанию раз в секунду) происходит refresh — данные становятся видимы для поиска. Поэтому только что вставленный документ может появиться в результатах поиска с задержкой до секунды. Bulk эффективнее, потому что амортизирует накладные расходы: один сетевой запрос, один разбор, пакетная запись в шарды.
Частые ошибки
- Слать обычный JSON-массив в _bulk. Bulk ждёт NDJSON: пары строк команда/данные, разделённые
\n. Массив[...]не примет. - Ждать мгновенной видимости. Из-за refresh документ виден в поиске не сразу. Для тестов можно форсировать:
?refresh=true, но в проде так делать не стоит — это бьёт по производительности. - Слишком большие bulk-пачки. Пачка на сотни мегабайт перегрузит узел. Оптимум обычно — несколько тысяч документов или 5–15 МБ на запрос.
Итоги
- Одиночно:
PUTс id (замена целиком) илиPOSTбез id; частичное изменение — через_update. - Массово — через
_bulkв формате NDJSON (две строки на операцию). - Из-за refresh свежий документ виден в поиске с небольшой задержкой (по умолчанию ~1 секунда).