Pull to refresh

Comments 24

А чем вертика не устроила, если не секрет? Тоже раздумываем над выбором.
Скажем, у нас возникли организационные трудности с Вертикой (HP), когда мы разговаривали с вендорами баз данных. Потому Вертика вышла из рассмотрения продуктов, еще до глубокого технического ревью.
С вертикой в то время не удалось связаться и организовать полноценный PoC. Похоже, просто были какие-то административные проблемы внутри HP. Но сейчас у них уже всё хорошо с этим.

В поисках принципиальных отличий Exasol от Vertica, на последнем Highload++ поговорил с представителями HP и с коллегами из Avito. Узнал следующее:

1). Vertica всегда читает диск, даже если анализ идёт по «горячим» данным.
2). Vertica не так эффективно использует большие объёмы памяти. Если я правильно понял ребят из HP, объём памяти на типичной ноде для вертики не превышает 200-300Гб.
3). В Vertica необходимо строить проекции для того, чтобы делать эффективные локальные join'ы. На больших объёмах нельзя просто придти и начать join'ить какие-то совершенно случайные колонки. В Exasol'е — можно. Индексы построятся сами после первого SELECT'а.

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

Но Вертика тоже отличный продукт. Ничего против неё не имею.
А вы же, вроде, использовали Vectorwise, если мне память не изменяет. Почему отказались?
Vectorwise — это single-node система. Наступил момент, когда наши объёмы уже нельзя было уместить на одной машине.
Позволю несколько комментариев по поводу Вертики.

1). Vertica всегда читает диск, даже если анализ идёт по «горячим» данным.

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

2). Vertica не так эффективно использует большие объёмы памяти. Если я правильно понял ребят из HP, объём памяти на типичной ноде для вертики не превышает 200-300Гб.

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

3). В Vertica необходимо строить проекции для того, чтобы делать эффективные локальные join'ы. На больших объёмах нельзя просто придти и начать join'ить какие-то совершенно случайные колонки. В Exasol'е — можно. Индексы построятся сами после первого SELECT'а.

И это тоже не совсем правда :) В Вертике можно прийти и джойнить совершенно случайные колонки. В стар-схемах обычно одна таблица очень большая, и много маленьких — этот сценарий работает без проблем. Две большие таблицы могут джойниться небыстро, но как часто в реальной жизни встречаются такие сценарии? Некоторое подобие индекса в Вертике строится в памяти во время выполнения запроса (hash join). Но чтобы получить максимальную производительность (оптимизировать WHERE-clause, group by, джойны) — да, надо делать проекции. Но оно того стоит.

Спасибо за развернутый ответ)
Спасибо вам громное за отличную статьи. И отдельно спасибо за статистику по нагрузке на реальных данных. Хотелось бы еще обратить внимание на то что у Exasol отличный результаты в «синтетических бенчмарках» аля TPC-H, в чем можно убедиться тут.
Эх, как жаль, что это не опенсорс! Звучит уж очень вкусно. Спасибо за статью.
Спасибо, давно хотел поподробнее посмотреть на это европейское чудо, и Вы частично удовлетворили любопытство.

Несколько вопросов по тексту:

1. «Как правило, в реальной жизни пользователи работают в первую очередь с «горячими» данными (последний день, неделя, месяц). Если у кластера достаточно памяти, чтобы вместить их целиком, то Exasol не будет трогать диск вообще.» — а что происходит с кешем, если происходит постоянная загрузка данных?

2. «Один аналитический запрос обрабатывает в среднем около 4,5 миллиардов рядов.» — как Вы это определили? Есть подробная статистика?

3. «Эффективный джойн всегда происходит по индексу. Индексы создаются автоматически в тот момент, когда вы впервые пытаетесь объединить две таблицы по определенным ключам. Если индекс не используется долгое время, то он так же автоматически удаляется. » — то есть базу данных требуется «разогревать», чтобы построить индексы? Это несколько противоречит утвреждению «Вы просто загружаете данные и можете сразу их анализировать на высокой скорости.» Не совсем понятно, кстати, какие это индексы, если JOIN работает только по условию «равно». Бинарные индексы прекрасно подходят и под условия больше-меньше. То есть это хеш-индексы, скорее всего. Оно конечно хорошо, но для джойнов больших таблиц может быть неэффективно.

