Re[14]: WA: 3 млн tcp соединений на одном сервере
От: SkyDance Земля  
Дата: 08.08.20 00:24
Оценка:
N>Потому что туда поступили сообщения из внутренней обработки, и их надо отправить. Терять нельзя.

Напоминает проблему reliable exactly once delivery в distributed environment, а также все ту же handling overload статью.
Что значит "терять нельзя"? В случае перегрузки либо load shedding, либо backpressure, третьего не дано (или по крайней мере мне неизвестно).

N>Спасибо, что снизошли до нас, простых смертных. А все предыдущие ~15+ лет нам что делать было?


Решать конкретную проблему, попутно улучшая инструменты. Заодно — учиться.

N>Опять "мышки, станьте ёжиками". У меня совершенно конкретный вопрос: как вы предлагаете писать, чтобы не получать квадратичную зависимость затрат CPU (хотя бы) от скорости потока, в условиях подобных неоптимизируемых синхронных вызовов (которые неизбежны хоть изредка)?


Как обычно, есть варианты:
а) научиться мыслить асинхронно, и более не вспоминать про неизбежность
б) внести необходимые изменения в инструмент, сделав его доступным всем (contribute to open source)
в) научиться работать с тем, что есть

N>Роскошно. Повторю вопрос: как вы будете решать иначе?


Я уже объяснил. Можно было написать NIF-based socket, можно было реализовать backpressure, можно было разобраться и поднастроить GC. Можно было подумать про архитектуру и ее огранчиения: почему у вас строго одна выходная "труба"? Ведь это single point of failure. Это по определению ненадежно.

N>Ну-ка попробую ("давно не брал я в руки шашек", но основы помню). Итак, процесс A получает входные сообщения, процесс B отправляет пусть штатным образом через gen_tcp:send (напоминаю, socket API нам ещё не доступен). Мы защищаемся от слишком длинной очереди процесса B, передавая эту обязанность на A.


N>Очевидно, что если B передаёт каким-то образом "а ну дай следующие данные на передачу", их надо собрать одним сообщением.


Пока логично. Процесс "А" просто выгребает приходящее и складывает в iolist, или, как вариант, накапливает длинный binary (unbounded, судя по условию вашей задачи, когда "ничего терять нельзя").

N> Проблема в receive именно в количестве сообщений, а если я все передаваемые пакеты набью в одно, оно будет тяжёлым (объём данных никуда не девается), но одним. Потом процесс будет разбирать его и отправлять по кусочкам.


На этом месте следовало бы остановиться и посмотреть, как именно сделан message passing для тяжелых сообщений (точнее, binaries внутри). Само сообщение будет крошечным, и будет содержать только ref-counter pointer на binary (который лежит вообще в отдельном аллокаторе).
Иными словами, процесс "А" является ровно тем, чего вы от него и хотите — буфером. Все, что он делает, это выгребает приходящие сообщения и сериализует в большой binary. Когда процесс "В", который вызывает (потенциально блокирующийся) вызов gen_tcp:send готов что-то еще отправить, он запрашивает у "А" все, что тот успел накопить.

Позвольте мне пропустить дальнейшее описание рукопашного flow control protocol, ибо сама необходимость водружать столь сложный механизм была основна на заведомо неверном предположении. Ох неспроста "The Art of Challenging Assumptions" по статистике Whova App набрал максимум посещаемости из всех выступлений на той конференции

N>Вот это как раз тот случай, когда мы применили ETS, что вам так не нравится.


Ну, в моей практике встречались и еще более странные применения ETS. Равно как и еще более необычные side channels — скажем, вместо ETS можно было бы завести отдельный процесс (условно назовем его locker) который бы отвечал на вопрос "уже можно или еще нет". Способов организвать flow control — не счесть. И не только на Эрланге, на Java/C++/Python есть ничуть не меньше разных забавных конструкций на эту тему. Скажем, проверять глобальные флажки. А что, отличный вопрос для собеседования — поговорить о семантике volatile переменных
В общем, можно построить очень много ненужных велосипедов, если с самого начала не разобраться в задаче как следует.

Но. Хочу отдать должное, даже в самых простых решениях вроде этого самого binary buffer можно очень легко сделать ошибку. Вот пример. Прям один-в-один ваш случай, включая gen_tcp:send, и middleman-process. Неспроста я про TLS и ужасы говорил


N>Теперь: у этой системы есть свой мониторинг мониторинга.


Каждый такой пример "системы мониторинга" зачастую следствие неумения организовать supervision tree. Эта тема мне уже очень набила оскомину. Более двух лет я и лекции читаю, и мастер-классы провожу, и дизайн ревью, и code review, но, видимо, сказывается отсутствие этой темы в фундаментальном образовании программистов. Корректная организация supervision порой требует познаний в архитектуре. Выделить failure domains, договориться об SLA, о том, какие ошибки считать transient, какие permanent, и на каком периоде времени/частоте — этому всему нужно учиться. Ни в школе, ни в курсев "девелопер на РНР за 21 день" про это не рассказывают. И, кстати, зря — тема не такая и сложная, просто требует развернуть мозги, чтобы те заработали в нужном направлении. Тогда многое щелкнет и станет ясно, как именно (и, главное — ДЛЯ ЧЕГО) нужно организовать мониторинг.

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

N>Просто: пусть у нас 3 очереди


Нет, это не просто. Это как раз сложно. Просто — это когда у вас 3 процесса, и у каждого своя очередь.

N>И, что крайне важно, это всё не помешало бы всему остальному, что вы тут пишете про "правильный дизайн".


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

N>Понимаете, почему я во втором проекте всё это выбросил, и просто перегнал на другой язык? И взлетело за 3 дня.


Понимаю, конечно. Я ровно про то и писал в стартовом сообщений этого суб-треда. Кривая обучения нелинейна — вы научились базовому синтаксису, а потом ударились о резкий рост сложности концепций. Повторяю, вы не одиноки — это действительно cliff, на который, когда заберешься, начинаешь понимать, _ПОЧЕМУ_ система получилась столь стройной и красивой.

Есть простая аналогия. Лопата и экскаватор. Кто-то с самого начала сел в экскаватор, и оказалось очень удобно в нем сидеть! Но совершенно непонятно, как этим ковшом копать. Тяжелый, неудобный. В общем, потыркаться, а потом взять лопату и за три дня выкопать канаву. Это явно быстрее, чем месяц осваивать экскаватор.
Учиться в целом очень сложная задача. Мало кто в состоянии. Это надо иметь к тому склонность.

N>И от синхронных вызовов отказаться как-то нереально. Хотя сократить их долю, да, можно.


Представляете, что было бы, если бы ваш мозг мог синхронно зависнуть, ожидая ack'а от сердечной мышцы?

N>Там есть функции пощупать, чем занят сейчас процесс (без вопроса ему самому) — собственно, process_info() с соответствующими параметрами. Если бы вы были правы, там оно показывало бы GC. Но оно не показывало GC. Так что этот фактор если и влиял, то далеко не главным.


Куда именно выс смотрели? Что именно показывали erlang:system_monitor(long_gc) ?

N>Вот именно, что ищет решения. Нашим решением для второго проекта был уход от Erlang. И не пожалели.


Очень разумный подход. Если задача в том, чтобы "быстрее сделать", выбирать надо тот инструмент, в котором разбираешься лучше всего. А если таких инструментов много, то — тот, который лучше подходит к предметной области.
Иными словами, если у вас есть программист на С++, и нужно сделать что-то небольшое и быстро, заставлять его учить Эрланг не было бы разумным подходом. Вы попытались, не осилили, вернулись к знакомым инструментам. По мне так, вполне правильный процесс, fail fast, fail early.

N>А кто вам сказал, что не были знакомы?


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

N>Поставьте сами эксперимент такого же рода.


Вы описали один из test cases для off-heap message queue. В 2010 ее не было (появилась в OTP 19, то есть 5 лет назад).

N>Потом повторить на R12-R13 и на железе того времени (пусть будут Nehalem всех видов).


Нет смысла, я и так знаю, что произойдет (gc death spiral, можно найти слайды от Rick Reed'a где про это вкратце рассказывается).

Поймите важность root cause analysis. Кстати, да, тоже одна из тем, кажется, в 2019 на code BEAM SF я рассказывал про troubleshooting process и RCA. Не следует делать поспешных выводов на тему "ограничено размером кэша" и т.п.. Следует разобраться в проблеме — прочитав source code, например. И тогда уже оперировать знанием, а не ощущениями.

N>Core team — это общее понятие. Мне пофиг, как оно называется в конкретном случае — core team, cardinal crew, steering committee или как-то ещё.


Нет таких "общих понятий". И не было.

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


К делу не относится, но здесь мне хочется поддержать Pzz,

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


N>У меня такого заблуждения никогда не было. Но вы вместо обсуждения технических деталей опять впали в философию, где святой дух гения помогает Эрлангу, но не помогает Джаве.


Это ваши личные домыслы. Я лишь утверждаю, что вне зависимости от языка или парадигмы, разработчик, который в состоянии challenge assumptions, который может провести root cause analysis, а затем решить проблему, на порядок более производителен "среднего качества разработчика".

Отдельный котел в аду хотелось бы приготовить для тех, кто решает проблему наворачиванием очередного слоя поверх существующих. Причем слои почти всегда leaky, без соответствующей модели, и почти никогда не в состоянии выдержать property-based testing.
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.