Оптимизация потребления оперативной памяти

Reindexer хранит все данные, мету и индексы в RAM, поэтому он не имеет опции, позволяющей явно ограничить объём потребляемой оперативной памяти. Однако общее потребление памяти можно регулировать косвенно.

Dense-индексы

Флаг dense у индексов сокращает потребление памяти за счёт отключения колоночной оптимизации. Dense-индексы имеют менее эффективные компараторы, но зато не аллоцируют дополнительную память для хранения непрерывного массива ID. Также использование флага dense для tree/hash-индексов влияет на внутреннюю структуру хранения, оптимизируя потребялемую ею память, но снижая производительность вставки в индекс в случае, когда там встречается большое количество повторяющихся значений.

В случае полнотекстовых индексов опция dense отключает многопоточное построение индекса и не влияет на потребляемую память.

Фоновая оптимизация индексов

После изменения данных неймспейса Reindexer может выполнять в фоне два независимых процесса: оптимизацию сортировки по обычным индексам и очистку устаревших записей полнотекстовых индексов. Каждый процесс запускается не ранее чем через заданный для него интервал после последнего обновления неймспейса. Новые обновления откладывают запуск до завершения этого интервала.

Оптимизация сортировки

Reindexer создаёт дополнительные маппинги для unordered и ordered индексов в фоновом потоке. Эти маппинги используются для оптимизации сортировок в запросах, в которых используются одновременно tree и hash индексы.

Например:

SELECT * from ns WHERE hash1 = 12345 AND hash2 = 5678 ORDER BY tree

В запросе выше без дополнительных маппингов планировщику приходится выбирать: либо использовать более эффективные индексы hash1 и hash2 для выборки, а затем выполнить сортировку полученных результатов, либо выбрать в качестве основного индекса tree, а условия для hash1/hash2 проверить через компараторы. В случае, если фоновая сортировка индексов завершена, Reindexer сможет использовать hash1/hash2 в качестве основных индексов, а затем, используя вспомогательные маппинги, выполнить сортировку за O(N) без применения общего алгоритма сортировки.

Параметр optimization_timeout_ms в неймспейсе #config задаёт задержку перед началом оптимизации после последнего обновления неймспейса. Значение по умолчанию — 800 мс, значение 0 отключает оптимизацию. Параметр optimization_sort_workers ограничивает количество используемых фоновых потоков. Значение по умолчанию — 4, значение 0 отключает оптимизацию сортировки.

Очистка полнотекстовых индексов

При удалении или изменении документов в полнотекстовом индексе могут оставаться устаревшие записи (postings). Они не попадают в результаты поиска, но продолжают занимать память. Reindexer удаляет такие записи в фоновом режиме без полного перестроения индекса.

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

  • после последнего обновления неймспейса прошло не менее ft_cleanup_timeout_ms миллисекунд;
  • количество документов, удалённых после завершения предыдущего цикла очистки, стало больше заданной параметром DeletedDocsOptimizationThreshold доли от текущего количества неудалённых документов.

Параметр ft_cleanup_timeout_ms задаётся для неймспейса в #config. Значение по умолчанию — 50 мс, значение 0 отключает фоновую очистку полнотекстовых индексов. Этот параметр не зависит от optimization_timeout_ms и optimization_sort_workers.

Сброс данных на диск

Непосредственно в момент записи в Reindexer новые данные в сериализованном виде складываются в промежуточные буферы. Эти буферы в большинстве случаев асинхронно записываются на диск в фоновом потоке с периодом 100-200 мс (в некоторых случаях запись происходит синхронно). Опция sync_storage_flush_limit в config-неймспейсе позволяет ограничить максимальное число записей, помещаемых в промежуточный буфер для каждого неймспейса. При превышении этого лимита сброс буфера на диск будет происходить синхронно. Ограничение не распространяется на транзакции, update и delete запросы (их Reindexer старается сбрасывать на диск единым батчем в любом случае).

Конфигурирование размеров индексных кешей

В Reindexer каждый индекс имеет свой индивидуальный независимый LRU-кеш для выборок. По умолчанию размер этого кеша 128 Мб на индекс. Сконфигурировать его можно через объект cache в config-неймспейсе.

Атомарные копирующие транзакции

Reindexer в некоторых случаях может использовать копирование неймспейса при выполнении транзакции. Этот механизм нужен для того, чтобы избежать долгой эксклюзивной блокировки - вместо этого транзакция применяет к копии неймспейса, и затем происходит атомарная подмена указателя. Такое копирование может приводить к значительному росту потребления RAM в момент коммита транзакции (особенно, если целевой неймспейс занимает много памяти). Доступные стратегии коммита описаны в соответствующем разделе

Кеши аллокаторов

Пользовательские аллокаторы (в частности tcmalloc) могут иметь достаточно агрессивную политику кеширования, приводящую к тому, что значительный объём памяти, помеченный приложением как “свободная” фактически не возвращается в систему, оставаясь в кеше аллокатора. Reindexer позволяет явно управлять размером таких кешей при помощи опций allocator_cache_limit и allocator_cache_part (более детальное описание этих опций приведено в разделе конфигурации сервера).

Конфигурирование WAL

Write ahead log в reindexer является виртуальным и хранится в памяти в виде циклического буфера ограниченного размера (по умолчанию до 4000000 записей для каждого неймспейса кроме системных). Этот WAL используется только для репликации, чтобы follower мог получить историю изменений с leader’a. Если текущий инстанс не участвует в репликации или является исключительно follower’ом (то есть сам никуда не реплицирует), то wal_size в config-неймспейсе можно задать равным 1, чтобы не хранить лишние данные в RAM. Точно также можно задать меньший размер WAL для отдельных неймспейсов, если текущий инстанс их никуда не реплицирует.