4. «Также маленькие таблицы автоматически реплицируются (копируются целиком) на все ноды, что автоматически гарантирует быстрые локальные джойны к ним. Это особенно актуально для многочисленных небольших списков.» — то есть разработчик не имеет над этим контроля? Или все таблицы по дефолту реплицируются везде, и разработчик должнен явно указывать 'distribute by' для больших таблиц? Может ли быть несколько колонок в distribute by? Чем еще управляет разработчик в физическом дизайне?

Вообще, статья хороша, как вводная, но она плохо объясняет, как именно Exasol достигает высоких результатов. Можно поверить, что он быстро работает, но скорость запросов на ваших дорогущих серверах (768GB RAM) и сравнительно небольших объемах не выглядит большой в сравнении с той же Вертикой.
Класс! Приятно получить комментарий от человека, который в теме.

1. а что происходит с кешем, если происходит постоянная загрузка данных?

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

Судя по этим данным, в Badoo ETL процесс практически не вытесняет кеш.
Вероятно, это связано с двумя самыми популярными стратегиями загрузки данных:

  1. Перезагружаем таблицу целиком каждый день. Это работает для маленьких таблиц до 1 миллиарда рядов.
  2. Загружаем только последнюю неделю\день\час. Это работает для больших и очень больших таблиц.


В итоге, в кеше оказываются маленькие таблицы целиком и самые новые данные для больших таблиц. А это именно то, над чем аналитики чаще всего проводят анализ.

2. как Вы это определили? Есть подробная статистика?

Да, Exasol сохраняет много различной статистики. Он делает скрытый профайлинг всех запросов и хранит очень подробную информацию за последние 24 часа. Буквально хранится каждая стадия выполнения запроса на каждой отдельно взятой ноде. Время от времени он делает агрегацию и сохраняет обобщённую информацию за день, неделю, месяц.

Собственно, я посмотрел среднее количество ROWS за день для всех типов SCAN в рамках одного запроса.
Другое дело, что, в случае с колоночными базами, понятие «ряды» очень-очень растяжимое.

Например, следующие запросы могут отличаться по сложности в сто-двести раз, но при этом формально сканировать одно и то же количество «рядов».

SELECT id FROM table;
SELECT * FROM table;

3. то есть базу данных требуется «разогревать», чтобы построить индексы?

Не совсем. Индексы персистентные. Они сохраняются и после полного рестарта базы.

Юзер ощущает построение индексов только в тот момент, когда он в самый первый раз пытается сделать JOIN по таким колонкам, которые за последний месяц ни разу не использовались для JOIN'а. После этого индекс поддерживается автоматически во время ETL-процесса (IMPORT, INSERT, UPDATE, DELETE, MERGE), и юзер этого процесса не видит.

Что это за волшебные индексы — великий вопрос. Я много раз задавал его самым разным людям внутри Exasol и даже лично беседовал с их автором: Falko Mattasch. No luck! Подробностей они не рассказывают и очень тщательно охраняют этот секрет.

4. то есть разработчик не имеет над этим контроля? Или все таблицы по дефолту реплицируются везде, и разработчик должнен явно указывать 'distribute by' для больших таблиц? Может ли быть несколько колонок в distribute by? Чем еще управляет разработчик в физическом дизайне?

В физическом плане разработчик может только указать DISTRIBUTE BY для больших таблиц. Можно делать distribute по нескольким колонкам, но тогда они все вместе должны участвовать в JOIN'е.

Если DISTRIBUTE BY явно не указан, то ряды случайно раскидываются по нодам таким образом, чтобы везде было поровну. Если баланс в силу каких-то причин нарушается, то в профилировщике можно увидеть фазу REDISTRIBUTE, которая его восстанавливает.

Особо маленькие таблицы дополнительно полностью загружаются в память каждой ноды для того, чтобы всегда делать к ним локальный JOIN. Существует параметр, который позволяет управлять тем, что считается «маленькой таблицей» в данном случае, но он задаётся сразу на весь кластер.
> Судя по этим данным, в Badoo ETL процесс практически не вытесняет кеш.

То есть вы загружаете данные, на чаще, чем раз в час, правильно я понимаю? А realtime аналитику не пробовали делать? Возможно?

