Пул задач что это

Асинхронный многопоточный пул воркеров на Perl

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

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

Понятное дело, хорошо, когда все задачи выполняются быстро и без проволочек.

Для ускорения выполнения задач желательно решить две проблемы:

Модуль AnyEvent

Для программирования в асинхронном режиме в Перле есть отличный модуль AnyEvent.

На всякий случай следует сказать, что на самом деле AnyEvent является оберткой над другими низкоуровневыми асинхронными модулями. Как DBI является оберткой и универсальным интерфейсом к разным базам данных, так и AnyEvent является оберткой и универсальным интерфейсом к различным реализациям асинхронных движков.

Для AnyEvent имеется огромное количество всевозможных расширений, в том числе есть и расширение для написания многопоточных приложений — модуль AnyEvent::Fork::Pool.

Модуль AnyEvent::Fork::Pool предоставляет простой способ создания пула воркеров, которые будут обрабатывать задачи в асинхронном многопоточном режиме.

Скрипт

Рассмотрим скрипт anyevent_pool.pl:

Несмотря на небольшой объем, этот скрипт представляет собой полноценное асинхронное многопоточное приложение.

Разберем его по частям.

Переменные

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

Например, у вас может быть модуль Text для обработки текста, а в модуле функции length и trim. И еще у вас может быть модуль Image, в котором могут быть функции resize и crop. Пулу совершенно без разницы, что делают ваши функции и как они устроены. Вам нужно просто сказать пулу, в каком модуле они находятся и как они называются, и пул их выполнит.

Важно! Модуль воркера не нужно подключать в скрипте через «use Worker». Пул сам автоматически подгрузит модуль воркера, вам нужно только правильно указать название модуля в переменной.

Количество ядер

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

Если по каким-то причинам количество ядер не удалось определить, то будет использоваться значение, указанное вручную. В данном случае это 1.

Пояснения к параметрам:

Постановка задач пулу

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

В нашем случае функция-коллбэк просто печатает все, что она получает.

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

Запуск движка

Эта строка говорит модулю AnyEvent, что нужно запустить в работу событийный движок и далее работать бесконечно.

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

Сам воркер

Теперь возникает вопрос — а где же, собственно, сам воркер? Где код, исполняющий непосредственно работу?

Вот код модуля Worker:

Как видите, в модуле две функции — init и work.

Функция init инициализирует воркер. В нашем случае функция открывает лог-файл, в который далее будут выводиться результаты работы рабочей функции work. Как уже говорилось выше — функция init является необязательной, в нашем случае я сделал ее просто для наглядности.

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

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

Вот, собственно, и весь код.

Запускаем пул

Теперь запустим наш пул и посмотрим, что получится:

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

Тут мы видим результаты работы пула. Можно заметить, что порядок вывода результатов отличается от порядка строк, заданного в цикле в скрипте. Причина понятна — у строк разная длина, поэтому воркеры обрабатывают строки с разной скоростью. Чем проще задача — тем она быстрее выполняется.

Теперь посмотрим не просто на результаты, но и на процесс работы воркеров. Для этого во втором окне запустим tail для лог-файла:

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

Обратите внимание — результаты работы выводятся вперемешку, так-как задачи выполняются одновременно. Слева видны идентификаторы процессов — видим, что задействованы 4 процесса. У меня в системе 4 ядра, поэтому одновременно выполняются все 4 задачи.

И, наконец, посмотрим на таблицу процессов:

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

Так выглядит дерево процессов нашего пула.

Первым в списке идет скрипт, далее менеджер пулов (да-да, пулов может быть несколько штук), потом менеджер пула, и, наконец, воркеры.

Если не полениться и сравнить идентификаторы процессов, то можно увидеть, что идентификаторы воркеров совпадают с идентификаторами в лог-файле.

Источник

Не диспансеризация, а чекап: как мы формализовали проведение аудитов инфраструктуры

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

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

— А вы примерно понимаете, что является причиной такой низкой скорости приложений?

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

Это один из сценариев, когда начинать нужно с технического аудита. По итогам он дает возможность, например, не перекраивать всю инфраструктуру, а вносить тонкие настройки, которые помогут жить эффективно с тем же пластом бизнес- приложений.

Слова «аудит» многие пугаются. Думают, будто это длительный и сложный процесс, который потребует от ИТ-директора полного вовлечения. Поэтому мы все чаще говорим про «обследования» или даже «чекапы» — аудиты минимум на половину автоматизированные. Ниже расскажем, в каких еще случаях такие аудиты могут быть полезны компаниям и какие методики помогают выжать максимум пользы из них при достаточно небольших затратах.

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

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

Со временем мы пришли к пониманию, что эту индивидуальность можно и нужно формализовать. Причем сделать это так, чтобы удобно было всем сторонам: и тем, кто аудит проводит, и бизнесу, и ИТ.

Наша методика позволяет вне зависимости от особенностей проекта подходить к каждому централизованно. Но обо всем по порядку.

Определяем пул задач

Первое и самое главное — разобраться в причинах, побуждающих компании на проведение аудита.

Ситуации отранжированы по частоте возникновения:

Ситуация

Ранг

