# About Columnar storage in Manticore Search

In this article, we will examine the purpose of Manticore Columnar storage, how it differs from the row-wise storage, and in which cases it makes sense to use it. We will also get acquainted with the basic structure of the storage format and the specifics of its integration into the query processing workflow of the search daemon.

### Введение

В этой статье мы рассмотрим назначение колонного хранилища Manticore, как оно отличается от строкового хранилища и в каких случаях имеет смысл его использовать. Мы также познакомимся с базовой структурой формата хранилища и особенностями его интеграции в процесс обработки запросов поискового демона.

### Хранилище атрибутов по умолчанию (строковое)
В Manticore существуют две отдельные сущности: полнотекстовые поля, поддерживающие только полнотекстовые запросы, и атрибуты различных типов, которые могут использоваться для группировки, сортировки и фильтрации. Хранилище по умолчанию (`engine='rowwise'`) сохраняет все атрибуты всех документов в памяти.

Для загрузки атрибутов в память используется mmap, который можно настроить через параметры `access_plain_attrs` и `access_blob_attrs`. Первый параметр отвечает за загрузку файлов `.spa`, содержащих все атрибуты фиксированной длины (integer, bigint, float и т.д.). Второй параметр — за загрузку файлов `.spb`, содержащих атрибуты переменной длины (string, mva, float_vector и т.д.). Поскольку mmap загружает данные в память только при обращении, начальные запросы, использующие атрибуты, могут быть медленнее. Чтобы смягчить это, по умолчанию для `access_plain_attrs` и `access_blob_attrs` установлен режим `mmap_preread`, при котором Manticore читает файлы `.spa` и `.spb` в фоновом потоке при старте. Это обычно означает (но не гарантирует), что файлы атрибутов будут находиться в памяти, избегая необходимости чтения данных с диска во время запросов. Однако, если система решит, что памяти недостаточно, атрибуты могут быть частично или полностью выгружены из памяти, что снова замедлит запросы. Чтобы избежать этого, в `access_plain_attrs` / `access_blob_attrs` можно указать `mlock`. Если памяти достаточно, атрибуты будут гарантированно оставаться в памяти, и система не будет их выгружать. Однако при недостатке памяти никаких гарантий нет.

### Зачем нам колонное хранилище?

В ситуациях, когда доступной памяти достаточно для хранения всех атрибутов, традиционное «строковое» хранилище работает эффективно. **Проблемы возникают, когда памяти недостаточно для всех атрибутов.**
Действительно, mmap может автоматически выгружать и перезагружать части файлов атрибутов по мере необходимости, позволяя работать даже при ограниченной памяти. Однако на практике такой подход может значительно снизить производительность до неприемлемого уровня. Проблема частично кроется в архитектуре строкового хранилища, которое сохраняет все атрибуты одного документа последовательно, затем атрибуты следующего документа и так далее. Ниже пример такой таблицы данных:
![](./mcl/table_example.png)

Файл `.spa` имеет следующую структуру:

![](./mcl/spa_table.png)

Файл `.spb` выглядит так:

![](./mcl/spb_table.png)


Когда запрос включает группировку по конкретному атрибуту, требуется получить только значение этого атрибута для каждого документа. Однако mmap работает так, что не может читать отдельные байты; вместо этого он загружает одну или несколько страниц памяти, обычно по 4 КБ каждая. Это означает, что при попытке прочитать значение одного атрибута система часто загружает значения всех соседних атрибутов, даже если они не нужны для обработки запроса.

Еще одна особенность `rowwise` хранилища — отсутствие сжатия данных. Поскольку оно работает через прямой доступ к памяти, код предполагает, что готовые к использованию данные доступны сразу. Кроме того, данные разных атрибутов внутри одного документа разнородны, что затрудняет эффективное сжатие отдельного документа. Более того, в этом формате хранилища нет понятия блоков документов, которые могли бы быть сжаты вместе.

Колонное хранилище (`engine='columnar'`) было разработано именно для ситуаций, когда памяти недостаточно, чтобы загрузить все атрибуты. Этот формат хранилища предлагает следующие преимущества:

* Данные каждого атрибута хранятся отдельно и могут читаться без влияния на другие атрибуты.
* Поскольку данные внутри одного атрибута обычно однородны, их можно сжимать.
* Значения атрибутов могут делиться на блоки из нескольких документов для сжатия.
* После распаковки блоков можно применять оптимизации для потоковой обработки.
* В ОЗУ хранится лишь очень небольшой кусок метаданных, всё остальное находится на диске.
* Для быстрого доступа к «горячим» данным вместо загрузки страниц в память через mmap используется кэш файловой системы.

Схематически структуру данных в колонном хранилище можно представить так:

![](./mcl/data_structure.png)

Колонное хранилище поставляется в отдельной библиотеке под названием MCL (Manticore Columnar Library). Эта библиотека отвечает за создание хранилища, упаковку и чтение данных.
Кроме того, в сам поисковый демон было добавлено значительное количество кода для работы с атрибутами, учитывающего особенности колонного хранилища.

Изначально демон реализовал `rowwise` хранилище, ориентированное на случайный доступ к данным. Можно было просто заменить доступ к данным в памяти на колонное хранилище, но делать это без учёта того, что колонное хранилище предназначено для потоковой обработки и хранит данные в сжатых блоках, означало бы серьёзное падение производительности.

Ниже приведены несколько примеров того, что было добавлено в демон для работы с колонным хранилищем:

* **Колонный сортировщик**
  В архитектуре Manticore документы, найденные через полнотекстовый поиск, сразу отправляются в то, что называется Сортировщиком. Сортировщик может просто сортировать, либо группировать документы, вычислять агрегаты и многое другое. Однако доступ к атрибутам в колонковом хранилище медленный, потому что значения извлекаются по одному.
  Если запрос относительно лёгкий — то есть полнотекстовый поиск быстрый или вовсе отсутствует — извлечение атрибутов из хранилища по одному может существенно влиять на производительность. Поэтому в некоторых случаях выгодно использовать дополнительный сортировщик поверх стандартного Сортировщика. Этот дополнительный сортировщик не сортирует, а аккумулирует документы и извлекает колонковые атрибуты пакетами перед передачей документов основному Сортировщику для окончательной обработки, что значительно быстрее.

* **Колонный группировщик**
  Группировщик в Manticore отвечает за преобразование входящих документов в один или несколько ключей группировки для последующей передачи в Сортировщик. Основная цель колонного группировщика — улучшить производительность за счёт уменьшения количества [virtual calls](https://en.wikipedia.org/wiki/Virtual_function). Например, когда работает обычный группировщик, он запрашивает данные из выражения, которое извлекает данные из хранилища. Это выражение реализовано на стороне демона и обеспечивает прозрачный доступ к различным типам хранилищ атрибутов. Внутри оно использует колонковый итератор (рассмотренный ниже), реализованный на стороне библиотеки MCL, который напрямую обращается к хранилищу. Специализированный колонный группировщик, однако, знает, что будет работать только с колонковым хранилищем, и убирает некоторые абстракции, работая напрямую с колонковым итератором.
  При группировке по строкам используется иной механизм. Когда строки добавляются в колонковое хранилище, хеш каждой строки (с учётом текущей [collation](https://manual.manticoresearch.com/Searching/Collations)) автоматически вычисляется и сохраняется как отдельный целочисленный атрибут. Колонный группировщик извлекает эти хеши из колонкового хранилища вместо чтения самой строки и вычисления хеша для использования в качестве ключа группировки.

* Аналогично, удаляя колонковое выражение, которое через колонковый итератор получает значение атрибута для текущего документа, в демоне реализуются колонковые фильтры и колонковые агрегаты.

* Оптимизатор запросов ([Cost based optimizer](https://manual.manticoresearch.com/Searching/Cost_based_optimizer), CBO) также должен учитывать колонковое хранилище. CBO определяет путь выполнения запроса. Например, он может заменить один из фильтров поиском по соответствующему вторичному индексу, убрав фильтр из запроса. В результате вторичный индекс вернёт номера документов (row IDs), на которых будут работать оставшиеся фильтры.
  Когда используется колонковое хранилище, оптимизатор должен сравнивать производительность вторичного индекса и колонкового анализатора (который выполняет быстрый поиск в колонковом хранилище, подробнее ниже). В зависимости от данных и количества доступных потоков иногда один метод работает быстрее другого. Например, колонковый анализатор лучше масштабируется параллельно.


### Что включено в MCL?

Среди компонентов, непосредственно входящих в библиотеку MCL, можно выделить две основные группы:

1.**Builder**
Отвечает за получение необработанных данных от демона и построение колонкового хранилища.

2.**Accessors**
Отвечает за доступ к данным. Существует два основных типа:

  * **Iterators**
  Более медленный способ доступа к хранилищу. Итератор может переместиться к указанному документу и извлечь значения атрибутов. Он работает медленнее, потому что не адаптирован для потоковой обработки. Однако в некоторых случаях демон не может обрабатывать данные в потоке, и тогда используются **columnar iterators**.

  * **Analyzers**
  Значительно более быстрый способ доступа к данным. Анализаторы принимают набор фильтров на вход и выводят номера документов (row IDs), удовлетворяющих этим фильтрам. Они работают быстро, потому что используют всю доступную метадату колонкового хранилища во время обработки данных и могут сразу распаковывать и обрабатывать множество документов за один вызов. Например, они могут полностью пропустить блоки данных (не распаковывая их), если значения min‑max блока указывают, что требуемые значения отсутствуют.
В демоне такой тип доступа автоматически включается на основе анализа CBO, но также может быть включён вручную через подсказки индекса; такой тип подсказки называется ColumnarScan.

### Заключение

В этой статье мы в общих чертах рассмотрели принципы колонкового хранилища Manticore и некоторые детали его интеграции в демон Manticore Search.