>Например, следующие запросы могут отличаться по сложности в сто-двести раз, но при этом формально сканировать одно и то же количество «рядов».

Вообще, в колоночной базе количество строк — это не самый объективный параметр :) Но лучше не придумали, я пробовал оценивать по количеству «ячеек» — то есть строки умножить на столбцы, это несколько адекватнее, но такую статистику сложнее получить.

>Подробностей они не рассказывают и очень тщательно охраняют этот секрет.

Обычно это означает, что особого секрета нет :) Но судя по тому, что поддерживается только «равно» — это хеш.

>Если DISTRIBUTE BY явно не указан, то ряды случайно раскидываются по нодам таким образом, чтобы везде было поровну.

>Особо маленькие таблицы дополнительно полностью загружаются в память каждой ноды для того, чтобы всегда делать к ним локальный JOIN. Существует параметр, который позволяет управлять тем, что считается «маленькой таблицей» в данном случае, но он задаётся сразу на весь кластер.


Ясно. В данных условиях разумно.

Спасибо за ответы!
То есть вы загружаете данные, на чаще, чем раз в час, правильно я понимаю? А realtime аналитику не пробовали делать? Возможно?

Есть данные, которые загружаются каждые 15 минут. При этом для ETL используем целиком собственное программное решение, которое позволяет дробить загрузку как угодно. Можно хоть раз в минуту загружать, только практического смысла мало.

Глобально по realtime аналитике задавал вопрос их основателю. Ответ был буквально следующий: «Последние блоки колонок не сжаты, можно часто импортировать по чуть-чуть рядов, существенного оверхеда на это нет».

Но лучше не придумали, я пробовал оценивать по количеству «ячеек» — то есть строки умножить на столбцы, это несколько адекватнее, но такую статистику сложнее получить.

На самом деле, даже это не подходит. Exasol в большинстве случаев работает со сжатыми данными, не разжимая их и не получая оригинальные значения в процессе выполнения. Это означает, что колонка, в которой всего 5-10 вариантов значений, сожмётся в разы лучше, чем колонка, в которой десятки тысяч вариантов. При этом формально количество «ячеек» будет одинаковым.

Вопрос оценки сложности выполнения запроса в аналитической базе достоин отдельной большой статьи. Я так троллил других вендоров во время PoC, когда специально писал генератор с очень неудобной селективностью на VARCHAR, а потом спрашивал: «А почему так медленно?» :)

Но судя по тому, что поддерживается только «равно» — это хеш.

Мне всё же кажется, что это не хеш. По крайней мере, не классический хеш, как в row-based базах данных.

Сужу по тому признаку, что JOIN по уже существующему индексу ест очень мало памяти. Никакая хеш таблица в такой объём не влезла бы. Также Exasol без проблем делает streaming частично завершённого JOIN'а в какой-нибудь дальнейший GROUP BY. Как это было бы возможно без предварительной сортировки данных — не очень представляю. Больше склоняюсь к какому-то очень хитрому распределённому Merge Join, но это всё чистой воды теория.

Есть одна интересная статья по поводу индексов в аналитике:
www.vldb.org/pvldb/vol7/p97-schuhknecht.pdf

В ней упоминаются волшебные вещи: ART-tree, AVL-tree, CSB-tree, FAST.
Похоже, мир академических деревьев намного обширнее, чем мы могли бы представить с юзерской точки зрения. :)

>Есть данные, которые загружаются каждые 15 минут. При этом для ETL используем целиком собственное программное решение, которое позволяет дробить загрузку как угодно. Можно хоть раз в минуту загружать, только практического смысла мало.

Так мой вопрос был изначально про кеш. Если данные грузятся в таблицу часто, то что происходит с кешем. Скажем, добавилось в очередной загрузке новое значение в колоноку — надо полностью перестраивать, если я правильно понимаю.

>На самом деле, даже это не подходит. Exasol в большинстве случаев работает со сжатыми данными, не разжимая их и не получая оригинальные значения в процессе выполнения. Это означает, что колонка, в которой всего 5-10 вариантов значений, сожмётся в разы лучше, чем колонка, в которой десятки тысяч вариантов. При этом формально количество «ячеек» будет одинаковым.

