Может ли быть заблокирован Event Loop?
Комментарии (1)
Ответ сгенерирован нейросетью и может содержать ошибки
Event Loop блокировка в Node.js
Да, Event Loop может быть заблокирован! Это одна из самых опасных и трудноуловимых проблем в Node.js.
Что такое Event Loop?
Event Loop — это сердце Node.js. Это одноточный механизм, который обрабатывает все события:
Новый запрос → Event Loop → Обработка → Ответ
Пока Event Loop занят — все остальные запросы ждут.
Как заблокировать Event Loop
1. Synchronous (синхронная) обработка данных
const express = require('express');
const app = express();
// ❌ ПЛОХО: синхронное вычисление
app.get('/compute', (req, res) => {
// Этот цикл заблокирует Event Loop!
let result = 0;
for (let i = 0; i < 1_000_000_000; i++) {
result += Math.sqrt(i);
}
res.json({ result });
});
app.listen(3000);
Что происходит:
- Запрос приходит
- Event Loop начинает считать (занят)
- Все остальные запросы ждут в очереди
- Если цикл занимает 5 секунд — все ждут 5 секунд!
Запрос 1: /compute (занимает Event Loop 5 сек)
Запрос 2: /health (простой) (ждёт 5 сек, хотя должен выполниться за 1ms)
Запрос 3: /data (простой) (ждёт 5 сек)
2. JSON парсинг больших объектов
app.post('/data', (req, res) => {
// ❌ Если body огромный — парсинг заблокирует Event Loop
const data = JSON.parse(req.body);
res.json({ processed: true });
});
// Пример: 1GB JSON
// Парсинг может занять 10+ секунд
// Event Loop будет заблокирован на всё это время
3. Синхронные операции с файлами
const fs = require('fs');
app.get('/read', (req, res) => {
// ❌ ОЧЕНЬ ПЛОХО: синхронное чтение
const data = fs.readFileSync('large-file.bin');
res.json({ size: data.length });
});
// Event Loop заблокирован на всё время чтения с диска!
4. Криптографические вычисления
const crypto = require('crypto');
app.post('/hash', (req, res) => {
// ❌ Дорогостоящее вычисление
const iterations = 100_000;
const hash = crypto.pbkdf2Sync(
req.body.password,
'salt',
iterations, // 100K итераций = 2+ секунды
64,
'sha512'
);
res.json({ hash: hash.toString('hex') });
});
5. Регулярные выражения (ReDoS — Regular Expression Denial of Service)
// ❌ Опасное регулярное выражение
const regex = /(a+)+b/;
const maliciousInput = 'a'.repeat(30) + 'x';
app.get('/validate', (req, res) => {
// Это может зависнуть на 10+ секунд!
if (regex.test(maliciousInput)) {
res.json({ valid: true });
}
});
// Алгоритм переполняет себя и тратит экспоненциальное время
Симптомы заблокированного Event Loop
1. Простые запросы становятся медленными
const express = require('express');
const app = express();
// Очень быстрый endpoint
app.get('/health', (req, res) => {
res.json({ status: 'ok' });
});
// Медленный endpoint
app.get('/heavy', (req, res) => {
// Блокировка на 10 секунд
for (let i = 0; i < 10_000_000_000; i++) {
Math.sqrt(i);
}
res.json({ done: true });
});
app.listen(3000);
// Тестирование:
// curl http://localhost:3000/health # В нормальное время: 1ms
// curl http://localhost:3000/heavy # Запускается /heavy
// curl http://localhost:3000/health # ЖДЁТ 10 секунд! Должно быть 1ms
2. Логирование отстаёт
const logger = require('winston');
app.get('/heavy', (req, res) => {
logger.info('Started processing'); // Логируется с задержкой!
// Синхронная операция
for (let i = 0; i < 10_000_000_000; i++) {
Math.sqrt(i);
}
logger.info('Finished processing'); // Логируется с задержкой!
res.json({});
});
// Логи появляются не сразу, а с задержкой
3. Connections дропаются
Лоад балансер думает, что сервер дохлый (не отвечает долго).
Как избежать блокировки
1. Асинхронность везде
// ✅ ХОРОШО: async вычисления
app.get('/compute', async (req, res) => {
// Выполнится в потоке, не блокируя Event Loop
const result = await new Promise((resolve) => {
setImmediate(() => {
let sum = 0;
for (let i = 0; i < 1_000_000_000; i++) {
sum += Math.sqrt(i);
}
resolve(sum);
});
});
res.json({ result });
});
2. Worker Threads для CPU-intensive операций
const { Worker } = require('worker_threads');
const path = require('path');
app.get('/compute', (req, res) => {
const worker = new Worker(path.join(__dirname, 'worker.js'));
worker.on('message', (result) => {
res.json({ result });
});
worker.on('error', reject);
worker.postMessage({ n: 1_000_000_000 });
});
// worker.js
const { parentPort } = require('worker_threads');
parentPort.on('message', ({ n }) => {
let sum = 0;
for (let i = 0; i < n; i++) {
sum += Math.sqrt(i);
}
parentPort.postMessage(sum);
});
3. Асинхронные операции с файлами
const fs = require('fs').promises;
// ✅ Асинхронное чтение
app.get('/read', async (req, res) => {
const data = await fs.readFile('large-file.bin');
res.json({ size: data.length });
});
// Event Loop не будет заблокирован!
4. Асинхронная криптография
const crypto = require('crypto').promises;
app.post('/hash', async (req, res) => {
// ✅ Асинхронная версия
const hash = await crypto.pbkdf2(
req.body.password,
'salt',
100_000,
64,
'sha512'
);
res.json({ hash: hash.toString('hex') });
});
5. Безопасные регулярные выражения
const RE2 = require('re2');
// ❌ Опасно
const dangerousRegex = /(a+)+b/;
// ✅ Безопасно (использует RE2)
const safeRegex = new RE2('(a+)+b');
app.get('/validate', (req, res) => {
// Гарантировано не зависнет
const valid = safeRegex.test(req.body.input);
res.json({ valid });
});
Monitoring блокировок Event Loop
1. Использование clinic.js
npm install -g clinic
clinic doctor -- node app.js
# Покажет блокировки Event Loop
2. Custom мониторинг
const blockDetector = setInterval(() => {
const now = Date.now();
setTimeout(() => {
const delay = Date.now() - now - 100; // Ожидаемое = 100ms
if (delay > 50) {
console.warn(`Event Loop blocked for ${delay}ms`);
}
}, 100);
}, 1000);
3. Prometheus метрики
const promClient = require('prom-client');
const eventLoopDelay = new promClient.Histogram({
name: 'event_loop_delay_ms',
help: 'Event Loop delay in milliseconds'
});
const start = Date.now();
setImmediate(() => {
const delay = Date.now() - start;
eventLoopDelay.observe(delay);
});
Production примеры
Плохая функция (блокирует):
app.post('/process-image', (req, res) => {
// ❌ Синхронная обработка 10MB изображения
const image = sharp(req.file.buffer)
.resize(1000, 1000)
.jpeg({ quality: 80 })
.toBufferSync(); // БЛОКИРУЕТ!
res.json({ success: true });
});
Хорошая функция (асинхронная):
app.post('/process-image', async (req, res) => {
// ✅ Асинхронная обработка
const image = await sharp(req.file.buffer)
.resize(1000, 1000)
.jpeg({ quality: 80 })
.toBuffer(); // НЕ блокирует
res.json({ success: true });
});
Итого: ключевые моменты
- Event Loop может быть заблокирован синхронным кодом
- Блокировка на миллисекунды = все запросы ждут
- Признаки: простые запросы медленные, логирование отстаёт
- Решение: используй async/await, Worker Threads, или setImmediate
- Мониторинг: clinic.js, Prometheus, custom детекторы
- Главное правило: избегай синхронных операций, всё должно быть асинхронным
Even Loop — это фундамент Node.js производительности. Понимание его работы — ключ к написанию масштабируемых приложений.