RSS

Комментарии

Выбор Node.js в качестве платформы для серверной прослойки помимо множества плюсов повлек за собой и определенное количество головной боли для разработчиков.

Например, таким большим приложениям, как наше, даже при наличии хорошего покрытия тестами и документацией, каждое обновление дается нелегко: постоянное нахождение в процессе изменений вытекает не только из желания разработчиков порадовать себя или улучшить свой продукт, но и из реалий современного мира JavaScript-разработки.

Node.js мы используем в связке с биндингами C++ кода в JavaScript, отказаться от которых в силу сложившейся инфраструктуры мы не можем. При каждом обновлении Node.js мы сталкиваемся не только со стандартными сложностями обновления по гайдам миграции, но и с обновлением наших биндингов для поддержки очередной версии Node.js: libxml, libxslt и наших внутренних C++ библиотек.

Вообще, зависимость от биндингов привносит дополнительные накладные расходы по поддержке, поэтому их лучше избегать – например, наши внутренние биндинги стараемся переводить на отдельные HTTP-сервисы.

Отдельная история – асинхронность Node.js, которую еще нужно уметь «готовить». Например, в какой-то момент мы заметили, что время от времени работающий воркер падал из-за ошибки бизнес-логики и перезапускался, хотя весь код логики в Express и Koa обернут в отлов ошибок и должен перехватываться. То есть бизнес-логика, описанная в обработчиках роута, никак не должна останавливать весь процесс Node.js, но у нас это все равно происходило.

Анализ ситуации вскрыл любопытную особенность. При передаче в process.nextTick функции обратного вызова она запустится в текущем такте асинхронного цикла после выполнения остального кода текущего такта, включая код отлова ошибок. Если внутри такой функции возникал Exception, его было не отловить, потому что process.nextTick вовсе не «следующий такт», а конец текущего.

Еще есть нюансы с ES2015: весь наш клиентский код проходит через транспиляцию в Babel, что позволяет нам использовать любые новшества языка и не только стандарта ES2015. Но Node.js не имеет такой хорошей поддержки и ряд возможностей в нем отсутствуют, а прогонять серверный код через Babel — слишком большая головная боль при поддержке и сборке кода. Таким образом, при написании JavaScript-кода нам необходимо всегда держать в голове окружение, где этот код будет исполняться, а также по-разному настраивать правила линтера.

Приятной вишенкой на торте трудностей можно считать «усложнившийся» процесс выбора модулей для использования. Безусловно, есть признанные лидеры в той или иной сфере применения, но и среди них можно обоснованно искать более подходящие для каждого конкретного случая. Да, количество logic-ревью возросло, но тем и хорош Node.js и NPM, что у нас есть широкий выбор открытых модулей и возможность не подстраиваться под технологию, а использовать удобную для нас.
Посмотрите внимательнее на картинку к посту и вам все станет понятно, что вы все правильно понимаете
По поводу буфера веб-сервера, то он может попробовать сбросить используя cgi api, если я ничего не путаю
Написано, что flush() сбрасывает SAPI-буфер. Правильно ли я понимаю, что он также сбрасывает и Default Output Buffer?

Кроме того, в доках написано, что он пытается сбросить также буфер веб-сервера (как?).
Поток вывода в PHP содержит байты, обычно в виде текста, которые разработчику надо вывести на экран. Чаще всего для этого используется конструкция echo или printf(). Во-первых, нужно понимать, что любая функция, которая что-то выводит, будет использовать БВ из области PHP. Если говорить о расширениях для PHP, то можно получить доступ к функциям, пишущим в SAPI напрямую, в обход любого вышерасположенного БВ. API C задокументировано в lxr.php.net/xref/PHP_5_5/main/php_output.h, отсюда можно почерпнуть немало информации, например, о размере буфера по умолчанию.

Второй важный момент: слой БВ является не единственным слоем, в котором буферизуются выводимые данные.

И третье: в зависимости от SAPI, который вы используете (веб или cli), слой БВ может вести себя по-разному.

Ниже представлена схема, которая поможет понять всё вышесказанное:

Здесь мы видим, что для управления выводимыми данными в PHP используется три логических слоя буферизации. Два из них принадлежат тому самому «буферу вывода», а третий — SAPI. Когда поток вывода покидает область PHP, чтобы попасть на нижний уровень архитектуры, «по пути» могут возникнуть новые буферы: буфер терминала, буфер FastCGI, буфер веб-сервера, буфер операционной системы, буферы стеков TCP/IP. Не забывайте об этом. Хотя в рамках данной статьи мы будем говорить только о PHP, в стеке на пути данных к нижнему слою и пользователю встретится ещё немало программных средств.