Если мы хотим что-то сравнивать, то это нормальный критерий для колоночных баз данных. Что там как сожмется — второй вопрос. Кстати, если ли возможность управлять, как именно колонка сжимается, или это все внутри и автоматом?

>Сужу по тому признаку, что JOIN по уже существующему индексу ест очень мало памяти. Никакая хеш таблица в такой объём не влезла бы.

А что значит «ест»? Если в процессе выполнения запроса, то дополнительная память не нужна, индекс же уже в памяти (или на диске, наверное, тоже может быть, да?). А сколько занимает сам индекс, можно оценить?

Мир экзотических деревьев, конечно, существует, но кроме LSM-деревьев и TokuDB Fractal-trees, по-моему ничего в боевых системах не используется (spatial-индексы не берем). Для merge join необходима сортировка, что дорого на больших данных.
Если данные грузятся в таблицу часто, то что происходит с кешем. Скажем, добавилось в очередной загрузке новое значение в колоноку — надо полностью перестраивать, если я правильно понимаю.

Судя по всему, кеш представляет собой просто те же самые блоки, которые хранятся на диске, только загруженные в память. Добавление нового значения не приводит к перестройке старых блоков и, соответственно, не дропает уже закешированные старые блоки. Возможно, есть какие-то сценарии, когда это всё же происходит, но в общей практике я не замечал.

Кстати, если ли возможность управлять, как именно колонка сжимается, или это все внутри и автоматом?

Внутри и автоматом. В какой-то документации видел информацию о том, что алгоритм компрессии колонки определяется по первым 100.000 рядам. Если у юзера есть подозрение, что всё сжато недостаточно хорошо, то есть команда RECOMPRESS. На практике пользовались 2-3 раза, много не выиграли.

Если в процессе выполнения запроса, то дополнительная память не нужна, индекс же уже в памяти (или на диске, наверное, тоже может быть, да?). А сколько занимает сам индекс, можно оценить?

Действительно, индекс уже и на диске, и в памяти. Для индекса используются такие же блоки, как и для данных. И он точно так же лежит в кеше.

Вот живой пример сделал. Запрос такой:
SELECT count(distinct a.activity_user_id) 
FROM ingres.F_ACTIVITY_BY_PLATFORM a 
    JOIN ingres.F_ACTIVITY_PASSIVE b ON (a.activity_user_id=b.activity_user_id AND a.activity_dt=b.activity_dt);


Результат профилирования:

Крупно: habrastorage.org/files/bb4/211/538/bb4211538754475aacc057e5e74bd7fe.png

Самый простой JOIN, 14 миллиардов на 47 миллиардов, простая группировка. Выполняется чуть больше минуты.

DURATION — это время выполнения стадии в секундах.
TEMP_DB_RAM_PEAK — это сколько максимально было памяти использовано в мегабайтах.
HDD_READ — сколько диска прочитали (в данном случае всё из кеша)
NET — сколько данных передано по сети (в данном случае 0, потому что локальный JOIN)

Индекс суммарно занимает 64Гб. Всего лишь по 8Гб на одну ноду.
>Судя по всему, кеш представляет собой просто те же самые блоки, которые хранятся на диске, только загруженные в память. Добавление нового значения не приводит к перестройке старых блоков и, соответственно, не дропает уже закешированные старые блоки.

Ок, логично. На самом деле в Вертике то же самое происходит, только этим занимается кэш файловой системы (блок = файл), поэтому об этом мало кто знает :)

>Результат профилирования:
Интересно, а почему в 3 и 4й строчке out rows меньше, чем in?

Кстати из этого результата не очевидно, как индексы используется (и используются ли вообще). А какая селективность индекса (или кардинальность колонок) activity_user_id и activity_dt?

Интересно, а почему в 3 и 4й строчке out rows меньше, чем in?

А это inner join. Вернулись только те ряды, которые существуют и в первой таблице, и во второй.

Кстати из этого результата не очевидно, как индексы используется (и используются ли вообще).

Там есть поле REMAKRS, которое я сократил, потому что оно длинное. На 4-ой стадии его значение выглядит вот так:

F_ACTIVITY_BY_PLATFORM(ACTIVITY_DT,ACTIVITY_USER_ID) => LOCAL INDEX (ACTIVITY_DT,ACTIVITY_USER_ID)

