К блогу

PostgreSQL и Drizzle ORM

Основные группы таблиц в PostgreSQL

PostgreSQL — единственный источник правды в шаблоне. Пользователи, сессии, подписки, платежи, события аналитики, usage агентов, job queue pg-boss — всё в одной базе.

Схема описана декларативно в app/lib/db/schema.ts через Drizzle ORM. Таблицы, колонки, индексы, foreign keys и enum-ы — всё в TypeScript. При запросе db.select().from(users) вы получаете типизированный результат, опечатка в имени колонки — ошибка компиляции, а не undefined в runtime.

Структура логически разбита: таблицы better-auth (user, session, account, verification), бизнес-таблицы (subscriptions, payments), аналитика (analytics_events, rollup-таблицы), usage (agent_usage). При добавлении фичи сначала проектируйте таблицу здесь, потом пишите server-логику.

Подключение централизовано в db.server.ts. Модуль создаёт singleton-клиент с пулом соединений. Критическое правило: импортируйте db только из server-only кода — loaders, actions, *.server.ts. Никогда из компонентов, которые могут попасть в клиентский бандл.

Для локальной разработки достаточно npm run db:push. Drizzle сравнивает schema.ts с реальной БД и применяет diff.

npm run db:push
npm run db:migrate

Это быстро, но не оставляет истории миграций — подходит для прототипирования и тестовых стендов.

В production используйте versioned migrations: drizzle-kit generate создаёт SQL-файлы в папке migrations, drizzle-kit migrate применяет их по порядку. Так вы можете откатить деплой и накатить схему на новый инстанс предсказуемо.

Индексы уже расставлены на частых фильтрах: userId в событиях аналитики, expiresAt в подписках, createdAt для сортировок. При добавлении тяжёлых запросов в админке проверяйте EXPLAIN — Drizzle не волшебник, N+1 остаётся возможным.

Транзакции: для операций «списать квоту + записать usage + отправить событие» используйте db.transaction. usage.server.ts резервирует квоту атомарно, чтобы два параллельных запроса не превысили лимит из-за race condition.

Тестовая БД: integration-тесты в tests/integration используют отдельный DATABASE_URL или ту же базу с очисткой через seed helpers. Не гоняйте тесты против production. В CI PostgreSQL поднимается как service container в GitHub Actions.

pg-boss использует те же PostgreSQL для очереди задач: rollup аналитики, daily digest, продление подписок. Отдельный Redis для очереди не нужен — меньше moving parts при деплое.

При форке не храните бизнес-логику в триггерах и stored procedures без крайней необходимости. Шаблон держит логику в TypeScript — так проще тестировать и рефакторить. SQL — для схемы, индексов и редких оптимизаций.

Бэкапы: настройте ежедневный pg_dump или снапшоты на уровне облака. Таблица analytics_events растёт быстрее остальных — purge.server.ts чистит старые сырые события, но rollup остаётся навсегда.