Важное замечание относительно CLI SAPI: он отключает в PHP любой буфер вывода по умолчанию, присвоив в ini параметру output_buffering значение 0. Так что, пока вы в CLI не пропишете вручную функции ob_(), по умолчанию все выводимые данные будут напрямую попадать в слой SAPI. Более того, в CLI для параметра implicit_flush жёстко указано значение 1. Суть этого параметра разработчики вечно понимают неправильно, хотя код говорит совершенно недвусмысленно: когда implicit_flush имеет значение 1, буфер слоя SAPI сбрасывается при каждой записи. То есть каждый раз, когда вы записываете данные для вывода с помощью CLI SAPI, они немедленно отправляются на нижний уровень, где записываются в виде stdout, а потом сбрасываются.

Стандартный PHP-слой буферизации вывода

Если вы используете SAPI не так, как CLI, а например, PHP-FPM, то можете поэкспериментировать с тремя параметрами в ini, имеющими отношение к буферу:

— output_buffering
— implicit_flush
— output_handler

Обратите внимание, что использование с ними ini_set() не даст никакого эффекта, поскольку их значения считываются в момент запуска PHP, до того, как он может запустить какой-либо скрипт. Если использовать ini_set() с любым из этих параметров, то он меняет значение, однако оно уже нигде не будет использоваться. Слишком поздно — слой БВ уже запущен и активен. Изменить эти параметры можно, отредактировав php.ini или применив ключ –d к бинарнику PHP.

По умолчанию в php.ini, идущем в составе поставки PHP, output_buffering присвоено значение «4096» (байт). Если вы не используете php.ini (или запускаете PHP с ключом –n), то значением по умолчанию будет «0», то есть отключено. Если захардкодить значение «On», то будет назначен стандартный размер буфера вывода (16 КБ).

Как вы уже, наверное, догадались, использование буфера для вывода в веб-окружении благотворно влияет на производительность. Начальных 4 КБ вполне достаточно, ведь это означает, что вы можете записать до 4096 ASCII-символов, пока PHP не начнёт взаимодействовать с нижерасположенным слоем SAPI. В условиях веба отправка данных побайтно, напротив, производительность не улучшает. Гораздо лучше, если сервер отправляет весь контент скопом или большими частями. Чем реже уровни обмениваются данными, тем лучше с точки зрения производительности. Поэтому обязательно используйте буфер вывода. PHP отправит его содержимое в конце запроса и вам для этого ничего не придётся делать.

В предыдущей главе я упоминал об implicit_flush в контексте CLI. В случае с любым другим SAPI implicit_flush изначально отключён. Это хорошо, поскольку вряд ли вы будете приветствовать сброс SAPI сразу же после записи в него. Для протокола FastCGI сброс можно сравнить с завершением и отправкой пакета после каждой записи. Однако лучше сначала полностью заполнить буфер FastCGI, а уже потом слать пакеты. Если вам нужно вручную сбросить буфер SAPI, используйте для этого PHP-функцию flush(). Для сброса после каждой записи, как уже говорилось выше, можно использовать параметр implicit_flush в php.ini. Как вариант — однократный вызов PHP-функции ob_implicit_flush().

К содержимому буфера до его вывода можно применять callback output_handler. Вообще, благодаря расширениям PHP нам доступно немало callback-ов (пользователи тоже могут их писать, об этом я расскажу в следующей главе).

— ob_gzhandler: компрессия вывода с помощью ext/zlib
— mb_output_handler: перевод кодировки символов с помощью ext/mbstring
— ob_iconv_handler: перевод кодировки символов с помощью ext/iconv
— ob_tidyhandler: очистка вывода HTML с помощью ext/tidy
— ob_[inflate/deflate]_handler: компрессия вывода с помощью ext/http
— ob_etaghandler: автоматическая генерация заголовков ETag с помощью ext/http

Вы можете использовать только один callback, который получит содержимое буфера и сделает полезные преобразования для вывода, что не может не радовать. Для анализа данных, которые PHP отправляет веб-серверу, а тот отсылает пользователю, полезно использовать callback-и буфера вывода. Кстати, под «выводом» я подразумеваю как заголовок, так и тело. HTTP-заголовки тоже являются частью слоя буферизации вывода.

Тело и заголовки

