Модуль 12. Финал: баттл и защита
Цель модуля: проверить себя по-настоящему. Финальный квиз на 50 вопросов по всем модулям (15 — в интерактивном блоке ниже, 35 — статичным разбором «вопрос → эталонный ответ»), затем — защита MVP «Аналитика OrderFlow»: чек-лист код-ревью и типовые вопросы «почему выбрал X» с эталонными ответами.
Перед началом: Модули 1–11. Защита предполагает собранный MVP (Модуль 11); Модуль 10 (RAG) в экзамен не входит, но вопросы по pgvector бонусом отметит любой интервьюер. Формат: интерактивный квиз (15) + статичный разбор (35) = 50 вопросов. Трудоёмкость: ~5–7 ч (квиз 1.5 ч · доработка проекта 3–4 ч · защита 1 ч).
🔥 Инцидент: ночной звонок
03:14, телефон: дежурный не спит и говорит, что дашборд OrderFlow показывает падение выручки на 60 % за последние два часа. PostgreSQL отвечает по 8 секунд вместо привычных 40 мс, ClickHouse бодр — 150 мс на те же запросы, а лаг консьюмеров Kafka в норме. До утра нужно ответить на один вопрос: клиенты правда перестали покупать — или пайплайн потерял события и занизил выручку на графике?
Первое, что сделали, — перезапустили синкер и подождали пять минут: цифры не
изменились. Платежей в Kafka за эти часы столько же, сколько вчера, ошибок в
логах ClickHouse нет, а PG при этом заметно тяжелее обычного. Значит, ответ
где-то между «мастер деградировал» и «дашборд врёт», и каждая версия стоит
проверки инструментом из курса: планы запросов, состояние слотов, чтение из
system.parts, честность витрин.
Это экзамен на всё, что было в курсе: от MVCC и планов до CDC и агрегатов ClickHouse. А дальше — настоящий выпускной: финальный баттл из 50 вопросов по всем модулям и защита MVP OrderFlow, где на каждое «почему выбрал X» придётся отвечать с цифрами, альтернативой и ценой решения.
🏆 Часть 1. Интерактивный квиз-баттл (15 вопросов)
Вопросы собраны из всех секций финального баттла: PostgreSQL-внутренности (3), SQL-арсенал (1), расширения и распределённый PG (3), ClickHouse: движки и кластеры (3), пайплайны CDC (3), ситуационные (2). Остальные 35 — ниже, в статичном разборе.
🧩 Quiz "Проверь себя"
📚 Часть 2. Статичный разбор: 35 вопросов с эталонами
Так отвечайте на собеседовании — коротко, структурно, с ценой решения.
Секция A. PostgreSQL-внутренности (в финале 8, здесь 5)
-
Вопрос: checkpoint_timeout подняли с 5 минут до часа — что изменится? Эталон: Реже сброс dirty pages: меньше усреднённой нагрузки на IO и меньше WAL (full_page_writes срабатывает реже). Обратная сторона: дольше восстановление после сбоя (больше WAL проигрывать) и выше риск залпа при чекпоинте. Компромисс: 15–30 минут +
checkpoint_completion_target = 0.9— размазать запись. -
Вопрос: Почему
SELECT count(*)медленный в PG и быстрый в CH? Эталон: В PG MVCC: видимость каждой строки проверяется по снапшоту — только полный обход (параллельный seq scan помогает, но читает всё). В CH count по неселективному запросу считается по метаданным частей и сжатым колонкам. Мораль: счётчики строк держать агрегатами, не гонять через OLTP. -
Вопрос: VACUUM vs VACUUM FULL vs pg_repack — когда что? Эталон: VACUUM — рутина: чистит место для переиспользования, но не возвращает ОС. VACUUM FULL — переписывает таблицу и возвращает место, но под EXCLUSIVE-локом: прод стоит. pg_repack — online-перепись почти без блокировок, нужен свободный диск ≈ размера таблицы. Прод-выбор для лечения bloat — pg_repack.
-
Вопрос: Что такое xid wraparound и как до него не дойти? Эталон: XID 32-битный и сравнивается «по кругу»; несфрижённые старые строки однажды окажутся «из будущего» и катастрофически изменят видимость. Защита: autovacuum freeze по таблицам с возрастом >
autovacuum_freeze_max_age, алерт наage(datfrozenxid)(порог ~1.5 млрд), autovacuum никогда не отключать. -
Вопрос: Зачем fillfactor и что даёт HOT-обновление? Эталон: fillfactor < 100 оставляет место на странице для новой версии строки → HOT-апдейт: версия на той же странице, индексы не трогаются (если изменённая колонка не индексная). Меньше WAL и меньше index bloat на write-heavy таблицах. Цена: чуть больше места и польза только при обновлениях.
Секция B. SQL-арсенал (в финале 4, здесь 3)
-
Вопрос: Рекурсивный CTE: обойти дерево категорий и не зациклиться. Эталон:
WITH RECURSIVE+UNION(не UNION ALL — дедупликация путей) + массив пути в SELECT с условиемNOT child_id = ANY(path). Плюсmax_depth-предохранитель. Циклы в данных — вопрос «когда», не «если». -
Вопрос: JSONB: когда GIN, когда jsonb_path, когда обычные колонки? Эталон: GIN — для containment-запросов
@>; jsonb_path_query — для структурных выборок; поле, по которому фильтруем постоянно, — честная колонка (или expression index по->>). JSONB — гибкая периферия схемы, не ядро, по которому джойнятся. -
Вопрос: Почему полнотекстовый поиск, а не
LIKE '%...%'? Эталон: LIKE с ведущим%не использует обычный B-tree и не понимает морфологию. FTS:to_tsvector('russian', body)+ GIN, запрос черезwebsearch_to_tsquery, ранжирование ts_rank с весами по заголовку. Для «похожих строк» (опечатки, SKU) — pg_trgm, это другая задача.
Секция C. Расширения и распределённый PG (в финале 8, здесь 5)
-
Вопрос: Citus reference table: что даёт и чем платим? Эталон: Маленький справочник реплицируется на все шарды — джойны с distributed-таблицами локальны. Цена: каждая запись — распределённая операция (2PC), поэтому reference — для читаемых справочников, не для горячих на запись данных.
-
Вопрос: Что в Citus не работает «как в одиночном PG» без дизайна? Эталон: UNIQUE/PK/FK должны включать ключ шардирования; глобальные ограничения — только через reference-таблицы; кросс-шардные джойны без колокации — перемешивание или gather на координатор; распределённые транзакции — 2PC с ценой латентности. Дизайн начинается с ключа шардирования, не с таблиц.
-
Вопрос: Patroni: зачем DCS (etcd) и что делает watchdog? Эталон: DCS — источник истины о лидере: кворум выборов ловит расщепление мозга. Watchdog на узле убивает PostgreSQL, если тот считает себя мастером, а кворум этого не подтверждает, — fencing зомби-мастера, который иначе писал бы в сторону.
-
Вопрос: Transaction pooling (PgBouncer/Odyssey): что ломается и как жить с prepared statements? Эталон: Сессию-состояние пережить нельзя:
SET(толькоSET LOCAL), advisory locks — в явной сессии, listen/notify — отдельно. Protocol-level prepared statements поддерживаются современным PgBouncer (1.21+); для старых — pgx в режиме без именованных statements. Правило: transaction mode — контрактом, а не надеждой. -
Вопрос: postgres_fdw / clickhouse_fdw: где уместны? Эталон: Разовые кросс-системные джойны, ад-хок сверка, скриптовые отчёты. Не для прод-пайплайнов: статистика удалённой стороны неизвестна, данные тащатся по сети без оптимизаций. Пайплайн данных — CDC или батч, FDW — лупа, а не транспорт.
Секция D. ClickHouse: движки и кластеры (в финале 10, здесь 7)
-
Вопрос: ORDER BY vs PARTITION BY в MergeTree — разделение ролей? Эталон:
ORDER BY(ключ сортировки) — главный индекс: гранулы упорядочены, primary key отсекает при чтении; выбирается под WHERE главных запросов.PARTITION BY— физические директории: prune целых партиций, TTL, DROP PARTITION; по времени. Ошибка — партиционировать по высококардинальному полю: тысячи мелких партиций = merge pressure. -
Вопрос: Skip-индекс bloom_filter: когда спасает и когда бесполезен? Эталон: Спасает точечные фильтры по колонке вне ключа сортировки (user_id, тег): отсекает гранулы без совпадений. Бесполезен для диапазонов (это работа minmax) и для колонок из ключа сортировки (primary key уже отсекает). Это не B-tree: ORDER BY он не ускоряет.
-
Вопрос: Кодеки DoubleDelta/Gorilla — для каких колонок? Эталон: DoubleDelta — монотонные ряды (timestamp, счётчики): дельты дельт почти константы. Gorilla — float с локальной стабильностью (метрики, координаты). Поверх — общий ZSTD/LZ4. На случайных данных спецкодек может увеличить размер — меряйте на своих данных.
-
Вопрос: SummingMergeTree vs AggregatingMergeTree: в чём ловушка первого? Эталон: Summing складывает все числовые колонки при мердже и «какую-то» строку оставляет для нечисловых — годится только для чисто аддитивных мер без деталей. AggregatingMergeTree хранит состояния (
sumState,quantileState), комбинируемые корректно (sumSum,quantileMerge). p99 через Summing физически не построить. -
Вопрос: ASOF JOIN: сценарий и ограничения. Эталон: «Последнее известное значение на момент события»: цена акции на момент сделки, курс валюты. Требует упорядоченности по времени и ровно один inequality-ключ; остальных ключей джойна может быть несколько, но inequality — один. Идеален для витрин цен, опасен для «всего подряд».
-
Вопрос: Зачем CHKeeper и сколько узлов? Эталон: Координация ReplicatedMergeTree: выбор лидера мерджей, блокировки, дедуп вставок, метаданные реплик. Raft-кворум: 3 узла (переживает 1 отказ), 5 — для больших кластеров. Это не ZooKeeper — легче, но роль та же; без кворума репликация останавливается.
-
Вопрос: TTL строк/колонок и tiered storage: как работает и чем платим? Эталон: TTL DELETE чистит при мерджах;
ttl_only_drop_parts = 1позволяет удалять целые части — радикально дешевле.TTL ... TO S3— холодные части на дешёвом хранилище, читаются медленнее. Изменение TTL на живом объёме — перезапись (мутация): проектируйте TTL сразу.
Секция E. Пайплайны CDC (в финале 10, здесь 7)
-
Вопрос: Почему логический слот «держит» WAL и что при его потере? Эталон: Слот — обещание не выбрасывать WAL, пока консьюмер не забрал: мёртвый консьюмер = растущий pg_wal до переполнения диска. Удалённый или потерянный слот — история с его позиции недоступна: пересоздание + пересинк (snapshot); «дыру» в WAL не зачитать.
-
Вопрос: Зачем heartbeat.interval.ms в Debezium, если трафика мало? Эталон: Heartbeat двигает точку слота на тихих базах: без него restart_lsn не ползёт, и диск переполнится при «мёртвом» CDC без единой ошибки. Плюс живые метрики здоровья коннектора. Дешёвая страховка от классического инцидента.
-
Вопрос: Failover прошёл, слот потерян: дыра в CH за N минут. Как штопать? Эталон: Определить окно: последний LSN в CH против текущего. Дозалить данные из PG за окно (
SELECTпо updated_at + вставка с версией = LSN) — ReplacingMergeTree схлопнет дубли с уже имеющимися. По Kafka «угадывать» бессмысленно: часть событий потеряна — лечим данными из источника. -
Вопрос: schema.history.internal и auto.evolve: что умеют и чего не умеют? Эталон: Debezium хранит историю DDL и пишет схему в сообщения; auto.evolve досоздаёт missing-колонки у консьюмеров. Не умеют: менять PK, даункасты типов, семантику переименований. Поэтому additive-first и контракт схем — ваша дисциплина (Модуль 8.2), а не настройка коннектора.
-
Вопрос: Одна транзакция на 4GB в PG: что случится в пайплайне? Эталон: Debezium отдаст её как один длинный поток событий: слот надолго «застынет», consumer lag взлетит, возможны проблемы с размером батчей. Лечение — дисциплина батч-операций: дробить большие миграции данных на транзакции.
-
Вопрос: Kafka engine в CH против собственного Go-синка: критерии выбора? Эталон: Kafka engine + MV — меньше кода, быстрее прототип: но потребление хрупко к схеме, нет сложных трансформаций, ретраи и DLQ управляются хуже, меньше наблюдаемости. Go-синк: полный контроль (батчинг, идемпотентность, метрики, DLQ) ценой ~500 строк ответственности. Для MVP курса — Go-синк; для прототипа — честно Kafka engine.
-
Вопрос: Как доказать, что PG и CH совпадают (reconciliation)? Эталон: Три уровня: 1) счётчики вставок по минутам/часам PG vs CH — постоянный дашборд; 2) контрольные суммы по окнам (хэш ключей) по расписанию; 3) полный сверочный прогон по горячему окну при инциденте. Всё это метрика с алертом, не ручная сверка раз в квартал.
Секция F. Ситуационные задачи (в финале 10, здесь 8)
-
Вопрос: Утро понедельника: главный дашборд пуст, в CH нет данных с 03:10. Первые действия? Эталон: 1) Жив ли синк, что в его ошибках. 2) Слот в PG: активен? не потерян на failover? 3) Kafka UI: топик растёт (консьюмер умер) или стоит (сломан Debezium/PG)? Так локализуете сегмент за минуты, чините его и дозаливаете данные окном-пересинком. Не «перезапустить всё подряд».
-
Вопрос: Кто-то «чинит» данные мутациями в CH: очередь на 10k, вставки деградировали. Действия? Эталон: Остановить источник мутаций. Оценить system.mutations, зависшие — KILL MUTATION. Дать мерджам выдохнуть (вставки — во временное окно). Затем процесс: мутации только через runbook с лимитом одновременных, точечные исправления — через версии синка. Это и инцидент, и сигнал сломанного процесса.
-
Вопрос: Продакт: «хочу реалтайм-аналитику с задержкой меньше секунды». Ваш разговор? Эталон: Вопрос «какая задача решается секундой?». Для дашборда 5–30 сек лага незаметны, а «меньше секунды» стоит: синхронные вставки, мелкие батчи → parts и merge pressure → дорогой кластер. «Почти-реалтайм 2–5 сек» часто решает задачу в десять раз дешевле — показываете график лага и предлагаете SLI вместо лозунга.
-
Вопрос: pg_stat_statements: mean 0.4ms, но total — 40% времени БД. Что за зверь? Эталон: N+1 из приложения: миллионы дешёвых вызовов. Чинить кодом: батч-выборка (
WHERE id = ANY($1)), джойн, кэш. Метрика-подсказка — огромный calls при крошечном mean; индекс тут не поможет, он уже хорош. -
Вопрос: После ALTER TABLE в PG в дашборде CH-колонка пустая. Где рассинхрон? Эталон: Контракт схем не выполнили: проверить, отдаёт ли Debezium поле (сообщение в Kafka), знает ли маппинг синка, есть ли колонка в CH. Историю — перезалить окном с версией. Урок: additive-first и порядок «читатели → продюсер» (Модуль 8.2) — это про такой день.
-
Вопрос: Диск PG-мастера 92%, раздут pg_wal. Порядок действий? Эталон: 1) Виновник: pg_replication_slots — неактивный/отставший слот (обычно умерший Debezium). 2) Решение по слоту: поднять консьюмер или осознанно удалить слот (теряется CDC-история → пересинк). 3) WAL руками не удалять никогда. 4) Алерты на размер pg_wal и лаг слота — чтобы в следующий раз узнать раньше.
-
Вопрос: Бизнес: «события храним 7 лет, дашборд смотрит 90 дней». Дизайн? Эталон: Один ClickHouse, два температурных слоя: raw с TTL 7 лет на дешёвом хранилище (S3 tiered storage), MV-агрегаты под дашборд — на быстрых дисках (они крошечные). Не «вторая СУБД», а температура. Плюс retention Kafka и пересинк из PG как страховка источника.
-
Вопрос: Одна реплика шарда CH недоступна. Что видят пользователи? Эталон: Distributed-запросы едут на живую реплику (load_balancing, max_replica_delay_for_distributed_queries) — почти незаметно, если лаг живой реплики мал. Вставки через Distributed ждут живого шарда. Опасность — «тихая» деградация: кластер уже без запаса, алерт на доступность реплик обязателен до, а не после второй поломки.
🛡 Часть 3. Защита MVP «Аналитика OrderFlow»
Чек-лист код-ревью перед защитой
Финальный проход по чек-листу из Модуля 11 — плюс пункты защиты:
- Каждый checkpoint S1–S6 подтверждён артефактом: скриншот Grafana, вывод EXPLAIN/команды в README.
- Сбой-тесты автоматизированы: kill мастера, kill синкера, pause sink — инвариант «ноль потерь, дубли переживаются» прогоняется скриптом.
- Три лага на дашборде: слот, consumer group, счётчики PG vs CH — с алертами.
- Витрины восстановимы перезаливкой из raw — процедура описана и отработана.
- Постмортем намеренного инцидента S6 в README: таймлайн, обнаружение, действие, профилактика.
- Каждый ADR отвечает на «почему», с альтернативой и ценой — не только на «как поднять».
Типовые вопросы защиты и эталонные ответы
«Почему ReplacingMergeTree, а не Collapsing?» Эталон: Collapsing требует строгих пар insert/cancel (sign ±1) и терпит непарность только до мерджа: одно потерянное событие — строка навсегда «живая» или «удалённая». Наш поток (Kafka at-least-once, фейловеры) парности не гарантирует. ReplacingMergeTree + версия из LSN: дубли безвредны, порядок доставки не важен, DELETE — новая версия с флагом. Цена: дубли видны до мерджа — FINAL/argMax в витринах.
«Почему CDC, а не dual-write?» Эталон: dual-write — два независимых утверждения без общей транзакции: падение между ними — вечный рассинхрон, который ретраем не ловится. CDC читает WAL — единственный источник правды PG: порядок гарантирован, потеря возможна только вместе с потерей самого PG. Цена CDC — лаг и инфраструктура (Debezium, слоты), и она мониторится (Модуль 6.5), а не прячется.
«Что сломается при 10x нагрузке?» Эталон: называю конкретную первую точку из S6 — [ваша цифра: например, батч синка не успевает → consumer lag; за ним merge pressure в CH; параллельно connection storm мимо PgBouncer]. План: консьюмеры по числу партиций, крупнее батчи, async_insert в CH, pool size. «Поднимем ещё подов» — не ответ; у меня есть график лага под нагрузкой и точка перегиба.
«Когда добавите Citus?» Эталон: по лестнице Модуля 9: сначала партиции (уже есть) и read-replicas (есть) — они дешевле. Citus — когда упрусь в запись/объём одного узла и рабочее множество перестанет влезать. Ключ шардирования осознан заранее (tenant_id), FK/unique спроектированы с ним — миграция планом на два спринта, а не авралом.
«Почему Kafka между Debezium и ClickHouse, а не напрямую?» Эталон: Kafka — буфер и лог: простой CH/синка копится и доигрывается (retention), несколько проекций читают один поток, replay для пересинков. Прямой путь (Kafka engine или http) экономит компонент, но отнимает буфер, replay и наблюдаемость. Цена Kafka — второй stateful-компонент; при малых масштабах честно рассмотреть движковую таблицу (Модуль 4.8) — и я могу сказать, на каких объёмах поменяю решение.
«Где у вас exactly-once?» Эталон: нигде «по сети»: at-least-once до CH, идемпотентный эффект — ReplacingMergeTree по версии LSN + FINAL в чтении, оффсеты коммятся после вставки. Тест S4: kill -9 синкера посреди батча — дублей в отчётах нет. Это exactly-once по бизнес-эффекту, а не по маркетинговому слайду.
🎓 Итоги курса
За двенадцать модулей вы прошли путь от «зачем две СУБД» до защищённого MVP с HA-кластером PostgreSQL, CDC-пайплайном через Kafka и кластером ClickHouse 2×2. Теперь вы можете:
- Выбирать хранилище по профилю нагрузки — и называть цену решения вслух.
- Читать PostgreSQL изнутри: MVCC и bloat, планы и буферы, блокировки, пулы.
- Осознанно применять экосистему расширений: Citus, Patroni, pg_partman, pg_failover_slots, Debezium, pgvector.
- Строить MergeTree-модели: ключи сортировки, MV-агрегаты, дедупликация версиями, слои raw → витрины.
- Эксплуатировать пару СУБД: бэкапы с PITR, мониторинг с алертами на реальные болезни, миграции без остановки пайплайна, безопасность.
- Масштабировать и защищать решение в споре: «что сломается при 10x» — не угроза, а ваш бэклог.
Что читать и смотреть дальше
- «PostgreSQL 16 изнутри» — Е. Рогов (postgrespro): систематизация Модуля 2 — MVCC, WAL, vacuum из первых рук.
- «PostgreSQL: Up and Running» — Р. Отворски, Е. Калинина: практика эксплуатации без воды.
- «ClickHouse in Action» — Setchenkov: движки, кластеры, рецепты под реальные нагрузки.
- «Designing Data-Intensive Applications» — M. Kleppmann, глава 11 «Streams»: фундамент, на котором стоят все CDC-пайплайны курса.
- blog.altinity.com — эксплуатация ClickHouse-кластеров в бою: мерджи, рескелинг, Keeper, обновления.
- Документация: postgresql.org/docs, clickhouse.com/docs, github.com/pgvector, debezium.io/documentation.
Если «Аналитика OrderFlow» дожила до защиты — у вас на руках работающий стенд из двух миров данных с честным пайплайном между ними, которым можно гордиться в портфолио. До встречи в следующем курсе.