Вообще Exasol всегда делает нормальный JOIN только по индексу. Других сценариев у него нет.

А какая селективность индекса (или кардинальность колонок) activity_user_id и activity_dt?

Несколько сотен миллионов юзеров. Тысяча с хвостиком дат.
Точные цифры не буду приводить, чтобы не поругали потом :)
>Несколько сотен миллионов юзеров. Тысяча с хвостиком дат.

Тогда 64GB на индекс не выглядит чем-то очень маленьким. Впрочем, если памяти много, то почему бы и нет :)

Спасибо за разговор. Приятно поговорить с умным человеком :)
Я хочу ещё один пример показать, как иллюстрацию того, что тема с индексами идёт сильно дальше, чем кажется на первый взгляд.

Берём вот такой запрос:
SELECT count(*)
FROM bi_financial.F_ROUTINGLOG a
	LEFT JOIN ingres.F_INCOMELOG_AUDIT b ON (REGEXP_SUBSTR(a.external_id, '^(\d+)-(\d+)-(\d+)-(\d+)') = REGEXP_SUBSTR(b.external_id, '^(\d+)-(\d+)-(\d+)-(\d+)'));

JOIN по регулярному выражению.

Профайлинг:

Крупно: habrastorage.org/files/9ff/791/f17/9ff791f17ac54c01979404cdc122e68b.png

За 4 секунды динамически создаётся индекс по Expression, и сразу же используется для последующего JOIN.
Для разового анализа колонку материализовывать не обязательно.
Хм, то есть за 4 секунды читается колонка, вычисляется регулярное выражение и строится индекс. Вторая таблица маленькая, для нее только вычисляется выражение. Потом идет джойн, используя уже построенный индекс (кстати то, что он строится только для одной таблицы, еще один аргумент в пользу хеша: для merge надо строить с двух сторон). То есть, насколько я понимаю, это как раз пример, когда заранее построенный индекс НЕ используется, а строится динамически в памяти.
Да, всё совершенно правильно.

Такой индекс отличается от обычного только тем, что он будет автоматически дропнут после завершения выполнения запроса. Это потому, что в Exasol нет понятий IMMUTABLE и VOLATILE, и он не может гарантировать, что, условно, 2 + 2 всегда равно 4. Каждый раз нужно заново посчитать и убедиться.

Но строится всё это реально очень быстро и прозрачно для пользователя. На практике это означает, что аналитики могут сделать любой анализ в 90% случаев самостоятельно. Даже такой, который не был задуман изначально. И который в страшном сне не мог присниться никому заранее.

Если же окажется, что такой JOIN нужно делать постоянно, то нет проблем материализовать куб или добавить ещё одну колонку к основной таблице. И тогда индекс не будет умирать. Но обычно такое редко нужно.
А вот еще вопрос:

«3. Fault tolerance. В своей практике мы сталкивались с несколькими аварийными сценариями. Например, заканчивалось свободное место, или кто-то случайно физически отключал часть работающих серверов от сети. Никаких существенных проблем с этим мы не заметили. База заранее пишет о проблеме, останавливается и ждет исправления ситуации. „

То есть база не может работать хотя бы без одной ноды? Ей необходимы все?
Зависит от параметра «redundancy». Например, при «redundancy=2» можно потерять одну ноду. При «redundancy=3» можно потерять две ноды. При «redundancy=1» нельзя потерять ни одной, но зато всё место на дисках — ваше.

На самом деле, всё немного сложнее. Exasol представляет собой объединение нескольких сервисов. Например, ExaSolution управляет SQL запросами и предоставляет интерфейс для юзеров. ExaStorage управляет хранением данных, дисками, кешами. ExaOperation осуществляет общение нод друг с другом, управляет лицензиями. Есть сервис для логирования, для синхронизации времени, и т.д.

В некоторых очень редких случаях возможно такое, что один из сервисов начинает глючить, теряет кворум, пишет warning'и какие-нибудь. Но это всё либо само восстанавливается, либо быстро патчится. С какими-то серьёзными проблемами пока не сталкивались, данные не теряли.
Ага, насчет redundancy понятно, спасибо.

Насчет сервисов — это не столь существенно, вопрос реализации и именования.
Sign up to leave a comment.