Когда вы используете буфер вывода (неважно, пользовательский или один из стандартных), то можете отправлять HTTP-заголовки и содержимое как угодно. Любой протокол требует сначала отсылать заголовок, а уже потом тело, но за вас это сделает сам PHP, если вы используете слой БВ. Любая PHP-функция, работающая с заголовками (header(), setcookie(), session_start()), фактически использует внутреннюю функцию sapi_header_op(), которая просто заполняет буфер заголовков. Если после этого записать выводимые данные, например, с помощью printf(), то они запишутся в один из соответствующих буферов вывода. И во время отправки буфера PHP сначала

отсылает заголовки, а уже потом тело. Если вам не нравится такая забота со стороны PHP, то придётся вообще отключить слой БВ.

Пользовательские буферы вывода

Давайте разберём на примерах, как это работает, и что вы можете сделать. Имейте в виду, что если вы хотите использовать стандартный PHP-слой буферизации, то не сможете воспользоваться CLI, поскольку он отключается как слой.

Ниже приведён пример работы со стандартным PHP-слоем с помощью внутреннего веб-сервера SAPI:

/* запущено так: php -doutput_buffering=32 -dimplicit_flush=1 -S127.0.0.1:8080 -t/var/www */
 
echo str_repeat('a', 31);
sleep(3);
echo 'b';
sleep(3);
echo 'c';


Мы запустили PHP со стандартным буфером вывода на 32 байта, после чего сразу же записали в него 31 байт, пока не включилась задержка исполнения. Экран чёрный, пока ничего не отправлено. Затем действие sleep() заканчивается, и мы записываем ещё один байт, тем самым полностью заполняя буфер. После этого он сразу же сбрасывает себя в буфер слоя SAPI, а тот сбрасывает себя в вывод, поскольку implicit_flush имеет значение 1. На экране появляется строка aaaaaaaaaa{31 раз}b, после чего опять начинает действовать sleep(). По его завершении пустой 31-байтный буфер заполняется одним-единственным байтом, после чего PHP завершается и сбрасывает буфер. На экране появляется с.

Так выглядит работа стандартного PHP-буфера без вызова каких-либо ob-функций. Не забудьте, что это именно стандартный буфер, то есть он уже имеется в наличии (только нельзя использовать CLI).

Теперь с помощью ob_start() можно запускать пользовательские буферы, причем столько, сколько нужно, пока память не закончится. Каждый буфер будет помещаться за предыдущим и немедленно сбрасываться в следующий, что постепенно приведёт к переполнению.

ob_start(function($ctc) { static $a = 0; return $a++ . '- ' . $ctc . "\n";}, 10);
ob_start(function($ctc) { return ucfirst($ctc); }, 3);
 
echo "fo";
sleep(2);
echo 'o';
sleep(2);
echo "barbazz";
sleep(2);
echo "hello";
 
/* 0- FooBarbazz\n 1- Hello\n */


Устройство буферизации вывода

Как я уже говорил, начиная с версии 5.4 механизм буферизации вывода был полностью переписан. До этого код был очень неаккуратным, многие вещи сделать было непросто, часто возникали баги. Подробнее об этом можно почитать по ссылке. Новая кодовая база получилась гораздо чище, лучше организована, появились новые возможности. Правда, совместимость с версией 5.3 обеспечивается лишь отчасти.

Пожалуй, одним из самых приятных нововведений стало то, что расширения теперь могут объявлять свои callback-и буфера вывода, конфликтующие с callback-ами других расширений. Ранее было невозможно полностью управлять ситуациями, когда другие расширения тоже могли декларировать свои callback-и.

Вот небольшой пример на скорую руку, демонстрирующий, как можно зарегистрировать callback, преобразующий данные в верхний регистр:

#ifdef HAVE_CONFIG_H
#include "config.h"
#endif
#include "php.h"
#include "php_ini.h"
#include "main/php_output.h"
#include "php_myext.h"

static int myext_output_handler(void **nothing, php_output_context *output_context)
{
char *dup = NULL;
 
dup = estrndup(output_context->in.data, output_context->in.used);
php_strtoupper(dup, output_context->in.used);
 
output_context->out.data = dup;
output_context->out.used = output_context->in.used;
output_context->out.free = 1;
 
return SUCCESS;
}
 
PHP_RINIT_FUNCTION(myext)
{
php_output_handler *handler;
 
handler = php_output_handler_create_internal("myext handler", sizeof("myext handler") -1, myext_output_handler, /* PHP_OUTPUT_HANDLER_DEFAULT_SIZE */ 128, PHP_OUTPUT_HANDLER_STDFLAGS);
 
php_output_handler_start(handler);
 
return SUCCESS;
}
 
