Читальный зал: чем меряют ответ

Ответ есть, а цены у него нет: чем мерить без секундомера

18 мин
Чему научишься
  • измерять цену ответа тремя числами: сколько кусков, строк и засечек пришлось прочитать
  • сравнивать два запроса с одинаковым ответом, но разной ценой, и объяснять причину разницы
  • не полагаться на секундомер, если вместе с запросом он измеряет создание базы и загрузку данных
  • читать список колонок и типов чужой таблицы, даже если показать её полное определение нельзя

10:15. Три числа за март

27 марта 2184 года. Читальный зал Академии на станции «Хранилище-9»: высокий светлый ярус, длинные столы, окно выдачи.

Три дня назад ты сдал смену на приёмном уровне. Лента ночной смены работает без тебя: данные доезжают, повторный запуск ничего не ломает. Наверху вопросы уже другие.

Смотритель зала кладёт перед тобой лист с тремя вопросами за март: сколько было визитов, сколько разных посетителей, сколько раз материал дошёл до рук. Сами вопросы простые, ответы найдутся быстро.

Проблема в другом. Один и тот же ответ можно получить двумя запросами, но один обойдётся залу примерно в двадцать раз дороже другого. Значит, мало получить правильное число — нужно ещё понять цену, которую движок заплатил за ответ.

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

Поэтому с этой смены ты отвечаешь не только за ответ, но и за то, сколько данных пришлось прочитать, чтобы его получить.

КВЕРИ: С возвращением, архивист. Внизу спрашивали, доехало ли. Здесь спрашивают, сколько, — а потом сразу: сколько ты прочитал, чтобы это узнать. Второй вопрос мой. Без числа не приму.

Над столом разворачивается панель выдачи: восемь пустых ячеек — по одной на главу — и журнал в три колонки: вопрос → приём → цена. Пока пусто и там, и там.

К концу этого урока в панели появится первая цена, а к концу главы — первый ответ.

Тёмный читальный ярус станции: архивист со спины у окна выдачи, перед ним висит панель из восьми пустых ячеек ответов и раскрытый журнал в три колонки, смотритель протягивает лист с тремя вопросами; за переборкой — планета, часов в кадре нет.
Вопросов три, ответов ноль — и мерить их будут не временем: часов в зале нет ни одних.
Один вопрос — «сколько разных посетителей было в зале 5 марта» — и два способа его задать. Ответ должен совпасть. Сравнивать будем три последних числа: они покажут, сколько данных пришлось прочитать в каждом случае. Пока смотри на результат; устройство учебной обёртки разберём сразу после ячейки.
Двадцать четыре куска марта под двумя фильтрами: по колонке дня подсвечен один кусок, по вычислению над отметкой времени — все двадцать четыре. Ответ в обеих строках 174.

Три числа вместо миллисекунд

Оба запроса дали 174. Но цена ответа оказалась разной: первый прочитал 1172 строки, второй — 23 565. Тот же вопрос обошёлся движку примерно в двадцать раз дороже.

Почему так произошло? Дело не в результате, а в том, смог ли ClickHouse заранее отбросить ненужные данные или был вынужден их читать.

rr_events разбита на дневные партиции: события каждого дня лежат в отдельном куске на диске. Фильтр по event_date совпадает с ключом партиционирования. Поэтому движок берёт один кусок из двадцати четырёх, а остальные даже не открывает.

Фильтр по toDate32(event_ts) задаёт тот же вопрос иначе — через вычисление над колонкой. Чтобы понять, подходит ли строка, движку сначала приходится её прочитать. Поэтому он открывает двадцать четыре куска, затрагивает двадцать четыре засечки и читает все 23 565 строк.

Разница в цене появилась ещё до вычисления ответа: первый запрос помог движку сразу сузить чтение, второй — нет.

