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

Меня зовут Константин, я работаю в Газпромбанке. Занимаюсь транзакционными системами: платежами, переводами, также участвовал в разработке системы СБП в Газпромбанке. В своей практике столкнулся с десятками языков программирования, но в настоящий момент остановился на Java и Kotlin. Поэтому я буду рассказывать про проблемы backend разработки на этих языках.

Highload для разработчика? 

Я часто провожу технические собеседования, ассессменты, и мне очень нравится задавать вопрос: «Работал ли ты с highload-системами?». Можно по ответу понять, как себя чувствует разработчик. Если это джун, он начинает стесняться, сомневаться, отказывается отвечать или говорит, что не работал. Мидл начинает спорить про RPS и ТРS, мериться цифрами. Сеньоры начинают задумываться и задавать встречные вопросы: «А что такое highload-система для тебя? Как ты оцениваешь эту систему?». 

Наверняка у каждого из вас есть свой ответ на вопрос, что же такое highload-система. Для меня это система, которой недостаточно стандартной инфраструктуры, настроек, чтобы обслуживать поступающие запросы. Это система, которую необходимо “твикать” и точно настраивать. Например, вы развернули PostgreSQL в докере и вам этого хватает, поэтому вы никогда не задумывались про ТСР-соединения, и, скорее всего, у вас не highload.

Вот основные понятия/метрики, связанные с высоконагруженными системами:

Память 

Популярные проблемы, с которыми вы можете столкнуться:

→ Высокая нагрузка.

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

→ Неправильная конфигурация.

В последнее время участились внедрения систем виртуализации: Kubernetes, OpenShift, Docker. И не все понимают как правильно их настраивать. Если настройку сделать некорректно, высок риск out of memory.

→ Неожиданные бизнес-данные.

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

→ Утечка памяти.

Несмотря на то, что большинство современных языков программирования самостоятельно управляют памятью, тем не менее разработчики иногда допускают ошибки, создают цепочки зависящих друг от друга объектов. Garbage collector не справляется с этими испытаниями, и возникают утечки памяти.

Утечка памяти

У нас есть график потребления памяти одного микросервиса:

Как видно, в конце, он не выдержал. Случилась ошибка out of memory и последующая перезагрузка.

По графику видно, что временной период — 10 дней, то есть это долгий процесс. А значит, мы имеем дело с утечкой памяти.

Давайте посмотрим, с чем эта утечка была связана. 

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

Как справляться с утечками? Утечка памяти, обязательно, в какой-нибудь момент случится. Особенно, если  система — развивающаяся, и ваши сотрудники периодически производят в ней новые доработки. Шанс ошибиться всегда есть, бояться этого не стоит. Нужно проинструктировать, людей, сопровождающих ваш пром, о том, как с этим бороться: как только сервис начинает падать по памяти, нужно сразу снимать heap dump. 

Но есть нюанс. Если вы снимаете heap dump с прода, пожалуйста, не заливайте такие дампы в паблик шары и не выдавайте разработчикам на анализ, потому что в дампе с прода могут оказаться пароли от ТУЗов и прочие чувствительные данные пользователей. Поэтому анализировать их лучше тем же сотрудникам, которые сопровождают пром.

Анализ тривиален. Есть прекрасные инструменты. Например, memory analyzer tool от Эклипса. Загружаете дамп и инструмент сразу же “предполагает” где  утечка. И часто действительно это происходит там, где он указал. Кроме этого, можно снимать периодические дампы памяти. Есть инструмент JProfiler, который умеет сравнивать между собой несколько heap dump. Также рекомендую с помощью стандартных средств мониторинга - Микрометра, Prometheus и Grafana настраивать превентивную реакцию. Мы настроили алерты так, что когда любой из сервисов достигает потребления памяти в 95% от допустимого лимита, сразу же подключается поддержка.  Они снимают heap dump и  перезагружают сервис, чтобы вернуть его в норму. Дальше heap dump уже анализируется и расследуется первопричина проблемы.

Неожиданные бизнес-данные

У нас была задача — выгружать отчёты по транзакциям. Когда аналитики проектировали этот процесс, они примерно представляли, какими объемами клиенты будут выгружать информацию. Приняли решение: один POS терминал в день производит около 3500 операций. Такой объем легко помещается в памяти приложения, даже если запланировать увеличение в два раза. Поэтому минимальный блок для выгрузки приняли за количество транзакций по одному терминалу в один день.

Этот бизнес-процесс прекрасно работал на протяжении двух или трёх лет. Но в какой-то момент начали запускаться транспортные проекты, терминалы стали ставить в автобусах. Изменилась механика работы, выросла скорость. И, самое интересное, операции с этих терминалов агрегировались на один виртуальный терминал. Тот отчёт, который раньше работал с 5-7 тысячами транзакций, теперь начал подгружать 500 000 - 1 000 000 транзакций.

Что же произойдет с системой в этом случае? Представим, что сотрудник запустил выгрузку. Сервис загрузил 500 000 транзакций и начал их обрабатывать. Сотрудник оказался нетерпелив, в течение 20 минут он не дождался своего отчёта и решил перезапустить выгрузку. Сервис подгрузил еще 500 000 транзакций и в итоге мы получили график потребления памяти с двумя пиками на временном интервале в пол часа:

Как эту проблему решали? Заранее неизвестно, сколько операций на каком-то терминале будет обработано. При первой загрузке транзакций в базу им начали присваивать номер пачки, ввели понятие - батч, номер который прикрепляли к каждой операции. Нумеровали транзакции по тысяче. Выгружать в отчеты начали блоками по одному батчу. Так получили некую фиксированную линейную сложность по памяти. Теперь нам не важно, сколько будет операций по какому терминалу. Мы всегда знаем, что их в памяти будет  или до 1 000, или несколько батчей по 1 000.

База данных

Причин, по которым приложение может упасть из-за базы данных — множество:

Но я остановлюсь на нескольких основных.

Пул соединений

Вот схематичное представление, что такое пул соединений между приложением и БД:

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

Вот несколько советов как с этим жить: 

  1. Помните о существовании пула. Если вы работаете с Java и у вас SpringBoot, то под капотом будет JPA. А у JPA — Hibernate, у Hibernate, в свою очередь — Hikari. И вот Hikari – это и есть пул соединений, который мы настраиваем.

  2. Многие знают, что можно настраивать максимальный размер пула, но не догадываются о том, что можно настраивать и таймаут на закрытие соединений: idle-timeout. Это позволит приложению закрывать соединения, когда они ему не требуются. Таким образом, когда вы разворачиваете 100 микросервисов, если по дефолту каждый из этих 100 микросервисов потребляет 10 соединений, вам понадобится 1 000 соединений, которые должна поддерживать база данных. Такой объем соединений слишком велик и вы непременно  столкнетесь с большими проблемами.

Если же вы решили  использовать такие настройки:

Тогда 100 приложений будут потреблять около 100-200 соединений (в среднем), и в такой конфигурации вполне можно комфортно существовать.

Когда приложение исчерпывает лимит соединений, ваши запросы встают в очередь. Эти моменты очень важно мониторить. Стандартный пул мониторинга Micrometer, Prometheus, Graphana позволяет строить прекрасные графики. Вот один из них:

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

Вот как это можно сделать:

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

Второе – это запрос из таблицы pg_stat_activity, если мы говорим про Postgresql. Это часто наиболее простой способ, понять, что у вас не так. Если вы видите деградацию базы банных, сделайте запрос на просмотр текущих активных сессий. Так вы увидите, какие запросы и микросервисы эти соединения расходуют. А дальше подключаете либо DBA, либо сами начинаете исследовать, в чем именно корень проблемы.

Конфигурация

Кроме того, проблемы БД могут быть конфигурационными. В нашем случае в highload-системе база данных — живой организм. Она сама думает, принимает решение, потому что в ней есть анализатор, планировщик. И подсказывать ему можно только сбором статистики.

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

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

