Перейти к основному контенту
Tech Path Finder
КурсыИнтервьюКод-ревьюБлог
Tech Path Finder

Персонализированный путеводитель в IT. Квизы, мок-интервью, код ревью и аналитика прогресса.

@potapov_me

Платформа

  • Курсы
  • Прогресс
  • Мок-интервью
  • Код ревью
  • Живое ревью с ИИ
  • Тренажёр переговоров
  • Закладки

Контент

  • Блог
  • Главная
  • Обратная связь

Компания

  • О проекте
  • Тарифы
  • Условия использования
  • Конфиденциальность
  • Согласие на обработку данных
  • Cookie
  • Реквизиты

Аккаунт

  • Войти
  • Зарегистрироваться
  • Профиль

© 2026 Tech Path Finder. Все права защищены.

·ИП Потапов К.С.·Политика конфиденциальности·
Сделано с ❤️ в России
  1. Итоговый практикум
capstone

Итоговый практикум

Сквозной проект аналитической платформы: схема, загрузка, измерения, отказ, восстановление и инженерная защита

Итоговый практикум: аналитика SaaS-событий

Соберите систему с нуля, измерьте её поведение и проведите учебное восстановление

#Задача

Команда развивает B2B SaaS. Приложение отправляет просмотры, действия и покупки клиентов из разных часовых поясов. Дашборд должен отвечать за секунду на рабочих объёмах, данные хранятся год, а каждый арендатор видит только свои строки.

Вам нужно подготовить техническое решение и воспроизводимый протокол испытаний. Одного SQL-файла недостаточно: приложите DDL, запросы, планы, метрики, инструкции для дежурного и отчёт о восстановлении.

#Ограничения

  • базовая версия — ClickHouse 26.3 LTS;
  • локальные этапы должны работать на машине с 4 ядрами и 8 ГБ RAM;
  • генератор данных не зависит от внешних файлов;
  • результат должен воспроизводиться из чистого контейнера;
  • значения SLO, RPO и RTO задаются до испытаний;
  • секреты и пароли не сохраняются в репозитории или истории запросов.

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

#Этап 1. Зафиксируйте эксперимент

Создайте каталог отчёта со следующими артефактами:

clickhouse-capstone/ ├── README.md ├── ddl.sql ├── load.sql ├── queries.sql ├── security.sql ├── backup_restore.sql ├── evidence/ │ ├── baseline.tsv │ ├── optimized.tsv │ └── plans.txt └── instructions/ ├── slow-query.md ├── replica-lag.md └── restore.md

В README.md укажите:

  • результат SELECT version(), timezone();
  • CPU, RAM, тип диска и способ запуска;
  • все изменённые настройки ClickHouse;
  • ожидаемый объём данных;
  • SLO дашборда, RPO и RTO;
  • какие этапы выполнены на одном узле, а какие на кластере.

#Этап 2. Создайте исходный поток

Исходная таблица намеренно неидеальна. Она нужна как контрольная точка:

CREATE DATABASE IF NOT EXISTS saas; CREATE TABLE saas.events_raw ( event_time DateTime64(3, 'UTC'), tenant_id UInt32, user_id UInt64, event_id UUID, event_type String, country String, plan String, duration_ms UInt64, revenue Float64, properties String ) ENGINE = MergeTree PARTITION BY toYYYYMMDD(event_time) ORDER BY event_id;

Сгенерируйте не менее 10 миллионов строк. Для короткой проверки уменьшайте число в одном месте:

INSERT INTO saas.events_raw SELECT toDateTime64('2026-01-01 00:00:00', 3, 'UTC') + toIntervalMillisecond(number * 250) AS event_time, 1 + number % 1000 AS tenant_id, cityHash64(number) % 5000000 AS user_id, generateUUIDv4() AS event_id, ['view', 'click', 'search', 'purchase'][1 + number % 4] AS event_type, ['RU', 'DE', 'US', 'BR', 'IN'][1 + number % 5] AS country, ['free', 'team', 'business'][1 + number % 3] AS plan, 1 + number % 30000 AS duration_ms, if(number % 4 = 3, (number % 100000) / 100.0, 0.0) AS revenue, concat('{"source":"', ['web', 'mobile'][1 + number % 2], '"}') AS properties FROM numbers(10000000);

Проверьте количество активных частей, размер на диске и сжатие:

SELECT table, count() AS active_parts, sum(rows) AS rows, formatReadableSize(sum(bytes_on_disk)) AS disk, round(sum(data_uncompressed_bytes) / sum(data_compressed_bytes), 2) AS ratio FROM system.parts WHERE database = 'saas' AND active GROUP BY table;

#Этап 3. Снимите базовую линию

Выберите три запроса:

  1. активность одного арендатора за час;
  2. выручка по тарифу и стране за неделю;
  3. последние события пользователя.

Перед каждым запуском задайте осмысленный query_id. Сохраните план и фактические показатели:

EXPLAIN indexes = 1 SELECT event_type, count() FROM saas.events_raw WHERE tenant_id = 42 AND event_time >= '2026-01-10 10:00:00' AND event_time < '2026-01-10 11:00:00' GROUP BY event_type; SYSTEM FLUSH LOGS; SELECT query_id, query_duration_ms, read_rows, read_bytes, memory_usage, result_rows FROM system.query_log WHERE type = 'QueryFinish' AND query_id LIKE 'capstone-baseline-%' ORDER BY event_time;

Не сравнивайте только wall-clock time. На локальной машине он меняется из-за кэша и соседних процессов. Главные показатели этого этапа — прочитанные строки и байты, память и устойчивость результата на нескольких запусках.

#Этап 4. Перепроектируйте схему