zend_module_entry myext_module_entry = {
STANDARD_MODULE_HEADER,
"myext",
NULL, /* Function entries */
NULL,
NULL, /* Module shutdown */
PHP_RINIT(myext), /* Request init */
NULL, /* Request shutdown */
NULL, /* Module information */
"0.1", /* Replace with version number for your extension */
STANDARD_MODULE_PROPERTIES
};
 
#ifdef COMPILE_DL_MYEXT
ZEND_GET_MODULE(myext)
#endif

Подводные камни

По большей части они задокументированы, некоторые из них вполне очевидны, а некоторые не слишком. К очевидным можно отнести, например, то, что не следует вызывать какие-либо функции буфера изнутри callback-а БВ, также как и записывать выводимые оттуда данные.

К неочевидным подводным камням можно отнести то, что некоторые функции PHP используют внутренний БВ для самих себя, заполняя его, а затем сбрасывая или возвращая. При этом следующий буфер ставится в стек. К подобным функциям относятся print_r(), highlight_file() и SoapServer::handle(). Не следует использовать их изнутри callback-а БВ – это может привести к непредсказуемым последствиям.

Заключение

Слой вывода можно сравнить со своеобразной сетью, которая улавливает любые возможные «утечки» вывода из PHP и сохраняет их в буфере заданного размера. Когда буфер заполняется, он сбрасывается (записывается) в нижний уровень, если таковой есть. Как минимум в самый нижний из имеющихся — в буфер SAPI. Пользователи могут управлять количеством буферов, их размером и операциями, которые могут быть разрешены в каждом слое буфера (очистка, сброс или удаление). Это очень гибкий инструмент, позволяющий, например, создателям библиотек и фреймворков полностью контролировать поток вывода, направляя его в глобальный буфер и обрабатывая там. При этом PHP сам регулирует порядок отправки заголовков и потока вывода.

По умолчанию имеется один буфер вывода, управляемый тремя настройками в ini-файле. Он устроен так, чтобы реже осуществлять операции записи и не слишком часто обращаться к слою SAPI, а значит и к сети. Это сделано для улучшения общей производительности. Также расширения PHP могут декларировать callback-и, запускаемые в каждом буфере — например, для компрессии данных, замены строк, управления HTTP-заголовками и многих других операций.
Буферизация вывода используется PHP для повышения производительности и выполнения нескольких трюков.

— Вы можете заставить PHP сохранять все выходные данные в буфер и выводить их все сразу, повышая производительность сети.
— В определенных ситуациях вы можете получить доступ к содержимому буфера, не отправляя его обратно в браузер.

Рассмотрим этот пример:
<?php
    ob_start( );
    phpinfo( );
    $output = ob_get_clean( );
?>

Приведенный выше пример преобразует выходные данные в переменную вместо отправки их в браузер. output_buffering по умолчанию отключен.

— Вы можете использовать буферизацию вывода в ситуациях, когда вы хотите изменить заголовки после отправки содержимого.

Рассмотрим этот пример:
<?php
    ob_start( );
    echo "Hello World";
    if ( $some_error )
    {
        header( "Location: error.php" );
        exit( 0 );
    }
?>
Спасибо за статью, а можно простыми словами — что такое буферизация вывода и зачем мне использовать ее в PHP?
Без буферизации вывода (по умолчанию) ваш HTML отправляется в браузер по частям по мере обработки PHP вашим скриптом. При буферизации вывода ваш HTML сохраняется в переменной и отправляется в браузер как единое целое в конце вашего скрипта.

Преимущества буферизации вывода для веб-разработчиков

— Включение только буферизации вывода уменьшает количество времени, необходимое для загрузки и рендеринга нашего HTML, потому что он не отправляется в браузер по частям, поскольку PHP обрабатывает HTML.
— Все необычные вещи, которые мы можем делать со строками PHP, теперь мы можем делать со всей нашей HTML-страницей как с одной переменной.
— Если вы когда-либо сталкивались с сообщением «Предупреждение: невозможно изменить информацию заголовка — заголовки уже отправлены (output)» при настройке файлов cookie, вы будете рады узнать, что буферизация вывода — это ваш ответ.
Я не большой знаток истории верстки, но всегда считал, что прорывной идеей это было на Западе. В России так верстали с самого начала, и никакого специального названия у этого не было. Просто у нас с самого начала был зоопарк разных разрешений, примерно от 640×480 до 1600×1200. Потом вариантов на десктопах стало поменьше, но стали появляться лэптопы, нетбуки, смартфоны… В общем число вариантов скорее росло. Не думаю, что вообще был сколько-нибудь продолжительный период, когда какое-то одно разрешение полностью доминировало.

