Какие production-риски есть у Fastify: blocking code, connection pooling, config, auth, observability, deploy или graceful shutdown?
Production-риски Fastify: синхронный блокирующий код в async handler, отсутствие connection pool настройки в плагинах БД, небезопасные defaults в JWT, пропущенные lifecycle hooks для observability и некорректный graceful shutdown без дренажа соединений.
Обзор production-рисков Fastify
Fastify — неблокирующий фреймворк, но он не защищает от ошибок, которые разработчик вносит сам. Рассмотрим каждую категорию риска с конкретными примерами и решениями.
1. Blocking code
Синхронные операции в async handler блокируют event loop Node.js целиком:
// ПЛОХО
app.get('/compute', async (req, reply) => {
const result = heavyCryptoSync(req.body.data); // блокирует ~200ms
return result;
});
// ХОРОШО — offload в worker thread
import { Worker } from 'worker_threads';
app.get('/compute', async (req, reply) => {
const result = await runInWorker('./heavy.js', req.body.data);
return result;
});
Инструмент диагностики: --prof флаг Node.js + node --prof-process, либо clinic.js doctor.
2. Connection pooling
Плагин @fastify/postgres использует pg pool. Дефолтный размер — 10 соединений, что мало для нагрузки:
await app.register(fastifyPostgres, {
connectionString: process.env.DATABASE_URL,
pool: {
max: 20, // под нагрузку
min: 2,
idleTimeoutMillis: 30_000,
connectionTimeoutMillis: 5_000,
},
});
Аналогично для Redis через @fastify/redis — настраивайте lazyConnect: true и обрабатывайте error события.
3. Config и секреты
Используйте @fastify/env с JSON Schema для валидации переменных окружения при старте:
await app.register(fastifyEnv, {
schema: Type.Object({
PORT: Type.Integer({ default: 3000 }),
JWT_SECRET: Type.String({ minLength: 32 }),
DATABASE_URL: Type.String({ format: 'uri' }),
}),
dotenv: true,
});
Если секрет не задан, приложение падает сразу — это лучше, чем runtime undefined.
4. Auth
@fastify/jwt по умолчанию не проверяет exp, если не передать { complete: true }. Используйте preHandler:
app.addHook('preHandler', async (req, reply) => {
try {
await req.jwtVerify(); // бросает если exp истёк
} catch (err) {
reply.code(401).send({ error: 'Unauthorized' });
}
});
Применяйте только к защищённым маршрутам через onRoute hook или отдельный plugin с prefix.
5. Observability
Fastify логирует через Pino. Добавьте reqId в каждый лог и интегрируйте с OpenTelemetry:
import { trace } from '@opentelemetry/api';
app.addHook('onRequest', async (req) => {
const span = trace.getActiveSpan();
span?.setAttribute('http.route', req.routerPath);
});
app.addHook('onResponse', async (req, reply) => {
req.log.info({
statusCode: reply.statusCode,
responseTime: reply.elapsedTime,
}, 'request completed');
});
6. Graceful Shutdown
const signals = ['SIGTERM', 'SIGINT'];
for (const signal of signals) {
process.on(signal, async () => {
app.log.info('Shutting down...');
await app.close(); // дренирует keep-alive соединения
process.exit(0);
});
}
app.close() вызывает хуки onClose плагинов — пул БД закрывается корректно.
Подводные камни
- Unhandled promise rejection в Fastify 5 завершает процесс — оберните все async handler в try/catch или используйте глобальный
setErrorHandler. reply.send()послеreply.hijack()вызывает исключение — частая ошибка при работе с WebSocket.- Pino async transport (worker thread) при резком завершении процесса теряет последние строки лога — используйте
pino.finalв обработчике сигналов. - JWT refresh token rotation без Redis blacklist приводит к невозможности инвалидировать токены при компрометации.
- Метрики Prometheus через
fastify-metricsпо умолчанию открыты на том же порту — нужен отдельный internal server или IP-whitelist. - При горизонтальном масштабировании rate-limit плагина (
@fastify/rate-limit) без Redis-стора лимиты считаются на каждый pod отдельно. - Database connection pool size × число pod не должно превышать max_connections PostgreSQL — иначе новые соединения получат ошибку.
- Keep-alive timeout Fastify (72s по умолчанию) должен быть меньше таймаута upstream load balancer — иначе 502 при idle соединениях.
What hurts your answer
- Говорить только о запуске Fastify, но не об эксплуатации
- Не упоминать observability, обновления, безопасность и rollback
- Описывать риски абстрактно, без способов их снижать
What they're listening for
- Видит production-риски Fastify
- Говорит про monitoring, rollout, rollback и безопасность
- Умеет ранжировать риски по вероятности и влиянию