🔨 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% функциональности. Как отрезать ноги чтобы меньше жрать.

"В PG16 Hash прокачали, теперь можно"
Реальность: Прокачали скорость на 15%, но он все равно умеет только =. Это как научить мопса бегать быстрее — он все равно мопс, не борзая.

🐕 сравнение для тупых
Пес умеет: сидеть, лежать, дать лапу, принести мяч, охранять дом.
Hash индекс умеет: =.
Кого ты возьмешь домой? Собаню или одноразовый инструмент?

🎯 Практический чеклист
Используй Hash ТОЛЬКО если:

Таблица >100M строк
Колонка уникальная (UUID, токены, хэши)
Запросы ТОЛЬКО с =
Никогда не нужны: >, <, ORDER BY, LIKE
Проверил что B-tree реально тормозит

НЕ используй Hash если:

Хоть раз нужен диапазон
Хоть раз нужна сортировка
Таблица <100M строк (разница незаметна)
Колонка с дубликатами
Просто "хочу попробовать"

Рекомендация:
Используй B-tree
. Серьезно. Hash имеет смысл только на огромных таблицах с токенами/UUID где ты на 146% уверен что нужен только точный поиск.

P.S. Если после этой статьи ты пошел менять все индексы на Hash — ты не понял ничего. Перечитай часть про собак.