Telegram-бот з PostgreSQL: як спроектувати БД, щоб не переробляти
Найчастіша помилка в Telegram-ботах — БД, спроектована на коліні. Через 6 місяців і 10k користувачів починаються повільні запити, дедлоки і переписування з нуля.
Базова схема, з якої ми починаємо
-- users CREATE TABLE users ( id BIGSERIAL PRIMARY KEY, telegram_id BIGINT UNIQUE NOT NULL, -- BIGINT! не INT username TEXT, first_name TEXT, last_name TEXT, language_code VARCHAR(10), is_bot BOOLEAN DEFAULT FALSE, is_blocked BOOLEAN DEFAULT FALSE, -- юзер заблокував бот created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_users_telegram_id ON users(telegram_id); -- messages (для аудиту і аналітики) CREATE TABLE messages ( id BIGSERIAL PRIMARY KEY, user_id BIGINT REFERENCES users(id), direction VARCHAR(3) CHECK (direction IN ('IN', 'OUT')), text TEXT, payload JSONB, -- ButtonClicks, callbacks created_at TIMESTAMPTZ DEFAULT NOW() ); CREATE INDEX idx_messages_user_created ON messages(user_id, created_at DESC); -- fsm_states (якщо не Redis) CREATE TABLE fsm_states ( user_id BIGINT PRIMARY KEY REFERENCES users(id), state TEXT, data JSONB DEFAULT '{{}}'::jsonb, updated_at TIMESTAMPTZ DEFAULT NOW() );5 типових помилок
- telegram_id як INT — після 2 млрд айдішників бот падає. Завжди BIGINT.
- Немає is_blocked — продовжуєте слати повідомлення, ловите 403 Forbidden, Telegram банить бот за спам.
- Немає індексу на (user_id, created_at) — пагінація історії стає Seq Scan через мільйони рядків.
- FSM-state у пам'яті — рестарт бота і всі юзери в підвішеному стані. Зберігайте в Redis або БД.
- Немає updated_at тригера — не знаєш, коли користувач востаннє писав. Робить аналітику неможливою.
Що додаємо для продакшну
- Партиціонування messages по місяцях (PostgreSQL 16 native partitioning)
- pg_trgm для повнотекстового пошуку по історії
- JSONB для callback_data — гнучкість + GIN-індекс
- Materialized views для дашборду адміна
- Бекапи pg_dump щодня + WAL-G у S3
Проектуємо БД та розробляємо ботів під ключ — /services/telegram-bots. Детально про стек — тут.