Сам термин «резиновая верстка» появился вместе с концепцией фиксированной верстки из-за тлетворного влияния Запада :) В верстку хлынуло много новичков, которые тащили в рот всякую гадость читали первые попавшиеся руководства, разумеется западные, потому что они там вона какие умные, куда нам до них, какой смысл читать наши учебники. А во всех адекватных российских руководствах фиксированная верстка под конкретное разрешение всегда оценивалась однозначно как грубейшая ошибка.
Когда мобильного веба не было даже в самых смелых фантазиях, весь интернет был на десктопных компьютерах с небольшими экранами. Оптимизирован для разрешения 1024 на 768 и браузера Internet Explorer 5! — гордо писали тогда на сайтах. Но прошло время, экраны подросли и пришлось к этому как-то подстраиваться. Сначала это были просто резиновые таблицы: колонки по 25%, содержимое 50%, а в отдельной строке colspan=3 и распорка для минимальной ширины. Надеюсь, вы не поняли о чём я сечас говорил.

Резиновая вёрстка — это когда вы задаёте всему макету и отдельным его частям не фиксированную ширину, а эластичную — в процентах. За исключением минимальной и максимальной ширины. Высоту в вебе в принципе не принято задавать — обычно она подстраивается под содержимое. Фикс или резина? — спрашивали верстальщики 2000-х про макет и втайне надеялись на второе, поинтереснее. Сейчас сложно представить, но когда-то это было прорывной идеей, которая усложнила вёрстку, но сделала первый шаг в правильном пути.

Таблицы для раскладки ушли, а резиновая вёрстка осталась. Если у вас эластичные флоаты, флексы… да хоть гриды! — это всё равно резиновая вёрстка. Но если ширина внешнего контейнера зафиксирована — это уже фикс, сколько бы ни было резины внутри. Чуть более универсальный фикс, но всё равно. В общем, главное чтобы сайт двигался так или иначе вместе с окном, а не торчал кирпичом где-то в центре или с краю.

Когда расширился круг устройств с интернетом и появились первые мобильные браузеры — встала задача как-то подстраиваться под них. Простая резина здесь уже не справлялась — нужно было переписывать стили. Одной из первых, в 2006 году появилась техника адаптивной раскладки Марка ван ден Доббельстина. В статье на A List Apart Марк предложил добавлять классы при загрузке или ресайзе окна и на каждый диапазон вешать стили. До первой реализации медиавыражений в Safari оставалось два года.

Когда в начале десятых годов появилось для чего адаптировать и чем адаптировать — мобильные браузеры и медиавыражения — вышли книги, давшие названия подходам: «Адаптивный веб-дизайн» Аарона Густавсона и «Отзывчивый веб-дизайн» Итана Маркота. Подходы Аарона и Итана продолжали идеи Марка, но с более современными технологиями и несколько отличались направлением мысли.

«Адаптивный веб-дизайн» Аарона предлагал гибко адаптировать сайты к возможностям устройств и браузеров. Важной частью этой философии был ненавязчивый JavaScript с прогрессивным улучшением — и всё это поверх семантической разметки. Хотя Аарон писал не совсем об этом, сегодня принято считать, что главное в адаптивной вёрстке — привязка к конкретным разрешениям и устройствам. Стили переключаются от одного брейкпоинта к другому, то есть у вас есть фиксированные макеты для iPad и iPhone, а то, что между ними вас не волнует.

«Отзывчивый веб-дизайн» Итана ставил во главу три вещи: резиновый макет, гибкие картинки и медиавыражения. Все размеры и отступы Итан предлагал указывать в процентах с сумасшедшими дробями для точности. Отличительной чертой подхода стало плавное изменение сайта, с ориентацией не на конкретные устройства, а на содержимое. То есть ваш резиновый макет хорошо выглядит не только на iPhone и iPad, но и в любой точке между ними.

Чуть позже Итан сформулировал ещё один важный принцип в книге «Сначала мобильные». Если до тех пор отправной точкой для адаптации вёрстки служила десктопная версия, то он предложил перевернуть схему и начинать с мобильной версии, а потом её улучшать. Почему так? Потому, что усложнять простое проще, чем упрощать сложное. Вдуматесь! А ещё потому, что нет соблазна просто спрятать сложно адаптируемое и обделить мобильных пользователей.

Ну как, стало понятно? Вот адаптивная, вот отзывчивая… М-м, нет, не очень. Из-за путаницы между техникой адаптивной раскладки и философией адаптивного веб-дизайна, из-за того, что все эти подходы прекрасно сочетаются и уже не проследить чёткую границу между ними — из-за всего этого, я плюнул на терминологическую чистоту и стал называть всё это адаптивным дизайном или адаптивной вёрсткой. Это понятие всегда было достаточно широким, чтобы вместить все остальные способы.

Да и принцип дискретной адаптации от размеров устройств и без резины имел смысл лет 10 назад. При сегодняшнем разнообразии устройств полагаться на конкретные размеры и жёстко переключаться между ними — значит однажды очень больно промахнуться. Притом, никто вам не мешает при полностью резиновой вёрстке дописывать адаптацию под нужные разрешения.

Ну и как теперь верстать? Просто! Сначала делаете мобильный резиновый макет, который хорошо вписывается в небольшие устройства. А когда размеры экрана или окна начинают расти — начинаете использовать появившееся пространство для улучшения интерфейса. Появляется боковая колонка, вторая, растут размеры картинок и подгружаются новые разрешения и так далее. То есть медиавыражения меняют стили не когда вы дошли до экрана самого модного телефона, а когда это нужно для содержимого сайта и удобства пользователя. Мы именно так и учим делать на интенсиве по продвинутой вёрстке.

Чтобы сделать хороший адаптивный сайт, нужно понимать много нюансов: вьюпорт, медиавыражения, адаптивные картинки и другое.
В чём разница между резиновой, адаптивной и отзывчивой вёрсткой? Как правильно их применять?
Обеспечение конфиденциальности и безопасности данных может быть реализовано на уровне каждой отдельной системы. Однако при объединении данных из разных систем необходимо создать соответствующую систему защиты. В противном случае данные могут быть подвержены риску нарушения конфиденциальности и безопасности, что может нанести ущерб организациям
Основываясь на проведённых исследованиях, оценках и анализе, примите взвешенное решение о выборе технологической платформы для вашего проекта.

Важно понимать, что универсального решения не существует. Речь идёт скорее о выборе оптимального решения, которое будет соответствовать конкретным потребностям вашего бизнеса и предоставит все необходимые инструменты для успешной реализации проектов.

Компании необходимо быть в курсе последних тенденций в выбранной области, чтобы обеспечить актуальность и безопасность проекта.

Любая из упомянутых технологий может стать подходящим решением для конкретных проектов. Главное, чтобы выбор был обоснован необходимыми характеристиками и преимуществами, которые получит ваш продукт.

Некоторые технологии становятся лидерами рынка и получают более широкое распространение, например, Node.js, ASP.NET и другие. Однако окончательное решение зависит от потребностей и предпочтений команды в отношении Node.js или его альтернатив.
Имхо, Node тут как спутниковый навигатор для авто: позволит создать оптимальный маршрут. Но если дороги загружены на 7+ баллов, то чуда конечно же не случится.
Почему-то вспомнилась реализация многозадачности в win 3.1 (или 95, но в меньшей мере) и та же беда с CPU ёмкими задачами, а появление модуля Worker Threads говорит том, что чуда не произошло.

Собственно, а существуют ли исследования о том, на сколько подобная экономия на «переключении задач» эффективна? Вот почему-то мне кажется, что значительное количество одновременно обслуживаемых клиентов у Node будет, если эти клиенты в основной массе простаивают (затраты на переключение задачи в многопоточной реализации сопоставимы с затратами на выполнение полезного действия), но в случае, когда все клиенты работают (затраты на переключения задачи малы по сравнению с затратами на выполнение полезного действия) эффект будет минимальный (если вообще будет).
Не работал с монгой, так что не дам комментариев по её поводу, но вот утверждение о том, что Оракл или постгрес блокируют поток — однозначно ложный. Если бы это было так, то применение этих СУБД с нодой было бы невозможным :)

Если внешняя система не поддерживает неблокирующий I/O, нода использует пул потоков, о котором в статье как раз говорится.
Спасибо, здесь действительно содержатся те факты про nodejs и worker threads, которые надо держать в уме, если хочется сконструировать на nodejs что-то более или менее нагруженное. Правда, для тех, кто интересуется платформой, эти факты, скорее всего, не будут новостью.

Тем не менее, совсем недавно мне рассказывали, что неблокирующим образом nodejs умеет обращаться только к MongoDB, а когда используются «старые» реляционные БД (Oracle, Postgres), то обращение к ним из nodejs все равно блокирует поток, потому что для них нету асинхронных неблокирующих драйверов (какие есть для монги). Может быть, кто-нибудь из экспертов откомментирует, так это или не так?
В наши дни платформа Node.js является одной из самых популярных платформ для построения эффективных и масштабируемых REST API's. Она так же подходит для построения гибридных мобильных приложений, десктопных программ и даже для IoT.

