OpenSearch -это мощная поисковая система с открытым исходным кодом, форк Elasticsearch. Если вы только начинаете работать с поисковыми системами, эта статья поможет разобраться в основах: как создавать индексы, настраивать структуру данных и использовать бустинг для улучшения релевантности результатов.

Что такое OpenSearch?

OpenSearch -это распределённая поисковая и аналитическая система, которая позволяет:

  • Быстро искать по большим объёмам данных
  • Индексировать структурированные и неструктурированные данные
  • Выполнять сложные запросы с фильтрацией и ранжированием
  • Работать с геопространственными данными
  • Использовать векторный поиск для семантического поиска

Простыми словами: OpenSearch -это как Google для ваших данных. Вы загружаете документы, а система позволяет быстро находить нужные по запросу.

Основные понятия

Индекс (Index)

Индекс -это коллекция документов, имеющих схожие характеристики. Можно сравнить с таблицей в реляционной БД, но с важными отличиями:

  • В БД вы определяете структуру (схему) заранее
  • В OpenSearch структура определяется через маппинг (mapping), но может быть динамической
  • Индекс может содержать миллионы документов и быстро находить нужные

Пример: индекс locations содержит информацию о локациях для бизнеса, индекс products -о товарах.

Документ (Document)

Документ -это базовая единица информации в OpenSearch. Это JSON-объект, который содержит данные.

{
  "id": "loc_1",
  "name": "Кафе на Тверской",
  "address": "ул. Тверская, д. 10, Москва",
  "traffic_score": 8.5,
  "coordinates": {
    "lat": 55.7558,
    "lon": 37.6173
  }
}

Маппинг (Mapping)

Маппинг -это определение структуры данных в индексе. Он описывает:

  • Какие поля есть в документе
  • Какой тип данных у каждого поля (текст, число, дата, геокоординаты)
  • Как индексировать и искать по полям

Маппинг похож на схему таблицы в БД, но более гибкий.

Типы данных в OpenSearch

OpenSearch поддерживает множество типов данных. Рассмотрим основные:

1. Text (текст)

Используется для полнотекстового поиска. Текст анализируется и разбивается на слова (токены).

{
  "name": {
    "type": "text"
  }
}

Особенности:

  • Текст разбивается на слова при индексации
  • Поддерживает поиск по части слова, синонимам
  • Можно настроить анализатор (как разбивать текст)

Пример использования: названия, описания, комментарии.

2. Keyword (ключевое слово)

Используется для точного совпадения. Значение хранится как есть, без анализа.

{
  "status": {
    "type": "keyword"
  }
}

Особенности:

  • Идеально для фильтрации и сортировки
  • Быстрый поиск по точному совпадению
  • Подходит для enum-значений (статусы, категории)

Пример использования: статусы заказов, категории, коды.

3. Number (числа)

Поддерживает разные числовые типы: long, integer, short, byte, double, float.

{
  "price": {
    "type": "double"
  },
  "quantity": {
    "type": "integer"
  }
}

Особенности:

  • Используется для диапазонных запросов (больше, меньше)
  • Подходит для сортировки и агрегаций
  • Точность зависит от типа

4. Date (дата)

Хранит дату и время в формате ISO 8601.

{
  "created_at": {
    "type": "date"
  }
}

Особенности:

  • Можно искать по диапазонам дат
  • Поддерживает разные форматы
  • Используется для временных фильтров

5. Boolean (логический)

Хранит true или false.

{
  "is_active": {
    "type": "boolean"
  }
}

6. Geo Point (геокоординаты)

Хранит географические координаты (широта, долгота).

{
  "location": {
    "type": "geo_point"
  }
}

Особенности:

  • Позволяет искать объекты в радиусе от точки
  • Вычислять расстояние между точками
  • Сортировать по расстоянию

Пример использования: поиск локаций рядом с пользователем.

7. Object (объект)

Вложенный JSON-объект.

{
  "address": {
    "type": "object",
    "properties": {
      "street": {"type": "text"},
      "city": {"type": "keyword"},
      "zip": {"type": "keyword"}
    }
  }
}

8. Array (массив)

Массив значений одного типа.

{
  "tags": {
    "type": "keyword"
  }
}

В JSON это будет выглядеть так:

{
  "tags": ["cafe", "restaurant", "coffee"]
}

9. Dense Vector (вектор)

Используется для векторного поиска (kNN, семантический поиск).

{
  "embedding": {
    "type": "dense_vector",
    "dims": 128
  }
}

Особенности:

  • Хранит числовой вектор фиксированной размерности
  • Используется для поиска похожих документов
  • Требует специальных запросов (kNN)