Наличие ограничений в виде требований закона/регулятора

Смена ИТ-руководства или ощутимое обновление ИТ-команды

Обязателен процесс обоснования ИТ-решения и бюджета

Реализуется стратегия снижения затрат

Снижение доступности и производительности информационных систем

Планируется/В процессе/Завершен процесс слияния и/или поглощения (M&A)

Бизнес находится/планируется стадия ощутимого роста

Осуществляется/Планируется внедрение информационной системы/информационных технологий

В наличии недоверие бизнеса к ИТ-команде

Вот основные задачи аудита:

Инвентаризация имеющейся инфраструктуры

Выявление «узких» мест и оптимизация ИТ-инфраструктуры

Определение причин и устранение проблем с производительностью прикладных информационных систем

Передача части процессов по поддержке и обслуживанию инфраструктуры на аутсорсинг

Обоснование ИТ-решения или ИТ-бюджета

Разработка планов миграции инфраструктуры в облако

Разработка планов Disaster Recovery (DRP)

Замена/модернизация парка устаревшего оборудования

Разработка планов (стратегии) развития ИТ

В рамках одного аудита может решаться как одна, так и несколько задач.

Посмотрим, какие реальные ситуации нередко толкают к проведению аудита.

Проблемы с утилизацией ИТ-ресурсов

Часто у заказчиков случаются проблемы либо с производительностью, либо с загруженностью ресурсов. Классическая для on-premise ситуация — есть N серверов, каждый из них используется бизнесом процентов на 30-50. Полную загрузку создавать нельзя, должен быть запас на пики и прочие «из ряда вон» моменты, но и слишком низкая утилизация — тоже проблема.

Нередко бывает и такое, что бизнес неверно оценивает свои задачи, ошибается в расчетах, и часть мощностей простаивает. Причины могут быть разные, здесь это не так важно. Главное — деньги заказчика могут тратиться впустую. У нас были ситуации, когда в ходе аудита инфраструктурных ресурсов мы внимательно изучали все что можно — от ВМ до СХД — и приходили к выводу, что, если перераспределить ресурсы между ВМ, освободится до 30% общих мощностей. Не нужно покупать новое оборудование, можно переиспользовать то, что есть.

Миграция

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

А если переезд сопряжен с модернизацией и масштабированием — изучаем, как это сделать правильно.

Миграция в облако также осуществляется по этой методологии — отличается только набор галочек в опроснике.

У нас был кейс с одним иностранным банком. Переезд в облако был продиктован решением главного офиса. Соответственно, в рамках аудита важно было понять, насколько облако совместимо с текущей инфраструктурой, весь ли функционал останется доступен. Опять же, возникли вопросы утилизации и порядка переноса систем.

Эксплуатация

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

Ох уж это жадное ИТ-подразделение

Часто случается, что бизнес обвиняет ИТ-департамент едва ли не во всех грехах: и жадничают, и обманывают, и работать не умеют. Задача внешнего эксперта — определить, в чем проблема. И, главное, есть ли она на самом деле. Как-то мы работали с одной крупной сетью магазинов, в которой де-факто сосуществовало сразу два ИТ-департамента. Первый — основной. Второй — вырос из другого отдела, размножился и понемногу оброс собственными задачами, собственным оборудованием. Руководителей интересовало, как так вообще вышло насколько это рентабельно: стоимость затрат непрозрачна, деньги уходят, финансовая нагрузка постоянно растет. По итогам аудита выяснилось, что из 200 систем 90% денег «съедали» всего 5. Возник вопрос, почему эти 5 стоят так дорого и зачем тогда нужны еще 195?

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

Новая метла — новый аудит

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

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

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

Определяем пул проверяемых систем и компонентов

Наш подход к методологии проведения аудитов чем-то похож на тот, что принят в медицине:

Сетевухи, вроде бы, барахлят.

Ну-с, молодой человек, держите направление…

*окидывает пациента профессиональным взглядом и дает направление на чекап*

Небольшой интерактивный опросник позволяет отметить «галочками» проблемы и задачи и на лету формирует набор систем, которые надо проверять. Притом сразу с оценкой стоимости.

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

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

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

​ Системы хранения данных и сеть хранения данных

​ Система резервного копирования

​ Корпоративная сеть передачи данных

​ Локальная вычислительная сеть

​ Беспроводная локальная сеть (WiFi)

Инженерные системы и системы жизнеобеспечения

Базовые инфраструктурные сервисы

Каждый блок можно дополнительно детализировать, например, «Системы хранения данных и сеть хранения данных»:

СХД типа «Mid-range entry-level»

Набор обследуемого в каждом блоке оборудования может быть ограничен в зависимости от задачи аудита. Например, при локализации проблем производительности конкретной ИС обследование ограничивается только тем оборудованием, которое задействовано в работе этой ИС.

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

Определяем направление обследования

Опыт проведения аудитов позволил нам проанализировать все параметры, которые необходимо проверять в рамках обследований, и сформировать из них следующие направления. Список ниже применим к каждому инфраструктурному блоку, отобранному на предыдущем этапе.

Общая информация и архитектура

