🔨 Hash индексы: от "не используй никогда" до "ну ладно, иногда можно"
Hash индексы в PostgreSQL — как та бывшая, которая была токсичной, исправилась, но все равно никто не доверяет. И правильно делают в 95% случаев.
💀 История позора: PostgreSQL 9.6 и ниже
До версии 10 Hash индексы были говном:
Не логировались в WAL → крашнулась база = индекс сгнил
После сбоя: REINDEX вручную или Seq Scan
Не реплицировались на слейвы
Не работали с CONCURRENTLY
Вывод до 2017: Hash индексы = профессиональное самоубийство.
🐕 собака vs Hash индексы до PG10
пес помнит где его миска даже после того как отключили свет. Hash индекс до PG10 забывал все после сбоя питания. пес надежнее.
✅ PostgreSQL 16: Hash индексы выросли
Что изменилось с PG10:
WAL logging — выживают после краша
Репликация работает
CONCURRENTLY поддерживается
В PG16: оптимизация коллизий (+15% скорость)
Технически:
💰 Бенчмарки: Hash vs B-tree на миллиардах
Тестовая база:
1 миллиард строк
Колонка: UUID токены (уникальные)
Запросы: точные поиски WHERE token = '...'
Железо: 16CPU, 128GB RAM, NVMe
Результаты (1000 запросов, кэш прогрет):
B-tree:
Avg: 0.42ms
P95: 0.68ms
Index size: 42GB
Hash:
Avg: 0.38ms
P95: 0.52ms
Index size: 38GB
Разница: 10-12% в пользу Hash. Охуенно, правда? За эти 10% ты потерял:
Диапазонные запросы (не работают)
Сортировки (не работают)
Частичные совпадения (не работают)
🐕 B-tree vs Hash
B-tree — швейцарский нож: режет, открывает бутылки, подпиливает ногти.
Hash — штопор: открывает бутылки охуенно, но только бутылки.
Твой выбор: швейцарский нож за 100₽ или штопор за 90₽ который ничего кроме бутылок не умеет?
⚡ Когда Hash реально быстрее (спойлер: почти никогда)
Hash выигрывает ТОЛЬКО когда:
Таблица >100M строк (иначе разница незаметна)
Только точные поиски (=)
Колонка уникальная или почти уникальная (UUID, токены)
Никогда не нужны: диапазоны, сортировки, LIKE
💩 Отмазка | Реальность
"Hash быстрее, переделаю все индексы на Hash"
Реальность: Первый же запрос WHERE created_at > NOW() - INTERVAL '1 day' ляжет в Seq Scan. Продакшн лег, ты уволен.
"Hash экономит место"
Реальность: 38GB vs 42GB на таблице в 1TB. Экономия 10% места за потерю 90% функциональности.Как отрезать ноги чтобы меньше жрать.
"В PG16 Hash прокачали, теперь можно"
Реальность: Прокачали скорость на 15%, но он все равно умеет только =. Это как научить мопса бегать быстрее — он все равно мопс, не борзая.
🐕 сравнение для тупых
Пес умеет: сидеть, лежать, дать лапу, принести мяч, охранять дом.
Hash индекс умеет: =.
Кого ты возьмешь домой? Собаню или одноразовый инструмент?
🎯 Практический чеклист
Используй Hash ТОЛЬКО если:
✅ Таблица >100M строк
✅ Колонка уникальная (UUID, токены, хэши)
✅ Запросы ТОЛЬКО с =
✅ Никогда не нужны: >, <, ORDER BY, LIKE
✅ Проверил что B-tree реально тормозит
НЕ используй Hash если:
❌ Хоть раз нужен диапазон
❌ Хоть раз нужна сортировка
❌ Таблица <100M строк (разница незаметна)
❌ Колонка с дубликатами
❌ Просто "хочу попробовать"
Рекомендация:
Используй B-tree. Серьезно. Hash имеет смысл только на огромных таблицах с токенами/UUID где ты на 146% уверен что нужен только точный поиск.
P.S. Если после этой статьи ты пошел менять все индексы на Hash — ты не понял ничего. Перечитай часть про собак.
Hash индексы в PostgreSQL — как та бывшая, которая была токсичной, исправилась, но все равно никто не доверяет. И правильно делают в 95% случаев.
💀 История позора: PostgreSQL 9.6 и ниже
До версии 10 Hash индексы были говном:
Не логировались в WAL → крашнулась база = индекс сгнил
После сбоя: REINDEX вручную или Seq Scan
Не реплицировались на слейвы
Не работали с CONCURRENTLY
-- PostgreSQL 9.6:
CREATE INDEX idx_hash ON sessions USING HASH (token);
-- База упала → индекс INVALID
-- Продакшн лёг в 3 утра
Вывод до 2017: Hash индексы = профессиональное самоубийство.
🐕 собака vs Hash индексы до PG10
пес помнит где его миска даже после того как отключили свет. Hash индекс до PG10 забывал все после сбоя питания. пес надежнее.
✅ PostgreSQL 16: Hash индексы выросли
Что изменилось с PG10:
WAL logging — выживают после краша
Репликация работает
CONCURRENTLY поддерживается
В PG16: оптимизация коллизий (+15% скорость)
Технически:
CREATE INDEX idx_sessions_token ON sessions
USING HASH (session_token);
-- PG16:
-- - Логируется в WAL ✓
-- - Реплицируется ✓
-- - Не умирает при краше ✓
-- - Быстрее чем в PG15 на 15%
💰 Бенчмарки: Hash vs B-tree на миллиардах
Тестовая база:
1 миллиард строк
Колонка: UUID токены (уникальные)
Запросы: точные поиски WHERE token = '...'
Железо: 16CPU, 128GB RAM, NVMe
-- Создаем индексы:
CREATE INDEX idx_btree ON huge(token); -- B-tree: 42GB
CREATE INDEX idx_hash ON huge(token) USING HASH; -- Hash: 38GB
-- Запрос: точный поиск
SELECT * FROM huge WHERE token = 'cafebabe-1234-5678-90ab-cdef12345678';
Результаты (1000 запросов, кэш прогрет):
B-tree:
Avg: 0.42ms
P95: 0.68ms
Index size: 42GB
Hash:
Avg: 0.38ms
P95: 0.52ms
Index size: 38GB
Разница: 10-12% в пользу Hash. Охуенно, правда? За эти 10% ты потерял:
Диапазонные запросы (не работают)
Сортировки (не работают)
Частичные совпадения (не работают)
🐕 B-tree vs Hash
B-tree — швейцарский нож: режет, открывает бутылки, подпиливает ногти.
Hash — штопор: открывает бутылки охуенно, но только бутылки.
Твой выбор: швейцарский нож за 100₽ или штопор за 90₽ который ничего кроме бутылок не умеет?
⚡ Когда Hash реально быстрее (спойлер: почти никогда)
Hash выигрывает ТОЛЬКО когда:
Таблица >100M строк (иначе разница незаметна)
Только точные поиски (=)
Колонка уникальная или почти уникальная (UUID, токены)
Никогда не нужны: диапазоны, сортировки, LIKE
-- Идеальный кейс:
CREATE TABLE sessions (
session_id BIGSERIAL PRIMARY KEY,
token UUID UNIQUE NOT NULL,
user_id BIGINT,
created_at TIMESTAMPTZ
);
-- Hash для токенов (только = поиск):
CREATE INDEX idx_token_hash ON sessions USING HASH (token);
-- B-tree для остального:
CREATE INDEX idx_user_created ON sessions(user_id, created_at);
Реальная статистика использования:
SELECT
indexrelname,
idx_scan,
idx_tup_read
FROM pg_stat_user_indexes
WHERE indexrelname LIKE '%hash%';
-- У 99% проектов: idx_scan = 0
-- Потому что забыли или побоялись
💩 Отмазка | Реальность
"Hash быстрее, переделаю все индексы на Hash"
Реальность: Первый же запрос WHERE created_at > NOW() - INTERVAL '1 day' ляжет в Seq Scan. Продакшн лег, ты уволен.
"Hash экономит место"
Реальность: 38GB vs 42GB на таблице в 1TB. Экономия 10% места за потерю 90% функциональности.
Реальность: Прокачали скорость на 15%, но он все равно умеет только =. Это как научить мопса бегать быстрее — он все равно мопс, не борзая.
🐕 сравнение для тупых
Пес умеет: сидеть, лежать, дать лапу, принести мяч, охранять дом.
Hash индекс умеет: =.
Кого ты возьмешь домой? Собаню или одноразовый инструмент?
🎯 Практический чеклист
Используй Hash ТОЛЬКО если:
✅ Таблица >100M строк
✅ Колонка уникальная (UUID, токены, хэши)
✅ Запросы ТОЛЬКО с =
✅ Никогда не нужны: >, <, ORDER BY, LIKE
✅ Проверил что B-tree реально тормозит
НЕ используй Hash если:
❌ Хоть раз нужен диапазон
❌ Хоть раз нужна сортировка
❌ Таблица <100M строк (разница незаметна)
❌ Колонка с дубликатами
❌ Просто "хочу попробовать"
Рекомендация:
Используй B-tree. Серьезно. Hash имеет смысл только на огромных таблицах с токенами/UUID где ты на 146% уверен что нужен только точный поиск.
P.S. Если после этой статьи ты пошел менять все индексы на Hash — ты не понял ничего. Перечитай часть про собак.