Также в некоторых ситуациях нам помогала опция force generic plan:

Но это не серебряная пуля.

Отчеты

Как разработчикам, так и DBA полезно выгружать и уметь читать отчёты по производительности БД. Раньше я всегда думал, что долгие запросы приносят проблемы, быстрые запросы проблем не приносят. Поэтому когда я видел долгий выполняющийся запрос, то в случае каких-то проблем считал его виновным.

Но вот вам такой запрос:

Этот запрос с виду несложный. Но в нём есть интересная конструкция distinct on. Она сделана, чтобы, используя мозги Posrgresql, выта$щить сразу все необходимые данные, которые нам нужны, не прибегая к обработке в коде.

Я был удивлен, когда по отчету производительности увидел, что такой запрос выполнялся быстро, за миллисекунды, но создавал, по сравнению с остальными запросами, сумасшедшую нагрузку на ЦПУ, примерно 60%.

Мы его с лёгкостью переделали, заменили на 2 простых запроса, добавили обработку в коде, и наши проблемы ушли. Но сам факт, что такое возможно, для меня стал интересным сюрпризом.

ORM

Такие ORM, как Hibernate, разрабатывают для масс-маркета, и когда ваша нагрузка уникальная и серьезная, вы можете обнаружить забавные ситуации. 

Например, когда мы смотрели запросы Хибера профилировщиком, обнаружили, что перед каждым Insert он делает Select. Это нужно, чтобы понять, есть ли в базе эта запись, и действительно ли нужно делать Insert, или  достаточно заменить insert командой update. Наверное, это не то, чего вы действительно хотите, ведь в этом случае приложение делает два запроса вместо одного, и время значительно деградирует.

Мы столкнулись с ещё одной очень хитрой проблемой: наш любимый Хибернейт кэширует планы запросов. У нас они выглядят так: обновляем или селектим какие-то данные, в них указываем ключевое выражение in и перечисляем идентификаторы. Причём этих идентификаторов у нас не 5 или 4, как на слайде, а 1 000 вариаций.

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

На каждый такой in было около 2 500 этих планов. Есть простейшая опция in clause parameter padding, которая решает конкретно эту проблему. Благодаря ей планы начинают кэшироваться по степени двойки. А промежуточные планы не сохраняются. 

Дополнительно

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

  • Используйте, при необходимости, частичные индексы. Их называют по-разному: частичные, условные или предикатные. У них довольно простая реализация: они не требует много места на жёстком диске, но очень помогает ускорить процессы.

  • И, конечно, самый лучший запрос в базу — тот, который не был сделан вовсе. Поэтому если у вас есть справочники с редко меняющимися данными, обязательно их кэшируйте и не заставляйте вашу базу данных выполнять лишние запросы.

TCP

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

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

Мы используем Project Reactor, и пул TCP соединений веб-клиента. Его можно настроить так, чтобы бездействующие более чем полчаса соединения автоматически закрывались самим приложением, а не Firewall.

Еще ситуация - у Project Reactor под капотом нет краёв, там — буферы, лимитированные количеством ядер и умноженные на 256. Также в системе есть файловые дескрипторы. Например, TCP-соединение – это файловый дескриптор. Их количество в системе ограничено как на процесс, так и на систему в целом. 

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

Таймауты

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

Несмотря на то, что Project Reactor позволяет cancel-сигналами реагировать и на сброс соединения, я рекомендую использовать классический вариант настройки “лесенкой”.

Кейс: HTTP Max Connections

Интересный инцидент произошел с системой быстрых платежей. Есть три системы: А — это платёжный хаб, В — промежуточная система и С — внешняя система.

Мы посылали запросы из системы А в систему В, и всё было прекрасно. SLA у нас был 5 секунд. В какой-то момент время ответа стало увеличиваться. С 1 секунды мы постепенно получали 1,5 секунды, потом 2. Потом стали возникать таймауты. У нас на таймауты срабатывал circuit breaker: мы замыкали соединение, переставали делать запросы, давали промежуточной системе отдышаться в течение минуты, возобновляли трафик, и всё налаживалось. Но через какое-то врем снова начинало увеличиваться время ответа.

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

По логам мы наблюдали очень странную ситуацию. Время ответа от системы С достигало 15-20 секунд, хотя таймаут стоит на 3-4. Сделали thread dump, обнаружили следующее: около сотни потоков в этом сервисе висели в ожидании соединения из пула. Когда начали разбираться, оказалось, что  у SpringBoot приложения под капотом RestTemplate. А у него, в свою очередь, под капотом — Apache веб-клиент. Каково же было наше удивление, когда у Apache веб-клиента лимит пула соединений составил всего 10. Этот сервис работал годами раньше, и нагрузка не достигала пика, то есть система С просто немного замедлила время ответа. Из-за этого соединения всё больше и больше начинали накапливаться. 

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

http-max-connections=100

Мониторинг соединений

Посмотрев на эту проблему в системе В, в системе А мы тоже сразу же начали пилить мониторинг на количество TCP-соединений у наших веб-клиентов. Обнаружили, что Project Reactor по умолчанию использует клиента, у которого 500 соединений на каждый end point. 

Если вы подключаетесь по 10 разным endpoint’ам, у вас будет, грубо говоря, 10 пулов по 500 соединений (общий лимит 1000).

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

Выводы

  • Выбирайте правильные HTTP-клиенты (или настраивайте неправильные =). 

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

  • Настраивайте таймауты правильно.

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

  • Кэшируйте веб-запросы. 

Комментарии (9)


  1. ermadmi78
    25.06.2024 07:55
    +1

    Например, когда мы смотрели запросы Хибера профилировщиком, обнаружили, что перед каждым Insert он делает Select. Это нужно, чтобы понять, есть ли в базе эта запись, и действительно ли нужно делать Insert, или достаточно заменить insert командой update. Наверное, это не то, чего вы действительно хотите, ведь в этом случае приложение делает два запроса вместо одного, и время значительно деградирует.

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

    Много альтернатив перепробовал, в итоге остановился на jOOQ. С одной стороны, по удобству он сопоставим с классическими ОРМ решениями, с другой позволяет жёстко контролировать генерируемые запросы.

    И есть ещё одно колоссальное преимущество. Если говорить про Postgres, то в нём есть огромный набор фич, далеко выходящих за рамки ANSI SQL. Я, если честно, уже давно не считаю постгрес исключительно реляционной СУБД. Благодаря этим фичам на нем можно эффективно реализовывать многие нереляционные решения. И все эти возможности поддерживает jOOQ.

    Например упомянутую автором операцию insert or update в постгресе можно выполнить одним запросом с условием ON CONFLICT. В jOOQ такой запрос генерируется с помощью ON DUPLICATE KEY UPDATE. Я эту возможность очень часто использую для обеспечения идемпотентности вставки (в качестве первичного ключа использую случайный uuid, генерируемый на клиенте).

    И пример, который я здесь привёл, это крохотная часть возможностей, которые предоставляет связка Postgres + jOOQ.


  1. ermadmi78
    25.06.2024 07:55
    +1

    По поводу мониторинга соединений. Если вы используете Kubernetes (а вы его наверняка используете), то мониторить нужно не только сами микросервисы, но и куберовские прокси вроде Istio. Нам Istio в своё время массу нервов попортил. У нас жёсткое ограничение по latency в 100 миллисекунд. А в микросервисной архитектуре 90% latency жрёт взаимодействие по HTTP. А самое дорогое удовольствие во взаимодействии по HTTP - это установка соединения. А если у вас сконфигурирован TLS внутри кластера - то это не просто дорого, а адски дорого. TLS Handshake с радостью жрёт все ваши лимиты. Чтобы решить эту проблему, мы вместо стандартных http клиентов попытались использовать пулы соединений, чтобы эти самые соединения не переоткрывать на каждый запрос. И, каково же было наше удивление, когда выяснилось, что переход на пул соединений никак не уменьшил latency. После долгих исследований мы выяснили, что пул держал нам постоянные соединения до Istio. А Istio при каждом запросе переоткрывал HTTP соединение до целевого сервиса. Подкрутили настройки Istio, и проблема была решена.


    1. GameOwner
      25.06.2024 07:55
      +1

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

      HTTP очень дорогой для хайлоада, как это не кощунственно звучит, слишком велики сетевые издержки на всю обвязку отправки полезных данных, на голом TCP даже без запуска его в User space латентность в 1мс на передающую цепочку из двух серверов+сам клиент обеспечивается(за вычетом издержек на саму сетевую передачу).

      Хотя в случае кубера там все плохо с латентностью будет по определению, в том числе и по перцентилю.

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


      1. ermadmi78
        25.06.2024 07:55
        +1

        Снимаю шляпу! Я тоже баловался со своим асинхроным транспортным уровнем поверх постоянно открытого TCP сокета. Тоже хорошие результаты получал. Но, к сожалению, это решение не вписывается в наши корпоративные стандарты. Поэтому пришлось шаманить с HTTP.

        По поводу кубера тоже согласен - он хорош для high-throughput решений, а для low latency совершенно не годится. Тоже приходится шаманить :)


      1. ermadmi78
        25.06.2024 07:55
        +1

        HTTP очень дорогой для хайлоада, как это не кощунственно звучит,

        Я сейчас ещё более кощунственную вещь скажу. Если у вас low latency хайлоад, то там не только http но и микросервисная архитектура в целом слишком дорогое удовольствие. Там, в идеале, нужны старые добрые монолиты.

        Но у нас одновременно low latency + high throughput, поэтому без горизонтального масштабирования никак. Вот и приходится проскальзывать между молотом и наковальней. Шаманим :)


  1. aaanisimovvv
    25.06.2024 07:55

    Интересная статья! Очень много узких мест) В эту тему не так давно прочитал книгу Цель Голдратта - там интересно описано, что делать для повышения эффективности производства, ещё и в художественном контексте. Может бысть есть что-то похожее для high load систем?)


    1. GameOwner
      25.06.2024 07:55
      +1

      Конвееризация процессинга, минимизация издержек самого сетевого протокола в кластере, прибивать потоки к ядрам для уменьшения переключений контекста, избегать вызова ядра, т.к. проверки на секюрность дороги, в пределе-вообще переход на TCP стек, работающий в user space, а не kernel, очевидно-пулирование потоков, учет в организации сетевого протокола размеров MTU Size для уменьшения цены утери пакета из-за необходимости перепосыла всех пакетов, а далее из вышеобозначенных проблем приходим к выводу-что внутрикластерный процессинг на HTTP неэффективен, и как итог-спускаемся на уровень TCP :) Туда же минимизация мемори траффика, использование арен памяти и преаллокации, в случае использования инфраструктуры с GC-минимизация его работы(пулинг, структуры и т.д и т.п.) и прочая, прочая, прочая.


  1. GameOwner
    25.06.2024 07:55
    +1

    "Их количество в системе ограничено как на процесс, так и на систему в целом. "

    Так есть же ulimit -n N, где N натуральное число, оно же ожидаемое количество открытых дескрипторов в процессе.

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

    Я вообще не понимаю боязни открыть большое количество TCP соединений с компактным буфером вычитывания на современном ядре линуха, что много лет точилось под подъем большого количества TCP соединений. Главное-грамотно раскидать по конвеерам сам процессинг, а самих соединений натурально может быть на сервере миллионы и он будет бодро бегать :) Если перцентиль не устраивает-поднимаем TCP стек в user space и вообще хорошо становится.


    1. avovana7
      25.06.2024 07:55

      Хотел уточнить про раскидывание по конвейерам. Имеется ввиду делать N потоков, обслуживающих M соединений?

      Про вынос TCP стека в user space ещё хотел уточнить - ради избавления от context switch, походов в ядро? Благодаря этому будет буст производительности?