Сводная информация по производителю, модели, конфигурации, базовым характеристикам и архитектуре. На выходе – полная инвентаризационная информация и оценка архитектур на наличие грубых ошибок и соответствие «лучшим практикам».

HealthCheck

Анализ состояния аппаратной части, выявление сбоев и поломок, сообщений встроенных систем мониторинга. Определение мер по локализации и устранению неисправностей.

Отказоустойчивость

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

Утилизация и производительность ресурсов

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

Масштабируемость

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

Актуальность ПО

Определение установленных версий микрокодов, ОС, системного ПО. Оценка необходимости обновления с точки зрения актуальности, поддержки вендорами, наличия критических проблем.

Управление и мониторинг

Проверка покрытия системами мониторинга (при наличии), глубина и степень детализации собираемых метрик. На выходе – оценка достаточности собираемых мониторингом параметров и метрик для полноценного наблюдения за работоспособностью и своевременного реагирования на сбои.

Резервное копирование

Проверка покрытия системой резервного копирования (при наличии). Оценка адекватности частоты резервного копирования, продолжительности хранения копий, достаточности окна резервного копирования. Выявление сбоев создания копий.

Эксплуатация

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

Импортозамещение

Оценка возможности замены и миграции на аналоги в рамках стратегии импортозамещения.

Проведение обследования

Чек-лист обследования — это набор направлений обследования для выбранных инфраструктурных блоков. Выглядит это, как матрица, где в колонках инфраструктурные элементы, а в строках направления обследования. Каждая ячейка — это типовое обследование, включающее:

Чек-лист показателей, входящих в состав критерия «лучших практик», значения которых требуется определить.

Критерий «лучших практик», определяющий нормальное состояние элемента инфраструктуры по каждому конкретному направлению обследования. Эталонные значения параметров критерия сформированы на основе рекомендаций вендоров и опыта КРОК полученного в более чем 2000 инфраструктурных проектах.

Набор инструментария для определения значений показателей (специальные утилиты, опросники, инструкции по анализу логов и т.д.)

Заключение эксперта о причине выявленного отклонения от «нормы», сформированного путем сравнения полученных значений показателей с критерием.

Ниже представлен пример одного из Критериев «Лучших практик» по направлению «Утилизация и производительность ресурсов» для инфраструктурного блока «Серверы»:

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

Полученные по всем направлениям и инфраструктурным блокам результаты, сводятся в общую «тепловую» матрицу, и эксперт делает общее заключение по каждому инфраструктурному блоку и по инфраструктуре в целом, а также дает рекомендации по исключению проблемных «зон».

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

Ретроспектива ноу-хау

Раньше аудит был в некоторой степени творческим, креативным процессом, который отнимал много времени. Соответственно, результат сильно зависел от эксперта, ответственного за аудит.

Сейчас наличие методики снижает влияние человеческого фактора на результаты аудитов: при наличии формальных критериев «нормально/плохо» вероятность ошибки стремится к нулю. Кроме того, такая формализация процесса помогает нам максимально автоматизировать и ускорить обследование — в том числе силами самого заказчика. Используя наши наработки, он его может провести самостоятельно.

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

Источник

Понимаем соединения и пулы

Прим. перев.: автор этой статьи — технический архитектор Sudhir Jonathan — рассказывает об одном из тех базовых механизмов, с которым сталкивается каждый пользователь, разработчик и системный администратор. Однако до возникновения определённых (и иногда довольно специфичных) проблем многие не задумываются о том, как всё работает «под капотом». Автор устраняет этот пробел, используя популярные фреймворки, серверы БД и приложений в качестве понятных примеров.

Пул задач что это. Смотреть фото Пул задач что это. Смотреть картинку Пул задач что это. Картинка про Пул задач что это. Фото Пул задач что это

Соединения — это скрытый механизм, который компьютерные системы используют для общения друг с другом. Они стали настолько неотъемлемой частью нашей жизни, что мы часто забываем, насколько они важны, не замечаем, как они работают и терпят неудачу. Часто мы забываем о них до тех пор, пока не возникает проблема. При этом обычно она проявляется массовым отказом именно в то время, когда системы загружены сильнее всего. Поскольку соединения встречаются повсюду и они важны практически для каждой системы, стоит потратить немного времени на их изучение.

Соединения — что это?

Соединение — это связующее звено между двумя системами, позволяющее им обмениваться информацией в виде последовательности нулей и единиц: посылать и принимать байты.

В зависимости от того, как расположены системы по отношению друг к другу, комбинация нижележащего программного и аппаратного обеспечения активно работает, чтобы обеспечить физическое перемещение информации, абстрагируя ее. Например, при взаимодействии двух Unix-процессов за выделение памяти для обмена данными и приём/доставку байтов с обеих сторон отвечает система межпроцессного взаимодействия (IPC). Если системы расположены на разных компьютерах, они скорее всего будут взаимодействовать по протоколу TCP, который и обеспечит перемещение данных по проводной или беспроводной системе связи между компьютерами. Детали совместной работы компьютеров для надёжной обработки, передачи и приёма данных скорее относятся к проблеме стандартизации, и большинство систем используют базовые блоки, предоставляемые протоколами UDP и TCP. То, как эти соединения обрабатываются на каждом из концов, является более актуальной проблемой для разработки приложений. О ней мы и поговорим сейчас.

