log4j ?

Dec. 24th, 2021 01:04 pm
malyj_gorgan: (Default)
[personal profile] malyj_gorgan
Слухайте, а хтось може в парі абзаців пояснити, що то таке сабж і з чим його їдят? Дідько з нею, з його vulterability'ею, я хочу зрозуміти, чим був такий хороший чи зручний чи потрібний сам log4j, що та вульнерабіліті виявилася такою проблємною? Тільки пояснити так, на хлопский розум, без відсилань в непотрібні абревіатури.
Дякую!

Date: 2021-12-30 09:27 am (UTC)
From: [personal profile] mprotsenko
А ще весело, якщо ці процеси ще належать різним сервісам, і треба прослідкувати за тим, як запитання скаче по різним сервісам (додати trace ID в контекст логів). А для того, щоб життя не здавалося медом - зберігати контекст в треді не можна, бо в нас велике навантаження і тред на запит ми не можемо дозволити, бо ніяких тредпулів не вистачить (а замість того треба реактивну модель та non-blocking IO).

З стандартними рішеннями на кшталт grpc+opentracing+log4j усе реалізується невеличким інтерсептором буквально на 5 рядків без втрати перформансу. А ось як цей довольно банальний сценарій реалізувати через System.out.println? (Про це і много чого іншого читайте в свіжому номері журналу "Ніяк".)

Сорі, "музикою навіяло" (с)
Edited Date: 2021-12-30 09:28 am (UTC)

Date: 2021-12-30 12:55 pm (UTC)
From: [personal profile] sassa_nf
о, як цікаво. Добре, що ви згадали про тредпули, реактивну модель та non-blocking IO. Я якраз нещодавно розмірковував, що саме за цією модою стоїть, окрім віри, що вона рятує від купи threads, та workload-specific виграш у приблизно 15%. Не допоможете розібратись?

Ну, от візьмемо нижчезгадані 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)

Date: 2021-12-30 06:08 pm (UTC)
From: [personal profile] mprotsenko
Класне питання!

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.

Edited Date: 2021-12-30 06:09 pm (UTC)

Date: 2021-12-30 11:54 pm (UTC)
From: [personal profile] sassa_nf
Ну, 15% це 15% відсотків.

А про ріст context switch cost із ростом кількості threads - це за рахунок чого? Якщо це про CPU contention, то чим відрізняється wait for CPU від wait to be read from socket buffers?

Date: 2021-12-31 12:14 am (UTC)
From: [personal profile] mprotsenko
Відрізняється тим, що в випадку context switch доводиться перезаповняти L1/L2 кеш.

В випадку малої кількості threads, кожна з яких оперує незначною кількістю даних - цього може і не відбуватися. Як тільки кількість threads/даних зростає - упс. (Якщо код виконується в віртуалізованому середовищі - дабл-упс. Але для випадку 3 це зазвичай не актуально, там bare metal.)

Date: 2021-12-31 09:56 am (UTC)
From: [personal profile] sassa_nf
а чим це відрізняється від реактивного стилю? ми ж кеш перезаповнюємо лише тому, що інший код та інші дані чіпаємо.

Date: 2021-12-31 05:37 pm (UTC)
From: [personal profile] mprotsenko
Тут я вже не справжній зварювальник - я маску на будмайданчику знайшов...

Але мені здається, що в випадку реактивного стилю ми не так часто чіпаємо інший код - особливо інший boilerplate code, якого при виконанні `new Thread(...)` повним повен кошик.

І boilerplate data ми не чіпаємо, а в реальному `new Thread(new Runnable{...})`, який робить щось реальне, типу HTTP request, його може бути декілька мегабайт, Java - це ще той memory hog.

Date: 2021-12-30 10:25 pm (UTC)
From: [personal profile] mprotsenko
і введемо thread pools обмеженого розміру та черги, де б запити проводили решту часу, тобто, 49ms

Перечитав і подумав - чи не є реактивною моделлю, але вже без blocking IO? Ні, звісно, так теж можна, але це теж ускладнює код, ні?

Date: 2021-12-30 11:34 pm (UTC)
From: [personal profile] sassa_nf
Так, ускладнює, але в одному добре визначеному місці, і ускладнення обмежено, тоді як реактивне/CPS is contagious.

Скажімо, взаємодія із дуже повільним сервісом - в цьому місці вводимо CompletableFuture, продовження якого буде виконано в окремому thread pool, а решту пишемо "нормально".

Тому випадок номер 2 із 60 секунд - там якраз thread per request most of the way.