Создание индекса и маппинга

Шаг 1: Создание индекса с маппингом

Создадим индекс для локаций бизнеса:

PUT /locations
{
  "settings": {
    "number_of_shards": 1,
    "number_of_replicas": 1
  },
  "mappings": {
    "properties": {
      "id": {
        "type": "keyword"
      },
      "name": {
        "type": "text",
        "fields": {
          "keyword": {
            "type": "keyword"
          }
        }
      },
      "address": {
        "type": "text"
      },
      "region": {
        "type": "keyword"
      },
      "city": {
        "type": "keyword"
      },
      "coordinates": {
        "type": "geo_point"
      },
      "traffic_score": {
        "type": "float"
      },
      "competition_density": {
        "type": "float"
      },
      "business_types_suitable": {
        "type": "keyword"
      },
      "demographics": {
        "type": "object",
        "properties": {
          "age_group": {"type": "keyword"},
          "average_income": {"type": "integer"},
          "population_density": {"type": "integer"}
        }
      },
      "embedding": {
        "type": "dense_vector",
        "dims": 128
      }
    }
  }
}

Разбор маппинга

Settings (настройки индекса):

  • number_of_shards -количество шардов (частей индекса). Влияет на производительность и масштабируемость
  • number_of_replicas -количество реплик. Обеспечивает отказоустойчивость

Mappings (структура данных):

  • properties -описание полей документа

Multi-fields (множественные поля): Обратите внимание на поле name:

"name": {
  "type": "text",
  "fields": {
    "keyword": {
      "type": "keyword"
    }
  }
}

Это позволяет:

  • Искать по name как по тексту (полнотекстовый поиск)
  • Использовать name.keyword для точного совпадения и сортировки

Шаг 2: Добавление документа

POST /locations/_doc
{
  "id": "loc_1",
  "name": "Кафе на Тверской",
  "address": "ул. Тверская, д. 10, Москва",
  "region": "Москва",
  "city": "Москва",
  "coordinates": {
    "lat": 55.7558,
    "lon": 37.6173
  },
  "traffic_score": 8.5,
  "competition_density": 2.3,
  "business_types_suitable": ["cafe", "restaurant"],
  "demographics": {
    "age_group": "26-35",
    "average_income": 75000,
    "population_density": 5000
  },
  "embedding": [0.1, 0.2, 0.3, ...] // 128 чисел
}

Поиск и запросы

Простой поиск

GET /locations/_search
{
  "query": {
    "match": {
      "name": "кафе"
    }
  }
}

Поиск с фильтрацией

GET /locations/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "кафе"
          }
        }
      ],
      "filter": [
        {
          "term": {
            "city": "Москва"
          }
        },
        {
          "range": {
            "traffic_score": {
              "gte": 7.0
            }
          }
        }
      ]
    }
  }
}

Разбор запроса:

  • must -документ должен соответствовать (влияет на релевантность)
  • filter -документ должен соответствовать (не влияет на релевантность, но быстрее)

Геопространственный поиск

GET /locations/_search
{
  "query": {
    "geo_distance": {
      "distance": "5km",
      "coordinates": {
        "lat": 55.7558,
        "lon": 37.6173
      }
    }
  },
  "sort": [
    {
      "_geo_distance": {
        "coordinates": {
          "lat": 55.7558,
          "lon": 37.6173
        },
        "order": "asc",
        "unit": "km"
      }
    }
  ]
}

Этот запрос найдёт все локации в радиусе 5 км от указанной точки и отсортирует их по расстоянию.

Бустинг (Boosting)

Бустинг -это механизм увеличения релевантности документов, которые соответствуют определённым критериям. Это позволяет “поднять” более важные результаты в выдаче.

Простой бустинг

Увеличим релевантность локаций с высоким traffic_score:

GET /locations/_search
{
  "query": {
    "bool": {
      "must": [
        {
          "match": {
            "name": "кафе"
          }
        }
      ],
      "should": [
        {
          "range": {
            "traffic_score": {
              "gte": 7.0,
              "boost": 2.0
            }
          }
        }
      ]
    }
  }
}

Как это работает:

  • should -документ может соответствовать (необязательно)
  • boost: 2.0 -если документ соответствует, его релевантность умножается на 2

Функциональный бустинг (Function Score)

Более гибкий способ -использовать function_score:

GET /locations/_search
{
  "query": {
    "function_score": {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "name": "кафе"
              }
            }
          ],
          "filter": [
            {
              "term": {
                "city": "Москва"
              }
            }
          ]
        }
      },
      "functions": [
        {
          "filter": {
            "range": {
              "traffic_score": {
                "gte": 7.0
              }
            }
          },
          "weight": 2.0
        },
        {
          "filter": {
            "range": {
              "competition_density": {
                "lte": 3.0
              }
            }
          },
          "weight": 1.5
        }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  }
}