Где используются соединения?

Соединения используются прямо сейчас. Ваш браузер установил соединение с веб-сервером, на котором размещен этот блог, и по нему получил байты, составляющие HTML, CSS, JavaScript и изображения, на которые вы сейчас смотрите. При работе по протоколу HTTP/1.1 браузер устанавливал множество соединений с сервером — по одному для каждого файла. Протокол HTTP/2 позволил получить все файлы по одному соединению (с помощью мультиплексирования). Во всех этих случаях браузер выступал клиентом, а сервер блога, собственно, был сервером.

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

Организовать «отгрузку» статических файлов, таких как JS, CSS и изображения, помогает CDN-система, расположенная между браузером и сервером блога. Браузер (клиент) установил соединение с ближайшим сервером CDN, и, если нужных файлов не оказалось в кэше CDN-сервера, тот (выступая как клиент) связался с сервером блога (сервер).

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

Почему важна обработка соединений?

Понимание того, как именно ведется работа с соединениями, важна, поскольку их стоимость асимметрична: издержки, связанные с созданием соединения, отличаются на стороне клиента и сервера. В одноранговой (P2P) системе это не так, и соединения имеют одинаковую «стоимость» на обоих концах, но такое бывает редко. Типичное использование соединений всегда предполагает наличие клиента и сервера, при этом издержки, связанные с созданием соединения, различны на стороне клиента и сервера.

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

При старте программы операционная система запускает код как один экземпляр процесса. Во время работы процесс занимает одно ядро CPU и некоторый объём памяти, и не делится своей памятью ни с какими другими процессами.

Процесс может запускать так называемые threads (потоки выполнения) — дочерние элементы процесса, способные работать параллельно. Потоки используют память совместно с процессом, который их породил (тот может выделять больше памяти для их использования).

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

Другой способ предполагает использование внутренних конструкций, таких как fibers (файберы), green-threads (зелёные потоки), coroutines (сопрограммы) или actors (акторы). Каждая из этих конструкций чуть отличается от остальных (в том числе, в смысле издержек), но все они внутренне управляются процессом и его потоками.

Возвращаясь к обработке соединений, давайте сначала рассмотрим подключения к базе данных. Для сервера приложений (клиента в данном случае) установка TCP-соединения связана с выделением небольшого объёма памяти для буфера и одного порта.

Если используется PostgreSQL, на стороне сервера каждое соединение обрабатывается путём создания нового процесса, который занимается всеми запросами, поступающими по данному соединению. Такой процесс занимает ядро процессора и около 10 Мб памяти (или больше).

MySQL для обработки каждого подключения создаёт поток внутри процесса. Требования к памяти гораздо ниже в потоковой модели, но платить за это приходится постоянным переключением контекстов.

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

Представьте себе запрос к серверу приложений. Браузер инициирует TCP-соединение в качестве клиента (это ему обходится дёшево — небольшой объём памяти для буфера и один порт). На стороне сервера ситуация совершенно иная:

Если сервер использует Ruby on Rails, каждое соединение обрабатывается одним потоком, порождённым внутри фиксированного числа запущенных процессов (в случае веб-сервера Puma), или одним процессом (Unicorn).

Если используется PHP, система CGI запускает новый PHP-процесс для каждого соединения, а более популярная реализация FastCGI поддерживает несколько активных процессов, чтобы ускорить обработку новых соединений.

В случае Go для обработки каждого соединения создается goroutine (дешёвая и легковесная потокоподобная структура, управляемая & планируемая исполняемой средой Go).

В Node.js/Deno входящие соединения обрабатываются в цикле событий путём последовательного перебора и ответа на запросы по одному за раз.

В системах вроде Erlang/Elixir каждое соединение обрабатывается актором — ещё одной легковесной и внутренне-планируемой потокоподобной конструкцией.

Архитектуры обработки соединений

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

Процессы. Каждое соединение обрабатывается отдельным процессом, который либо создается специально для этого соединения (CGI, PostgreSQL), либо входит в некую группу доступных процессов (Unicorn, FastCGI).

Потоки. Каждое соединение обрабатывается отдельным потоком, который либо специально создаётся, либо берётся из специального резерва. Потоки могут быть распределены по нескольким процессам, при этом все потоки эквивалентны между собой (Puma/Ruby, Tomcat/Java, MySQL).

Цикл событий. Каждое соединение включается в цикл событий в виде задачи, и соединения с данными для чтения обрабатываются последовательно (Node, Redis). Обычно подобные системы — однопроцессные и однопотоковые, но в некоторых случаях бывают многопроцессными, когда каждый процесс действует как полунезависимая система с отдельными циклами событий.

Coroutines / Green-Threads / Fibers / Actors. Каждое соединение обрабатывается легковесной конструкцией с внутренним управлением (Go, Erlang, Scala/Akka).

