Синхронные методы в 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 полностью блокируется до завершения вычислений. В этот момент:
В реальной production-системе даже кратковременная блокировка event loop приводит к каскадным последствиям.
При использовании hashSync или compareSync
нагрузка на процессор резко возрастает. Если одновременно приходит
несколько запросов на регистрацию или авторизацию, каждый из них будет
синхронно блокировать поток.
Типичный сценарий:
bcrypt.hashSyncДаже при умеренной нагрузке это приводит к росту latency и снижению throughput.
bcrypt.js предоставляет две основные пары методов:
hash / hashSynccompare / 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 продолжает обслуживать входящие запросы.
Основной показатель здоровья Node.js приложения — задержка event loop. Синхронные операции увеличивают эту задержку напрямую.
Даже одна операция hashSync с высоким cost factor может
занимать десятки или сотни миллисекунд CPU времени. В этот период:
При масштабировании системы это превращается в эффект домино: один медленный экземпляр начинает деградировать весь кластер.
Синхронные bcrypt-операции создают поверхность для простого отказа в обслуживании.
Если злоумышленник начнёт массово отправлять запросы, вызывающие
hashSync или compareSync, сервер быстро уйдёт
в состояние:
Поскольку операция намеренно дорогая, её использование в синхронном режиме делает систему уязвимой к CPU-based DoS без необходимости сложных атак.
В некоторых проектах синхронные методы выбираются из-за кажущейся простоты. Однако при увеличении нагрузки проявляются следующие проблемы:
Время выполнения зависит от нагрузки CPU и конкуренции потоков. Это делает latency нестабильным.
Node.js не может параллельно обрабатывать другие запросы в том же процессе.
Каждый процесс становится узким местом, и горизонтальное масштабирование лишь частично компенсирует проблему.
bcrypt основан на Blowfish-ключевой деривации и многократных раундах шифрования. Параметр cost factor (например, 10, 12, 14) экспоненциально увеличивает вычислительную сложность:
2^{cost factor}
Рост значения cost factor увеличивает время хеширования нелинейно, что усиливает негативный эффект синхронных вызовов.
В асинхронной модели это контролируемо, потому что нагрузка распределяется и не блокирует основной поток. В синхронной — каждая операция полностью останавливает обработку событий.
Синхронные методы часто появляются в следующих случаях:
const hash = bcrypt.hashSync(password, 10);
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 секунды и более.
Асинхронные операции Node.js могут использовать thread pool libuv, который позволяет выполнять CPU-интенсивные задачи вне основного потока. Однако синхронные методы полностью обходят этот механизм и выполняются непосредственно в event loop.
Это ключевая причина, почему синхронные методы в bcrypt.js считаются антипаттерном в production.
Использование sync-методов приводит к необходимости компенсировать их недостатки на уровне инфраструктуры:
Все эти меры лишь маскируют проблему, но не устраняют её причину.
При резком увеличении трафика синхронные bcrypt-операции вызывают:
Это делает систему нестабильной даже при кратковременных всплесках нагрузки.
Асинхронный bcrypt позволяет эффективно использовать event loop и распределять нагрузку по времени. Синхронный — концентрирует нагрузку в одном моменте времени, создавая пики CPU.
В системах с высокой конкуренцией запросов это приводит к эффекту “залипания” процесса, когда сервер перестаёт восстанавливаться без перезапуска.
Наличие hashSync и compareSync в bcrypt.js
объясняется не их пригодностью для production, а удобством для:
В серверных приложениях с высокой нагрузкой их использование создаёт системный риск, связанный с блокировкой event loop и деградацией всей обработки запросов.