Я работаю с платформой Node.js более 6 лет и я на самом деле люблю её.

Мир до Node.js

Многопоточный сервер

Веб-приложения, написанные следуя клиент/серверной архитектуре, работают по следующей схеме — клиент запрашивает нужный ресурс у сервера и сервер отправляет ресурс в ответ. В этой схеме сервер, ответив на запрос, прерывает соединение.

Такая модель эффективна поскольку каждый запрос к серверу потребляет ресурсы (память, процессорное время и т.д.). Для того чтобы обрабатывать каждый последующий запрос от клиента, сервер должен завершить обработку предыдущего.

Значит ли это, что сервер может обрабатывать только один запрос за раз? Не совсем! Когда сервер получает новый запрос он создаёт отдельный поток для его обработки.

Поток, если простыми словами, это время и ресурсы, что CPU выделяет на выполнение небольшого блока инструкций. С учётом сказанного, сервер может обрабатывать несколько запросов одновременно, но только по одному на поток. Такая модель так же называться thread-per-request model.

Для обработки N запросов серверу нужно N потоков. Если сервер получает N+1 запросов, тогда он должен ждать пока один из потоков не станет доступным.

На рисунке выше, сервер может обрабатывать до 4 запросов (потоков) единовременно и когда он получает следующие 3 запроса, эти запросы должны ждать пока любой из этих 4 потоков не станет доступным.

Один из способов избавиться от ограничений — добавить больше ресурсов (памяти, ядер процессора и т. д.) на сервер, но это не самое лучшее решение….

И, конечно, не забываем о технологических ограничениях.

Блокирующий ввод/вывод

Ограниченное число потоков на сервере не единственная проблема. Возможно, Вам стало интересно почему один поток не может обрабатывать несколько запросов одновременно? всё из-за блокирующих операций ввода/вывода.

Допустим, Вы разрабатываете онлайн магазин и Вам нужна страница где пользователь может просматривать список всех товаров.

Пользователь стучится на yourstore.com/products и сервер рендерит HTML файл со всеми продуктами с базы данных в ответ. Совсем не сложно, да?

Но, что же происходит за кулисами?

— Когда пользователь стучится на /products особый метод или функция должна выполниться, что бы обработать запрос. Маленький кусочек кода (Ваш или фреймворка) анализирует URL-адрес запроса и ищет подходящий метод или функцию. Поток работает.
— Теперь нужный метод или функция выполняться, так как и в первом пункте — поток работает.
— Так как Вы хороший разработчик, Вы сохраняете все системные логи в файл, ну и конечно же, что бы быть уверенными, что роутер выполняет нужный метод/функцию — Вы так же логируете строку «Method X executing!!». Но всё это блокирующие операции ввода/вывода. Поток ждёт.
— Все логи сохранены и следующие строки функции выполняются. Поток работает снова.
— Время обращаться к базе данных и получать все продукты – простой запрос, вроде SELECT * FROM products, выполняет свою работу, но угадайте что? Да-да, это блокирующая операция ввода/вывода. Поток ждёт.
— Вы получили массив или список всех продуктов, но убедитесь, что Вы всё это залогировали. Поток ждёт.
— Теперь у Вас есть все продукты и пришло время рендерить шаблон для будущей страницы, но перед этим Вам нужно их прочитать. Поток ждёт.
— Движок рендеринга делает свою работу и шлёт ответ клиенту. Поток работает снова.
— Поток свободен, словно птица в небесах.
Операции сети и чтения с диска слишком медленные. Представьте сколько запросов или обращений к внешним API ваша система могла бы обработать за это время.

Подбивая итоги: операции ввода/вывода заставляют поток ждать и тратить ресурсы впустую.

Node.js мощная технология, которую стоит изучить при возможности.
Моя личная рекомендация – всегда будьте любопытными! Если Вы знаете, как что-то работает изнутри, Вы сможете работать с этим более эффективно.
Два комментария с Reddit по теме:
Консультант по SEO/a11y на проводе. Не могу передать, сколько раз я сталкивался с SEO-специалистами и/или клиентами, которые клялись, что вам нужен SSR, чтобы поисковые роботы правильно индексировали ваш SPA. Это не так уже почти десять лет. Есть множество гигантских Ecommerce брендов, которые используют SPA уже много лет и имеют чрезвычайно высокие показатели SEO (на ум приходит Walmart). Анекдотично, я лично руководил конверсией сайта .NET MVC Ecommerce со 100 000+ SKU на Vue 2 SPA, и мы действительно увидели улучшение наших SEO-метрических показателей. Это было 8 лет назад.

