bcrypt.js реализует алгоритм bcrypt полностью на JavaScript и предоставляет асинхронные API для хеширования паролей. Несмотря на асинхронный интерфейс, важно учитывать, что криптографические операции остаются вычислительно тяжёлыми и при росте нагрузки начинают формировать узкое место в системе.
Асинхронность в bcrypt.js достигается через разбиение вычислений на
микрошаги с использованием
setImmediate/nextTick, однако это не устраняет
фундаментальную проблему: каждое хеширование требует значительного
CPU-времени. При высокой интенсивности запросов это приводит к
накоплению задач и росту задержек.
Сценарии, в которых bcrypt.js испытывает повышенную нагрузку:
Каждый вызов bcrypt.hash() или
bcrypt.compare() создаёт вычислительную задачу, которая
конкурирует за ресурсы event loop. При недостаточном контроле
параллелизма происходит деградация времени отклика API.
Несмотря на наличие callback- и Promise-интерфейсов, bcrypt.js не выполняет операции в отдельном потоке автоматически. Основные ограничения:
При увеличении числа параллельных вызовов происходит эффект очереди внутри самого event loop, но без контроля приоритизации и лимитов.
При росте нагрузки становится необходимым вынесение операций хеширования в управляемую очередь. Очередь задач позволяет:
Базовая модель включает:
Внутри Node.js часто применяется ограничение параллелизма через семафоры или очереди с concurrency control.
Пример использования очереди с ограничением параллельных задач:
import bcrypt fr om 'bcryptjs';
import PQueue from 'p-queue';
const queue = new PQueue({ concurrency: 4 });
function hashPassword(password) {
return queue.add(() => bcrypt.hash(password, 10));
}
Такая модель ограничивает число одновременно выполняемых хеширований, предотвращая перегрузку event loop.
При отсутствии очереди и ограничений наблюдается характерная деградация:
Особенно критично это проявляется при saltRounds выше
12, где стоимость одной операции возрастает экспоненциально.
Для систем с высокой нагрузкой применяется распределённая очередь задач. Общая схема:
Пример логики постановки задачи:
queue.add('hash-password', {
userId,
password
});
Воркеры:
import bcrypt from 'bcryptjs';
queue.process('hash-password', async (job) => {
const hash = await bcrypt.hash(job.data.password, 10);
await db.users.update({
id: job.data.userId,
passwordHash: hash
});
});
При промышленной эксплуатации часто применяется Redis-очередь, например через BullMQ. Это позволяет:
Структура обработки становится независимой от API-сервера.
Параметр saltRounds напрямую влияет на время выполнения
задачи и, следовательно, на поведение очереди.
T = 2^{r}
где:
Рост значения приводит к экспоненциальному увеличению времени обработки, что увеличивает длину очереди и задержки.
Практическое следствие:
In-process очереди полезны для малых и средних систем, но имеют ограничения:
Такие очереди эффективно работают только как механизм backpressure внутри одного инстанса.
Очередь выполняет роль буфера, предотвращающего перегрузку CPU:
Без backpressure система переходит в состояние неконтролируемой деградации.
Распространённый подход — выделение пула воркеров для CPU-операций. Каждый воркер выполняет ограниченное количество задач.
Пример концепции:
Это снижает нагрузку на event loop и стабилизирует latency.
Очереди часто сочетаются с ограничением частоты запросов:
Rate limiter отсекает лишние запросы, очередь распределяет допустимые.
Типичные проблемы систем без управления задачами:
Такая архитектура плохо масштабируется даже при умеренном трафике.
Время ответа становится функцией длины очереди:
Контроль очереди фактически эквивалентен контролю качества пользовательского опыта в системах аутентификации.