Четыре знакомых слова. Ты уже встречал их в главе про колоночное хранилище, и дальше курс использует их в том же смысле:

  • кусок — отдельная порция данных таблицы на диске;
  • гранула — минимальная порция строк внутри куска, которую движок умеет прочитать (у большинства таблиц зала это 8192 строки, у витрины dm_vydacha — 1024);
  • засечка — метка на границе гранулы: по засечкам движок прыгает внутрь куска, не читая его с начала;
  • ключ сортировки — порядок, в котором строки лежат внутри куска; засечки расставлены по нему, поэтому вопрос «в порядке ключа» стоит дёшево, а поперёк — дорого.

Отсюда и мера, которой будем пользоваться дальше: цена ответа — это три числа: кусков / строк / засечек.

Они удобнее времени ещё и потому, что не зависят от загрузки станции и от того, кто ещё читает зал в эту минуту. Два прогона одного запроса дадут одинаковые числа, а одинаковое время — не обязательно.

Обязательная форма

Цену показывает EXPLAIN ESTIMATE, но напрямую в уроке его не вызывают.

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

Чтобы оставить только устойчивую часть результата, цену всегда снимаем через обёртку и явно перечисляем нужные колонки:

SELECT parts, rows, marks FROM viewExplain('EXPLAIN ESTIMATE', '', ( ... ваш запрос ... ))

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

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

Это не ошибка. Ответ взялся из служебных записей о кусках, поэтому сами строки читать не понадобилось. Если хочешь увидеть цену чтения, добавь в вопрос хотя бы одну настоящую колонку.

Один объект в трёх масштабах: 24 куска — по одному на день; внутри каждого куска — гранулы по 8192 строки; на их границах — засечки. У витрины гранулы меньше — по 1024 строки, поэтому засечки стоят чаще.
В последнем уроке понадобится витрина зала, а пока посмотрим её колонки и типы. SHOW CREATE TABLE песочница не пропустит — это правило песочницы, а не ограничение ClickHouse. Поэтому используем доступную, но неполную замену.

Что в песочнице закрыто и чем оно заменено

В курсе ты работаешь с одиночным сервером через браузер, без администраторских прав. Поэтому часть привычных инструментов здесь недоступна.

Это не мешает разбирать механику ClickHouse: для каждой закрытой возможности есть рабочая замена. Именно её курс использует до последнего урока.

закрыточем замененочто теряется
SHOW CREATE TABLEDESCRIBE TABLE — 12 строк по числу колонок и 7 колонок описания, включая способ сжатия и срок жизнине видно движка таблицы, ключа сортировки и настроек; их придётся спрашивать у того, кто таблицу заводил
служебная база с данными о кускахuniqExact(_part) и список кусков массивом прямо в запросене видно размеров на диске и истории слияний
несколько машиндве локальные таблицы и UNION ALLнет сети между машинами, а значит и её цены — самой дорогой части настоящего распределённого ответа

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

Всё остальное настоящее: тот же движок, та же версия, те же ошибки дословно. Если урок использует замену, он говорит об этом прямо — как здесь.

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

Как это спрашивают на собеседовании

Типичный вопрос: «Как вы поймёте, что запрос читает лишнее?»

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

Сильный ответ опирается на объём чтения: назови три числа из плана — сколько кусков, строк и засечек пришлось прочитать — и объясни, как они изменятся после фильтра по ключу партиционирования.

В вакансиях и документации встретишь индустриальные названия этих терминов: кусок — part, засечка — mark, гранула — granule. На собеседовании почти наверняка назовут именно их.

Проверь себя
Ячейка урока показывает время исполнения. Почему курс не меряет им цену ответа?
Главное из урока
вопросприёмцена
Сколько разных посетителей было 5 марта?фильтр по event_date — ключу партиционирования таблицы1 кусок / 1172 строки / 1 засечка
То же самоефильтр по вычислению над отметкой времени24 куска / 23 565 строк / 24 засечки

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

Это и есть главный вывод урока: правильного ответа недостаточно. Нужно понимать, сколько данных ClickHouse прочитал по дороге к нему.

Первая ячейка панели выдачи больше не пустует. В ней стоит не ответ, а мера, которой курс будет оценивать все остальные семь.

КВЕРИ: Принято. Не потому, что число красивое, — потому что рядом написано, во что оно обошлось. Дальше сложнее: ответы станут длиннее, а цена научится прятаться.