malyj_gorgan: (Default)
malyj_gorgan ([personal profile] malyj_gorgan) wrote2021-12-24 01:04 pm
Entry tags:

log4j ?

Слухайте, а хтось може в парі абзаців пояснити, що то таке сабж і з чим його їдят? Дідько з нею, з його vulterability'ею, я хочу зрозуміти, чим був такий хороший чи зручний чи потрібний сам log4j, що та вульнерабіліті виявилася такою проблємною? Тільки пояснити так, на хлопский розум, без відсилань в непотрібні абревіатури.
Дякую!

[personal profile] sassa_nf 2021-12-25 09:54 am (UTC)(link)
ну, там окремі фічі життя полегшують, а всі разом - діра.

log enrichment - додавати потрібні контекстні дані до логів, які або замахаєшся додавати сам, або забудеш. Так з'являється потреба ${ctx.something}.

remote log destination - писати в локальний файл - це годиться для невеликої кількості процесів. Якщо у нас ферма із кількасот або тисяч процесів, з'являється потреба скидати все в одне місце. (ну і, звісно, enrich логи ще й ${ctx.hostname})

(скажімо, з отих двох можна побудувати ще й log censoring - фільтрувати або обскурувати потрібні контекстні дані, залежно від log destination. gdpr, і таке інше)

different types of log destination - а що, є один-єдиний стандарт і протокол передачі логів і одна-єдина архітектура організації процесу передачі логів?

ну, і от тепер все разом взяти докупи - маємо діру. З одного боку щось виймати з контексту - класна і потрібна річ, з іншого боку - якщо для контексту використовувати JNDI, маємо змогу з контексту виймати arbitrary stuff, або зациклити. Ну, і от arbitrary stuff, виявляється, може бути вказівкою JNDI піти поговорити з іншим сервером, який підтримується JNDI - наприклад, лівий LDAP. Але це вже пішли абревіатури.

[personal profile] mprotsenko 2021-12-30 06:12 am (UTC)(link)
Чекаю, коли хтось зламає github або, краще, Atlassian suite і вкраде пів-інтернету.

Сенсу нема. :)

Якщо хтось вкраде наш source code - то (а) нічого особливо секретного в 99% коду він не знайде, усі ідеї давно є публічними, (б) без відповідної інфраструктури (включно з людьми) фіг ти той код запустиш.
Edited 2021-12-30 06:14 (UTC)

[personal profile] mprotsenko 2021-12-30 11:44 pm (UTC)(link)
І так, і не так. Security model, яка побудована на obscurity (якщо ніхто не знає нашого кода, то і не здогадається, де там дірка) в реалі як раз ламається набагато легше, бо як раз в таких системах і забувають поновити версії бібліотек тощо.

Для того, щоб отримати доступ до нашого системного коду нічого не треба ламати. Наприклад, Titus та Zuul лежать просто у вільному доступі. Заходь на гітхаб і качай.

Минулого року хакнули приватне репо майкрософту на гітахбі і що? а нічого. Ну знаєш ти, що є такий код і така дірка в ньому - і що далі? Доступа до environment'а в тебе нема, і не буде, бо критичні enviroments як раз і проєктуються з розрахунком на те, що в них будуть деплоїти неідеальний код.

А якщо в твоїй thread model є серйозні загрози, то (а) дірки в коді вони знайдуть навіть без доступа до сирців - просто по непрямим ознакам, (б) захист коду - це дуже вторинне. Бо якщо треба, то серйозні люди просто прийдуть в гості до твоїх SRE, завербують їх чи просто приставлять пістолет до голови (і тоді починати треба з фізичного захисту персоналу).



розділенням engineering і QA

Часто, до речі, це є анті-паттерном. Є ситуації де це має сенс, але в більшості випадків від цього більше шкоди, ніж користі.

[personal profile] mprotsenko 2021-12-31 02:15 am (UTC)(link)
. Але навіть в тих трьох місцях, де я працював, спущена зверху всякими просвітленими менеджерами і cost-saving віцепрезидентами мантра "be your own QA" була абсолютно, категорично неправильною.

