Комментарии 6
Спасибо за интересную статью.
Там на фото и в описании (1 и 3) дважды слой memtx это так и задумано? Т.е. это действительно один слой переиспользуется или опечатка?
Спасибо за Ваш комментарий!
Это слои memtx, но организованные по-разному:
(1) Строчный буфер -- данные разложены как в обычном спейсе. Мы пробовали разные варианты
(3) Блочное хранение -- данные разделены на блоки последовательных значений размером N (степень двойки). Здесь используется memtx: блоки являются таплами, у которых примерно такой формат (offset, bin_data (массив arrow c данными колонки), validity_mask, ..., some other meta)
Здесь построено несколько индексов по метаданным, сделан аналог BRIN-индекса, и еще разработан отдельный тип вторичного ключа для данных типа HighCardinality.
Основная сложность хранения колоночных данных в таплах -- это, как ни странно, заточенный на максимальную компактность формат msgpack: нам для векторизованной обработки нужны выровненные данные (к 8-ми байтам как минимум, а лучше к 64-м), что крайне не простая задача.
На вебинаре мы не говорили о компактинге и о том, что ридвью (как слепок данных в памяти) немного отстает от реального положения дел, поэтому в нас еще есть буфер изменений (очень похоже на LSM)
При этом оно обеспечивает высокую эффективность аналитической обработки с производительностью до 1,5 миллиарда сканов в секунду на одно ядро, то есть подходит для разных сценариев и профилей нагрузки.
А можно привести более точные данные ? Например, размер колонки, по которой производится полноценный скан. Опять же, хотелось бы понимать, это сканы одной и тоже колонки или разных ? Было бы также интересно узнать производительность по задаче поиска похожего вектра: размер вектора, и количество векторов в базе.
Я могу привести числа из нескольких проектов.
1) Скорость скана одной колонки с параллельным подсчетом примитивного агрегата составляет 1,5 млрд значений в секунду, причем утилизировано было только 1-1.2 ядра процессора. 1 000 000 RPS™
, но для колоночной обработки.
В этом же профиле нагрузки были такие же запросы, но с ограничением 100 000 записей, и для них получились такие результаты:
Система обрабатывала 200 000 Запросов с агрегацией 100к значений в секунду, причем каждый из запросов выполнялся менее, чем за 300мкс (микросекунд), из которых 100-150мкс -- это сетевые задержки между двумя соседними серверами в стойке.

2) Запрос, выполняющий 500 агрегирующий функций в таблице с 1000 колонками при скане 1000 000 записей:

Latency AVG
незначительно отличается от Latency p99
, что говорит о стабильном времени откликаПриложу некоторый анализ этих данных:

А если уж совсем честно, то, конечно, максимальная производительность достигается за счет прибивания шардов на конкретные NUMA-ноды.
Вот пример (скан с агрегецией 500 колонок 5000 записей):

3) Рассчет агрегата на выборке с JOIN двух таблиц по условию, не предполагающему возможность построения индекса (то есть фуллсканом):
SELECT COUNT(some_column) from TABLE1 t1
JOIN TABLE2 t2 ON
t1.attr_1=t2.attr_1 and t1.attr_2=t2.attr_2 and t1.attr_3=t2.attr_3 and t1.attr_4=t2.attr_4 and t1.attr_5 > 10
Table1 -- 200 млн записей, Table2 -- 20 млн записей
Время выполнения < 500ms
Это то, что сейчас было под рукой. Мы еще делаем тесты TPC-H и TPC-C, о них расскажем позже.
Что касательно векторного поиска, то это тема для отдельной статьи. Очень сильно различаются показатели в зависимости от рзмерности пространства и требованиям к точности, об это тоже напишем
Спасибо за статью!
А чем объясняется наличие 2 буфферов? (как будто, наличие дополнительной области хранения должна влиять на время исполнения читающих запросов, так как нужно произвести дополнительную UNION-подобную операцию)
Или подразумевается альтернатива - или строчный или колоночный буффер. Тогда могли бы вы привести примеры профилей нагрузок для каждого из них и в чем между ними отличия?
Спасибо за вопрос! Мы исследовали этот вопрос, и пришли к выводу, что два буфера (колоночный и строчный) нужны в том случае, если много фулсканов (требеющих колоночной органзации данных), точечных апдейтов и запросов по вторичным ключам.
Дело в том, что колоночная организация данных усложняет организацию вторичных ключей (не все колоночные СУБД поддерживают вторичные ключи, а те ,что поддерживают, имеют разные типы под разные распределения данных). Например, существуют такие типы вторичных ключей под разные условия, зависящие от мощности множества уникальных записей, характер возрастания или убывания данных и так далее.
Вот несколько примеров таких ключей: Column Imprints, BRIN, Radix, Interleave-индекс, Skip-index. Некоторые из типов индексов предназаначены для сокращения объема фуллскана, а не поиска конкретных строк. Иными словами, эта область науки еще развивается.
Зато некоторые из типов индексов могут использоваться одновременно с другими при выполнении запроса (а иногда разные индексы по разным колонкам могут быть использованы в одном запросе).
Мы сделали такие индексы: свое прочтение BRIN-индекса, Value-to-offset, Value-to-block, индексы по метаданным блоков для ускорения запросов.
Однако, в силу колоночной природы организации данных, эти индексы медленнее извлекают точечные значения (особенно когда в проекции много колонок), чем стандартные индексы строчных СУБД.
А в строчных СУБД вторичные ключи общего назначения (например, b+tree) прекрасно справляются со своей задачей, и работают.
Но строчные (row based) структуры неэффективны при выполнении фулсканов, в особенности если много колонок в схеме, а в запросе всего несколько.
Поэтому мы совмещаем строчные и колоночные буферы.
Наличие строчного, колоночного буферов подходит не для всех задач, поэтому их наличие настраивается.
Также скажу, что движок memcs в будущем заменит колоночный буфер.
Как организовать анализ большого объема данных в реальном времени