OpenSearch

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 пропускает его через анализатор.

Анализатор может:

  1. разбить текст на слова;
  2. перевести их в нижний регистр;
  3. удалить знаки пунктуации;
  4. убрать стоп-слова;
  5. привести слова к нормальной или корневой форме.

Например:

"Беспроводные Наушники 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 — распределённый индекс над документами, оптимизированный для быстрого поиска, ранжирования, фильтрации и агрегации, но не для выполнения критических бизнес-транзакций.

Прокрутить вверх