У Node нет бесконечной параллельности «бесплатно». За каждой операцией стоит ограниченный ресурс: поток libuv, сокет, соединение с базой, память или квота внешнего API.
Пул libuv
UV_THREADPOOL_SIZE задает размер нативного пула до запуска процесса. В него попадают поддерживаемые файловые операции, DNS через getaddrinfo, сжатие и криптография.
Если все потоки заняты pbkdf2, следующий вызов ждет. Увеличение пула иногда уменьшает очередь, но упирается в число ядер, память и цену переключений. JavaScript-вычисления оно не ускоряет.
Проверку делают под нагрузкой: измеряют время в очереди и CPU, а затем выбирают размер. Менять переменную нужно до старта Node, а не внутри работающего приложения.
Сокеты и агенты
HTTP Agent ограничивает соединения к зависимости. keepAlive позволяет переиспользовать сокеты, maxSockets и maxFreeSockets ограничивают общее число и число свободных соединений на origin.
Слишком большой лимит просто переносит очередь на сервер партнера или PostgreSQL. Слишком маленький заставляет запросы ждать сокет даже при свободной зависимости.
Таймаут соединения, таймаут ответа и отмена настраиваются вместе с лимитом: бесконечное ожидание занимает и сокет, и место в очереди приложения.
Семафор для своих операций
Когда API не дает подходящего пула, параллелизм ограничивают явным семафором. Разрешение получают перед операцией и возвращают в finally.
class Semaphore {
private available: number;
private readonly waiting: Array<() => void> = [];
constructor(limit: number) {
this.available = limit;
}
async acquire(): Promise<void> {
if (this.available > 0) {
this.available -= 1;
return;
}
await new Promise<void>(resolve => this.waiting.push(resolve));
}
release(): void {
const next = this.waiting.shift();
if (next) next();
else this.available += 1;
}
}
При ручной реализации важно не потерять разрешение при ошибке и ограничить саму очередь ожидания. Для production обычно берут проверенную библиотеку с такими же гарантиями.
Коротко
UV_THREADPOOL_SIZEвлияет на нативные задачи, не на JavaScript.- Пул расширяют после измерения очереди и CPU, а не по привычке.
- HTTP Agent и база требуют согласованных лимитов соединений.
- Таймауты не дают ожиданию занимать ресурсы бесконечно.
- Разрешение семафора возвращают в
finally, очередь тоже ограничивают.
Что почитать дальше
- Event-loop lag - отличие очереди нативного пула от блокировки V8.
- Таймауты и отмена - как освобождать сокеты и операции.