Опирается на правила:
PG-T-080…PG-T-093из PostgreSQL Style Guide → раздел Антипаттерны типов.
Сводный список того, что чаще всего встречается в продовых схемах и больше всего мешает потом. Каждый пункт — со ссылкой на развёрнутую статью.
1. varchar(255) по привычке
PG-T-080: наследие MySQL/Oracle.
В PostgreSQL text и varchar(n) хранятся одинаково. Длина — это CHECK, ничего больше.
→ Строковые типы
2. timestamp без TZ для бизнес-времени
PG-T-081: «локальное время непонятно где».
Через год никто не помнит, в какой зоне работал сервер. Заказ от 23:30 пятницы превращается в воскресенье после переезда на UTC-host.
→ Время и таймзоны
3. varchar(36) для UUID
PG-T-082: 16 vs 36+ байт, без валидации, чувствителен к регистру.
→ UUID и идентификаторы
4. float/real/double для денег
PG-T-083: 0.1 + 0.2 ≠ 0.3.
На длинной цепочке расчётов копятся погрешности, проявляются на сверке с банком.
→ Числа и точность
5. serial/bigserial в новой схеме
PG-T-084: устарел.
С PG10+ — GENERATED ALWAYS AS IDENTITY. Лучше связан с колонкой, корректно копируется при pg_dump.
→ Числа и точность
6. smallint 0/1 или char(1) Y/N вместо boolean
PG-T-085: boolean — 1 байт, типобезопасный, удобен в SQL.
→ Enum, boolean и перечисления
7. PG ENUM для часто-меняющегося списка
PG-T-086: удалить значение нативно невозможно. Атрибуты не привесишь.
Reference table если растёт / переименовывается / нужны атрибуты.
→ Enum, boolean и перечисления
8. JSONB как «гибкая схема» для основных полей
PG-T-087: если по полю фильтруют — это колонка.
JSONB — только полиморфные / опциональные / редко-используемые.
→ JSONB — когда оправдан, когда нет
9. Массив там, где должна быть таблица
PG-T-088: jsonb[] для order items — таблица, перевернутая на бок.
Массив — только скаляры без идентичности (теги, локали). Объекты — отдельная таблица с FK.
→ Массивы и range-типы
10. valid_from/valid_to вместо range
PG-T-089: race condition без EXCLUDE.
tstzrange / daterange + EXCLUDE USING gist (key WITH =, period WITH &&).
→ Массивы и range-типы
11. Тип money
PG-T-090: привязан к локали, нет кода валюты.
numeric(p, s) + currency char(3) (ISO 4217).
→ Числа и точность
12. LocalDateTime в Java для timestamptz
PG-T-091: теряет таймзону.
На UTC-сервере и MSK-ноутбуке — разные значения.
Instant или OffsetDateTime.
→ Время и таймзоны
13. Instant.now() напрямую в коде
PG-T-092: ломает детерминированность тестов.
DateTimeService (или аналог) с моком в integration tests.
→ Время и таймзоны
14. UUID v4 для PK
PG-T-093: субоптимально.
Случайные UUID разбрасывают вставки по btree — больше random IO, плохая упаковка страниц.
UUID v7 (timestamp-ordered).
→ UUID и идентификаторы
Сводный чек-лист на ревью схемы
Перед merge миграции:
- [ ] Все id —
bigint GENERATED ALWAYS AS IDENTITYилиuuid(v7). - [ ] Все «когда что-то произошло» —
timestamptz, неtimestamp. - [ ] Все деньги —
numeric(p, s), неfloat. - [ ] Все строки без бизнес-длины —
text, неvarchar(255). - [ ] UUID — тип
uuid, неvarchar(36). - [ ] Boolean —
boolean, неsmallint/char(1). - [ ] Перечисления, которые могут расти — reference table, не PG
ENUM. - [ ] JSONB — только полиморфные / опциональные атрибуты, не основные поля.
- [ ] Массивы — только скаляры без идентичности; объекты — отдельная таблица.
- [ ] Интервалы — range-типы с
EXCLUDEдля непересечения. - [ ] На Java —
Instant/OffsetDateTimeдляtimestamptz,enumдля перечислений. - [ ]
DateTimeServiceиUuidGeneratorвместо прямых вызовов. - [ ] FK обязательно с индексом.
- [ ] Функциональные индексы для
LOWER().
Чек-лист на ревью индексов
- [ ] Composite индексы — левый префикс соответствует частому WHERE.
- [ ] Range-условия — последние в composite.
- [ ] FK имеет индекс (PG не строит автоматически).
- [ ] UNIQUE через CONCURRENTLY в проде.
- [ ] Partial index если 90% строк не нужны.
- [ ]
LOWER(email)через functional index, не seq scan.
Чек-лист на ревью миграции
- [ ]
lock_timeoutв начале миграции. - [ ]
CREATE INDEX CONCURRENTLY+runInTransaction="false". - [ ]
SET NOT NULLчерезCHECK NOT VALID + VALIDATE. - [ ]
ADD FKчерезNOT VALID + VALIDATE. - [ ] Rename — expand-contract на 3+ релиза.
- [ ]
UPDATEмиллионов — backfill-job, не миграция. - [ ] N-1 совместимость с предыдущей версией кода.
Куда дальше
- PG → Антипаттерны типов — нормативные формулировки.
- Числа и точность
- Строковые типы
- Время и таймзоны
- UUID и идентификаторы
- Enum, boolean и перечисления
- JSONB
- Массивы и range-типы
- Composite-индексы
- Миграции без даунтайма