Индексы

Каждый неймспейс может иметь до 256 одиночных индексов (1 системный псевдоиндекс tuple и 255 пользовательских индексов). Число композитных и sparse индексов не ограничено.

Типы индексов в Reindexer

Типы индексов, предусмотренные в Reindexer, представлены в таблице:

Тип индекса Назначение Особенности
hash
Используется по умолчанию
Быстрый поиск по запросам EQ и SET Медленная и неэффективная сортировка результатов по полю
tree Быстрый поиск по запросам RANGE, GT, GE, LT и LE Быстрая сортировка результата по полю. По сравнению с типом hash работает немного медленнее для запросов EQ и SET
rtree Используется для выполнения запросов DWithin для определения расстояния между точками (Подробнее — в разделе «Геометрические типы данных») Поддерживается только геометрический (географический) тип данных point ([2] float64, reindexer.Point)
text Полнотекстовый поиск
ttl Для автоматического удаления документов/записей через заданное время Поле может содержать только данные с типом int64 для хранения времени в UNIX-формате
-
Колоночный индекс
Поиск полным перебором Не может обеспечить быстрый поиск по индексной структуре, однако при использовании компараторов работает намного эффективнее, чем поля без индексов. Характеризуется наименьшим потреблением памяти по сравнению с другими типами индексов
float_vector Векторные индексы

Также Reindexer поддерживает еще один тип индекса для полнотекстового поиска — fuzzytext. В этом случае используется полнотекстовый движок на триграммах. Функция экспериментальная. В большинстве случаев для организации полнотекстового поиска лучше использовать индексы типа text.

Допустимые типы значений индексных полей в Reindexer:

  • int,
  • int64,
  • double,
  • string,
  • bool,
  • composite,
  • point ([2]float64, reindexer.Point),
  • uuid,
  • float_vector.

Допустимые сочетания типов индексов и типов значений индексных полей в Reindexer

В Reindexer допускаются сочетания типов индексов и типов значений индексных полей согласно таблице (+ на пересечении означает допустимую комбинацию):

Тип индекса/тип значения индексного поля hash tree rtree text ttl - hnsw ivf brute-force
int + + +
int64 + + + +
double + +
string + + + +
bool +
composite + + +
point +
uuid + +
float_vector(float32) + + +

Параметры индексных полей

Описание значений параметров индексных полей в Reindexer представлены в таблице:

Параметр Описание/значение Особенности
pk Поле — часть первичного индекса В каждом неймспейсе должно быть хотя бы одно поле с параметром pk
composite Создание композитного (составного) индекса. Перед созданием композитного индекса для всех полей из его состава требуется создать собственные индексы (любого типа, например -). Для полтонекстовых композитных индексов это условие не является обязательным, но позволит ускорить загрузку данных с диска и пересоздание индекса При взаимодействии с Reindexer через Go тип поля должен быть обозначен пустой структурой: struct{} (пример)
joined Joined-поле Поле с этим тегом используется для хранения документов, полученных в результате join’a к текущему документу (SubitemType), и обращения к ним. При взаимодействии с Reindexer через Go поле должно иметь тип []*SubitemType (пример)
dense Уменьшение размера памяти, занимаемого индексным полем Параметр полезно использовать для индексных полей с высокой селективностью.
Экономия памяти составляет:
* для полей, помеченных индексом типа hash и tree, ~8-32 байта для каждого уникального значения ключа (за счёт отсутствия оверхда на хранение дополнительного btree).
Использование этой опции для низкоселективных (с большим количеством повторяющихся ключей) индексов типа hash и tree может существенно снизиться скорость обновления
is_no_column Отключение колоночного саб-индекса Если установлен в true, то отключает использование колоночного саб-индекса, позволяя сэкономить память (от 4 до 16 байт на каждый докумен в зависимости от типа поля). По умолчанию колоночный саб-индекс используется для скалярных hash, tree и - индексов (если они не sparse) для более эффективной реализации компараторов. Обычно колоночный саб-индекс не требуется для hash/tree индексов с хорошей селективностью
sparse Строка (документ) содержит значение индекса Sparse только в том случае, если он задан специально - нет пустых (или по умолчанию) записей этого типа индексов в строке (документе) Позволяет экономить оперативную память, но в то же время может снизиться производительность по сравнению с использованием обычных индексов
collate_numeric Устанавливает режим сравнения строк как чисел (порядок при обычном сравнении строк: 1, 10, 2; порядок при сравнении строк как чисел: 1, 2, 10) Значение индексного поля должно быть задано в строковом формате
collate_ascii Создает нечувствительный к регистру строковый индекс в ASCII-кодировке Значение индексного поля должно быть задано в строковом формате
collate_utf8 Создает нечувствительный к регистру строковый индекс в кодировке UTF-8 Значение индексного поля должно быть задано в строковом формате
collate_custom=<ORDER> Пользовательский порядок сортировки (задается последовательностью символов <ORDER>). Сначала сортировка выполняется в соответствии с заданным правилом, затем — в обычном порядке (пример) Значение индексного поля должно быть задано в строковом формате
linear, quadratic, greene или rstar Задает алгоритм построения индекса типа rtree Значение по умолчанию — rstar
appendable Создает array-индекс из нескольких полей (в том числе из полей основной и вложенной структур). Позволяет сгенерировать индекс с несколькими jsonpath Параметр доступен только для Go-коннектора. appendable можно использовать для двух массивов: при поиске по индексу с этим параметром они будут рассматриваться как один массив

