Базы данных
Что такое индекс и как он ускоряет запросы
Дашборд команды «фильтр по status» выполняется за 50 мс на 100k строк в staging. В продакшне с 50M строк — 8 секунд. Схема корректна. Запрос корректен. Одна DDL-строка — CREATE INDEX CONCURRENTLY ON orders(status) — возвращает 20 мс. Эта строка — весь смысл урока.
Что такое индекс
Индекс — это отдельная структура данных (обычно B-tree), которая хранит отсортированную копию значений одного или нескольких столбцов плюс указатели на фактические строки. Когда запрос фильтрует по индексированному столбцу, Postgres проходит дерево за O(log n) шагов вместо сканирования каждой строки.
Метафора книжного индекса точна: предметный указатель книги — это отсортированный список терминов с номерами страниц. Чтобы найти «MVCC», открываешь указатель (один поиск O(log n)), читаешь номер страницы и переходишь туда. Без указателя — сканируешь каждую страницу. Индекс базы данных идентичен: копия значений столбца, организованная для быстрого поиска по одному конкретному вопросу.
| Без индекса | С индексом |
|---|---|
| Sequential scan — каждая строка просматривается | B-tree walk — O(log n), затем fetch matching rows |
| 1M строк: ~10мс; 100M строк: ~2–10с | 1M или 100M строк: ~1–5мс |
| Нет доп. хранилища, нет overhead на запись | ~10–30% размера таблицы на индекс; каждая запись поддерживает индекс |
Цена: индексы облагают налогом каждую запись
Каждый индекс — это отдельная структура на диске, которая должна оставаться синхронизированной с таблицей. Каждый INSERT, UPDATE или DELETE, затрагивающий индексированный столбец, также должен обновить индекс — что стоит CPU и I/O. Таблица с десятью неиспользуемыми индексами при write-heavy нагрузке тратит на запись в 10 раз больше, чем необходимо.
Типичный overhead на запись одного индекса: 5–50 мкс на операцию записи. При 10 000 inserts/сек с десятью индексами это дополнительные 0,5–5 мс overhead в секунду — измеримо, и часто узкое место при избыточной индексации.
Размер индекса на диске: примерно bytes_per_key × row_count × 1.3. Для BIGINT-ключа (8 байт) и таблицы из 100M строк получается ~1 GB индекс. Каждый дополнительный индекс умножает это.
Конкретный сценарий
Запрос: SELECT * FROM orders WHERE user_id = 42 на таблице с 10M строк. Без индекса: sequential scan, ~2 секунды. После CREATE INDEX ON orders(user_id): index lookup плюс heap fetch, ~3 миллисекунды. Тот же запрос, ускорение в 600 раз, одна DDL-строка.
Postgres поставляется с шестью типами индексов. B-tree — дефолт и покрывает 95% продакшн-использования: равенство, диапазон, ORDER BY, LIKE 'abc%'. Остальные пять (GIN, GiST, BRIN, Hash, Bloom) служат для специфических форм данных и рассматриваются в уроке 04.
Продакшн-правило: всегда использовать CONCURRENTLY
CREATE INDEX берёт блокировку ACCESS EXCLUSIVE — все чтения и записи заблокированы на всё время построения. На таблице с активным трафиком даже 5-минутная блокировка — это инцидент.
CREATE INDEX CONCURRENTLY использует многофазное построение без эксклюзивной блокировки. Он сканирует таблицу дважды и ждёт in-flight транзакций между фазами. Он в 2–3 раза медленнее и не может быть обёрнут в транзакцию, но не создаёт видимого воздействия на трафик приложения.
Если построение CONCURRENTLY завершается неудачей (например, уникальный индекс находит дубликат или процесс убивается), индекс остаётся в состоянии INVALID (indisvalid = false в pg_indexes). Планировщик рассматривает его как несуществующий. Исправление: DROP INDEX CONCURRENTLY и повторная попытка.
Почему это работает
Почему Postgres не индексирует каждый столбец по умолчанию? Потому что каждый индекс на write-heavy таблице — это налог на запись. Таблица с 20 индексами и 50k inserts/сек может тратить больше времени на поддержку индексов, чем на запись строк. Дисциплина намеренная: измеряй горячие запросы, добавляй индексы для них, проверяй и удаляй неиспользуемые. Избыточная и недостаточная индексация — одинаково разрушительные продакшн-паттерны.
Упорядочи шаги для правильного добавления индекса в продакшне:
- 1 Запусти EXPLAIN ANALYZE на медленном запросе, чтобы подтвердить seq scan как узкое место
- 2 Определи столбцы, по которым происходит фильтрация или сортировка
- 3 Выбери тип индекса (B-tree по умолчанию; GIN для JSONB; GiST для гео)
- 4 Используй CREATE INDEX CONCURRENTLY, чтобы не блокировать таблицу
- 5 Дождись завершения построения (может занять минуты или часы на больших таблицах)
- 6 Снова запусти EXPLAIN ANALYZE, чтобы убедиться, что планировщик теперь использует индекс
- 7 Следи за задержкой записи после деплоя — каждый индекс добавляет небольшую стоимость записи
Создан индекс на столбец users(email). Какой запрос выигрывает от него?
В чём цена добавления индекса?
Заполни пропуск: индекс базы данных для SQL-запроса — то же, что _______ книги для поиска темы: копия части содержимого, организованная для быстрого поиска.
- 01В двух предложениях: почему индексы не бесплатны и почему продакшн-команды не индексируют каждый столбец?
- 02Что происходит, если CREATE INDEX CONCURRENTLY падает посередине, и как восстановиться?
- 03Запрос делает sequential scan на таблице из 50M строк. Ты добавляешь индекс. Что показывает EXPLAIN ANALYZE до и после?
Индекс — это отдельная отсортированная структура данных (как правило B-tree), которая хранит копии значений одного или нескольких столбцов с указателями на соответствующие heap-строки. Он превращает полное табличное sequential scan (O(n)) в проход по дереву (O(log n)), сокращая 2-секундный запрос на 10M строк до 3мс. Цена: каждый индекс стоит 10–30% размера таблицы в хранилище и 5–50 мкс на запись на каждый индекс. Всегда добавляй индексы с CREATE INDEX CONCURRENTLY, чтобы не блокировать таблицу в продакшне. Если построение падает, проверь pg_indexes на indisvalid = false и повтори после удаления неверной записи.
встречается в174
- Путь запроса: семь остановок от сокета до ответаjunior
- Accept и парсинг: от очереди ядра до типизированного запросаmiddle
- Маршрутизация и middleware: что выполняется и в каком порядкеmiddle
- Обработчик и ответ: от бизнес-логики до байтов на проводеmiddle
- Стриминг и backpressure: когда клиент читает медленнее, чем вы пишетеsenior
- Таймауты и хвостовая задержка: бюджеты, дедлайны и ловушка fan-outsenior
- Middleware и DI: два паттерна, формирующие любой backendjunior
- Пишем middleware: сигнатуры, next() и три модели фреймворковmiddle
- Инверсия управления: как зависимости добираются до классаmiddle
- Скоупы и время жизни DI: singleton, request, transientmiddle
- DI как шов для тестов: фейки, моки и граница, которая важнаsenior
- DI-контейнеры в продакшене: графы разрешения, циклы и когда не стоитsenior
- Блокирующий vs неблокирующий I/O: два способа ждатьjunior
- Event loop: один поток, упорядоченные фазыmiddle
- Что блокирует цикл: CPU-работа и синхронные вызовыmiddle
- Вынос CPU-работы: worker threads и пул libuvmiddle
- Backpressure и ограниченная конкурентностьsenior
- Пропускная способность под нагрузкой: хвостовая задержка и насыщениеsenior
- Зачем пул: цена создания соединенияjunior
- Размер пула: почему больше не значит быстрееmiddle
- Взятие и таймауты: очередь ожидания — настоящий дроссель задержкиmiddle
- Стратегии retry: backoff, jitter и thundering herdmiddle
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Задачи, микрозадачи и scheduler.yield()middle
- Точность таймеров, троттлинг и фоновая работаmiddle
- Event loop Node.js: фазы, nextTick и задержка циклаsenior
- Стратегии рендеринга: SSG, SSR, ISR, streaming и гидратацияjunior
- SSG, SSR, ISR, streaming и RSC — как работает каждая стратегияmiddle
- Цена гидратации: selective, progressive, острова, resumabilitymiddle
- Core Web Vitals: что измеряют LCP, INP и CLSjunior
- LCP: четыре фазы, одна доминирующая стоимостьmiddle
- INP: input delay, processing, presentationmiddle
- Lab vs field: почему они расходятся и как использовать каждыйmiddle
- Трейдоффы метрик, RUM-атрибуция и цикл CI+полеsenior
- Общая картина: от URL до LCP до INP как эстафетаjunior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Биты в проводеjunior
- Математика задержкиmiddle
- Bufferbloat и перегрузкаsenior
- Граница физического уровняsenior
- Номера последовательности и состояние соединенияmiddle
- Управление потоком и перегрузкойmiddle
- BBR, производственная наблюдаемость и за пределами TCPsenior
- CDN: контент по соседствуjunior
- Anycast и GeoDNS: маршрутизация к ближайшему edgemiddle
- Многоуровневый кеш и Cache-Controlmiddle
- Заголовок Vary и cache keysmiddle
- Stale-while-revalidate и cache stampedesenior
- Edge workers и edge-side compositionsenior
- CDN: операции и observabilitysenior
- WebSocket: HTTP-апгрейд до постоянного соединенияjunior
- WebSocket vs SSE vs long-polling: выбор правильного транспортаmiddle
- Backpressure в WebSocket: когда клиенты не успеваютmiddle
- Реконнект: jittered backoff, thundering herd, восстановление сообщенийsenior
- WebSocket в масштабе: HTTP/2 мультиплексирование, permessage-deflate, C10Msenior
- WebSocket в production: прокси, безопасность и распределённая архитектураsenior
- Что делают обратные проксиjunior
- Алгоритмы балансировки: от round-robin до power-of-two-choicesmiddle
- L4 vs L7 балансировка и сохранение IP клиентаmiddle
- Health checks, connection draining и slow startmiddle
- Retry-бури, circuit breakers и load sheddingsenior
- Устойчивая архитектура LB: anycast, zone-aware маршрутизация и observabilitysenior
- Почему QUIC, а не TCP+TLSjunior
- QUIC-потоки и head-of-line blockingjunior
- Объединённое рукопожатие и 1-RTTmiddle
- Connection ID и миграция сетиmiddle
- Обнаружение потерь и управление перегрузкойmiddle
- Возобновление 0-RTT и шифрование пакетовsenior
- Развёртывание и стоимость CPUsenior
- DDoS: что это и почему работаетjunior
- Атаки усиления и истощение состоянияmiddle
- Ограничение скорости: алгоритмы и архитектураmiddle
- WAF, межсетевые экраны, mTLS и HSTSmiddle
- Отравление DNS-кэша и BGP-перехватsenior
- Эшелонированная защита и экономика атакsenior
- Двенадцать слоёв: один URL, семь действующих лицjunior
- DNS, TCP, TLS по очереди: куда уходят миллисекундыmiddle
- Критический путь рендеринга и Core Web Vitalsmiddle
- Перехват прокси и шлюзы безопасности: rate limiter, WAF, mTLSmiddle
- Альтернативные пути: QUIC 0-RTT, WebSocket upgrade, миграция соединенияmiddle
- Наблюдаемость: распределённые трейсы, USE/RED и семплированиеsenior
- Устойчивость: каскадные повторы, circuit breakers и error budgetsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Метрики и cardinality: cost-модель time-series databasemiddle
- Логи и объём: cost-модель структурного логированияmiddle
- Трейсы и сэмплирование: cost-модель distributed tracingmiddle
- Join-ключи и exemplar''''ы: как три сигнала становятся компонуемымиmiddle
- Observability 2.0: широкие события и сдвиг стоимостиsenior
- Режимы сбоя и инженерная практика: cardinality budget''''ы, PII и сэмплированиеsenior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- Log levels и маршрутизация алертовmiddle
- Стратегии sampling и стоимость логовmiddle
- PII-редакция и log injectionsenior
- Propagation trace-контекста в логахsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- Сигналы OTel, Semantic Conventions и проводной формат OTLPmiddle
- Авто-инструментирование и ручные спаны: правило 80/20 в OTelmiddle
- Collector OTel: receivers, processors, exporters и паттерны развёртыванияmiddle
- Стратегии сэмплирования: head, tail и parent-basedmiddle
- Vendor-нейтральность, eBPF-инструментирование, Operator и OTel в браузере и serverlesssenior
- Эксплуатация OTel Collector: надёжность, version skew, режимы отказа и управлениеsenior
- RED и USE: два чек-листа, одна дисциплина триажаjunior
- Инструментация RED в Prometheus: счётчики, гистограммы и дисциплина cardinalitymiddle
- USE на Linux: CPU, память, диск, сеть и PSImiddle
- Golden signals, структура дашборда и auto-RED в service meshmiddle
- Cardinality как драйвер затрат: label, PII, exemplars и семплированиеmiddle
- Native histograms, SLO и паттерны production-сбоевmiddle
- Выбор SLI и SLO-целей: отношения, не ощущенияmiddle
- Multi-window multi-burn-rate-алертинг: почему AND лучше ORmiddle
- Error budget policy, latency SLO и составные journeysmiddle
- Iceberg SLI, математика составного SLO и SLA vs SLOsenior
- Flame graph: читаем картинку, которая показывает, куда ушло времяjunior
- Sampling vs instrumentation profiling: почему 99 Гц побеждает в productionmiddle
- Типы профилей: CPU, память, off-CPU, mutex — какой когда братьmiddle
- Continuous profiling: always-on flame graphs с eBPF и корреляцией trace-idmiddle
- Как flame graph строится из сэмплов и как использовать его в productionmiddle
- Linux perf, внутренности eBPF, PGO и ограничения sampling''''аsenior
- Profiling в production: безопасность, war stories, OTel profiles и дизайн инфраструктурыsenior
- Debugging-воронка: SLO → RED → trace → profilejunior
- Архитектура OTel: один SDK, четыре сигнала, один wire-форматmiddle
- Экономия на observability: удерживаем затраты в пределах 5% inframiddle
- Масштаб, безопасность и ROI наблюдаемых системsenior
- Сначала профиль: измерь куда реально уходит времяjunior
- Закон Амдала и self-time: потолок любого ускорения, которое ты можешь выпуститьmiddle
- Измерительный цикл: микробенч, макробенч, prod-профиль, эффект наблюдателяmiddle
- Чтение флейм-графов: формы, профайлеры по языкам и 60-секундный сканmiddle
- Статистические baseline''''ы: почему один запуск — не измерениеmiddle
- История профайлеров и ловушки микробенчей: от Кнута до GWPsenior
- Hardware counters, профили холодного старта и безопасность профилейsenior
- Непрерывное профилирование в масштабе: затраты, CI-гейты, корреляция с трейсами и антипаттерныsenior
- Что делает путь горячим: симптом против причиныjunior
- Пять форм hotspot''''а: CPU, аллокации, кэш, лок, syscallmiddle
- Чтение parent и child chains: где применять правкуmiddle
- JIT deopt, цикл fix-and-verify и PR-time профилированиеmiddle
- Аппаратные счётчики и Intel TMA: диагностика подкатегорийsenior
- False sharing и горячие пути нативных мостовsenior
- Горячие пути в production: безопасность, хвостовая латентность и происхождение инструментовsenior
- Иерархия памяти: почему расстояние важнее числа операцийjunior
- Row-major vs column-major: порядок доступа и разрыв в 9xjunior
- Branch prediction: 10–30 циклов штрафа за неожиданный ifmiddle
- Hardware prefetcher, TLB и memory-level parallelismsenior
- Основы GC: за что рантайм берёт налогjunior
- Алгоритмы GC: поколенческая гипотеза, concurrent marking и write barriermiddle
- GC tradeoffs: пауза, throughput, память и давление аллокацийmiddle
- Настройка GC: пейсинг, форма кучи и наблюдаемость аллокацийmiddle
- Внутреннее устройство GC: tri-color инвариант, write barriers и глубокое погружение в рантаймыsenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- N+1: одна логическая операция, много round-trip''''овjunior
- Семейства фиксов: JOIN, IN, preload и DataLoadermiddle
- Обнаружение N+1: query logs, APM traces и CI gatesmiddle
- DataLoader: батчинг по дереву резолверовmiddle
- Кросс-протокольный N+1: HTTP fan-out и Redis MGETmiddle
- N+1 в масштабе: исчерпание пула, изменения планов и денормализацияsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- Что на самом деле стоит bundle: download, parse, compile, executejunior
- Core Web Vitals: LCP, INP и CLSmiddle
- Code splitting: route-level, component-level, vendor splittingmiddle
- Tree shaking и compression: удаляем то, что не используемmiddle
- Third-party scripts: тихий убийца бюджетаmiddle
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior