сеньорчикОткрыть в Telegram
← все вопросывопросы для собеседований · ClickHouse

Вопросы по ClickHouse на собеседовании

ClickHouse стал стандартом аналитических хранилищ в российских компаниях, и вопросы по нему появляются у аналитиков и дата-инженеров. Проверяют понимание, почему колоночная база быстра на агрегатах и медленна на точечных обновлениях.

69 вопросов в банке·5 подтем·ниже разбор 9

Из чего состоит тема

Так тема разложена в тренажёре: движок ведёт прогресс по каждой подтеме отдельно и возвращает те, где вы ошибаетесь.

Разборы подтем

Конспект по каждой: что это, как отвечать вслух, на чём валятся, плюс вопросы для самопроверки.

Примеры вопросов с разбором

  1. #ch_aggregation1 / 9
    Чем uniq(x) отличается от uniqExact(x) в ClickHouse?
    A)uniq — быстрый приближённый счётчик уникальных (HyperLogLog); uniqExact — точный, но дороже по памяти
    B)Обе функции дают точный одинаковый результат, отличаясь названием
    C)Uniq считает все строки подряд, а uniqExact — те строки, где x не равно NULL
    D)Uniq работает лишь с числами, а uniqExact — со строковыми значениями колонки x
    показать ответ и разбор
    +A)uniq — быстрый приближённый счётчик уникальных (HyperLogLog); uniqExact — точный, но дороже по памяти

    // разбор: uniq(x) оценивает число уникальных приближённо (на базе HyperLogLog) — быстро и с почти константной памятью, погрешность доли процента. uniqExact(x) считает точно, но хранит все значения и жрёт память на больших кардинальностях. Есть промежуточный uniqCombined. В аналитике по умолчанию берут uniq. Проверено: на 1e6 уникальных uniq дал 1001943, uniqExact — ровно 1000000.

  2. #ch_distributed2 / 9
    Что важно помнить про обычный JOIN в ClickHouse?
    A)Правую таблицу CH обычно грузит в память (hash join) — большие×большие джойны дороги и рискуют по RAM
    B)ClickHouse оптимизирует джойны так же, как OLTP-СУБД, и хорошо соединяет две большие таблицы
    C)JOIN в ClickHouse запрещён: соединять две таблицы в одном запросе не получится
    D)JOIN выполняется на диске без использования оперативной памяти, поэтому размер таблиц неважен
    показать ответ и разбор
    +A)Правую таблицу CH обычно грузит в память (hash join) — большие×большие джойны дороги и рискуют по RAM

    // разбор: По умолчанию ClickHouse строит хеш-джойн, помещая ПРАВУЮ таблицу целиком в память, — поэтому справа должна быть меньшая таблица, а join двух больших дорог и упирается в RAM. CH исторически «не про джойны»: их часто избегают денормализацией, словарями (dictGet) или предагрегацией. Есть и другие алгоритмы (partial_merge, grace_hash) для больших правых таблиц, но их включают осознанно. Порядок таблиц в JOIN здесь важен.

  3. #ch_engines3 / 9
    Почему MergeTree — базовый движок таблиц ClickHouse для аналитики?
    A)Колоночное хранение с сортировкой по ключу и фоновым слиянием кусков — быстрые аналитические сканы
    B)Потому что MergeTree хранит данные строго построчно и обеспечивает полноценные транзакции ACID, ровно как классическая строчная OLTP-СУБД
    C)Потому что это основной движок ClickHouse, других вариантов хранения почти нет
    D)Потому что MergeTree держит всю таблицу целиком в оперативной памяти без записи её на диск
    показать ответ и разбор
    +A)Колоночное хранение с сортировкой по ключу и фоновым слиянием кусков — быстрые аналитические сканы

    // разбор: MergeTree — семейство основных движков CH: данные лежат колонками, отсортированы по ORDER BY и пишутся иммутабельными кусками (parts), которые фоном сливаются. Колоночность плюс сортировка дают быстрые сканы и сжатие на аналитике. Это не OLTP: строчных транзакций и точечных апдейтов нет, ставка на массовые вставки и чтения.

  4. #ch_performance4 / 9
    Почему в ClickHouse избегают SELECT * и перечисляют только нужные колонки?
    A)Хранение колоночное — читаются только запрошенные колонки; * тянет с диска все, часто лишние
    B)Потому что синтаксис SELECT * в ClickHouse запрещён и приводит к ошибке разбора запроса
    C)Потому что SELECT * в ClickHouse возвращает строки в случайном, каждый раз новом и разном порядке
    D)Потому что звёздочка заставляет базу заново пересчитать первичный индекс таблицы при каждом запросе
    показать ответ и разбор
    +A)Хранение колоночное — читаются только запрошенные колонки; * тянет с диска все, часто лишние

    // разбор: ClickHouse хранит данные по колонкам, поэтому запрос читает с диска ровно те колонки, что упомянуты. SELECT * тянет все столбцы, включая тяжёлые (длинные строки, массивы), которые не нужны, — лишний I/O и память. В аналитике на широких таблицах явный список колонок — базовая оптимизация. Отсюда и совет не хранить «на всякий случай» огромные колонки в горячих таблицах.

  5. #ch_query5 / 9
    Что возвращает argMax(name, ts)?
    A)Значение name из той строки, где ts максимально
    B)Максимальное значение самого поля name, проигнорировав значения поля ts
    C)Максимум сразу по обоим аргументам, то есть большее из значений полей 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.

  6. #ch_aggregation6 / 9
    Как ведёт себя count(DISTINCT x) в ClickHouse по умолчанию?
    A)Считает приближённо и не может быть настроен на точный подсчёт уникальных значений
    B)По умолчанию точный — эквивалент uniqExact(x); поведение регулируется настройкой count_distinct_implementation
    C)Считает общее число всех строк, игнорируя ключевое слово 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.

  7. #ch_distributed7 / 9
    Зачем в ClickHouse используют внешние словари и функцию dictGet?
    A)Чтобы заменить движок MergeTree и хранить в словарях основные таблицы фактов проекта
    B)Быстрый key-value справочник в памяти для обогащения — dictGet вместо джойна к таблице-измерению
    C)Чтобы проверять орфографию текстовых значений в строковых колонках прямо в момент вставки данных
    D)Чтобы сжимать числовые колонки высокой кардинальности перед непосредственной записью их на диск
    показать ответ и разбор
    +B)Быстрый key-value справочник в памяти для обогащения — dictGet вместо джойна к таблице-измерению

    // разбор: Словари (dictionaries) — это key-value справочники, которые CH держит в памяти и обновляет из источника (таблица, файл, СУБД). Вместо джойна к таблице-измерению пишут dictGet('dict', 'attr', key) — точечный поиск по ключу без хеш-джойна и без загрузки правой таблицы в память на каждый запрос. Классика обогащения: id -> имя, город -> регион. Дёшево, если справочник влезает в RAM и меняется не слишком часто.

  8. #ch_engines8 / 9
    Что задаёт секция 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.

  9. #ch_performance9 / 9
    Как работает первичный индекс MergeTree (разрежённый индекс)?
    A)Хранит отдельную индексную запись для каждой отдельной строки таблицы целиком, ровно как классический B-Tree в OLTP-СУБД
    B)Хранит по одной засечке на гранулу (~8192 строк); фильтр по префиксу ключа отсекает целые гранулы
    C)Индексирует все колонки таблицы разом, обеспечивая быстрый поиск по ним
    D)Является полнотекстовым индексом, ускоряющим поиск подстрок в строковых колонках
    показать ответ и разбор
    +B)Хранит по одной засечке на гранулу (~8192 строк); фильтр по префиксу ключа отсекает целые гранулы

    // разбор: Первичный индекс CH разрежённый: одна засечка (значение ключа) на гранулу — блок ~8192 строк (index_granularity), а не на строку. По фильтру на префикс ORDER BY движок находит диапазон гранул и читает только их, пропуская остальные. Индекс эффективен на префиксе ключа сортировки и на диапазонах, но не заменяет точечный поиск OLTP. Поэтому порядок колонок в ORDER BY подбирают под частые фильтры.

это 9 из 69

Ещё 60 вопросов по теме — в тренажёре, с движком повторения

Прочитать разбор и ответить самому — разные навыки. В Сеньорчике вопросы идут сессиями, а движок возвращает подтемы, где вы ошибаетесь, пока они не начнут отскакивать. Бесплатно, лимит по энергии.

Частые вопросы