Документ, индекс и JSON: модель данных
Документ — базовая единица данных
В Elasticsearch нет таблиц, строк и столбцов. Вместо этого всё строится вокруг документов. Документ — это JSON-объект: набор пар «ключ-значение», вложенных на любую глубину. Если вы уже работали с REST API, JSON вам знаком — это тот же формат.
Вот пример документа о товаре:
{
"title": "Ноутбук Lenovo IdeaPad 3",
"category": "laptops",
"price": 79990,
"in_stock": true,
"tags": ["ноутбук", "lenovo", "14 дюймов"],
"specs": {
"cpu": "Intel Core i5",
"ram_gb": 16,
"storage_gb": 512
}
}Каждая пара — это поле (field): title, category, price и т. д. Значение поля может быть строкой, числом, булевым, массивом или вложенным объектом (как specs выше). ES принимает всё это без предварительного объявления схемы — хотя явный маппинг рекомендуется: подробнее в статье Маппинг: явный, динамический и шаблоны индексов.
Индекс — коллекция документов
Документы группируются в индексы. Индекс — это именованная коллекция документов с общим маппингом (схемой полей). Грубая аналогия с SQL: документ — строка, индекс — таблица. Аналогия неточная: ES не требует, чтобы все документы одного индекса имели одинаковые поля, — но стремиться к однородности стоит.
Имя индекса пишется строчными буквами, допускаются цифры, дефисы и подчёркивания: products, articles, logs-2024-06.
flowchart TD
C["Кластер"]
I1["Индекс: products"]
I2["Индекс: articles"]
D1["Документ _id: 1"]
D2["Документ _id: 2"]
D3["Документ _id: 42"]
F1["title: string\nprice: number\nin_stock: boolean"]
C --> I1
C --> I2
I1 --> D1
I1 --> D2
I2 --> D3
D1 --> F1Когда вы кладёте первый документ в несуществующий индекс, ES создаёт его автоматически. Это называется динамическим маппингом — удобно для экспериментов, но в продакшне лучше объявлять схему заранее.
Историческая деталь: раньше внутри одного индекса можно было заводить «типы» (types). С версии 7.0 типы убраны; теперь у каждого индекса единственный тип _doc. Правило хорошего тона: разные сущности — в разных индексах. Товары в products, статьи в articles, логи в logs-2024-06, а не всё вместе.
Метаполя: что ES добавляет к вашему документу
Когда вы читаете или ищете документ, он возвращается не голым JSON-ом, а с набором метаполей — служебных полей ES. Они всегда начинаются с подчёркивания.
_id — уникальный идентификатор документа внутри индекса. Аналог первичного ключа в SQL. Можно задать вручную при индексации — или позволить ES сгенерировать автоматически (тогда это будет строка вроде "ZP4y5YIBNcTI_4Oo0Gzz"). Важно: _id уникален в рамках одного индекса, но не всего кластера.
_source — оригинальный JSON вашего документа в том виде, в котором вы его отправили. Именно _source возвращается в результатах поиска. ES хранит его отдельно от инвертированного индекса — чтобы можно было вернуть читаемый текст, а не просто факт «документ с таким-то _id содержит это слово».
_index — имя индекса, которому принадлежит документ. Полезно при запросах сразу по нескольким индексам.
Есть ещё технические метаполя: _version (номер версии), _seq_no и _primary_term — они нужны для оптимистичной блокировки при конкурентных обновлениях. На старте достаточно знать, что они существуют.
Полный цикл: создаём и читаем документ
Смотрим на реальный пример — прямо в Kibana Dev Tools или через curl.
Создаём документ с явным _id = 1:
PUT /products/_doc/1
{
"title": "Ноутбук Lenovo IdeaPad 3",
"category": "laptops",
"price": 79990,
"in_stock": true
}ES отвечает:
{
"_index": "products",
"_id": "1",
"_version": 1,
"result": "created",
"_shards": { "total": 2, "successful": 1, "failed": 0 }
}Читаем документ по _id:
GET /products/_doc/1Ответ:
{
"_index": "products",
"_id": "1",
"_version": 1,
"found": true,
"_source": {
"title": "Ноутбук Lenovo IdeaPad 3",
"category": "laptops",
"price": 79990,
"in_stock": true
}
}Структура ответа всегда одна и та же: верхний уровень — метаполя (_index, _id, _version, found), внутри _source — ваш оригинальный JSON. При поиске через _search каждый элемент массива hits.hits выглядит точно так же.
PUT с явным _id или POST с автогенерацией
Если у ваших данных уже есть естественный ключ (ID из основной БД, ISBN книги, артикул товара) — используйте его как _id. Повторный PUT на тот же _id просто перезапишет документ, не создав дубликат:
PUT /products/_doc/1
{
"title": "Ноутбук Lenovo IdeaPad 3 Gen 2",
"price": 84990
}Если естественного ключа нет — POST без _id в URL, и ES сгенерирует его сам:
POST /products/_doc
{
"title": "Мышь Logitech MX Master 3",
"category": "accessories",
"price": 8990
}Автогенерируемый _id — случайная URL-safe строка Base64, достаточно длинная, чтобы коллизии были практически исключены.
_source под контролем: выбираем нужные поля
По умолчанию ES возвращает весь _source. Если документ большой, а нужна лишь пара полей, ограничьте это через параметр _source в теле запроса:
POST /products/_search
{
"_source": ["title", "price"],
"query": {
"match_all": {}
}
}Тогда в каждом hit._source появятся только title и price. ES отфильтрует остальное перед отправкой ответа — экономит трафик при больших документах.
Иерархия сущностей: поле → документ → индекс → кластер
Соберём всё вместе:
- Поле (field) — пара «имя: значение» внутри документа.
- Документ (document) — JSON-объект, основная единица хранения и поиска. Имеет метаполя:
_id,_source,_index. - Индекс (index) — именованная коллекция документов с единым маппингом. Физически хранится в шардах.
- Кластер (cluster) — совокупность нод, между которыми распределены шарды всех индексов. Подробно — в Кластер, ноды, шарды и реплики.