Создайте saas.events. Обоснуйте каждое отличие от исходной таблицы:

  • порядок колонок в ORDER BY должен следовать реальным фильтрам;
  • партиционирование нужно для управления жизненным циклом, а не как замена индексу;
  • используйте точный тип для денег;
  • примените LowCardinality только там, где это подтверждено кардинальностью;
  • решите, нужен ли Nullable;
  • задайте TTL на год;
  • свойства храните как типизированные поля или JSON, если набор путей действительно динамический.

Пример — отправная точка, а не эталон для любого проекта:

CREATE TABLE saas.events ( event_time DateTime64(3, 'UTC'), tenant_id UInt32, user_id UInt64, event_id UUID, event_type LowCardinality(String), country LowCardinality(FixedString(2)), plan Enum8('free' = 1, 'team' = 2, 'business' = 3), duration_ms UInt32, revenue Decimal64(2), properties JSON ) ENGINE = MergeTree PARTITION BY toYYYYMM(event_time) ORDER BY (tenant_id, event_time, event_type, user_id) TTL toDateTime(event_time) + INTERVAL 1 YEAR DELETE;

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

#Этап 5. Спроектируйте загрузку

Опишите контракт доставки:

  • размер и максимальная задержка пакета;
  • поведение клиента при таймауте после отправки;
  • способ дедупликации повторов;
  • граница допустимой потери данных;
  • регулирование входного потока при перегрузке и контроль числа частей.

Для синхронной загрузки сравните пакеты 1, 1 000 и 10 000 строк. В ClickHouse 26.3 асинхронные вставки включены по умолчанию, но это не отменяет проверку подтверждения записи и наблюдаемость:

SELECT event_time, query, status, flush_time, rows, bytes, exception FROM system.asynchronous_insert_log ORDER BY event_time DESC LIMIT 20;

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

#Этап 6. Ускорьте один дорогой запрос

Выберите ровно один механизм:

  • иной ключ сортировки;
  • проекция;
  • data-skipping индекс;
  • инкрементальное материализованное представление;
  • обновляемое материализованное представление;
  • словарь или JOIN с подходящим алгоритмом.

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

#Этап 7. Добавьте безопасность

Создайте роли ingest, analyst и ops. Минимальные требования:

  • ingest пишет только в исходные таблицы;
  • analyst читает витрины без доступа к административным таблицам;
  • ops может диагностировать и прерывать запросы;
  • арендатор ограничен row policy по tenant_id;
  • сетевой интерфейс использует TLS;
  • учётные данные передаются через секрет-хранилище или переменные окружения и не попадают в system.query_log.

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

#Этап 8. Подготовьте отказ

Для кластера опишите число шардов и реплик, ключ шардирования и размещение Keeper. Затем проведите один безопасный эксперимент:

  • остановка одной реплики;
  • накопление очереди репликации;
  • исчерпание диска через контролируемый лимит учебного тома;
  • запрос, превышающий лимит памяти;
  • повреждённый пакет входных данных.

До эксперимента запишите ожидаемые симптомы, сигнал оповещения и действие дежурного. После — сравните ожидание с фактами в system.replicas, system.replication_queue, system.errors, system.query_log и серверном журнале.

Не имитируйте потерю данных на окружении, которое нельзя пересоздать.

#Этап 9. Докажите восстановимость

Создайте отдельную резервную базу и выполните встроенный BACKUP. Конкретный диск резервных копий должен быть настроен администратором заранее:

BACKUP DATABASE saas TO Disk('backups', 'saas-full.zip') SETTINGS id = 'capstone-full'; SELECT id, status, error FROM system.backups WHERE id = 'capstone-full';

Удалять исходную базу для проверки не нужно. Восстановите копию под другим именем:

RESTORE DATABASE saas AS saas_restore FROM Disk('backups', 'saas-full.zip') SETTINGS id = 'capstone-restore';

Сравните число строк, контрольные агрегаты и права доступа. Зафиксируйте фактические:

  • RPO — сколько подтверждённых данных отсутствует в восстановленной копии;
  • RTO — время от решения о восстановлении до успешной проверки приложения;
  • пропускную способность чтения из хранилища резервных копий;
  • ручные шаги, которые нужно автоматизировать.

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

#Этап 10. Защитите решение

Подготовьте инженерную записку на 2–4 страницы:

  1. нагрузка и SLO;
  2. схема и ключи;
  3. протокол загрузки и повторов;
  4. результат оптимизации «до/после»;
  5. модель отказов;
  6. фактические RPO/RTO;
  7. оставшиеся риски и следующий эксперимент.

#Рубрика

Критерий0 баллов1 балл2 балла
ВоспроизводимостьШаги неполныЗапуск требует ручных догадокЧистое окружение собирается по инструкции
СхемаРешения не объясненыЕсть общие аргументыРешения связаны с запросами и объёмом
ИзмеренияТолько субъективное «быстрее»Есть время одного запускаЕсть план, I/O, память и повторные прогоны
ЗагрузкаНет модели повторовПакеты описаныЕсть идемпотентность, регулирование потока и наблюдаемость
БезопасностьОдин администраторРоли созданыЕсть минимальные права и негативные тесты
ОтказоустойчивостьСбои не рассмотреныЕсть схема кластераПроведён эксперимент и обновлена инструкция дежурного
ВосстановлениеБэкап не восстановленRESTORE выполненПроверены данные, RPO и RTO
Инженерная защитаРешения перечисленыКомпромиссы частичныОграничения и риски сформулированы честно

Зачёт — не менее 12 из 16 баллов, при этом по восстановлению и измерениям нельзя получить ноль.

#Что считается сильной работой

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