Базы данных
Индексы: спроектировать и проаудитить реальный набор индексов
Читать про leading-column rule — не то же самое, что смотреть, как 4-секундный запрос дашборда падает до субмиллисекунды, потому что ты правильно собрал composite, partial-предикат и список INCLUDE. Построй реалистичную multi-tenant схему, загрузи её до масштаба и преврати каждое решение по индексу в измеренное до/после — со EXPLAIN как свидетелем на каждом шаге.
Преврати ментальную модель юнита в воспроизводимый инженерный цикл: загрузи таблицу до production-реалистичного масштаба, профилируй горячие запросы, спроектируй продуманный набор индексов (composite + partial + INCLUDE + правильные типы), разверни без блокировок, докажи каждый запрос через EXPLAIN, затем прогони playbook аудита и верни то, что тратится зря.
Возьми multi-tenant схему tasks/comments (стартер ниже или свою), загрузи до ~50-100M строк с реалистичным перекосом по status и спроектируй, разверни и проаудитируй полный набор индексов так, чтобы каждый горячий запрос выполнялся как Index Only Scan или селективный Index Scan — каждый доказан числами EXPLAIN (ANALYZE, BUFFERS) до/после, при суммарном хранилище индексов под ~20% от размера таблицы.
- Таблица до/после на каждый горячий запрос: тип скана (Seq Scan -> Index Only / Index Scan), Heap Fetches, Buffers и общее время выполнения — измеренные на полном масштабе, не на глаз.
- Каждый задуманный index-only scan показывает Heap Fetches: 0 после VACUUM, и ни один запрос, который должен обслуживаться индексом, не показывает Seq Scan или узел Sort после скана.
- Демонстрация каскада удаления родителя показывает, как многосекундный/минутный Seq-Scan каскад схлопывается до быстрого index lookup, как только FK-колонка comments проиндексирована.
- Суммарное хранилище индексов под ~20% от размера таблицы, с одним абзацем бюджетного учёта (размер каждого индекса, зачем он, какой запрос обслуживает) и leading-column обоснованием каждого composite.
- Сводка аудита с именами каждого найденного неиспользуемого/избыточного/раздутого индекса и освобождённым местом, плюс подтверждение, что ни один горячий запрос не деградировал после дропов.
- Добавь pg_trgm fuzzy-поиск (ILIKE-anywhere / similarity по title) и pgvector HNSW семантический поиск по колонке embedding; сравни размер индекса, write-стоимость и recall с базой GIN tsvector.
- Продемонстрируй ловушку неявного приведения end-to-end: покажи WHERE bigint_col = '42' (TEXT-литерал) с seq-scan, затем версию с типизированным параметром, использующую индекс — сними оба плана.
- Поставь эксперимент с HOT-обновлениями: снизь fillfactor до 70 на update-тяжёлой таблице, гони обновления, не меняющие ни одной индексируемой колонки, и покажи падение index writes (HOT n_tup_hot_upd в pg_stat_user_tables) против fillfactor 100.
- Добавь CI-гейт: запускай EXPLAIN (ANALYZE) на пяти горячих запросах против засеянного canary на каждой миграции и роняй сборку, если любой план регрессирует к Seq Scan, узлу Sort или Heap Fetches выше порога.
Это цикл, который ты будешь прогонять в любой реальной задаче индексирования: загрузи до реалистичного масштаба, сними базу горячих запросов через EXPLAIN (ANALYZE, BUFFERS), спроектируй продуманный набор (composite вокруг всегда присутствующего фильтра, partial для горячего подмножества, INCLUDE для проекции, правильный тип под форму данных), разверни CONCURRENTLY и проверь indisvalid, проиндексируй каждую FK-колонку, докажи, что каждый запрос обслуживается индексом с Heap Fetches: 0, затем проаудитируй неиспользуемые/избыточные/раздутые индексы и верни потери. Один раз на реалистичной схеме — и production-версия становится мышечной памятью.