← назад к разделу

У 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, очередь тоже ограничивают.

Что почитать дальше