Вопросы по ClickHouse на собеседовании
ClickHouse стал стандартом аналитических хранилищ в российских компаниях, и вопросы по нему появляются у аналитиков и дата-инженеров. Проверяют понимание, почему колоночная база быстра на агрегатах и медленна на точечных обновлениях.
Из чего состоит тема
Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.
- Агрегация и приближённость15
- Идиомы запросов15
- Движки таблиц и хранение14
- Производительность и индексы13
- Джойны и распределённость12
Разборы подтем
Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.
- Агрегации и приближённые вычисления15 вопросов
- Джойны и распределённые запросы12 вопросов
- Движки таблиц ClickHouse14 вопросов
- Производительность запросов ClickHouse13 вопросов
- Идиомы запросов ClickHouse15 вопросов
Примеры вопросов с разбором
- Чем uniq(x) отличается от uniqExact(x) в ClickHouse?A)uniq — быстрый приближённый счётчик уникальных (HyperLogLog); uniqExact — точный, но дороже по памятиB)Обе функции дают точный одинаковый результат, отличаясь названиемC)Uniq считает все строки подряд, а uniqExact — те строки, где x не равно NULLD)Uniq работает лишь с числами, а uniqExact — со строковыми значениями колонки x
показать ответ и разбор
+A)uniq — быстрый приближённый счётчик уникальных (HyperLogLog); uniqExact — точный, но дороже по памяти// разбор: uniq(x) оценивает число уникальных приближённо (на базе HyperLogLog) — быстро и с почти константной памятью, погрешность доли процента. uniqExact(x) считает точно, но хранит все значения и жрёт память на больших кардинальностях. Есть промежуточный uniqCombined. В аналитике по умолчанию берут uniq. Проверено: на 1e6 уникальных uniq дал 1001943, uniqExact — ровно 1000000.
- Что важно помнить про обычный JOIN в ClickHouse?A)Правую таблицу CH обычно грузит в память (hash join) — большие×большие джойны дороги и рискуют по RAMB)ClickHouse оптимизирует джойны так же, как OLTP-СУБД, и хорошо соединяет две большие таблицыC)JOIN в ClickHouse запрещён: соединять две таблицы в одном запросе не получитсяD)JOIN выполняется на диске без использования оперативной памяти, поэтому размер таблиц неважен
показать ответ и разбор
+A)Правую таблицу CH обычно грузит в память (hash join) — большие×большие джойны дороги и рискуют по RAM// разбор: По умолчанию ClickHouse строит хеш-джойн, помещая ПРАВУЮ таблицу целиком в память, — поэтому справа должна быть меньшая таблица, а join двух больших дорог и упирается в RAM. CH исторически «не про джойны»: их часто избегают денормализацией, словарями (dictGet) или предагрегацией. Есть и другие алгоритмы (partial_merge, grace_hash) для больших правых таблиц, но их включают осознанно. Порядок таблиц в JOIN здесь важен.
- Почему MergeTree — базовый движок таблиц ClickHouse для аналитики?A)Колоночное хранение с сортировкой по ключу и фоновым слиянием кусков — быстрые аналитические сканыB)Потому что MergeTree хранит данные строго построчно и обеспечивает полноценные транзакции ACID, ровно как классическая строчная OLTP-СУБДC)Потому что это основной движок ClickHouse, других вариантов хранения почти нетD)Потому что MergeTree держит всю таблицу целиком в оперативной памяти без записи её на диск
показать ответ и разбор
+A)Колоночное хранение с сортировкой по ключу и фоновым слиянием кусков — быстрые аналитические сканы// разбор: MergeTree — семейство основных движков CH: данные лежат колонками, отсортированы по ORDER BY и пишутся иммутабельными кусками (parts), которые фоном сливаются. Колоночность плюс сортировка дают быстрые сканы и сжатие на аналитике. Это не OLTP: строчных транзакций и точечных апдейтов нет, ставка на массовые вставки и чтения.
- Почему в ClickHouse избегают SELECT * и перечисляют только нужные колонки?A)Хранение колоночное — читаются только запрошенные колонки; * тянет с диска все, часто лишниеB)Потому что синтаксис SELECT * в ClickHouse запрещён и приводит к ошибке разбора запросаC)Потому что SELECT * в ClickHouse возвращает строки в случайном, каждый раз новом и разном порядкеD)Потому что звёздочка заставляет базу заново пересчитать первичный индекс таблицы при каждом запросе
показать ответ и разбор
+A)Хранение колоночное — читаются только запрошенные колонки; * тянет с диска все, часто лишние// разбор: ClickHouse хранит данные по колонкам, поэтому запрос читает с диска ровно те колонки, что упомянуты. SELECT * тянет все столбцы, включая тяжёлые (длинные строки, массивы), которые не нужны, — лишний I/O и память. В аналитике на широких таблицах явный список колонок — базовая оптимизация. Отсюда и совет не хранить «на всякий случай» огромные колонки в горячих таблицах.
- Что возвращает argMax(name, ts)?A)Значение name из той строки, где ts максимальноB)Максимальное значение самого поля name, проигнорировав значения поля tsC)Максимум сразу по обоим аргументам, то есть большее из значений полей name и ts между собойD)Индекс той строки таблицы, в которой поле ts принимает своё максимальное по выборке значение
показать ответ и разбор
+A)Значение name из той строки, где ts максимально// разбор: argMax(arg, val) возвращает arg той строки, где val максимально (argMin — где минимально). Идиома CH для «последнего значения»: argMax(status, updated_at) даёт статус на момент последнего апдейта без оконных функций и джойнов. Удобно поверх ReplacingMergeTree вместо FINAL: GROUP BY id, argMax(col, version). Проверено: argMax по ts дал 'b' при максимальном ts.
- Как ведёт себя count(DISTINCT x) в ClickHouse по умолчанию?A)Считает приближённо и не может быть настроен на точный подсчёт уникальных значенийB)По умолчанию точный — эквивалент uniqExact(x); поведение регулируется настройкой count_distinct_implementationC)Считает общее число всех строк, игнорируя ключевое слово DISTINCT в запросеD)Работает лишь с одной колонкой и запрещает передавать несколько аргументов
показать ответ и разбор
+B)По умолчанию точный — эквивалент uniqExact(x); поведение регулируется настройкой count_distinct_implementation// разбор: count(DISTINCT x) в CH по умолчанию отображается на uniqExact (точный подсчёт), а конкретную реализацию задаёт настройка count_distinct_implementation — можно переключить на приближённый uniq/uniqCombined ради скорости и памяти. На больших данных аналитики часто сознательно пишут uniq вместо count(DISTINCT). Проверено: count(DISTINCT number) = uniqExact = 1000.
- Зачем в ClickHouse используют внешние словари и функцию dictGet?A)Чтобы заменить движок MergeTree и хранить в словарях основные таблицы фактов проектаB)Быстрый key-value справочник в памяти для обогащения — dictGet вместо джойна к таблице-измерениюC)Чтобы проверять орфографию текстовых значений в строковых колонках прямо в момент вставки данныхD)Чтобы сжимать числовые колонки высокой кардинальности перед непосредственной записью их на диск
показать ответ и разбор
+B)Быстрый key-value справочник в памяти для обогащения — dictGet вместо джойна к таблице-измерению// разбор: Словари (dictionaries) — это key-value справочники, которые CH держит в памяти и обновляет из источника (таблица, файл, СУБД). Вместо джойна к таблице-измерению пишут dictGet('dict', 'attr', key) — точечный поиск по ключу без хеш-джойна и без загрузки правой таблицы в память на каждый запрос. Классика обогащения: id -> имя, город -> регион. Дёшево, если справочник влезает в RAM и меняется не слишком часто.
- Что задаёт секция ORDER BY при создании таблицы MergeTree?A)Порядок вывода строк в результатах SELECT-запроса к этой таблицеB)Физическую сортировку данных и первичный (разрежённый) индекс таблицыC)Список колонок, по которым таблица шардируется между разными физическими серверами кластераD)Набор колонок, значения в которых обязаны быть уникальными, как PRIMARY KEY в OLTP-СУБД
показать ответ и разбор
+B)Физическую сортировку данных и первичный (разрежённый) индекс таблицы// разбор: В MergeTree ORDER BY определяет, как данные физически отсортированы в кусках, и служит первичным ключом — по нему строится разрежённый индекс (одна засечка на гранулу ~8192 строк). Он НЕ гарантирует уникальность (в отличие от OLTP PK) и не задаёт порядок вывода — для него нужен ORDER BY в самом SELECT. Проверка: system.tables.sorting_key = primary_key.
- Как работает первичный индекс MergeTree (разрежённый индекс)?A)Хранит отдельную индексную запись для каждой отдельной строки таблицы целиком, ровно как классический B-Tree в OLTP-СУБДB)Хранит по одной засечке на гранулу (~8192 строк); фильтр по префиксу ключа отсекает целые гранулыC)Индексирует все колонки таблицы разом, обеспечивая быстрый поиск по нимD)Является полнотекстовым индексом, ускоряющим поиск подстрок в строковых колонках
показать ответ и разбор
+B)Хранит по одной засечке на гранулу (~8192 строк); фильтр по префиксу ключа отсекает целые гранулы// разбор: Первичный индекс CH разрежённый: одна засечка (значение ключа) на гранулу — блок ~8192 строк (index_granularity), а не на строку. По фильтру на префикс ORDER BY движок находит диапазон гранул и читает только их, пропуская остальные. Индекс эффективен на префиксе ключа сортировки и на диапазонах, но не заменяет точечный поиск OLTP. Поэтому порядок колонок в ORDER BY подбирают под частые фильтры.
это 9 из 69
Ещё 60 вопросов по теме — в тренажёре, с движком повторения
Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.
Частые вопросы
Чем первичный ключ в ClickHouse отличается от привычного?
Он не обеспечивает уникальность, а задаёт порядок хранения и разреженный индекс. От этого зависит, сколько данных придётся прочитать при фильтрации.
Почему в ClickHouse не любят джойны?
Правая таблица обычно целиком уезжает в память каждого узла, поэтому большие джойны дороги. Часто данные денормализуют заранее или используют словари.