Добавление индексов

При добавлении некоторых типов индексов им можно задать несколько jsonpath. Массив jsonpath определяет, из каких JSON-полей документа будут взяты значения для индексирования. Если у индекса несколько jsonpath, то проиндексированы будут все указанные jsonpath, встретившиеся в документе. Возможность использования нескольких jsonpath предусмотрена для композитных и array-индексов. Если вам нужно создать одиночный индекс с несколькими jsonpath, используйте array-индекс ("is_array": true).

Ниже представлены примеры создания индексов:

Пример добавления индекса через AddIndex:

db.AddIndex("items", reindexer.IndexDef{
    Name:      "id",           // Имя индекса
    JSONPaths: []string{"id"}, // Имя индекса в JSON-структуре
    IndexType: "text",         // Тип индекса
    FieldType: "string",       // Тип значения индексного поля
  })

Чтобы добавить индекс при объявлении структуры, нужно пометить индексное поле тегом reindex.

Тег reindex содержит имя индекса (name), его тип (type) и параметры/опции (opts):

reindex:"<name>[[,<type>],<opts>]"

Пример объявления структуры тегами reindex:

Поля структуры, которые требуется положить в БД (а также joined-поля), должны быть публичными (экспортированными из модуля). Иначе модуль байндинга не сможет к ним обращаться. Исключением являются поля для композитных индексов - они могут быть приватными.

type Item struct {
  ID       int64  `reindex:"id,,pk"`    // Добавление индекса `id`, являющегося частью первичного ключа
  Name     string `reindex:"name"`      // Добавление индекса `name`
  Articles []int  `reindex:"articles"`  // Добавление индекса `articles`
  Year     int    `reindex:"year,tree"` // Добавление индекса `year`. Параметр tree обеспечивает быстрый поиск по запросам RANGE, GT и LT
}

db := reindexer.NewReindex("builtin:///tmp/reindex/testdb") // Подключение к базе данных

db.OpenNamespace("items", reindexer.DefaultNamespaceOptions(), Item{}) // Создание нового неймспейса и добавление индексов
db = RxConnector(
    "cproto://127.0.0.1:6534/testdb",
    enable_compression=True,
    fetch_amount=500
)
index_definitions = [
    {
        "name": "id",
        "json_paths": ["id"],
        "field_type": "int",
        'is_pk': True,
    },
    {
        "name": "name",
        "json_paths": ["name"],
        "field_type": "string",
    },
    {
        "name": "articles",
        "json_paths": ["articles"],
        "field_type": "int",
        "is_array": True,
    },
    {
        "name": "year",
        "json_paths": ["year"],
        "field_type": "int",
        "index_type": "tree",
    },
]

db.namespace_open("items")

for index in index_definitions:
    db.index_add("items", index)
import java.util.List;
import ru.rt.restream.reindexer.NamespaceOptions;
import ru.rt.restream.reindexer.annotations.Reindex;
import static ru.rt.restream.reindexer.IndexType.TREE;


// Объявление структуры с индексными полями
public static class Item {
    // Поле 'id' — первичный ключ
    // Если для индекcа не указан тип, type = HASH
    @Reindex(name = "id", isPrimaryKey = true)
    private Integer id;

    // Текстовое индексное поле.
    @Reindex(name = "name")
    private String name;

    // Массив 'articles' — множество записей (документов, статей) в составе индекса
    @Reindex(name = "articles")
    private List<Integer> articles;

    // Добавление сортируемого индекса по полю 'year'
    @Reindex(name = "year", type = TREE)
    private Integer year;

    public Item() {
    }

    public Item(Integer id, String name, List<Integer> articles, Integer year) {
        this.id = id;
        this.name = name;
        this.articles = articles;
        this.year = year;
    }

    public Integer getId() {
        return id;
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public List<Integer> getArticles() {
        return articles;
    }

    public void setArticles(List<Integer> articles) {
        this.articles = articles;
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    @Override
    public String toString() {
        return "Item{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", articles=" + articles +
                ", year=" + year +
                '}';
    }
}

// Подключение к базе данных
Reindexer db = ReindexerConfiguration.builder()
    .url("builtin:///tmp/reindex/testdb")
    .getReindexer();

// Создание нового неймспейса
Namespace<Item> itemNamespace = db.openNamespace(
    "items",
    NamespaceOptions.defaultOptions(),
    Item.class
);

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

curl --location --request POST 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items/indexes' \
--header 'Content-Type: application/json' \
--data-raw '{
  "name": "id3",
  "json_paths": [
    "id3"
  ],
  "field_type": "string",
  "index_type": "hash",
  "expire_after": 0,
  "is_pk": false,
  "is_array": false,
  "is_dense": false,
  "is_sparse": false,
  "rtree_type": "linear",
  "is_simple_tag": false,
  "collate_mode": "none",
  "sort_order_letters": "",
  "config": {
    "enable_translit": true,
    "enable_numbers_search": false,
    "enable_warmup_on_ns_copy": false,
    "enable_kb_layout": true,
    "log_level": 0,
    "merge_limit": 20000,
    "extra_word_symbols": "-/+",
    "stop_words": [
      "string"
    ],
    "stemmers": [
      "en",
      "ru"
    ],
    "synonyms": [
      {
        "tokens": [
          "string"
        ],
        "alternatives": [
          "string"
        ]
      }
    ],
    "terms_boost": [
      {
        "terms": [
          "string"
        ],
        "boost": 1
      }
    ],    
    "bm25_boost": 1,
    "bm25_weight": 0.1,
    "distance_boost": 1,
    "distance_weight": 0.5,
    "term_len_boost": 1,
    "term_len_weight": 0.3,
    "position_boost": 1,
    "position_weight": 0.1,
    "full_match_boost": 1.1,
    "partial_match_decrease": 15,
    "min_relevancy": 0.05,
    "max_typos": 2,
    "max_typo_len": 15,
    "max_rebuild_steps": 50,
    "max_step_size": 4000,
    "sum_ranks_by_fields_ratio": 0,
    "fields": [
      {
        "field_name": "string",
        "bm25_boost": 1,
        "bm25_weight": 0.1,
        "term_len_boost": 1,
        "term_len_weight": 0.3,
        "position_boost": 1,
        "position_weight": 0.1
      }
    ]
  }
}'

Изменение и удаление индексов

Изменение индекса

Изменение индекса происходит в два этапа: его удаление (Drop) и добавление в неймспейс с новыми значениями (Add). Поэтому при изменении индекса следует заполнять IndexDef целиком, а не его отдельные поля. В противном случае опции индексного поля будут установлены в дефолтные значения.

Ниже представлены примеры создания неймспейсов:

db.UpdateIndex("items", reindexer.IndexDef{
    Name:      "year",                // Поиск индекса по имени
    JSONPaths: []string{"Date_year"}, // Изменение имени индекса в JSON-структуре
    IndexType: "tree",                // Изменение типа индекса
    FieldType: "int",                 // Изменение типа значения индексного поля
  })
db.index_update(
    "items",
    {
        "name": "year",
        "json_paths": ["Date_year"],
        "index_type": "tree",
        "field_type": "int",
    }
)
curl --location --request PUT 'http://127.0.0.1:9088/api/v1/db/testdb/namespaces/items3/indexes' \
--header 'Content-Type: text/plain' \
--data-raw '{
  "name": "id",
  "json_paths": [
    "New_id"
  ],
  "field_type": "int64",
  "index_type": "tree",
  "expire_after": 0,
  "is_pk": false,
  "is_array": false,
  "is_dense": false,
  "is_sparse": false,
  "rtree_type": "linear",
  "is_simple_tag": false,
  "collate_mode": "none",
  "sort_order_letters": "",
  "config": {
    "enable_translit": true,
    "enable_numbers_search": false,
    "enable_warmup_on_ns_copy": false,
    "enable_kb_layout": true,
    "log_level": 0,
    "merge_limit": 20000,
    "extra_word_symbols": "-/+",
    "stop_words": [
      "string"
    ],
    "stemmers": [
      "en",
      "ru"
    ],
    "synonyms": [
      {
        "tokens": [
          "string"
        ],
        "alternatives": [
          "string"
        ]
      }
    ],
    "terms_boost": [
      {
        "terms": [
          "string"
        ],
        "boost": 1
      }
    ],
    "bm25_boost": 1,
    "bm25_weight": 0.1,
    "distance_boost": 1,
    "distance_weight": 0.5,
    "term_len_boost": 1,
    "term_len_weight": 0.3,
    "position_boost": 1,
    "position_weight": 0.1,
    "full_match_boost": 1.1,
    "partial_match_decrease": 15,
    "min_relevancy": 0.05,
    "max_typos": 2,
    "max_typo_len": 15,
    "max_rebuild_steps": 50,
    "max_step_size": 4000,
    "sum_ranks_by_fields_ratio": 0,
    "fields": [
      {
        "field_name": "string",
        "bm25_boost": 1,
        "bm25_weight": 0.1,
        "term_len_boost": 1,
        "term_len_weight": 0.3,
        "position_boost": 1,
        "position_weight": 0.1
      }
    ]
  }
}'

Удаление индекса

Примеры команд для удаления индекса:

db.DropIndex("items", "articles")
db.index_drop("items", "articles")
db.dropIndex("items", "articles");
curl --location --request DELETE 'http://127.0.0.1:9088/api/v1/db/dbtest/namespaces/items/indexes/year'

Получение информации об индексах, доступных для неймспейса

Ниже представлен пример команды для получения списка индексов, доступных для неймспейса, по HTTP:

curl -X GET --header 'Accept: application/json' 'http://127.0.0.1:9088/api/v1/db/dbtest/namespaces/items/indexes'

Вложенные структуры

По умолчанию Reindexer сканирует все вложенные структуры и указатели на них и добавляет объявленные в них индексы в неймспейс (см. комментарии в примерах ниже).

Примеры:

При сканировании есть ряд исключений:

  • приватные (неэкспортируемые) вложенные поля пропускаются — ни их содержимое, ни объявленные в них индексы не будут добавлены в БД;
  • вложенные поля, отмеченные тегом reindex:"-" попадут в БД, но объявленные внутри них индексы будут проигнорированы;
  • вложенные поля, отмеченные тегом json:"-" не попадут в БД, а объявленные внутри них индексы будут проигнорированы.
type Actor struct {
  Name string `reindex:"actor_name"`
  Age  int    `reindex:"age"`
}

type BaseItem struct {
  ID int64 `reindex:"id,hash,pk"`
}

type ComplexItem struct {
  BaseItem              // Индексные поля из BaseItem будут добавлены в текущую структуру
  Actor         []Actor // Индексные поля из Actor ("name" and "age") будут добавлены в текущую структуру в виде отдельных индексов-массивов
  Name          string  `reindex:"name"`      // Hash-индекс для поля "name"
  Year          int     `reindex:"year,tree"` // Tree-индекс для поля "year"
  Value         int     `reindex:"value,-"`   // Колоночный индекс для поля "value"
  Metainfo      int     `json:"-"`            // Содержимое поля MetaInfo НЕ будет сохраняться в reindexer
  Parent        *Item   `reindex:"-"`         // Индексные поля из Parent НЕ будут добавлены в текущую структуру. Содержимое полей Parent попадёт в reindexer в виде неиндексируемых данных
  ParentHidden  *Item   `json:"-"`            // Индексные поля и любые данные из ParentHidden НЕ попадут в reindexer
  privateParent *Item                         // Индексные поля и любые данные из ParentHidden НЕ попадут в reindexer (поведение аналогично тегу `json:"-"`)
  AnotherActor  Actor   `reindex:"actor"`     // Индексные поля AnotherActor будут добавлены в текущую структуру с префиском "actor." (в этом конкретном примере это приведёт к добавлению двух индексов: "actor.actor_name" и "actor.age")
}

Добавления индексов в Python имеет следующие особенности:

  • каждому индексу соответствует словарь, который содержит все его параметры;
  • индекс имеет произвольное имя name, которое не привязано к полям записи, и путь json_paths, который должен соответствовать полю записи. При этом . означает вложенность поля, например: actor.name — это ключ name JSON-объекта в поле actor или ключ в массиве JSON-объектов;
  • индекс добавляется в неймспейс явно, вызовами команды index_add().

Для записи со схемой:

{
  "title": "complex_item",
  "type": "object",
  "properties": {
    "id": {
      "type": "integer"
    },
    "actor": {
      "type": "array",
      "items": {
        "type": "object",
        "properties": {
          "actor_name": {
            "type": "string"
          },
          "age": {
            "type": "integer"
          }
        }
      }
    },
    "another_actor": {
      "type": "object",
      "properties": {
        "actor_name": {
          "type": "string"
        },
        "age": {
          "type": "integer"
        }
      }
    }
  }
}

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

nested_index_definitions = [
    {
        "name": "id",
        "json_paths": ["id"],
        "field_type": "int64",
        "is_pk": True
    },
    # Индексные поля из actor ("name" and "age") добавляются в виде отдельных индексов-массивов ("is_array": True)
    {
        "name": "actor.name",
        "json_paths": ["actor.name"],
        "field_type": "string",
        "is_array": True
    },
    {
        "name": "actor.age",
        "json_paths": ["actor.age"],
        "field_type": "int",
        "is_array": True
    },
    # Индексные поля из another_actor добавляются с префиском "another_actor."
    {
        "name": "another_actor.name",
        "json_paths": ["another_actor.name"],
        "field_type": "string"
    },
    {
        "name": "another_actor.age",
        "json_paths": ["another_actor.age"],
        "field_type": "int"
    }
]
// Индексные поля из BaseItem будут добавлены в текущую структуру
public static class BaseItem {
    @Reindex(name = "id", isPrimaryKey = true)
    private Long id;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }
}

// Индексные поля из Actor ("name" and "age") будут добавлены в текущую структуру в виде отдельных индексов-массивов
public class Actor {
    @Reindex(name = "actor_name")
    private String name;

    @Reindex(name = "age")
    private Integer age;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }
}

public static class ComplexItem extends BaseItem {
    @Reindex(name = "actor")
    private List<Actor> actor;

    // Hash-индекс для поля "name"
    @Reindex(name = "name")
    private String name;

    // Tree-индекс для поля "year"
    @Reindex(name = "year", type = TREE)
    private Integer year;

    // Колоночный индекс для поля "value"
    @Reindex(name = "value", isSparse = true, isNoColumn = true)
    private Integer value;

    // Содержимое поля MetaInfo НЕ будет
    private Integer metainfo;

    // Индексные поля из Parent НЕ будут добавлены в текущую структуру. Содержимое полей Parent попадёт в reindexer в виде неиндексируемых
    @Reindex(name = "-")
    private Item parent;

    // Индексные поля и любые данные из ParentHidden НЕ попадут в reindexer
    private Item parentHidden;

    // Индексные поля и любые данные из ParentHidden НЕ попадут в reindexer (поведение аналогично тегу `json:"-"`)
    private Item privateParent;

    // Индексные поля AnotherActor будут добавлены в текущую структуру с префиском "actor." (в этом конкретном примере это приведёт к добавлению двух индексов: "actor.actor_name" и "actor.age")
    @Reindex(name = "actor")
    private Actor anotherActor;

    public List<Actor> getActor() {
        return actor;
    }

    public void setActor(List<Actor> actor) {
        this.actor = actor;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getYear() {
        return year;
    }

    public void setYear(Integer year) {
        this.year = year;
    }

    public Integer getValue() {
        return value;
    }

    public void setValue(Integer value) {
        this.value = value;
    }

    public Integer getMetainfo() {
        return metainfo;
    }

    public void setMetainfo(Integer metainfo) {
        this.metainfo = metainfo;
    }

    public Item getParent() {
        return parent;
    }

    public void setParent(Item parent) {
        this.parent = parent;
    }

    public Item getParentHidden() {
        return parentHidden;
    }

    public void setParentHidden(Item parentHidden) {
        this.parentHidden = parentHidden;
    }

    public Item getPrivateParent() {
        return privateParent;
    }

    public void setPrivateParent(Item privateParent) {
        this.privateParent = privateParent;
    }

    public Actor getAnotherActor() {
        return anotherActor;
    }

    public void setAnotherActor(Actor anotherActor) {
        this.anotherActor = anotherActor;
    }

    @Override
    public String toString() {
        return "ComplexItem{" +
                "id=" + getId() +
                ", actor=" + actor +
                ", name='" + name + '\'' +
                ", year=" + year +
                ", value=" + value +
                ", metainfo=" + metainfo +
                ", parent=" + parent +
                ", parentHidden=" + parentHidden +
                ", privateParent=" + privateParent +
                ", anotherActor=" + anotherActor +
                '}';
    }
}

Примеры объявления структур с индексными полями с указанием параметров на Go

Ниже представлены примеры объявления структур с индексными полями разных типов с указанием параметров на Go.

Пример 1

type Item struct {
  ID     int64 `reindex:"id,,pk"` // Добавление индекса `id`, являющегося частью первичного ключа (о чем говорит параметр `pk`)
  Rating int   `reindex:"rating"` // Добавление индекса `rating`
  Year   int   `reindex:"year"`   // Добавление индекса `year`

  _ struct{} `reindex:"rating+year,tree,composite"` // Композитный индекс с сортировкой по полям  `rating` и `year`
}

Пример 2

type Actor struct {
  ID        int    `reindex:"id"`           // Добавление индекса `id`
  Name      string `reindex:"name,tree"`    // Добавление tree-индекса `name`
  IsVisible bool   `reindex:"is_visible,-"` // Добавление колоночного индекса `is_visible`
  Metainfo int     `json:"-"`               // Поле "MetaInfo" не будет сохранено в reindexer

}

type ItemWithJoin struct {
  ID          int      `reindex:"id,tree,pk"`     // Добавление tree-индекса `id`, являющегося первичным ключом (о чем говорит параметр `pk`)
  Name        string   `reindex:"name"`           // Добавление индекса `name`
  ActorsIDs   []int    `reindex:"actors_ids"`     // Добавление индекса `actors_ids`
  ActorsNames []int    `reindex:"actors_names"`   // Добавление индекса `actors_names`
  Actors      []*Actor `reindex:"actors,,joined"` // Joined-поле с индексом `actors`
}

Пример 3

type SortModeCustomItem struct {
  ID      int    `reindex:"id,,pk"`                                    // Добавление hash-индекса `id`, являющегося первичным ключом
  InsItem string `reindex:"item_custom,hash,collate_custom=a-zA-Z0-9"` // Индекс типа `hash`, обеспечивающий быстрый поиск по запросам `EQ` и `SET`. Задан пользовательский порядок сортировки.
}

Особенности хранения данных для индексных полей с различными опциями в Reindexer

В отличие от многих классических СУБД, в Reindexer индексы могут влиять на логику хранения и, как следствие, на содержимое документа. Это сделано с целью оптимизации потребления памяти, а также для реализации более эффективной фильтрации по индексным полям и их модификации.

С точки зрения логики работы с данными индексы в Reindexer можно разделить на 3 основные группы:

  • Обычные (regular);
  • Sparse (разреженные);
  • Композитные.

Обычные (regular) индексы

Обычные индексы напрямую связаны со слоем хранения данных. Если для поля создан такой индекс, то значение такого поля всегда попадает в плоский буфер PayloadValue по заранее известному смещению (для строк там будет храниться указатель, а для массивов - дополнительный уровень косвенности).

Такой подход позволяет:

  1. Реализовать очень быстрый доступ к значениям при фильтрации с использованием компараторов, при сортировках, агрегациях и модификациях значений - фактически доступ к значению эквивалентен переходу по одному или нескольким заведомо известным указателям.
  2. Дедуплицировать одинаковые строки - документы не владеют индексными строками, вместо этого владельцем строк выступает индекс. Для совпадающих строк индекс просто ведёт подсчитывает активные ссылки на строку, не создавая её копий.
  3. Не хранить вектора в документе - у документа нет своей копии вектора, вместо этого он ссылается на соответсвующий векторный индекс.
  4. Дедуплицировать документы с одинаковой структурой - JSON-структура хранится в системном индексе -tuple и может быть дедуплицирована как обычная строка.

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

  1. Обычные индексы не могут иметь значения null. Несмотря на то, что в документе всё ещё допустимо указать "field": null для индексного поля (или вовсе пропустить его), в PayloadValue всё равно будет создано значение по умолчанию для этого поля (для string - пустая строка, для int/int64 - 0, для double - 0.0, для bool - false, для float_vector- пустой вектор, для uuid - nil UUID , для массива и point - пустой массив).
  2. Обычные индексы всегда выполняют конвертацию значиний при вставке и, если, например, в индекс с типом int попробовать вставить значение с плавающей точкой, то произойдёт его преобразование в целочисленный тип. Исходное значение при этом сохранено не будет.
  3. Для каждого подобного поля резервируется фиксированное место в PayloadValue, что делает невозможным исполнозование variadic-кодирования, применяемого для неиндексных и sparse-полей. То есть, в этом случае int64 всегда будет занимать 8 байт, даже если фактически в документе лежит значение 5, которое можно было бы закодировать в 1 байт.
  4. Таких индексов не может быть более 255.

Sparse индексы

Sparse индексы, в отличие от regular, никак не связаны со слоем хранения. Само значение индексируемого поля по-прежнему хранится внутри -tuple, как у неиндексных полей.

В отличие от обычных индексов, sparse индексы имеют следующие особенности:

  1. Могут иметь значение null.
  2. Могут эффективно использовать variadic-кодирование для целочисленных типов и не резервируют дополнительную память в случае отсутствующих и null-значений.
  3. При выборках избегают использования компараторов везде, где это возможно, используя IdSet (набор упорядоченных ID).
  4. Для извлечения значений (при точечной модификации, сортировке, агрегировании или в компараторах) полностью разбирают CJSON документа. Это может приводить к падению производительности в определённых сценариях.
  5. Не выполняют конвертацию значений в момент встаки.
  6. Нет явного ограничения на количество таких индексов.

Композитные индексы

Композитные индексы это индексы, построенные поверх нескольких полей в целях получения более высокого показателя селективности. В частных случаях могут строиться поверх одного поля - например, если требуется иметь несколько индексов с разными типами. Как и sparse, не имеют прямой связи со слоем хранения.

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

  1. Могут строиться только поверх других обычных (regular) индексов. Как следствие, не могут иметь null-полей в своём составе.
  2. Не резервируют дополнительного места под данные - хранят только саму индексную структуру.
  3. Не могут быть построены поверх массивов.
  4. Нет явного ограничения на количество таких индексов.