Представление о том, как сервер обрабатывает соединения, имеет решающее значение для понимания его ограничений и моделей масштабирования. Даже базовое использование или настройка требуют понимания того, как обрабатываются соединения: Redis и PostgreSQL, например, предлагают различную семантику транзакций и блокировок, на которую влияют их соответствующие механизмы обработки соединений. Серверы, основанные на процессах и потоках, могут «упасть» из-за исчерпания ресурсов, если их максимальное количество не задано в разумных пределах. С другой стороны, установка пределов может привести к масштабному недоиспользованию серверов из-за слишком низких лимитов. Системы, основанные на циклах событий, ничего не выигрывают от работы на 64-ядерных CPU (если, конечно, их 64 копии не настроены на совместную работу — что отлично работает в случае веб-серверов, но плохо подходит для баз данных).

Каждый из этих способов обработки соединений по-разному проявляет себя при использовании в серверах приложений и базах данных из-за распределённой или централизованной природы каждой системы. Например, серверы приложений, как правило, хорошо подходят для горизонтального масштабирования — они работают нормально и одинаково независимо от того, один ли у вас сервер, 10 или 10000. В этих случаях отказ от модели процессов/потоков обычно приводит к росту производительности, поскольку мы хотим выполнить как можно больше работы с минимальным использованием памяти и переключением контекста процессором.

Подходы на циклах событий вроде Node отлично зарекомендовали себя на одноядерных серверах, и для использования на многоядерных серверах их необходимо кластеризовать правильным образом. Системы, основанные на сопрограммах/акторах, такие как Go или Erlang, гораздо легче задействуют ядра процессора, так как разработаны специально для этого: на одной машине параллельно могут работать многие тысячи горутин и акторов.

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

На крупном сервере PostgreSQL может управлять несколькими сотнями соединений, в то время как MySQL способен обрабатывать пару тысяч. Redis способен обрабатывать наибольшее количество соединений (возможно, десятки тысяч), поскольку цикл событий помогает ему поддерживать согласованность данных, но платить за это приходится тем, что одновременно выполняется только одна операция.

Распределённые базы данных могут и будут пытаться отойти от модели, основанной на процессах и потоках. Поскольку данные распределяются по нескольким машинам, от блокировок обычно отказываются в пользу секционирования (partitioning). Такие базы данных способны поддерживать множество соединений между большим числом серверов. Например, AWS DynamoDB или Google Datastore, а также распределенные БД, написанные на Go, с готовностью примут миллионы или даже миллиарды одновременных подключений.

Однако все эти решения имеют последствия — они жертвуют многими операциями (join’ы, ad-hoc-запросы) и гарантиями согласованности, предоставляемыми централизованными/односерверными базами данных. Но, идя на эту жертву, они получают возможность обрабатывать соединения секционированным, горизонтально масштабированным, практически неограниченным способом, позволяя выбирать конструкцию, которая поддерживает множество соединений на множестве машин. Проблема соединений в данном случае снимается: каждый отдельный сервер должен сам заботиться об их обработке, но в совокупности, с тысячами и миллионами машин с умной маршрутизацией подключений, данные системы часто ведут себя так, словно они бесконечно масштабируемы.

Что такое пул и зачем он нужен?

«Дороговизна» соединений требует их эффективного и экономного использования. Часто бывает сложно понять, насколько дорого они обходятся. Связано это с асимметрией: с точки зрения клиента соединение стоит дёшево, и обычно от их чрезмерного числа страдает именно сервер.

В процессе работы клиент не может позволить себе роскошь использовать отдельное соединение для каждой операции. Например, сервер приложений как клиент подключается к базе данных. Устанавливая для каждого запроса новое соединение, он искусственно ограничил бы себя возможностями БД (к которой подключается) по обработке соединений. В некоторых случаях подобный способ работы абсолютно эффективен — например, если сервер приложений является прокси-сервером для базы данных. В реальности серверы приложений выполняют кучу другой работы: ожидают поступления запрашиваемых данных на сервер, анализируют его, формулируют запрос, пересылают его в БД по соответствующему соединению, ждут результатов, считывают и обрабатывают их, преобразуют выходные данные в формат HTML/JSON/RPC, посылают сетевые запросы другим сервисам, и т.д. Основную часть времени соединение простаивает — другими словами, дорогостоящий ресурс используется неэффективно. И это мы ещё не учли издержки, связанные с созданием соединения (запуск процесса, аутентификация) и завершением его работы на стороне сервера.

Чтобы повысить эффективность использования соединений, многие клиенты баз данных используют так называемые connection pools (пулы соединений). Пул — это объект, самостоятельно обслуживающий некоторый набор соединений, не предусматривающий прямого доступа или использования. Пул выдает соединения, когда необходимо связаться с базой данных. Соединения возвращаются в пул после завершения работы. Пул может быть инициализирован с заданным числом соединений, или может наполняться по необходимости. Идеальное применение пула соединений выглядит следующим образом: код запрашивает соединение у пула (checkout), когда оно ему необходимо, использует его и сразу возвращает в пул (release). Таким образом, код не удерживает соединение, пока выполняется работа, никак с ним не связанная, что существенно повышает эффективность. Это позволяет выполнять множество различных задач с использованием одного или нескольких соединений. Если все соединения в пуле заняты, когда происходит очередной запрос (checkout), запрашивающей стороне обычно приходится ждать (block), пока соединение не освободится.

Механизм пулов по-разному реализован в различных языках и фреймворках:

Ruby on Rails, например, автоматически выделяет и возвращает соединения в пул, при этом непонимание нюансов данного процесса приводит к неэффективному коду. Представьте, что совершается запрос к базе данных, за которым следует длинный сетевой запрос к другому сервису, и еще один запрос к БД. В этом случае соединение простаивает во время выполнения сетевого запроса (автоматическое управление Rails должно быть консервативным и осторожным, а потому — неэффективным).

В Go есть драйвер для работы с БД, входящий в стандартную библиотеку, который поддерживает автоматическую работу с пулом соединений. Однако неучет того, что соединения возвращаются в пул между обращениями к БД, приводит к неожиданным и трудно воспроизводимым багам. Иногда разработчики исходят из предположения, что последовательные операции в одном и том же запросе идут по тому же соединению, однако автоматический менеджер случайным образом выбирает соединения из пула (гейзенбаг в Go, связанный с Postgres advisory locks).

Транзакции усугубляют данную проблему: базы данных часто привязывают функционал транзакции к соединению (иногда это называют session — сессией). Начав транзакцию, вы можете её зафиксировать (commit) или откатить (roll back) только по тому же соединению, на котором она изначально запускалась. Автоматическое управление пулом должно учитывать этот фактор с тем, чтобы не вернуть соединение в пул во время выполнения транзакции. В зависимости от базы данных другие функции, такие как блокировки и подготовленные выражения (prepared statements), также могут иметь привязку к соединениям.

Так что, если мы хотим писать эффективный код, нам необходимо знать, как работает пулинг соединений в используемом фреймворке, какие действия выполняются автоматически, и когда автоуправление не работает или его применение контрпродуктивно. Pooling-прокси (вроде pgBouncer, Odyssey или AWS RDS Proxy) — один из инструментов, помогающий забыть об этих нюансах. Эти системы позволяют создавать столько соединений с базой данных, сколько нужно, не заботясь об управлении ими, поскольку выделяемые соединения не настоящие, а их «дешёвая» имитация, не требующая значительных ресурсов. Когда клиент пытается воспользоваться одной из имитаций, pooling-прокси извлекает настоящее соединение из внутреннего пула и сопоставляет имитацию с реальным соединением. Когда прокси замечает, что соединение больше не используется, он оставляет открытым соединение с клиентом, но агрессивно освобождает и повторно использует соединение с базой данных. Число соединений и уровень «агрессивности» можно настроить, в том числе с учётом таких нюансов, как транзакции, подготовленные выражения и блокировки.

Решите ли вы оптимизировать свой код и включить в него эффективное управление соединениями, или выберете инструмент вроде pgBouncer, в конечном счёте будет определяться требуемым компромиссом между производительностью и сложностью развертывания. Любой вариант подойдёт в зависимости от того, сколько кода вы готовы написать, насколько удобно реализовано управление соединениями в используемом языке программирования и насколько эффективным должен быть проект.

Pooling не ограничивается клиентами баз данных. Мы называли соединения на стороне клиента «дешёвыми», но — увы — они не бесплатны. Они тоже используют память, порты и файловые дескрипторы на стороне клиента — ресурсы, которые ограничены. По этой причине многие языки/библиотеки имеют пулы для HTTP-соединений с одним и тем же сервером, а также используют пулы для других ограниченных ресурсов. Как правило, эти пулы скрыты от глаз до тех пор, пока система не исчерпает ограниченный ресурс, и в этот момент она обычно падает. Знание о подобной специфике сильно помогает при отладке — помимо соединений, виновниками проблем часто выступают файловые дескрипторы.

Настройка общих пулов

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

1. Обработка на основе процессов и потоков

Puma, популярный сервер для запуска Ruby-приложений, предлагает пару механизмов для управления обработчиками входящих HTTP-запросов. Первый механизм — это число запускаемых процессов, представленное в конфигурации директивой workers. Каждый процесс сервера независим и загружает полный стек приложения в память. Таким образом, если приложение занимает N Мб памяти, необходимо убедиться, что на машине по крайней мере доступно workers×N Мб памяти для запуска всех копий. Есть способ смягчить эту проблему: Ruby 2+ поддерживает механизм копирования при записи (copy-on-write), позволяющий запускать множество процессов как один и потом разветвлять его на нужное число без необходимости копировать всю память — общие области памяти будут использоваться совместно до тех пор, пока в них не внесут изменения. Активация функции copy-on-write с помощью директивы preload_app! может помочь снизить потребление памяти по сравнению с приведенной выше формулой (workers×N). Однако не стоит возлагать на неё слишком большие надежды, не протестировав предварительно поведение при длительных нагрузках.

Серверы, основанные исключительно на процессах, такие как Unicorn, останавливаются на этом уровне конфигурации — как и популярные серверы для Python, PHP и других языков, использующие глобальную блокировку или построенные по схеме один поток/один процесс. Каждый процесс может обрабатывать один запрос за раз, но это не гарантирует полную загрузку: пока процесс дожидается выполнения запроса к БД или сетевого запроса к другому сервису, он не будет обрабатывать другие запросы, и соответствующее ядро процессора будет простаивать. Для устранения этой проблемы можно запустить больше процессов, чем имеется ядер CPU (что приведёт к издержкам, связанным с переключением контекста), или задействовать потоки.

Что приводит нас ко второму механизму, который предлагает Puma — числу потоков для запуска в каждом процессе/worker’e. Директива threads позволяет настроить минимальное и максимальное число потоков в пуле потоков каждого worker’а. Использование двух этих директив позволяет контролировать общее число потоков, которые будут действовать как параллельные обработчики запросов для приложения. Очевидно, оно равно произведению числа worker’ов на число потоков.

Эмпирическое правило состоит в том, что обычно на каждое доступное ядро CPU приходится по одному worker’у — при условии, конечно, что для этого достаточно памяти. Это позволяет эффективно использовать память, так что можно прикинуть, сколько RAM нужно, проведя несколько тестов с этим номером. Теперь желательно полностью задействовать CPU — сделать это можно, увеличивая максимальное количество потоков. Как помните, потоки используют память совместно с процессом, так что увеличение их числа не слишком отражается на потреблении памяти. Вместо этого большее число потоков будет всё сильнее загружать процессор, попутно позволяя обрабатывать больше запросов одновременно. Это разумно, поскольку, пока один поток спит в ожидании результатов запроса к БД или сетевого запроса, ядро CPU может переключиться на другой поток из того же процесса. Но помните, что большое число потоков приведёт к конкуренции за блокировки процессов, так что вам придется опытным путем установить, сколько потоков можно добавить для значимого увеличения производительности.

Но как все эта конфигурация влияет на количество соединений с базой данных? Rails использует автоматическое управление подключениями к базе данных, так что каждому запущенному потоку потребуется собственное подключение к базе данных для эффективной работы без ожидания, когда работу завершат другие потоки. Он поддерживает пул соединений (его настройки хранятся в файле database.yml и применяются на уровне процесса/worker’а). Таким образом, если оставить значение по умолчанию, равное 5, Rails будет поддерживать максимум 5 соединений для каждого worker’а. Такой лимит не слишком хорошо сработает, если изменить максимальное число потоков — все они будут бороться за эти пять соединений в пуле. Эмпирическое правило состоит в том, чтобы сделать число соединений в пуле равным максимальному количеству процессов, как рекомендовано в руководстве по развертыванию Heroku Puma.

Но тут возникает другая проблема: количество соединений, равное произведению числа worker’ов на число потоков (workers × threads), благотворно влияет на производительность сервера приложений, но совершенно не подходит для базы данных вроде PostgreSQL и, в некоторых случаях, для MySQL. В зависимости от того, сколько у вас оперативной памяти (в случае Postgres) и сколько CPU (в случае MySQL), данная конфигурация может не сработать. Можно уменьшить объём пула (pool) или значение threads, чтобы сократить число соединений, или число worker’ов, или оба этих параметра. В зависимости от конкретного приложения, все эти решения, вероятно, будут иметь один и тот же эффект: если каждый запрос требует обращений к базе данных, количество подключений к БД всегда будет выступать узким местом, ограничивая число запросов, которые реально обработать. Но если некоторые запросы могут обходиться без обращений к БД, то число worker’ов и потоков можно оставить высоким, а объём пула сделать относительно низким — в результате множество потоков будет обрабатывать запросы, но только небольшая их часть станет конкурировать за подключения к БД в пуле по мере необходимости.

Эту идею можно реализовать другим способом: перевести управление соединениями в код и убедиться, что они эффективно резервируются (checkout) и освобождаются (release). Это особенно важно, если между обращениями к БД делаются сетевые запросы.

Если станет понятно, что управлять соединениями должным образом или настраивать эти цифры затруднительно, и сервер приложений искусственно ограничен пределом на количество подключений к БД, можно будет воспользоваться инструментом вроде pgBouncer, Odyssey, AWS RDS Proxy. Запуск pooling-прокси позволит сделать размер пула равным максимальному числу потоков и попутно обеспечит уверенность в том, что прокси-сервер сделает все максимально эффективно.

Что касается баз данных, PostgreSQL использует обработчики на основе процессов, поэтому приходится проявлять сдержанность в отношении числа соединений при работе с этой БД. MySQL использует потоки, так что количество соединений можно увеличить — хотя это и способно привести к снижению производительности из-за переключения контекста и блокировок.

2. Обработка на основе цикла событий

Node / Deno — первый сервер на основе цикла событий (event loop), который мы рассмотрим. Подобный сервер с конфигурациями по умолчанию способен весьма эффективно использовать ядро процессора, на котором работает, но будет практически полностью игнорировать другие. Да, его внутренние подсистемы и библиотеки вполне могут задействовать и другие ядра, но сейчас мы больше заинтересованы в полезной нагрузке, и поможет в этом кластеризация (clustering). Она достигается путем запуска одного процесса, который принимает все входящие соединения, а затем действует как прокси-сервер и распределяет соединения по другим процессам, работающим на той же машине. У Node имеется модуль кластеризации, входящий в стандартную библиотеку, и популярные серверы вроде PM2 его активно используют. Основное правило здесь состоит в том, чтобы запускать столько же процессов, сколько ядер CPU доступно (конечно, при условии, что памяти достаточно).

