Последний раз, когда я разбирал скачок latency в 400% на одном из warehouse-проектов, виновником оказался синхронный инвентаризационный апдейт. Проблема классическая — пока один сервис честно блокировал строки в SQL, другие уже успевали отправить клиенту устаревшие данные о наличии товара.
Почему event-driven — не модное слово, а necessity
Нагрузка в 10K+ inventory-ивентов в час — это не про «давайте поставим Redis и забудем». Три главных pain point в таких системах:
- Duplicate events: два сканера штрихкодов отправили одинаковый ивент
- Visibility lag: пока данные идут через цепочку сервисов, кладовщик видит устаревший баланс
- Cascading failures: падение одного сервиса не должно блокировать прием новых поставок
// Типичный антипаттерн (но 80% кода в дикой природе)
app.post('/inventory', async (req, res) => {
await db.transaction(async (tx) => {
await tx.update('warehouse_balance', ...);
await tx.insert('audit_log', ...);
await externalERP.sync();
}); // 3+ секунд блокировки
});
Event sourcing на минималках
Вместо синхронного апдейта всех систем:
- Принимаем ивент и сразу кладем в SQS/SNS
- Отдельный воркер обрабатывает с идемпотентностью:
const handler = async (event) => {
if (await dynamodb.get({id: event.dedupeId})) {
return; // уже обработано
}
await Promise.all([
updateStock(event),
notifyWarehouseUI(event),
syncSlowERP(event) // даже если упадет - повторим позже
]);
await dynamodb.put({id: event.dedupeId});
};
- S3 как source of truth — все ивенты пишем в партиционированный JSON (год/месяц/день/час)
Чего не хватает в Node.js из коробки
- Нет нормального batch processing — приходится самому собирать пачки через
p-queueили аналоги - Слабовато с backpressure — если RabbitMQ/SQS завалит воркеры сообщениями, процесс умрет без proper circuit breaker
- Мониторинг — без метрик в CloudWatch вы даже не поймете, в какой момент началась деградация
На практике приходится допиливать:
- Idempotency keys для всех мутаций
- Dead letter queues для «битых» событий
- Windowed aggregation чтобы не апдейтить баланс после каждого чиха
Когда асинхронность больно бьет по UX
Главный trade-off такого подхода — eventual consistency. Пока все воркеры отработают:
- Менеджер видит «старый» остаток в отчете
- Клиент может заказать товар, которого уже нет
- Финансовые системы получают данные с лагом
Наш workaround — hybrid подход:
- Cache-aside для UI: при любом ивенте сразу инвалидируем кеш
- Synthetic events: если ERP не ответил за 5 секунд — создаем фоновый retry
- Priority lanes: события от сканеров обрабатываем в отдельной high-priority очереди
Если ваш CTO говорит «нужно 100% consistency» — покажите ему CAP-теорему и счет за Aurora Global Database. В реальном мире 500ms lag — приемлемая плата за отказоустойчивость.
P.S. Для особо параноидальных — пробуйте Rust+Tokio вместо Node.js, но готовьтесь к 3x разработки.