Date: 2021-12-31 12:04 am (UTC)
From: [personal profile] mprotsenko
Я трохи не зрозумів про "пишемо нормально". Ось в нас є CompletableFuture, який поверне результат через 60 секунд. Усе, що йдеться далі - пишеться в асинхронному стилі, через thenApply, що код таки ускладнює.

Отримуємо в найпростішому випадку:


// ... спочатку звичайний синхронний код

// потім
return CompletableFuture.supplyAsync(longRunningTask, separateExecutor)
  .thenApply(response -> process(response));


(Тут, звісно, треба фрейморк, який вміє працювати з такими відповідями, не викликаючи .get, а ціпляючи це до, наприклад, Netty, який крутиться під капотом.)

Собсна, тому що код далі виконується по факту відповіді 3rd party service - то виходить, що частина коду в нас таки реактивна, і єдина різниця в тому, що ми загортаємо "довгу" частину в окремий таск і виконуємо його на окремому тредпулі, який швидко насичується.

Але чому в такому випадку не використати non-blocking IO, раз в нас вже і так код реактивний?

Беремо

// ... спочатку звичайний синхронний код

// потім

return reactiveClient.someCall(someArguments)
  .map(response -> process(response));


Різниці в складності коду нема, а різниця в throughput - якщо я правильно пам'ятаю лоад-тести, які я робив як раз, коли в біллінгу працював - раза в два-три.

(Бо в non-blocking IO немає проблем з насиченим thread pool та нудною оптимізацією його розміру, підтримкою переліку тасків, що виконуються, і ще купою дрібничок - одна лише оптимізація garbage collection нас починає вбивати, бо на 60 секундах це вже зовсім інша справа, ніж на 50мс...)
Edited Date: 2021-12-31 02:43 am (UTC)

Date: 2021-12-31 10:16 am (UTC)
From: [personal profile] sassa_nf
> Усе, що йдеться далі - пишеться в асинхронному стилі

так, оце і є "CPS is contagious". Але якраз якщо ми не змушені всі blocking operations переписувати в реактив, то тоді не "Усе, що йдеться далі".
public void blah(@Suspended AsyncResponse response) {
   ...
   client.rx().get("/slow").whenCompleteAsync((r, th) -> { // так, в цьому місці асинхрон
      ...
      try(DBConn conn = pool.borrow()) { // але в цьому нам нормально і без асинхрону
        ...
      }
      response.resume("hello world");
   }, myExecutor);
}


> в non-blocking IO немає проблем з насиченим thread pool та нудною оптимізацією його розміру, підтримкою переліку тасків, що виконуються, і ще купою дрібничок

а от якраз перелік тасків, що виконуються, є проблемою асинхрону :) а насичення thread pool - це ніщо в порівнянні з необмеженим ростом задач, загубленим continuation та боротьбою із error propagation та resource management.

щодо нудної оптимізації розміру - так, вона нудна, але це підлягає автоматизації.


> а різниця в throughput - ... - раза в два-три

цікаво. я такого ще не бачив. а в порівнянні з чим?

Date: 2021-12-31 05:31 pm (UTC)
From: [personal profile] mprotsenko
      try(DBConn conn = pool.borrow()) { // але в цьому нам нормально і без асинхрону
        ...
      }


Якщо ми не влітаємо в 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 в порівнянні з виконанням

blockingClient.get("/someReallySlowStuff")


в окремому threadpool (який все одно насичується і примушує чекати батьківській threadpool). Черга тут теоретично може допомогти, а практично в нас є власний SLA і чекати безкінечно в черзі теж не можна - як мінімум, в тому випадку, що я тестував.

Звісно, якщо, наприклад, йдеться не про купівлю товара на сайті, а про відновлення щомісячної переплати, то там інша картинка буде, бо там не буде вимог до нашого latency.



а от якраз перелік тасків, що виконуються, є проблемою асинхрону :)

Але, слава Творцю, це проблема людей, що пишуть асинхронні фреймворкі. :)
Edited Date: 2021-12-31 05:47 pm (UTC)

Date: 2022-01-03 01:35 pm (UTC)
From: [personal profile] sassa_nf
Зрозуміло. Дякую.

Profile

malyj_gorgan: (Default)
malyj_gorgan

June 2025

S M T W T F S
12 345 67
89 1011 121314
15161718192021
22232425262728
2930     

Page Summary

Style Credit

Expand Cut Tags

No cut tags
Page generated Jun. 18th, 2025 12:26 pm
Powered by Dreamwidth Studios