Мы обычно исходим из следующих соображений:

— Является ли HTML семантическим и доступным (a11y)?

— Предоставляется ли схема через JSON+LD и/или теги?

— Соответствуют ли основные показатели сайта (core web vitals) требованиям?

— Требует ли контент действий пользователя перед загрузкой? Например, если навигация по категориям находится в меню, который по умолчанию скрыт, то такая навигация может не индексироваться. В общем случае, если что-то может быть сделано через SSR, то это же также может быть отображено на стороне клиента и проиндексировано.

Все это Google укажет в Search Console / Lighthouse, чтобы сообщить вам о наличии проблемы.

I keep reading advice about how necessary it is to use SSR for SEO, but that advice does not match my personal experience. That advice was probably valid years ago, before search engines became good at running Javascript.

From what I have seen personally, Bing is pretty good at running Javascript too.
Не понимаю, в чём сложность. Нужен сайт на JavaScript есть SSR. Он хорошо дружит с SEO (сужу по опыту компаний и по своему тоже), всё индексируется и продвигается. Не хочется постоянно запекать страницы, ну используйте ISR

Ах да, Angular Universal с ним не дружит, с ISR. Ну тогда вам придётся выбирать между Next.js или Nuxt.js (Ещё конечно можно использовать VuePress или VitePress, на них можно не только документации делать, ну и вполне себе хорошие сайты с динамическими компонентами и всё это хорошо для SEO — проверял)

Кстати, это не сарказм, это опыт, я попробовал, и у меня есть результаты. Да и не только у меня, у всех кто это пробовал тоже есть положительные результаты. Для профи там ничего сложного это всё настроить.

Если title статичный, значит там что-то с промисами начудили, или забыли убрать статические данные.

SEO оптимизация — это что-то среднее между backend и frontend, поэтому фронтенд разработчику сложно настроить семантическое ядро и учесть другие фишки и инструменты SEO. Знание OpenGraph и Schema.org — это далеко не всё. Я уже молчу про генерацию robots.txt и sitemap.xml (Столько тонкостей надо знать, это кстати легче поймёт бэкендер)

Про бэкенд я услышал только про PHP/ — так, уровень знаний мне понятен, стало очевидно, почему SSR для автора проблема. Если знать как работает контекст в динамических страницах SSR, то проблемы автоматически улетучиваются, и в настройках можно явно указать что показать, если такой страницы не будет, можно показать ошибку 404 или не отображать страницу вовсе, или будет ошибка при компиляции. Жаль что не упомянули про Rust, Java, С#, Python — на них же тоже можно сделать backend

Мне на самом деле сложно судить без Code Review
(чтобы понять почему автор не возлюбил SSR)
По теме. Буквально месяц назад стоял выбор — какую архитектуру использовать для SEO. Остановился на SSG… Как будто штаны через голову натягиваешь)) А уж SSR городить… Чур меня!

А вообще забавные кульбиты прогресс вытворяет — вот вам технология, которая может полноценно отображать весь контент без перезагрузки страницы… а, стоп! вам нужна статика для поисковых ботов? Тогда держите новую технологию, которая позволяет одностраничное приложение распарсить в виде многостраничной статики. Что, опять не так? Теперь нужна технология, которая SSR будет обратно в SPA собирать?..

И после каждой такой неоднозначной технологии начинаются холивары под лозунгом «даёшь всё новое!». Что, вы собрались манипулировать DOM во Vue-приложении? Это совершенно не так работает — уясните для себя что такое «декларативная отрисовка». Вы совершенно не понимаете методологии реактивных фреймворков!

Выходит Vue3 — Держите то, о чём вы так долго просили — ref-ссылки для манипуляции DOM…

Заявка на услуги DST

Наш специалист свяжется с вами, обсудит оптимальную стратегию сотрудничества,
поможет сформировать бизнес требования и рассчитает стоимость услуг.

Адрес

Ижевск, ул. Воткинское шоссе 170 Е.
Региональный оператор Сколково. Технопарк Нобель

8 495 1985800
Заказать звонок

Режим работы: Пн-Пт 10:00-19:00

info@dstglobal.ru

Задать вопрос по почте

Укажите ваше имя
Укажите ваше email
Укажите ваше телефон