OpenSearch — это распределённая система для полнотекстового поиска, фильтрации, агрегации и анализа больших объёмов данных.
Грубо:
OpenSearch берёт документы, строит по ним специальные поисковые индексы и позволяет быстро находить данные не только по точному совпадению, но и по словам, фразам, смысловой близости, диапазонам и множеству условий.
Например, у нас есть миллионы товаров:
{
"id": "product-142",
"name": "Беспроводные наушники Sony",
"description": "Наушники с активным шумоподавлением",
"price": 24990,
"brand": "Sony",
"category": "audio"
}
Мы можем искать:
sony наушники с шумоподавлением
И OpenSearch найдёт релевантные товары, даже если запрос не совпадает с названием документа символ в символ.
Зачем он нужен
Обычная реляционная база хорошо отвечает на запросы вроде:
SELECT *
FROM products
WHERE brand = 'Sony'
AND price BETWEEN 20000 AND 30000;
Но запрос:
WHERE description LIKE '%беспроводные наушники с хорошим шумоподавлением%'
на миллионах строк будет либо медленным, либо слишком примитивным.
OpenSearch специально построен для другого типа работы:
- полнотекстовый поиск;
- поиск по нескольким полям;
- исправление опечаток;
- ранжирование результатов;
- автодополнение;
- фильтрация;
- фасеты и агрегации;
- анализ логов;
- построение поисковых и аналитических панелей;
- векторный поиск для семантического поиска и RAG.
Основная идея: обратный индекс
Допустим, у нас есть документы:
1: Sony беспроводные наушники
2: Apple беспроводные наушники
3: Sony телевизор
Вместо того чтобы каждый раз перечитывать все документы, OpenSearch строит обратный индекс:
sony → [1, 3]
беспроводные → [1, 2]
наушники → [1, 2]
apple → [2]
телевизор → [3]
Теперь запрос:
sony наушники
можно выполнить через пересечение списков:
sony → [1, 3]
наушники → [1, 2]
результат → [1]
На практике индекс намного сложнее. Он хранит частоты слов, позиции слов, нормализованные формы, веса полей и другую информацию, необходимую для оценки релевантности.
Основные сущности
Document
Документ — отдельный JSON-объект:
{
"userId": 42,
"message": "Payment failed",
"timestamp": "2026-07-14T10:30:00Z"
}
Это примерно соответствует строке в реляционной базе, но аналогия неполная.
Index
Index — коллекция документов определённого типа.
Например:
products
orders
application-logs-2026-07
Индекс примерно напоминает таблицу, но физически является распределённой поисковой структурой.
Mapping
Mapping определяет типы полей:
{
"properties": {
"name": {
"type": "text"
},
"brand": {
"type": "keyword"
},
"price": {
"type": "double"
},
"createdAt": {
"type": "date"
}
}
}
Особенно важно различать:
text
keyword
text анализируется и используется для полнотекстового поиска.
keyword сохраняется как единое значение и используется для точного совпадения, сортировки и агрегаций.
Например:
"name": "Sony Wireless Headphones"
Поле text может быть разобрано на термы:
sony
wireless
headphones
А поле keyword останется одним значением:
Sony Wireless Headphones
Анализаторы
Перед добавлением текста в индекс OpenSearch пропускает его через анализатор.
Анализатор может:
- разбить текст на слова;
- перевести их в нижний регистр;
- удалить знаки пунктуации;
- убрать стоп-слова;
- привести слова к нормальной или корневой форме.
Например:
"Беспроводные Наушники Sony"
может превратиться в:
беспроводн
наушник
sony
Анализатор применяется и при индексации, и при поиске. Иначе пользователь искал бы одну форму слова, а в индексе находилась бы другая.
Как устроено распределение данных
OpenSearch работает как кластер из нескольких узлов:
Client
|
v
OpenSearch cluster
/ | \
Node 1 Node 2 Node 3
Каждый индекс разделяется на shards, то есть шарды.
Например:
products index
├── shard 0
├── shard 1
├── shard 2
└── shard 3
Документы распределяются между шардами.
При поиске запрос отправляется на все нужные шарды:
Search request
|
+--> shard 0
+--> shard 1
+--> shard 2
+--> shard 3
Каждый шард возвращает локальные результаты, после чего координирующий узел объединяет их и формирует общий рейтинг.
Primary shards и replicas
У шарда может быть основная и резервные копии:
Primary shard 0 → Node 1
Replica shard 0 → Node 2
Primary shard 1 → Node 2
Replica shard 1 → Node 3
Реплики нужны для:
- отказоустойчивости;
- восстановления после падения узла;
- увеличения пропускной способности чтения.
Если Node 1 падает, реплика шарда может стать основной.
Количество primary shards влияет на физическое разбиение индекса. Его нужно выбирать внимательнее, чем количество реплик.
Как выполняется запись
Клиент отправляет документ:
POST /products/_doc/product-142
Координирующий узел определяет, в какой primary shard должен попасть документ. Обычно выбор основан на хеше routing key:
shard = hash(document_id) mod number_of_primary_shards
Далее:
Client
|
v
Coordinating node
|
v
Primary shard
|
+--> Replica 1
+--> Replica 2
После записи документ не обязательно мгновенно появляется в поиске.
OpenSearch является системой near real-time search: между записью и видимостью документа в поисковом запросе может существовать небольшая задержка, связанная с refresh.
При этом получение документа непосредственно по ID может увидеть его раньше, чем обычный поисковый запрос.
Поиск и релевантность
Пример запроса:
{
"query": {
"match": {
"description": "беспроводные наушники"
}
}
}
OpenSearch не просто отвечает true или false. Он вычисляет _score для каждого документа.
Рейтинг может учитывать:
- сколько слов запроса найдено;
- насколько редко слово встречается во всём индексе;
- сколько раз слово встречается в документе;
- длину поля;
- вес поля;
- близость слов;
- пользовательские коэффициенты.
Например, поле name можно сделать важнее description:
{
"query": {
"multi_match": {
"query": "sony headphones",
"fields": [
"name^3",
"description"
]
}
}
}
name^3 означает усиление веса поля name.
Фильтр и полнотекстовый запрос — не одно и то же
Полнотекстовый запрос:
{
"match": {
"description": "sony headphones"
}
}
вычисляет релевантность.
Фильтр:
{
"term": {
"brand": "Sony"
}
}
проверяет точное условие:
подходит / не подходит
Обычно запрос строится так:
{
"query": {
"bool": {
"must": [
{
"match": {
"description": "wireless headphones"
}
}
],
"filter": [
{
"term": {
"brand": "Sony"
}
},
{
"range": {
"price": {
"lte": 30000
}
}
}
]
}
}
}
Здесь:
mustвлияет на релевантность;filterограничивает множество результатов;- фильтры часто легче кэшировать.
Агрегации
OpenSearch может не только искать документы, но и считать статистику.
Например, количество товаров по брендам:
{
"size": 0,
"aggs": {
"by_brand": {
"terms": {
"field": "brand"
}
}
}
}
Результат:
Sony 1240
Apple 830
Samsung 710
Или средняя цена:
{
"size": 0,
"aggs": {
"average_price": {
"avg": {
"field": "price"
}
}
}
}
На агрегациях строятся аналитические панели, графики и фасетная навигация интернет-магазинов.
Типичная архитектура
OpenSearch обычно не является основной транзакционной базой.
Архитектура выглядит так:
┌──────────────┐
│ PostgreSQL │
│ source of │
│ truth │
└──────┬───────┘
|
CDC / Outbox / Kafka
|
v
┌──────────────┐
│ Indexer │
└──────┬───────┘
|
v
┌──────────────┐
│ OpenSearch │
└──────┬───────┘
|
v
Search API
PostgreSQL хранит истинное состояние заказов, пользователей и платежей.
OpenSearch хранит поисковую проекцию, оптимизированную для чтения и поиска.
Например:
PostgreSQL:
products
brands
categories
reviews
inventory
В OpenSearch может находиться денормализованный документ:
{
"productId": "42",
"name": "Sony WH-1000XM6",
"brand": {
"id": "sony",
"name": "Sony"
},
"categories": [
"electronics",
"audio",
"headphones"
],
"rating": 4.8,
"available": true,
"searchableText": "Sony WH-1000XM6 wireless noise cancelling headphones"
}
Такая денормализация позволяет выполнить поиск одним обращением, без SQL join между множеством таблиц.
Почему нельзя просто заменить PostgreSQL на OpenSearch
OpenSearch не предназначен для строгих транзакционных инвариантов.
Например:
снять деньги со счёта A;
зачислить деньги на счёт B;
создать запись перевода;
гарантировать, что сумма денег не изменилась.
Для таких операций нужна транзакционная база.
OpenSearch имеет другие приоритеты:
быстро искать;
распределять нагрузку;
ранжировать;
агрегировать;
масштабировать чтение.
Поэтому OpenSearch не следует делать источником истины для:
- банковских балансов;
- остатков товаров;
- состояния платежа;
- уникальности критических бизнес-данных;
- операций, требующих строгой ACID-транзакции.
Он может содержать копию этих данных для поиска, но окончательное решение следует проверять по основной базе.
Например, OpenSearch может показать, что номер в гостинице доступен. Но перед бронированием система всё равно должна проверить доступность в транзакционном хранилище.
Работа с логами
Ещё один большой сценарий — сбор логов:
Applications
|
v
Fluent Bit / Data Prepper / Logstash
|
v
OpenSearch
|
v
OpenSearch Dashboards
В OpenSearch попадают записи:
{
"timestamp": "2026-07-14T10:40:13Z",
"service": "payment-service",
"level": "ERROR",
"traceId": "abc-123",
"message": "Payment provider timeout",
"durationMs": 5000
}
После этого можно искать:
service = payment-service
level = ERROR
timestamp between 10:00 and 11:00
Можно построить графики:
- количество ошибок в минуту;
- latency по сервисам;
- распределение HTTP-кодов;
- наиболее частые исключения;
- всплески нагрузки.
OpenSearch Dashboards
OpenSearch Dashboards — визуальный интерфейс над OpenSearch.
Через него можно:
- выполнять запросы;
- просматривать документы;
- строить графики;
- создавать дашборды;
- исследовать логи;
- настраивать алерты;
- анализировать метрики и трассировки.
То есть:
OpenSearch — поисковый и аналитический движок
OpenSearch Dashboards — пользовательский интерфейс
Векторный поиск
OpenSearch также может хранить embedding-векторы.
Например, текст:
Как отменить бронирование гостиницы?
преобразуется моделью в числовой вектор:
[0.017, -0.24, 0.81, ...]
Затем OpenSearch ищет ближайшие векторы и может найти документ:
Правила возврата средств при отмене номера
Хотя точные слова в запросе и документе различаются.
Это используется для:
- семантического поиска;
- поиска похожих товаров;
- рекомендаций;
- RAG;
- поиска по базе знаний;
- дедупликации документов.
Но embedding создаёт не сам индекс из воздуха. Обычно отдельная модель преобразует текст в вектор, после чего вектор записывается в OpenSearch.
OpenSearch и Elasticsearch
OpenSearch исторически произошёл от открытой версии Elasticsearch и Kibana 7.10.2.
Поэтому у них похожи:
- JSON Query DSL;
- REST API;
- понятия индексов и документов;
- shards и replicas;
- mappings;
- analyzers;
- агрегации;
- dashboards.
Но сейчас это отдельные проекты, которые развиваются независимо. Полная совместимость между современными версиями не гарантируется.
Грубо:
Elasticsearch + Kibana
и
OpenSearch + OpenSearch Dashboards
решают похожие классы задач, но являются разными продуктами.
Важные ограничения
Данные дублируются
Если источник истины находится в PostgreSQL, а поиск выполняется через OpenSearch, появляются две проекции состояния:
PostgreSQL → истинное состояние
OpenSearch → поисковая копия
Нужно организовать синхронизацию, повторную доставку событий, идемпотентность индексатора и переиндексацию.
Eventual consistency
После изменения основной базы поисковый индекс может некоторое время содержать старое состояние.
10:00:00 PostgreSQL обновлён
10:00:01 событие отправлено
10:00:02 OpenSearch обновлён
Этот промежуток должен быть допустим бизнесом.
Шарды не бесплатны
Слишком много мелких шардов создают накладные расходы:
- память;
- файловые дескрипторы;
- cluster metadata;
- сетевое взаимодействие;
- усложнение восстановления.
Слишком большие шарды тоже неудобны: их дольше переносить и восстанавливать.
Обновление документа фактически дорого
Поисковый индекс не изменяет произвольный кусок уже записанного сегмента так же, как база обновляет строку на месте. На нижнем уровне обновление связано с созданием новой версии документа и пометкой старой как удалённой.
Поэтому OpenSearch лучше подходит для поиска и аналитики, чем для непрерывных мелких транзакционных изменений одной записи.
Deep pagination
Запрос вида:
покажи результаты с 1 000 000 по 1 000 020
дорогой, потому что узлам приходится вычислить и отбросить огромное количество предыдущих результатов.
Для глубокого последовательного обхода используют search_after, стабильную сортировку и Point in Time.
Когда выбирать OpenSearch
OpenSearch уместен, когда требуется:
- искать по большому массиву текстов;
- ранжировать результаты;
- поддерживать автодополнение и опечатки;
- фильтровать товары по десяткам параметров;
- анализировать логи;
- строить оперативные дашборды;
- выполнять агрегации по большому объёму событий;
- реализовать семантический или гибридный поиск.
Он не нужен автоматически только потому, что данных много.
Если запросы выглядят так:
WHERE user_id = ?
ORDER BY created_at DESC
LIMIT 20
PostgreSQL с нормальными индексами может быть достаточен.
Самая короткая модель
PostgreSQL хранит бизнес-состояние. OpenSearch строит удобную для поиска проекцию этого состояния.
Или ещё точнее:
OpenSearch — распределённый индекс над документами, оптимизированный для быстрого поиска, ранжирования, фильтрации и агрегации, но не для выполнения критических бизнес-транзакций.