Почему синхронные методы опасны в production

Синхронные методы в bcrypt.js создают блокирующее поведение, которое напрямую конфликтует с моделью работы Node.js и приводит к деградации производительности в production-среде.

Node.js построен вокруг однопоточной модели выполнения JavaScript-кода. Основной поток событий (event loop) отвечает за обработку всех входящих запросов, таймеров, I/O операций и колбэков. Любая операция, которая занимает процессорное время и не передаёт управление обратно event loop, блокирует обработку других задач.

Хеширование паролей с использованием bcrypt относится к CPU-интенсивным операциям. Алгоритм bcrypt специально разработан так, чтобы быть вычислительно дорогим и замедлять атаки перебора паролей. Это достигается через настройку “cost factor”, увеличивающего количество итераций.

Когда используется синхронная версия bcrypt, например:

const bcrypt = require('bcryptjs');

const hash = bcrypt.hashSync('password123', 10);

Node.js полностью блокируется до завершения вычислений. В этот момент:

  • не обрабатываются HTTP-запросы
  • не выполняются колбэки таймеров
  • не обслуживаются WebSocket-сообщения
  • останавливается обработка очереди событий

Почему это критично в production

В реальной production-системе даже кратковременная блокировка event loop приводит к каскадным последствиям.

При использовании hashSync или compareSync нагрузка на процессор резко возрастает. Если одновременно приходит несколько запросов на регистрацию или авторизацию, каждый из них будет синхронно блокировать поток.

Типичный сценарий:

  1. Пользователь отправляет запрос на регистрацию
  2. Сервер выполняет bcrypt.hashSync
  3. В это время другие запросы стоят в очереди
  4. Время ответа для всех клиентов увеличивается
  5. При росте нагрузки сервер перестаёт отвечать вовремя

Даже при умеренной нагрузке это приводит к росту latency и снижению throughput.

Сравнение синхронного и асинхронного подхода

bcrypt.js предоставляет две основные пары методов:

  • hash / hashSync
  • compare / compareSync

Асинхронные версии используют либо внутренний thread pool, либо колбэки/Promise (в зависимости от обёртки), позволяя не блокировать event loop.

Пример асинхронного подхода:

const bcrypt = require('bcryptjs');

bcrypt.hash('password123', 10, (err, hash) => {
  if (err) throw err;
  console.log(hash);
});

или через Promise:

const hash = await bcrypt.hash('password123', 10);

В этом случае тяжёлая операция выполняется вне основного потока JavaScript, а event loop продолжает обслуживать входящие запросы.

Влияние на event loop latency

Основной показатель здоровья Node.js приложения — задержка event loop. Синхронные операции увеличивают эту задержку напрямую.

Даже одна операция hashSync с высоким cost factor может занимать десятки или сотни миллисекунд CPU времени. В этот период:

  • увеличивается lag всех запросов
  • падает RPS (requests per second)
  • возникают таймауты на уровне балансировщика нагрузки

При масштабировании системы это превращается в эффект домино: один медленный экземпляр начинает деградировать весь кластер.

Угроза отказа в обслуживании (DoS)

Синхронные bcrypt-операции создают поверхность для простого отказа в обслуживании.

Если злоумышленник начнёт массово отправлять запросы, вызывающие hashSync или compareSync, сервер быстро уйдёт в состояние:

  • 100% загрузки CPU
  • отсутствие обработки новых соединений
  • рост очередей запросов

Поскольку операция намеренно дорогая, её использование в синхронном режиме делает систему уязвимой к CPU-based DoS без необходимости сложных атак.

Ошибки масштабирования при использовании sync-методов

В некоторых проектах синхронные методы выбираются из-за кажущейся простоты. Однако при увеличении нагрузки проявляются следующие проблемы:

Непредсказуемое время ответа

Время выполнения зависит от нагрузки CPU и конкуренции потоков. Это делает latency нестабильным.

Блокировка всего процесса

Node.js не может параллельно обрабатывать другие запросы в том же процессе.

Невозможность эффективного масштабирования без увеличения количества инстансов

Каждый процесс становится узким местом, и горизонтальное масштабирование лишь частично компенсирует проблему.

Внутренние механизмы bcrypt и стоимость операций

bcrypt основан на Blowfish-ключевой деривации и многократных раундах шифрования. Параметр cost factor (например, 10, 12, 14) экспоненциально увеличивает вычислительную сложность:

2^{cost factor}

Рост значения cost factor увеличивает время хеширования нелинейно, что усиливает негативный эффект синхронных вызовов.

В асинхронной модели это контролируемо, потому что нагрузка распределяется и не блокирует основной поток. В синхронной — каждая операция полностью останавливает обработку событий.

Типичные ошибки в кодовых базах

Синхронные методы часто появляются в следующих случаях:

Быстрые прототипы

const hash = bcrypt.hashSync(password, 10);

Middleware без учёта нагрузки

app.post('/register', (req, res) => {
  const hash = bcrypt.hashSync(req.body.password, 10);
  res.send({ hash });
});

Проверка пароля в логине

if (bcrypt.compareSync(password, user.hash)) {
  // логика входа
}

Каждый из этих примеров становится критической точкой деградации при росте нагрузки.

Поведение при конкурентных запросах

В условиях параллельных запросов синхронный bcrypt создаёт очередь блокировок на уровне event loop. Это приводит к линейному увеличению времени обработки каждого следующего запроса.

Если один запрос занимает 200 мс CPU времени, а приходит 10 запросов одновременно, итоговая задержка для последнего может превышать 2 секунды и более.

Роль libuv thread pool и ограничения

Асинхронные операции Node.js могут использовать thread pool libuv, который позволяет выполнять CPU-интенсивные задачи вне основного потока. Однако синхронные методы полностью обходят этот механизм и выполняются непосредственно в event loop.

Это ключевая причина, почему синхронные методы в bcrypt.js считаются антипаттерном в production.

Практическое влияние на архитектуру

Использование sync-методов приводит к необходимости компенсировать их недостатки на уровне инфраструктуры:

  • увеличение количества инстансов
  • агрессивный load balancing
  • ограничение RPS на входе
  • введение очередей запросов

Все эти меры лишь маскируют проблему, но не устраняют её причину.

Поведение при пиковых нагрузках

При резком увеличении трафика синхронные bcrypt-операции вызывают:

  • рост CPU usage до 100%
  • падение throughput до минимальных значений
  • увеличение response time в десятки раз
  • возможные рестарты процессов из-за watchdog механизмов

Это делает систему нестабильной даже при кратковременных всплесках нагрузки.

Разница в модели масштабирования

Асинхронный bcrypt позволяет эффективно использовать event loop и распределять нагрузку по времени. Синхронный — концентрирует нагрузку в одном моменте времени, создавая пики CPU.

В системах с высокой конкуренцией запросов это приводит к эффекту “залипания” процесса, когда сервер перестаёт восстанавливаться без перезапуска.

Причины, по которым sync-методы остаются в API

Наличие hashSync и compareSync в bcrypt.js объясняется не их пригодностью для production, а удобством для:

  • скриптов миграции
  • тестовых окружений
  • однопоточных CLI утилит
  • небольших локальных приложений

В серверных приложениях с высокой нагрузкой их использование создаёт системный риск, связанный с блокировкой event loop и деградацией всей обработки запросов.