Stripe выпустил интересный проект Einhorn, который представляет собой менеджер соединений, существующий вне стека, в котором пишется код. Он запускает собственный процесс, которые принимает соединения, и распределяет их по экземплярам приложения, которые запускает и которыми управляет как дочерними процессами. Инструмент вроде этого очень полезен в системах, основанных на циклах событий, поскольку при возможности всегда будет максимально полно задействовать ядро процессора. Однако сам по себе он не так полезен с Ruby/Python, поскольку, хотя и позволяет запускать несколько процессов, отсутствие потоков означает, что каждый из процессов может обслуживать только один запрос за раз.

Подход на основе кластеризации цикла событий используется системами, которые меняют поведение по умолчанию для языка, основанного на процессах. Сервер Tornado для Python, например, преобразовывает обработчик запросов Python в систему на основе цикла событий, также известную как non-blocking I/O. Также можно настроить его кластеризацию на все доступные ядра CPU (при условии достаточного количества памяти).

Аналогичный подход используется в веб-сервере Falcon для Ruby. В новых версиях Ruby имеется аналог зеленых потоков (green-thread), называемый файбером (fiber), и каждый входящий запрос Falcon обрабатывает с помощью одного такого Ruby-примитива. Файберы не могут автоматически использовать все ядра CPU, поэтому Falcon запускает копию приложения в каждом доступном ядре процессора (опять же, при условии, что памяти достаточно).

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

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

3. Внутреннее управление / Кастомная обработка

Образцом в данном случае выступает Go, который, в отличие от всех других примеров, полностью свободен от каких-либо ограничений при обработке запросов — он может по-максимуму использовать доступные ресурсы CPU и памяти. Каждый входящий запрос обрабатывается новой горутиной (goroutine) — легковесной потокоподобной конструкцией. Исполняемая среда Go управляет ей, при этом планирование значительно опережает по эффективности аналогичные механизмы для потоков или процессов. Go автоматически «раскидывает» горутины по всем доступным ядрам CPU, хотя его аппетиты можно несколько обуздать, задав параметр runtime.GOMAXPROCS. Поскольку все происходит в рамках исполняемой среды, память не копируется. Go работает на всех ядрах CPU и не требует запуска отдельной копии приложения на каждом ядре.

Поскольку Go оптимизирован для работы с десятками тысяч параллельных запросов даже на совсем маленьких серверах, его сопряжение с базой данных на основе процессов, такой как PosgreSQL, можно сравнить с картинкой, на которой гоночный автомобиль на полной скорости врезается в кирпичную стену. Если каждая горутина использует SQL-пакет из стандартной библиотеки, будет создано столько соединений, сколько насчитывается горутин — ведь по умолчанию размер пула не ограничен.

Первое, что необходимо сделать с любым приложением на Go, работающем с SQL-базой, — задать предел соединений с помощью SetMaxOpenConns и связанных опций. Go использует внутренний пул с пакетом sql, поэтому каждый запрос будет получать соединение из пула, и сразу после завершения запроса оно будет возвращаться в пул. Другими словами, чтобы провести транзакцию, придется воспользоваться специальными методами, предоставляющими объект, который «обёртывает» открытое соединение, в котором транзакция была запущена — это единственный способ, позволяющий в дальнейшем зафиксировать данные или откатить транзакцию. Это имеет большое значение и при использовании других завязанных на соединение функций, таких как подготовленные выражения или рекомендательные блокировки (advisory locks).

Подобный автоматический подход даёт неожиданное преимущество: он устраняет необходимость в pooling-прокси, поскольку управление соединениями уже осуществляется крайне агрессивно. В вызовы БД по умолчанию встроено эффективное управление соединениями, и соединение используется так же эффективно, как и в pooling-прокси. Системы, устроенные подобным образом, по умолчанию работают эффективно, однако пользователю приходится разбираться с пограничными случаями, когда ручное управление действительно необходимо.

Помимо привычных нюансов, связанных с подготовленными выражениями и рекомендательными блокировками (advisory locks), подобное ручное управление соединениями также потенциально может приводить к проблемам, связанным со взаимной блокировкой. Если задано максимальное число соединений, и некоторым запросам для работы требуется более одного соединения, существует вероятность попадания в порочный круг, в котором запросы бесконечно дожидаются, пока другие запросы освободят соединения. В заметках об определении размера пула для HikariCP приводятся формулы, которые помогают справится с проблемами вроде этой.

Другие языки на основе VM, такие как Java, Scala/Akka, Clojure, Kotlin (все на JVM) и Elixir/Erlang (на BEAM VM) работают аналогичным образом: задействование всех доступных ядер CPU возможно без запуска отдельной копии приложения для каждого ядра. Реализация каждой конкретной системы или библиотеки подключений к БД обычно имеет свои тонкости, однако с ними легко разобраться, используя одну или несколько концепций, приведённых выше.

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

Источник

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *