Что реально происходит внутри single-instance n8n под нагрузкой
Весь n8n в single-instance режиме живёт в одном процессе Node.js. Этот процесс обрабатывает входящие вебхуки, крутит триггеры по расписанию, рисует UI и одновременно выполняет сами воркфлоу. Никакого разделения нет. Всё сидит в одном event loop.
При низкой нагрузке это работает нормально. Проблемы начинаются, когда воркфлоу запускаются параллельно.
Node.js однопоточный по природе, и когда несколько воркфлоу стартуют одновременно, они не работают параллельно в привычном смысле. Они чередуются: каждый отдаёт управление event loop'у на время ожидания I/O, а потом забирает его обратно. Пока нагрузка небольшая, переключения почти незаметны. Но после определённого порога latency начинает расти нелинейно: удвоение числа одновременных воркфлоу не удваивает время ответа, оно его утраивает или хуже.
В реальной жизни воркфлоу делают HTTP-запросы во внешние API, вызывают LLM, ходят в базы данных. Синтетические бенчмарки, где каждый шаг занимает микросекунды, не отражают эту картину. Пока внешний вызов висит в ожидании ответа, event loop формально свободен, но при накоплении таких ожиданий обработка колбэков начинает давить на очередь. Реальный практический потолок по concurrent executions до заметной деградации зависит от того, что именно происходит внутри воркфлоу, и может быть существенно ниже, чем показывают синтетические тесты.
Смотреть надо на P95, а не на P50. P50 (медиана) на насыщенном event loop держится относительно долго. А P95 начинает уходить вверх гораздо раньше: сначала 2x от нормы, потом 5x, и это прямой симптом того, что очередь событий начала накапливать задержки. Если у тебя P50=800ms выглядит приемлемо, а P95=12s, то система уже не справляется. Просто большинство запросов ещё успевает проскочить до того, как очередь окончательно забивается.
Отдельная история с HTTP timeout'ами. Представь воркфлоу, который вызывает внешний API с таймаутом в 30 секунд, и этот API завис. Воркфлоу не завершается 30 секунд. Все остальные воркфлоу, которые запустились за это время, встают в очередь на обработку своих колбэков. Если таких зависших воркфлоу накопилось несколько, очередь растёт, и даже быстрые воркфлоу начинают ждать. Один безобидный HTTP-вызов с неразумным таймаутом буквально тормозит весь инстанс.
Это не баг n8n. Это архитектурное ограничение single-instance Node.js под нагрузкой, и оно никуда не денется, сколько бы оперативной памяти ты ни добавил.

Один процесс Node.js обрабатывает все воркфлоу последовательно, и при нагрузке 10+ одновременных запусков event loop начинает «захлёбываться».
Три сценария, при которых single-instance падает в продакшне
Я видел это достаточно раз, чтобы перестать удивляться. n8n работает неделями без сбоев, а потом прилетает одновременно несколько нагрузок, и инстанс просто молча умирает.
Сценарий первый: пик из двух источников сразу. Ночной batch по расписанию ещё не закончился, а в 9:00 пользователи начинают дёргать Slack и Telegram, которые шлют webhook-триггеры. Оба потока приходят в один и тот же процесс. На VPS с 2 vCPU и 4 GB RAM, которые комьюнити рекомендует как стартовую конфигурацию в 2026, это работает нормально при небольшом количестве одновременных воркфлоу. При росте параллельных исполнений начинается деградация: сначала растут latency, потом Node.js event loop просто перестаёт успевать, и входящие запросы начинают отваливаться по таймауту.
Сценарий второй: один медленный воркфлоу блокирует всю очередь. LLM-агент отправляет запрос в OpenAI и ждёт. 30 секунд, иногда 45. В single-instance режиме без queue mode n8n обрабатывает всё в одном потоке выполнений, и за это время в памяти накапливаются ожидающие запросы. Когда GPT наконец отвечает, система пытается разгрести накопившееся разом. Это второй удар по CPU. При достаточном количестве одновременных инстанций воркфлоу, живущих в RAM одновременно, процесс может упасть с OOM. Именно эта проблема типична для AI-ассистентов поддержки клиентов на n8n, где каждый входящий запрос порождает цепочку LLM-вызовов.
Сценарий третий: деплой в неудачный момент. Стандартный сценарий обновления через docker pull и docker restart убивает контейнер немедленно. Всё, что выполнялось в этот момент, просто исчезает. Не failed, не retried. Исчезает из логов, как будто запуска не было. Без queue mode нет механизма, который бы зафиксировал состояние воркфлоу перед остановкой воркера и передал его дальше. Если у тебя в этот момент шёл финансовый отчёт или отправка уведомлений, ты узнаешь об этом только от пользователей.
Суть проблемы в архитектуре: single-instance n8n не разделяет понятия "принять задачу" и "выполнить задачу". Это один и тот же процесс. Любой сбой, даже плановый, убивает обе функции одновременно.

Типичная история: один «жирный» воркфлоу с HTTP-запросом к медленному API убивает всю очередь на несколько минут.
Что такое queue mode и как он меняет архитектуру
По умолчанию n8n запускается в режиме, где один процесс делает всё: принимает webhook-и, ведёт расписание, выполняет воркфлоу. Это нормально для тестирования, но при нагрузке этот же процесс начинает тормозить UI, пропускать триггеры и вешаться на тяжёлых цепочках. Queue mode разрывает этот монолит на части.
Идея простая. Main-инстанс берёт на себя только координацию: слушает webhook-и, следит за расписаниями, принимает решение о запуске воркфлоу. Но сам выполнять его не идёт. Вместо этого он кладёт задачу в очередь Redis (n8n использует BullMQ). Воркеры, запущенные отдельно, тянут задачи из очереди и выполняют их независимо.
Это не просто разделение ролей. Main в queue mode физически не запускает execution-логику. Он остаётся отзывчивым для UI и входящих запросов, даже если все воркеры прямо сейчас заняты по горло. Вот это принципиальное отличие от дефолтной установки, где один тяжёлый воркфлоу может заморозить весь интерфейс.
Передача задачи воркеру атомарна. BullMQ помечает задачу как "active" в момент, когда воркер её забрал. Если воркер крашнется в середине выполнения, Redis это видит через heartbeat, и задача возвращается в очередь. Другой воркер подхватит её. Без потери.
Горизонтальное масштабирование в этой схеме сводится к одной команде:
docker-compose up --scale worker=3
Три воркера, три параллельных потока выполнения, один Redis, один main. Столько воркеров, сколько нужно. Добавить пятый так же просто, как третий.
Queue mode появился в n8n где-то в районе версии 0.18x. В 2026 году это уже зрелая функция, и рекомендованный стек под неё: Redis 7+. Более старые версии Redis технически работают, но BullMQ тащит за собой зависимости, которые на Redis 6 начинают давать странные edge case-и под нагрузкой. Лучше не экспериментировать.

Main-инстанс принимает задачи и складывает их в BullMQ, а worker-инстансы разбирают очередь независимо друг от друга.
Минимальная production-архитектура с queue mode
Queue mode требует четыре обязательных компонента: main-инстанс, воркеры, Redis и реляционная база. Убери любой из них и система либо не запустится, либо будет падать под нагрузкой.
Начну с базы. SQLite здесь не работает физически: несколько процессов пишут в один файл, получают блокировки, воркеры зависают и теряют задачи. Нужен PostgreSQL. Redis 7 держит очередь BullMQ, main пушит туда задачи, воркеры их тянут и выполняют. Все процессы видят один и тот же PostgreSQL и один Redis, иначе состояние расходится.
Минимальный вариант для одной VPS: 4 vCPU и 8 GB RAM. При таком раскладе main + 2 workers + Redis + Postgres умещаются без свопа. Если меньше, workers начнут конкурировать за память с базой, и ты получишь OOM в самый неподходящий момент.
Вот docker-compose, который реально поднимает этот стек:
version: '3.8'
services:
n8n-main:
image: n8nio/n8n
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
- N8N_DISABLE_PRODUCTION_MAIN_PROCESS=true
ports:
- '5678:5678'
depends_on:
- redis
- postgres
n8n-worker:
image: n8nio/n8n
command: worker
environment:
- EXECUTIONS_MODE=queue
- QUEUE_BULL_REDIS_HOST=redis
- DB_TYPE=postgresdb
- DB_POSTGRESDB_HOST=postgres
scale: 2
depends_on:
- redis
- postgres
redis:
image: redis:7-alpine
postgres:
image: postgres:16
environment:
- POSTGRES_DB=n8n
- POSTGRES_USER=n8n
- POSTGRES_PASSWORD=changeme
Переменная N8N_DISABLE_PRODUCTION_MAIN_PROCESS=true на main-инстансе принципиальна. Без неё main начнёт сам выполнять воркфлоу параллельно с воркерами, и ты получишь дублирующиеся выполнения, которые потом очень неприятно отлаживать. Main при такой конфигурации занимается только UI, API и постановкой задач в очередь.
scale: 2 в compose-файле поднимает два воркера сразу. Горизонтально масштабируется просто: docker compose up --scale n8n-worker=5, и пять процессов начинают параллельно тянуть задачи из одной очереди.
Перед nginx или Traefik ставят только main. Воркеры не имеют HTTP-порта наружу, им он не нужен: они общаются с Redis и Postgres, больше ни с чем.
По отказоустойчивости: если хочешь, чтобы падение main не останавливало выполнение текущих задач, main выносят на отдельный лёгкий сервер (2 vCPU, 2 GB хватает), а воркеры масштабируют на отдельных машинах по нагрузке. Но для большинства проектов в 2026 году один VPS на 8 GB держит всё это без проблем до нескольких тысяч выполнений в сутки.
Redis в этой схеме не хранит ничего постоянного, кроме состояния очереди. Потеря Redis означает потерю задач, которые были в очереди в момент падения. Так что redis:7-alpine без volume и persistence подходит только для разработки. В prod добавь хотя бы appendonly yes и volume для данных.
Как читать метрики: P50, P95 и P99 latency для n8n
Начну с простого факта: большинство команд смотрят на среднее время выполнения воркфлоу и на этом останавливаются. Среднее врёт. Оно прячет проблему за усреднением хороших запросов и плохих.
Перцентили говорят честнее.
P50 (медиана) показывает, как ведёт себя типичный воркфлоу. Для n8n-воркфлоу без внешних HTTP-вызовов и тяжёлых трансформаций P50 обычно укладывается в 200-500ms. Если у тебя P50 уже за секунду при простых цепочках узлов, копай в сторону самого n8n-процесса: память, количество активных воркфлоу, версия.
P95 интереснее. Это значение, ниже которого укладываются 95% запросов. Те 5%, что не уложились, и есть твоя реальная зона риска. Именно на P95 виден эффект насыщения event loop: когда длинный воркфлоу занимает поток, короткие задачи встают в очередь и ждут. В single-instance n8n при смешанной нагрузке (быстрые webhook-хэндлеры рядом с тяжёлыми ETL-воркфлоу) P95 начинает ползти вверх задолго до того, как система "упадёт". Особенно это заметно, когда рядом работает воронка лидогенерации на n8n: пиковые webhook-и от форм и CRM-событий создают именно такую смешанную нагрузку.
P99 ловит аномалии. Зависший HTTP-клиент, который ждёт таймаута 30 секунд. Внешний API, который отвечает медленно именно по ночам. Memory spike в момент обработки большого JSON. В single-instance n8n разрыв между P50 и P99 может быть значительным. Это не баг, это архитектура: один процесс, один event loop, конкуренция за ресурсы.
Переход на queue mode меняет картину. Долгие воркфлоу уходят к воркерам и перестают блокировать обработку коротких. P95 и P99 падают, причём P99 падает сильнее, потому что исчезает главный источник аномалий: блокировка основного процесса.
Собирать всё это можно без дополнительных агентов. n8n с версии 0.214 отдаёт метрики в Prometheus-формате через /metrics endpoint. Включается переменной окружения N8N_METRICS=true. Дальше стандартно: Prometheus scrape каждые 15 секунд, Grafana поверх с гистограммой n8n_workflow_execution_duration_seconds.
Ориентир для продакшна такой: если P95 заметно выше P50 и разрыв стабильно растёт несколько дней, архитектура требует пересмотра. Не "посмотрим на следующей неделе", а прямо сейчас. Такой разрыв означает, что поведение системы под нагрузкой непредсказуемо, и пользователи это уже чувствуют, даже если мониторинг ещё не красный.