Так ця мантра і є неправильною. Якщо просто говорити "be your own QA" і більше нічого не робити - то краще не буде, а буде гірше. Але і розподіляти "тут QA, а тут developers" - це теж гірше. Це ж не дихотомія. :)

Для типової розробки я б починав з CI. CD - це набагато важче, але в плані deployment цілком реальна мета - 1-3 деплоймента за добу, не більше 6 годин від коміту до продакшена, в ідеалі - не більше години. Теоретично це можно робити і з окремими QA, але на практиці від підходу "окремі команди перекидають код через паркан" доводиться відмовлятися.

Але це не значить, що ролі QA чи SRE зникають. Вони просто починають виконувати зовсім інші функції.



Контрприклади/edge cases:

* година до продакшену не завжди значить година до кінця деплойменту
* коли галузь технічно унеможливлює швидкі зміни (наприклад, firmware development, де прошивку в чипах не можна змінювати "просто так") - то потрібні інші підходи.





Ця "більшість", вона у твоїх спостереженнях, чи за якимись об'єктивними вимірами?

За вимірами. Якщо цікаво - можеш почати з Accelerate, це seminal work. (Там і про тестування, і про infosec є.) Якщо зайде - подивись на щорічні DORA reports.

І я б розрізняв "більшість випадків, які існують" та "більшість випадків, де та чи інша практика є доречною" - це множини, які слабо перетинаються, хоч про дієти мова, хоч про інфосек.

[personal profile] mprotsenko 2021-12-31 02:37 am (UTC)(link)
в гілках розробників

На цьому можна зупинитися - це саме по собі яскравий antipattern, який вказує на існування більш суворіших antipatterns.

[personal profile] mprotsenko 2021-12-31 05:13 am (UTC)(link)
і то, що можна гадати, яка кількість дірок в майкрософтівських кодах і/або сервісах, які експлуатували після цього інциденту досі і експлуатуватимуть в майбутньому, була знайдена чи зроблена в результаті цього хаку.

Насправді, майкрософт доволі прозора в цьому плані корпорація, але то лірика.

Головне в тому, що ми можемо оцінити, як цій хак вплинув на БІЗНЕС майкрософту.

(no subject)

[personal profile] mprotsenko - 2021-12-31 17:58 (UTC) - Expand

(no subject)

[personal profile] mprotsenko - 2021-12-31 20:40 (UTC) - Expand

(no subject)

[personal profile] mprotsenko - 2021-12-31 20:53 (UTC) - Expand

(no subject)

[personal profile] mprotsenko - 2021-12-31 18:04 (UTC) - Expand

[personal profile] mprotsenko 2021-12-30 06:19 am (UTC)(link)
Як з цим боротися -- не знаю.

Одягаю Captain Obvious' cape: треба оптимізувати процеси під MTTR, бо спроба оптимізувати під MTTF зазвичай веде до погіршення як MTTF, так і MTTR.

(Я пам'ятаю, ти просив без абревіатур, вибач!)

UPD: мова, звісно, не про safety-critical інфраструктуру, там інші проблеми, і інші методи їх вирішення.
Edited 2021-12-30 09:37 (UTC)

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

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

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

[personal profile] sassa_nf 2021-12-30 12:55 pm (UTC)(link)
о, як цікаво. Добре, що ви згадали про тредпули, реактивну модель та 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)

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

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 2021-12-30 18:09 (UTC)

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

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

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

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

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

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

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

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

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

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

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

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

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

[personal profile] mprotsenko 2021-12-31 12:04 am (UTC)(link)
Я трохи не зрозумів про "пишемо нормально". Ось в нас є 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 2021-12-31 02:43 (UTC)

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

так, оце і є "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 - ... - раза в два-три

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

[personal profile] mprotsenko 2021-12-31 05:31 pm (UTC)(link)
      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 2021-12-31 17:47 (UTC)

(no subject)

[personal profile] sassa_nf - 2022-01-03 13:35 (UTC) - Expand