5.5 KiB
ADR-001: Выбор бэкенд-стека
Дата: 2026-03-20 Статус: Accepted Автор: Architect Agent (Kin pipeline, BATON-001)
Контекст
Baton — минималистичное PWA приложение экстренного сигнала. Бэкенд выполняет три задачи:
- Принять POST /signal от 300-400 пользователей (возможны одновременные запросы)
- Сохранить сигнал в SQLite
- Отправить уведомление в Telegram-группу через Bot API
Проект требует простого деплоя на один VPS, без kubernetes, без сложной инфраструктуры. Команда имеет опыт работы с Python/FastAPI (используется в проекте Kin).
Рассматривались три стека: FastAPI (Python), Express/Fastify (Node.js), Go (net/http).
Варианты
Вариант A: FastAPI (Python 3.11+)
Плюсы:
- Знакомость команды (используется в Kin)
- asyncio нативно
- Pydantic — автоматическая валидация входных данных
aiosqliteилиsqlite3черезrun_in_executor- Быстрый старт разработки
Минусы:
- В 2-3x медленнее Go по RPS
- Docker образ ~200-400 MB (Python runtime + зависимости)
- bcrypt блокирует event loop — нужен
run_in_executor(решение #1004)
Вариант B: Express/Fastify (Node.js 20+)
Плюсы:
- Единый язык с фронтендом (vanilla JS)
better-sqlite3— синхронный, самый быстрый SQLite биндинг для Node.js- Fastify ~24% быстрее FastAPI по RPS в независимых тестах
- Docker образ ~200-300 MB
Минусы:
- Нет опыта работы в команде
better-sqlite3синхронный — блокирует event loop при долгих запросах (на практике приемлемо для simple INSERT)- Дополнительное переключение контекста (JS для фронта, JS для бека)
Вариант C: Go (net/http)
Плюсы:
- Компилируется в единый статический бинарь ~8-15 MB (простейший деплой)
- В 2-3x быстрее Python, ~2x быстрее Node.js
- Горутины — нативный concurrency без event loop ограничений
- Нет проблем с bcrypt (не блокирует горутины)
- Cross-compile:
GOARCH=amd64 GOOS=linux go build
Минусы:
- Нет опыта работы в команде
- Более длительный онбординг
modernc.org/sqlite(CGO-free): 10-100% медленнее нативного SQLite — компромисс для кросс-компиляции
Решение
Выбран Вариант A: FastAPI (Python 3.11+)
Обоснование
-
Знакомость команды — главный фактор для минимального проекта. FastAPI используется в Kin. Нет времени на онбординг в Go или Node.js для задачи, которая требует ~200 строк бэкенд-кода.
-
Производительности FastAPI достаточно. При нагрузке 300-400 одновременных запросов бутылочное горлышко — Telegram rate limit (20 сообщений/минуту в группу), а не скорость Python. SQLite WAL +
busy_timeout=5000справится с 400 одновременными INSERT за ~400 мс (решения #1002, #1005). -
Pydantic даёт бесплатную валидацию входных данных (user_id, timestamp, geo) без дополнительного кода.
-
Размер деплоя приемлем. ~300 MB Docker образ — не проблема для одного VPS сервиса.
-
Вариант B отклонён: нет опыта у команды, преимущество в скорости (+24%) несущественно при текущей нагрузке.
-
Вариант C отклонён: несмотря на превосходную производительность и минимальный деплой, отсутствие опыта в команде создаёт риск для проекта без аргументированной причины переходить на Go.
Последствия
- Использовать
aiosqliteдля async SQLite операций (илиsqlite3черезrun_in_executor) - bcrypt (если понадобится в будущем) — только через
run_in_executor(решение #1004) - SQLite WAL обязателен:
busy_timeout=5000,synchronous=NORMAL— вместе (решение #1005) - Агрегатор Telegram: реализовать в Python как background task (asyncio) или через простой in-memory буфер с
asyncio.sleep - requirements.txt:
fastapi,uvicorn[standard],aiosqlite,httpx(для Telegram API) - Переменные окружения:
BOT_TOKEN,CHAT_ID,DB_PATH— читать из.envчерезpython-dotenv