P99-латентность в queue mode стабилизируется на уровне 2-3 секунд там, где одиночный инстанс уходит за 30 секунд при пиковой нагрузке.
N8N_DISABLE_PRODUCTION_MAIN_PROCESS: почему этот флаг критичен
Queue mode включён, Redis подключён, workers запущены. Кажется, всё готово. Но есть одна деталь, которую легко пропустить в документации, и именно она ломает весь смысл горизонтального масштабирования.
По умолчанию main-инстанс в queue mode продолжает выполнять часть воркфлоу самостоятельно. Не все, но часть. Он принимает триггеры, ставит задачи в очередь и одновременно сам же подхватывает некоторые из них. В итоге при пиковой нагрузке main занят исполнением тяжёлых задач, UI начинает тормозить, вебхуки отвечают с задержкой. В комьюнити n8n это одна из самых частых жалоб: "настроил workers, но main всё равно умирает под нагрузкой".
N8N_DISABLE_PRODUCTION_MAIN_PROCESS=true решает именно это. Флаг запрещает main-инстансу выполнять воркфлоу. Совсем. Он только принимает входящие триггеры и публикует задачи в Redis, а дальше его работа закончена.
# .env для main-инстанса
EXECUTIONS_MODE=queue
N8N_DISABLE_PRODUCTION_MAIN_PROCESS=true
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_REDIS_PORT=6379
# Без этой переменной main будет частично
# выполнять воркфлоу и конкурировать с workers
После этого main можно держать на маленькой машине: 2 vCPU и 2 GB RAM хватает для обслуживания UI и обработки триггеров даже при нескольких сотнях активных воркфлоу. Всё тяжёлое уходит на workers, которых можно масштабировать горизонтально под нагрузку.
Но есть жёсткое условие. Включать флаг можно только когда в системе есть хотя бы один живой worker. Если поднять main с этим флагом и без workers, воркфлоу просто встанут в очередь и не выполнятся никогда. Задачи будут накапливаться в Redis, никаких ошибок в интерфейсе не появится, и это создаёт иллюзию работающей системы.
Простая проверка перед включением: docker ps | grep worker или проверка списка consumers в Redis через redis-cli CLIENT LIST. Убедись, что worker живой, и только потом перезапускай main с новым флагом.
Настройка concurrency на уровне воркера
По умолчанию каждый воркер берёт 10 задач одновременно. Это значение задаётся переменной QUEUE_WORKER_CONCURRENCY и в большинстве случаев требует ручной коррекции под конкретный тип нагрузки.
Принцип простой: нагрузка бывает двух видов, и они требуют противоположных настроек.
Для воркфлоу с LLM-вызовами, Webhook-ожиданиями или любыми другими долгими HTTP-запросами concurrency можно смело поднимать до 25-50. Воркер просто сидит и ждёт ответа от OpenAI или Anthropic, CPU в это время почти не занят. Процесс Node.js отлично справляется с таким количеством параллельных "ожидалок" через event loop.
Другая история с CPU-интенсивными задачами: трансформация больших JSON-объектов, парсинг файлов, тяжёлая шаблонизация. Здесь concurrency выше числа vCPU даст не прирост, а деградацию. Если у воркера 4 vCPU, ставь 4, максимум 6.
Блокировки и TTL
Отдельная тема: QUEUE_BULL_SETTINGS_LOCK_DURATION. Это TTL блокировки задачи в миллисекундах. По умолчанию 30000ms (30 секунд).
Механика работает так: воркер забирает задачу и ставит на неё блокировку в Redis. Пока блокировка жива, другие воркеры задачу не трогают. Если воркер зависает и перестаёт слать heartbeat, Redis через LOCK_DURATION возвращает задачу в очередь. Это встроенная защита от потери задач при падении инстанса.
Проблема возникает, когда воркфлоу реально длится дольше 30 секунд, например при цепочке LLM-вызовов или обработке большого файла. Воркер должен периодически обновлять блокировку, и тут появляется QUEUE_BULL_SETTINGS_LOCK_RENEW_TIME. Это интервал, с которым воркер продлевает TTL.
Жёсткое правило: LOCK_RENEW_TIME должен быть меньше LOCK_DURATION / 2. Иначе есть шанс, что блокировка успеет истечь между двумя продлениями.
# .env для worker-инстанса
EXECUTIONS_MODE=queue
QUEUE_WORKER_CONCURRENCY=20
QUEUE_BULL_REDIS_HOST=redis
QUEUE_BULL_SETTINGS_LOCK_DURATION=60000
QUEUE_BULL_SETTINGS_LOCK_RENEW_TIME=15000
# LOCK_RENEW_TIME должен быть < LOCK_DURATION / 2
В примере выше: блокировка живёт 60 секунд, продлевается каждые 15. Запас четырёхкратный, так что даже небольшой сетевой лаг до Redis не приведёт к гонке.
Если твои воркфлоу стабильно укладываются в 20-25 секунд, дефолтные 30000ms не трогай. Но как только в продакшн попадают длинные цепочки или тяжёлые файловые операции, поднимай LOCK_DURATION до 60-120 секунд и выставляй LOCK_RENEW_TIME в четверть от этого значения.
Мониторинг очереди: как понять, что воркеров не хватает
Самый частый сценарий выглядит так: webhook возвращает 200 почти мгновенно, но в интерфейсе n8n execution появляется через 20-30 секунд. Люди начинают грешить на сеть, на базу, на что угодно. Проблема в другом. Main-процесс жив и принимает запросы, а воркеры разобрать задачи не успевают.
Первый инструмент диагностики, который открываю сразу: прямой запрос к Redis.
redis-cli LLEN bull:jobs:wait
Эта команда возвращает глубину очереди прямо сейчас. Если число растёт и не падает несколько минут подряд, воркеров не хватает. Никаких дополнительных инструментов для первичного диагноза не нужно.
Для систематического наблюдения подключаю метрики из n8n. Эндпоинт /metrics отдаёт два значения, которые меня интересуют: n8n_queue_jobs_waiting и n8n_queue_jobs_active. Оба добавляю в Grafana как отдельные серии на одном графике. Картина сразу становится читаемой: если waiting стабильно выше нуля, а active упёрся в потолок и не растёт, масштабирование нужно прямо сейчас.
Порог, с которого я начинаю действовать: больше 50 ожидающих задач в течение 5 минут. Это сигнал добавлять воркеров. Не ждать 15 минут, не разбираться сначала с причинами. Сначала разгрузить очередь, потом анализировать.
В queue mode это делается без рестарта main-процесса:
docker-compose up -d --scale n8n-worker=4
Compose поднимет нужное количество контейнеров воркеров, они подключатся к Redis и сразу начнут разбирать накопившееся. Текущие исполнения не прерываются. Это одно из главных преимуществ архитектуры с очередью перед обычным режимом, где всё крутится в одном процессе.
Alert в Grafana настраиваю по такому условию: n8n_queue_jobs_waiting > 50 дольше 5 минут. На практике это покрывает большинство ситуаций, кроме пиковых всплесков, которые сами рассасываются за минуту-две.

Показатель «waiting jobs» выше 50 на протяжении 5 минут сигнализирует о нехватке воркеров или зависших задачах в Redis.
Когда queue mode избыточен: честные пороги
Большинство материалов про n8n queue mode написаны с позиции "лучше перестраховаться". Я с этим не согласен. Лишняя инфраструктура это не просто деньги,





