![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
Слухайте, а хтось може в парі абзаців пояснити, що то таке сабж і з чим його їдят? Дідько з нею, з його vulterability'ею, я хочу зрозуміти, чим був такий хороший чи зручний чи потрібний сам log4j, що та вульнерабіліті виявилася такою проблємною? Тільки пояснити так, на хлопский розум, без відсилань в непотрібні абревіатури.
Дякую!
Дякую!
no subject
Date: 2021-12-30 12:55 pm (UTC)Ну, от візьмемо нижчезгадані 20K qps. Якщо response time 1 ms, то нам потрібно всього 20 threads. Це не такий і великий thread pool. Якщо ми візьмемо context switch за 50 мікросекунд, реактивна модель нам тут може подарувати десь 5% CPU на кожен eliminated context switch. Якщо ми позбуваємось 2....4 context switches, то виграємо десь 15% response time. Непогано, але звертаю увагу, що справа не у unmanageably large number of threads, і виграш workload-specific.
Для масштабу, розглянемо випадок 20K qps із response time 50ms. Тут би нам було потрібно 1000 threads, це забагато для одного невеличкого процесу. Але ми мусимо знати ще одне невідоме: скільки часу ми проводимо на CPU. (Це саме невідоме присутнє і у випадку з response time of 1 ms, але там ми можемо повністю ігнорувати це питання.) Якщо 1ms, то нам уже потрібно 20 CPUs - це не такий вже і малий процес. Ну, і тепер уже можна розглянути питання, а що ми втратимо, якщо ускладнимо реалізацію сервісу, і введемо thread pools обмеженого розміру та черги, де б запити проводили решту часу, тобто, 49ms. Цього разу context switch - це ледве 0.1% часу, і ми можемо собі дозволити 50 context switches before it becomes a measurable problem.
Тобто, 1000 threads таки з'являється, але для великих сервісів ми, по-перше, можемо собі дозволити, а по-друге, можемо таки зменшити кількість threads.
Мораль цієї історії така: якщо ми можемо використати CPU повністю, то ми CPU-bound, і реактивна модель нам дарує the cost of context switch; якщо ми не можемо використати CPU повністю, то ми bound на якомусь іншому ресурсі, і реактивна модель нічого не поліпшує, а лише ускладнює софт. На пітоні та nodejs реактивна модель - єдина можлива; а от на інших мовах треба дивитись.
Ну, і питання: це схоже на правду? Що я пропустив?
(У дужках додам, що реактивна модель і non-blocking IO нам ще дають inversion of control, який є наслідком CPS, але по-перше ніхто насправді цим не користується, бо API немає, а muxer пишуть одиниці (це, власне, netty і т.ін.), а по-друге люди геть про це не згадують, а обмежуються розмовами про розміри thread pools)
no subject
Date: 2021-12-30 06:08 pm (UTC)Short answer: так, в 90%+ випадках реактивна модель лише занадто ускладнює код і нафіг не потрібна.
Long answer: є низка випадків, де без реактивщини ніяк, я спробую згадати те, що бачив (і 100% щось забуду :) ).
1) High load. Ті самі 20к RPS перемножити на купу серверів. Виграш в 15% на навантаженні, яке створює computing costs в USD10М за рік - це USD1.5М економії. Якщо код ускладнюється настільки, що треба найняти ще одного інженера чи інженерку за USD500К - все одно автор реактивного коду отримає річний бонус. :)
А якщо рахунок USD100М? А якщо USD1-2-5B? Отож бо. Останнє, звісно, не такий частий приклад, але USD10-100М - не така вже і аберація.
2) High latency. У біллінгу, наприклад, payment processors' SLA часто гарантують відповідь з P99 latency 60s. s, not ms.
І тут навіть 1К RPS нас вбиває.
3) Ultra-low latency та non-linear context switch cost growth. Якщо ми працюємо над системою, де нам треба гарантувати under 1 ms latency, то навіть fixed cost of switching between 2 threads (2-7 microseconds) - це вже помітний імпакт. А в реальному світі, коли мова йдеться про switching между десятками чи сотнями тредів - то це вже буде high double digits (40-70 microseconds).
100% щось забув, але як в тому мемі - "йой, най буде".
Резюме: усе, що я згадав, аж ніяк не суперечить моралі про bottleneck.
Але реактивна модель дозволяє нам наблизитися до більш ефективного використання як CPU, так і IO, і як наслідок - в низці випадків (невеликої, але і не такої малої, щоб її можна було ігнорувати в індустрії в цілому) - отримати реальну економію, від грубих грошей до latency.
no subject
Date: 2021-12-30 11:54 pm (UTC)А про ріст context switch cost із ростом кількості threads - це за рахунок чого? Якщо це про CPU contention, то чим відрізняється wait for CPU від wait to be read from socket buffers?
no subject
Date: 2021-12-31 12:14 am (UTC)В випадку малої кількості threads, кожна з яких оперує незначною кількістю даних - цього може і не відбуватися. Як тільки кількість threads/даних зростає - упс. (Якщо код виконується в віртуалізованому середовищі - дабл-упс. Але для випадку 3 це зазвичай не актуально, там bare metal.)
no subject
Date: 2021-12-31 09:56 am (UTC)no subject
Date: 2021-12-31 05:37 pm (UTC)Але мені здається, що в випадку реактивного стилю ми не так часто чіпаємо інший код - особливо інший boilerplate code, якого при виконанні `new Thread(...)` повним повен кошик.
І boilerplate data ми не чіпаємо, а в реальному `new Thread(new Runnable{...})`, який робить щось реальне, типу HTTP request, його може бути декілька мегабайт, Java - це ще той memory hog.
no subject
Date: 2021-12-30 10:25 pm (UTC)Перечитав і подумав - чи не є реактивною моделлю, але вже без blocking IO? Ні, звісно, так теж можна, але це теж ускладнює код, ні?
no subject
Date: 2021-12-30 11:34 pm (UTC)Скажімо, взаємодія із дуже повільним сервісом - в цьому місці вводимо CompletableFuture, продовження якого буде виконано в окремому thread pool, а решту пишемо "нормально".
Тому випадок номер 2 із 60 секунд - там якраз thread per request most of the way.
no subject
Date: 2021-12-31 12:04 am (UTC)Отримуємо в найпростішому випадку:
(Тут, звісно, треба фрейморк, який вміє працювати з такими відповідями, не викликаючи .get, а ціпляючи це до, наприклад, Netty, який крутиться під капотом.)
Собсна, тому що код далі виконується по факту відповіді 3rd party service - то виходить, що частина коду в нас таки реактивна, і єдина різниця в тому, що ми загортаємо "довгу" частину в окремий таск і виконуємо його на окремому тредпулі, який швидко насичується.
Але чому в такому випадку не використати non-blocking IO, раз в нас вже і так код реактивний?
Беремо
Різниці в складності коду нема, а різниця в throughput - якщо я правильно пам'ятаю лоад-тести, які я робив як раз, коли в біллінгу працював - раза в два-три.
(Бо в non-blocking IO немає проблем з насиченим thread pool та нудною оптимізацією його розміру, підтримкою переліку тасків, що виконуються, і ще купою дрібничок - одна лише оптимізація garbage collection нас починає вбивати, бо на 60 секундах це вже зовсім інша справа, ніж на 50мс...)
no subject
Date: 2021-12-31 10:16 am (UTC)так, оце і є "CPS is contagious". Але якраз якщо ми не змушені всі blocking operations переписувати в реактив, то тоді не "Усе, що йдеться далі".
> в non-blocking IO немає проблем з насиченим thread pool та нудною оптимізацією його розміру, підтримкою переліку тасків, що виконуються, і ще купою дрібничок
а от якраз перелік тасків, що виконуються, є проблемою асинхрону :) а насичення thread pool - це ніщо в порівнянні з необмеженим ростом задач, загубленим continuation та боротьбою із error propagation та resource management.
щодо нудної оптимізації розміру - так, вона нудна, але це підлягає автоматизації.
> а різниця в throughput - ... - раза в два-три
цікаво. я такого ще не бачив. а в порівнянні з чим?
no subject
Date: 2021-12-31 05:31 pm (UTC)Якщо ми не влітаємо в 5 сек затримки (бо локі, насичений пул, неоптимальні індекси) - то, так, згоден.
Але якщо в нас і БД реактивна, то http worker ми можемо прив'язати до конкретного cpu core і мати два threadpool-а замість трьох.
(Бо синхронний код з цього прикладу не можна виконувати на http worker - інакше сервер просто не буде реагувати на нові запити. я підозрюю, що тут йдеться про стандартну модель http worker thread pool - framework threadpool - separate threadpool for slow operations.)
А якщо ми додаємо HTTP/2 multiplexing і позбавляємося необхідності обробляти кожен новий запит до нашого сервісу як окремий запит з власним connection (чи боротися з connection pooling)... ;)
Різниця в throughput в порівнянні з виконанням
в окремому threadpool (який все одно насичується і примушує чекати батьківській threadpool). Черга тут теоретично може допомогти, а практично в нас є власний SLA і чекати безкінечно в черзі теж не можна - як мінімум, в тому випадку, що я тестував.
Звісно, якщо, наприклад, йдеться не про купівлю товара на сайті, а про відновлення щомісячної переплати, то там інша картинка буде, бо там не буде вимог до нашого latency.
Але, слава Творцю, це проблема людей, що пишуть асинхронні фреймворкі. :)
no subject
Date: 2022-01-03 01:35 pm (UTC)