Разбор:

  • functions -список функций бустинга
  • filter -условие для применения бустинга
  • weight -вес бустинга
  • score_mode: "sum" -как объединять результаты функций (sum, multiply, avg, max, min)
  • boost_mode: "multiply" -как применять к исходному score (multiply, replace, sum, avg, max, min)

Практический пример: комбинированный бустинг

В рекомендательной системе для бизнеса мы используем комбинированный бустинг:

GET /locations/_search
{
  "query": {
    "function_score": {
      "query": {
        "bool": {
          "must": [
            {
              "match": {
                "name": "кафе"
              }
            }
          ],
          "filter": [
            {
              "term": {
                "city": "Москва"
              }
            },
            {
              "term": {
                "business_types_suitable": "cafe"
              }
            }
          ]
        }
      },
      "functions": [
        {
          "filter": {
            "range": {
              "traffic_score": {
                "gte": 7.0
              }
            }
          },
          "weight": 2.0,
          "description": "Бустинг для локаций с высоким трафиком"
        },
        {
          "filter": {
            "range": {
              "competition_density": {
                "lte": 3.0
              }
            }
          },
          "weight": 1.5,
          "description": "Бустинг для локаций с низкой конкуренцией"
        }
      ],
      "score_mode": "sum",
      "boost_mode": "multiply"
    }
  },
  "sort": [
    {
      "_score": {
        "order": "desc"
      }
    },
    {
      "traffic_score": {
        "order": "desc"
      }
    },
    {
      "competition_density": {
        "order": "asc"
      }
    }
  ]
}

Логика:

  1. Находим все кафе в Москве
  2. Увеличиваем релевантность тех, у кого traffic_score >= 7.0 (в 2 раза)
  3. Увеличиваем релевантность тех, у кого competition_density <= 3.0 (в 1.5 раза)
  4. Сортируем по релевантности, затем по traffic_score (убывание), затем по competition_density (возрастание)

Анализаторы (Analyzers)

Анализатор определяет, как текст разбивается на слова при индексации и поиске.

Стандартный анализатор

По умолчанию используется стандартный анализатор, который:

  • Разбивает текст по пробелам и знакам препинания
  • Приводит к нижнему регистру
  • Удаляет стоп-слова (опционально)

Кастомный анализатор

Можно создать свой анализатор:

PUT /locations
{
  "settings": {
    "analysis": {
      "analyzer": {
        "russian_analyzer": {
          "type": "custom",
          "tokenizer": "standard",
          "filter": [
            "lowercase",
            "russian_stop",
            "russian_stemmer"
          ]
        }
      }
    }
  },
  "mappings": {
    "properties": {
      "name": {
        "type": "text",
        "analyzer": "russian_analyzer"
      }
    }
  }
}

Компоненты анализатора:

  • tokenizer -разбивает текст на токены (слова)
  • filter -обрабатывает токены (нижний регистр, стемминг, синонимы)

Практические советы

1. Выбор между text и keyword

  • Используйте text для поиска по содержимому (названия, описания)
  • Используйте keyword для точного совпадения и фильтрации (статусы, коды, категории)
  • Используйте multi-fields, если нужно и то, и другое

2. Оптимизация производительности

  • Используйте filter вместо must для условий, которые не влияют на релевантность
  • Фильтры кэшируются и работают быстрее
  • Ограничивайте количество возвращаемых документов (size)

3. Бустинг

  • Не переусердствуйте с бустингом -слишком высокие значения могут исказить результаты
  • Тестируйте разные значения весов
  • Используйте function_score для сложной логики

4. Геопространственные запросы

  • Используйте geo_point для координат
  • Кэшируйте результаты для популярных локаций
  • Учитывайте, что геозапросы могут быть ресурсоёмкими

Заключение

OpenSearch -мощный инструмент для поиска и анализа данных. Основные моменты:

  1. Индекс -коллекция документов
  2. Маппинг -структура данных (типы полей)
  3. Документ -JSON-объект с данными
  4. Бустинг -увеличение релевантности важных результатов
  5. Анализаторы -обработка текста при индексации

При работе обычно начинаем с простых запросов, постепенно усложняя их. Если вы тоже работаете с OpenSearch, экспериментируйте с бустингом и анализируйте результаты. OpenSearch -это инструмент, который требует практики, но результаты того стоят.


Полезные ссылки: