![Ребёнок — это как енот, умеющий лгать](https://habrastorage.org/storage1/2b9e43a6/e41c045b/1fe1a7e7/8538843e.jpg)
Поскольку вы читаете это, вы, возможно, уже поняли, что моя догадка подтвердилась.
Node.js — это опухоль на программистском сообществе, не только оттого, что он совершенно безумен, но и оттого, что люди, использующие его, инфицируют других людей, не умеющих думать самостоятельно, пока, в конце концов, каждый встречающийся мне мудак не начинает читать проповеди об event loop'ах. Принял ли ты epoll в своё сердце?
Крах масштабируемости ждёт своего часа
Давайте начнём с самой ужасной лжи: Node.js масштабируем, потому что он «никогда не блокирует» (Радиация приносит пользу! Теперь в вашей зубной пасте!). На сайтe Node.js сказано:
В Node практически нет функций, напрямую выполняющих операции ввода-вывода, так что процесс никогда не блокируется. Из-за того, что ничего не блокируется, менее-чем-эксперты могут разрабатывать быстрые системы.
Это утверждение заманчиво, ободряюще и полностью, блядь, неверно.
Давайте начнём с определения, ведь ваша, хабровские всезнайки, специфика — педантизм. Вызов функции называется блокирующим, когда выполнение вызывающего потока будет приостановлено до завершения этой функции. Как правило, мы думаем об операциях ввода-вывода как о «блокирующих», например, если вызвать socket.read(), программа будет ожидать завершения этого вызова, так как ей нужно что-то сделать с возвращаемыми данными.
Вот вам забавный факт: вызов любой функции, использующей процессор, тоже блокирующий. Эта функция, вычисляющая N-ное число Фибоначчи, заблокирует текущий поток, потому что она использует процессор:
function fibonacci(n) {
if (n < 2)
return 1;
else
return fibonacci(n-2) + fibonacci(n-1);
}
(Да, я знаю про замкнутое решение. А ты разве не должен сейчас репетировать перед зеркалом то, что скажешь, когда всё-таки решишься подойти к Ней?)
Посмотрим, что происходит с программой для Node.js, с вот этим маленьким бриллиантом в качестве обработчика запроса:
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end(fibonacci(40));
}).listen(1337, "127.0.0.1");
На моём предыдущем ноутбуке результат таков:
ted@lorenz:~$ time curl http://localhost:1337/
165580141
real 0m5.676s
user 0m0.010s
sys 0m0.000s
Время ответа — 5 секунд. Круто. Итак, мы все знаем, что JavaScript не офигенно быстрый язык, но что в этом страшного? А то, событийная модель Node и ёбнутые на всю голову фанатики заставили вас думать, что всё хорошо. Вот простенький псевдокод, показывающий, как работает event loop:
while(1) {
ready_file_descriptor = event_library->poll();
handle_request(ready_file_descriptor);
}
Это все, конечно, хорошо, если вы знаете, что вы делаете, но, применяя это для серверных задач, вы множите это дерьмо. Если этот цикл работает в том же потоке, что и handle_request, любой адекватный программист заметит, что обработчик запроса может заблокировать цикл, и без разницы, насколько асинхронна ваша библиотека.
Итак, учитывая вышесказанное, давайте посмотрим, как мой маленький node-сервер ведёт себя при самой скромной нагрузке — 10 запросов, 5 одновременных:
ted@lorenz:~$ ab -n 10 -c 5 http://localhost:1337/
...
Requests per second: 0.17 [#/sec] (mean)
...
0.17 запросов в секунду. Зверь. Конечно, Node позволяет рожать дочерние процессы, но сейчас потоковая и событийная модель настолько сильно связаны, что вы уже получили гораздо большие проблемы, чем масштабируемость.
Учитывая оригинальную маркетинговую политику Node, я, чёрт побери, боюсь любых «быстрых систем», которые «менее-чем-эксперты» подарят этому миру.
Отрицая философию Unix, Node наказывает разработчика
Давным-давно стоявшие у истоков бородатые парни решили, что собирать в цепочку маленькие программы, каждая из которых выполняет специфическую задачу — отличная идея, а универсальным интерфейсом для них должен стать текст.
Если вы разрабатываете на платформе Unix, придерживаясь этого принципа, операционная система отблагодарит вас простотой и процветанием. К примеру, когда веб-приложения только появились, веб-приложение было просто программой, отдающей текст в стандартный вывод. Веб-сервер отвечал за принятие входящих запросов, выполнение этой программы и возврат результата клиенту. Мы назвали это CGI, и это был хороший способ выполнять работу, пока микро-оптимизаторы не сунули в него свои грязные пальцы.
Концептуально, любая архитектура веб-приложения, не являющаяся раком мозга, работает именно так и сейчас: у вас есть веб-сервер, работа которого — принять запрос, разобрать его и решить, что с ним делать дальше. Это может быть отдача статического файла, вызов CGI-скрипта, проксирование соединения куда-либо ещё, что угодно. Дело в том, что HTTP-сервер не должен выполнять работу приложения. Разработчики обычно называют это разделением ответственности, и оно существует по одной причине: слабосвязанные архитектуры очень просты в обслуживании.
И всё же, кажется, Node не обращает на это внимания. У Node есть (и не смейтесь, я не придумываю) свой собственный HTTP-сервер, и его вы должны использовать, чтобы обслуживать входящий трафик. Да, в примере выше, где я вызвал http.createServer(), это из документации.
Если вы поищете «node.js deployment» в интернете, вы найдёте кучу людей, сующих Nginx перед Node, а некоторые используют штуку под названием Fugue. Это другой JavaScript HTTP-сервер, рожающий кучу процессов для обработки входящих запросов, ведь никто не подумал, что вся эта «неблокирующая» чушь может иметь проблемы с производительностью CPU.
Если вы используете Node, есть 99-процентный шанс, что вы и разработчик, и сисадмин, потому что любой системный администратор первым делом отговорил бы вас от использования Node. Таким образом, вы, разработчик, будете наказаны этой оргией с HTTP-проксированием, если захотите поставить настоящий веб-сервер перед Node для штук типа отдачи статического контента, перезаписи запросов, ограничения скорости, балансировки нагрузки, SSL или любых других футуристичных вещей, которые умеют делать современные HTTP-серверы. Да, в вашей системе будет ещё один уровень, требующий мониторинга.
Хотя, будем честны сами с собой, если вы Node-разработчик, вы, вероятно, запускаете приложение прямо из Node, запущенной в вашей экранной сессии под вашей учётной записью.
Это блядский JavaScript
Возможно, худшее, что можно сделать с серверным фреймворком, — написать его на JavaScript.
if (typeof my_var !== "undefined" && my_var !== null) {
// идоты, вы опозорили Расмуса Лердорфа
}
Что это, я даже не…
Ниасилил?
Node.js — неприятное ПО, и я его использовать не буду.
Update: от переводчика
Я тоже JavaScript-разработчик, давно с интересом присматривающийся к Node.js. Мне тоже обидно и больно за любимый язык. Однако, если отвлечься от боли, в вышеизложенном тексте можно найти смысл, аргументы и доказательства. Я бы очень хотел внятной дискуссии, ибо переводил я его только ради этого.