> Как обеспечить корректную обработку запросов при параллельном выполнении для одного пользователя (JavaScript)

Уровень: senior · Роль: frontend · Язык: JavaScript · Категория: Технические вопросы

Компании: QueenInteractiveGamesLtd

Стек: Node.js, JavaScript

> Пример ответа

Короткий ответ

Для корректной обработки запросов при параллельном выполнении для одного пользователя в Node.js нужно использовать механизмы синхронизации: очереди, mutex или atomic операции. Основные подходы - сериализация запросов через очередь (например, async-mutex), использование транзакций в БД и idempotency keys для повторных запросов. Важно учитывать, что Node.js однопоточен, но асинхронность создает race conditions.

Подробное объяснение

Проблема возникает, когда несколько запросов от одного пользователя обрабатываются конкурентно и изменяют общее состояние. В Node.js, несмотря на однопоточность event loop, асинхронные операции (I/O, таймеры) могут привести к race conditions. Например, два запроса на обновление баланса могут прочитать одинаковое значение и записать некорректный результат.

Основные решения:

  • Очереди (queues): сериализуют обработку запросов для одного пользователя. Каждый запрос попадает в очередь и выполняется последовательно.
  • Mutex/lock: блокирует ресурс на время обработки. В Node.js популярна библиотека async-mutex.
  • Atomic операции в БД: используйте $inc в MongoDB или UPDATE ... RETURNING в PostgreSQL.
  • Idempotency keys: клиент передает уникальный ключ, сервер проверяет, не обработан ли уже запрос с таким ключом.
  • Optimistic locking: версионирование данных - при обновлении проверяем, что версия не изменилась.

Выбор зависит от сценария: для критичных финансовых операций лучше комбинировать очередь и idempotency, для менее критичных - atomic операции в БД.

На практике

В реальных проектах часто используют комбинацию подходов:

  1. Middleware для сериализации: создаем middleware, которое для каждого userId ставит запросы в очередь. Это удобно для API с высокой нагрузкой.
  2. Redis-блокировки: для распределенных систем используем Redlock или SETNX.
  3. Database transactions: для операций, затрагивающих несколько документов/таблиц.
  4. Retry с exponential backoff: если блокировка не получена, повторяем запрос.

Важно помнить про timeout для блокировок, чтобы избежать deadlock. Также нужно корректно обрабатывать ошибки в очереди - если один запрос упал, очередь не должна блокироваться.

Пример кода

JAVASCRIPT
const { Mutex } = require('async-mutex');
const mutex = new Mutex();
async function handleUserRequest(userId, requestData) {
const release = await mutex.acquire();
try {
// Критическая секция
const user = await User.findById(userId);
user.balance += requestData.amount;
await user.save();
} finally {
release();
}
}

Более продвинутый вариант с очередью на userId:

JAVASCRIPT
const queues = new Map();
async function enqueue(userId, fn) {
if (!queues.has(userId)) {
queues.set(userId, Promise.resolve());
}
const previous = queues.get(userId);
const current = previous.then(fn, fn); // продолжаем даже при ошибке
queues.set(userId, current);
return current;
}
// Использование
app.post('/api/transfer', async (req, res) => {
const result = await enqueue(req.user.id, async () => {
// логика перевода
});
res.json(result);
});

Как отвечать на собеседовании

Начните с объяснения проблемы race conditions в Node.js. Затем перечислите основные подходы, упомяните trade-off каждого. Приведите пример из практики - как вы решали подобную задачу. Покажите понимание, что нет серебряной пули: для разных сценариев подходят разные решения. Упомяните про idempotency как важный паттерн для повторных запросов. Если спросят про распределенные системы, добавьте про Redis-блокировки и согласованность.

Что проверяет интервьюер

  • Понимание асинхронности Node.js и event loop
  • Знание race conditions и способов их предотвращения
  • Умение выбирать подходящий механизм синхронизации
  • Опыт работы с реальными кейсами (не только теория)
  • Понимание trade-off между производительностью и корректностью

Типичные ошибки

  • Предложение использовать только async/await без синхронизации - это не решает проблему
  • Использование глобального mutex для всех пользователей - убивает производительность
  • Игнорирование timeout для блокировок - возможен deadlock
  • Неправильная обработка ошибок в очереди - один упавший запрос блокирует всю очередь
  • Забывают про idempotency для повторных запросов при сетевых ошибках

> ГОТОВЫ К СЛЕДУЮЩЕМУ СОБЕСЕДОВАНИЮ?

Запустите тренировочную сессию с ИИ и получите детальную обратную связь, чтобы увереннее проходить реальные интервью