Performance matters!
1.19K subscribers
11 photos
2 files
63 links
Канал про SRE, Linux и производительность от Александра Лебедева (@alebsys).

Разбираю сбои, ускоряю системы, делюсь опытом.

🔹 Обо мне: alebedev.tech/about
🧑‍💻 Менторинг: alebedev.tech/mentoring
Download Telegram
🔥 Топовые инженеры в сферах SRE, производительности и Linux, на которых стоит подписаться (Linkedin)

👉 Mark Dawson, Jr.
Ведет активную деятельность как на LinkedIn, так и в своем блоге. Море "deep dive" по устройству Linux, его взаимодействию с вычислительными ресурсами и тому, как это влияет на производительность приложений.

👉 Tanel Poder
Создатель 0xtools (утилиты для траблшутинга), частый гость P99CONF и автор множества статей в блоге о Linux и поиске проблем в системах.

👉 Jose Fernandez
Двигает тему eBPF в Netflix, разрабатывая утилиты для повышения наблюдаемости и надежности систем. Активно делится своим опытом с сообществом.

👉 Viacheslav Biriukov
В бытность Яндекса читал лекции в КИТ, а позже завел блог, где простыми словами рассказывает про внутреннее устройство Unix-систем. Супер познавательно!

🎯 Бонус трек — @bobrik (github)
Создатель ebpf_exporter и активный контрибьютер в популярные open-source решения. Его код полезно и интересно читать. А еще ведет страничку в Mastodon про eBPF, Linux и все вокруг.

💬 А кто у вас в подписках?
🔥13
The Art of System Debugging — Decoding CPU Utilization

Или как баг в Java вызвал перегрузку CPU из-за избыточного чтения cgroupfs.

Заметка показывает, как eBPF помогает быстро находить сложные проблемы, и насколько труднее обходиться без него.

Статья интересна:

1. обилием реальных кейсов с использованием bcc утилит — всегда полезно увидеть, как коллеги исследуют системы с их помощью;

2. баги связанные со spinlock сложны для диагностики - создают ложное впечатление работы (высокий CPU System Time), хотя на деле система просто "активно ждет", перегружая процессор. Поэтому такие расследования всегда увлекательны.

В общем два в одном;)

tags: #cpu #troubleshooting
👍7
Рубрика "Вредные советы" ч.2 — больше не всегда лучше.

Увеличение размера очередей - один из самых частых и эффективных советов по тюнингу, который можно услышать.

И сложно спорить, всего один параметр, а сколько пользы:
* сглаживание всплесков нагрузки;
* меньше переполнений => меньше потери данных;
* пропускная способность растет, как и эффективность обработки (пакетами, а не штуками);
* больше асинхронности, меньше боимся "морганий";
* ...

Но у любого совета, пусть и очень хорошего, всегда найдутся недостатки.

Представим два сервиса (1 и 2), взаимодействующих по сети через большую очередь, например на сетевой карте (2).

Пока (2) успевает обрабатывать поток данных, все работает гладко, с описанными выше преимуществами.

Но как только (2) замедляется на значительное время, могут возникнуть проблемы:

* latency каждого пакета в буфере растет кумулятивно, а хвосты ожидают слишком долго;
* срабатывают таймауты (на уровне приложения, TCP) и происходит переотправка, еще больше нагружая очередь;
* все накопленные данные будут обработаны (2), даже если они давно устарели;
* из-за отсутствия своевременной обратной связи TCP (1) реагирует с опозданием — отсутствует failfast;
* перегрузка накапливается в одной части системы, вместо того чтобы равномерно распределяться — отсутствует backpressure.

Список можно продолжить.

Вывод: не следует слепо следовать интернет-советам — всё нужно ставить под сомнение, проверять и подбирать оптимальные параметры именно под вашу систему.

P.S. проблеме раздутых буферов в свое время даже дали название - bufferbloat. Подробнее почитать о ней можно на www.bufferbloat.net.

tags: #network #tcp #theory
👍7
Задержка любой операции, будь то ответ базы данных или обработка HTTP запроса, всегда совокупность задержек на более низких уровнях.

Например, чтение из базы данных состоит из множества этапов:
* формирование запроса
* создание TCP сокета
* обработка и маршрутизация на уровнях ниже
* ожидания в очередях и обработка сетевой картой
* поход по сети, и тд. и т.п.

Потому наблюдение за работой всех уровней — ключ к точному и оперативному анализу состояния системы.

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

#latency
🔥10👍4
CPU Usage не так прост в интерпретации, как может показаться.

Сложность в том, что показатель включает в себя не только работу CPU, но и время обращения к памяти.

А так как память на порядки медленнее процессора, то на ожидание ответа может уходить 90%+ времени.

Чтобы точнее интерпретировать CPU Usage стоит присмотреться к показателю IPC (Instructions Per Cycle) - сколько процессор выполняет инструкций за один такт.

В условиях современных суперскалярных процессоров этот показатель может достигать четырех и более инструкций.
Хотя в реальных нагрузках значение IPC будет существенно меньше (0.7 - 1.5).

Таким образом высокий CPU Usage при низком IPC (<1) может говорить не о недостатке вычислительных мощностей (и это первое, что приходит в голову), а о проблемах с IO - недостаток кешей и/или их плохая локальность, слишком частое обращение к памяти и подобное.

Для лучшего погружения в тему читай:
* CPU Utilization is Wrong от Brendan Gregg;
* IPC is to CPU% What Milk is to Cinnamon Toast Crunch от Mark Dawson.

——————————————
P.S. получить значение IPC можно с помощью node_exporter через perf коллектор, например так:
sum(rate(node_perf_instructions_total{instance=~"$node",job="$job"}[$__rate_interval])) / sum(rate(node_perf_cpucycles_total{instance=~"$node",job="$job"}[$__rate_interval]))

———————————————
P.P.S. этот же пост, но на linkedin.

tags: #cpu #theory #linux
👍12🔥621
Effect of Hyper-Threading in Latency-Critical Multithreaded Cloud Applications and Utilization Analysis of the Major System Resources

Как гиперпоточность/SMT* влияет на производительность различных типов приложений в облачных средах.

Цель исследования — выяснить, какие типы нагрузок выигрывают от SMT, а какие нет.

* SMT позволяет одному физическому процессору выполнять несколько потоков одновременно.

1. Авторы выяснили, что приложения, активно работающие с вводом-выводом (базы данных, очереди, файловые и веб-серверы, кеши), нейтральны к включению SMT.

Их производительность в большей степени зависит от скорости памяти, дисков и сетевых карт, а не от работы процессора.

👉 Потому включение SMT позволяет лучше утилизировать оборудование и экономить ресурсы.

2. Приложения с высокой нагрузкой на процессор (рендеринг видео, сложные расчёты, криптография, high-frequency trading и т. д.) напротив реагировали на SMT негативно - потоки начинают конкурировать за общие ресурсы физического ядра, что приводит к росту задержек.

👉 В окружениях с CPU-bound нагрузкой рекомендуется отключать SMT.

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

P.S. Кстати метрика IPC (instructions per cycle), которую мы недавно обсуждали, должна помогать в определении профиля нагрузки.
👍10
Мониторинг TCP: метрики Zero Window 🚀

Протокол TCP обеспечивает множество функций, включая управление перегрузками.

Для этого используется механизм скользящего окна (sliding window) — объём данных, который отправитель передаёт без подтверждения от получателя.

Размер окна динамически регулируется в зависимости от объёма свободного места в буфере получателя (net.ipv4.tcp_rmem).

Когда буфер получателя заполняется (получатель не успевает за отправителем), TCP расценивает это как перегрузку и уменьшает размер окна в ACK-сегменте вплоть до нуля (Zero Window Advertisement).

Таким образом, при корректной настройке скорости между отправителем и получателем, переполнения очередей удаётся избежать.

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

❗️❗️❗️Интерпретация метрик ниже ошибочна, за исправленным описанием прошу сюда https://t.iss.one/troubleperf/65 ❗️❗️❗️

К счастью /proc/net/netstat позволяет отслеживать события, связанные с нулевым окном:
* TCPFromZeroWindowAdv — сколько раз отправитель получил уведомление о нулевом окне;
* TCPToZeroWindowAdv — сколько раз получатель установил нулевое окно;
* TCPWantZeroWindowAdv — общее время, когда отправитель не мог отправить данные из-за нулевого окна;
* TCPZeroWindowDrop — сколько раз отправитель отбрасывал пакеты из-за нулевого окна.

Эти метрики вместе с классическими показателями переполнения буфера и out-of-order очереди позволяют точнее оценивать состояние системы и вовремя выявлять возможные проблемы.

☝️ показатели нулевого окна доступны в node_exporter через коллектор netstat


P.S. Лайк можно занести сюда.

tags: #tcp #linux
🔥163
Рубрика "Вредные советы", №3. Swap.

Есть одно наблюдение и один факт:

* наблюдение: большинство советов по настройке подсистемы памяти Linux рекомендуют либо полностью отключить swap, либо установить vm.swappiness на минимальные значения.
* факт: в большинстве случаев по умолчанию swap включен, а vm.swappiness установлен на 60.

Не кажется ли странным такой диссонанс?

# Немного про память

Для начала краткий экскурс в то, как работает память в Linux.
Память делится на страницы, которые бывают двух типов: анонимные и file-backed.

- Анонимные страницы содержат данные, созданные во время работы приложений (например, heap или stack).
- File-backed страницы — это файлы, загруженные с диска и хранящиеся в файловом кеше.

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

Кстати, swap используется только для анонимных страниц. File-backed данные, находящиеся на диске, можно всегда подгрузить заново.

В итоге, отключение swap или минимальные значения vm.swappiness позволяют держать в памяти анонимные страницы до последнего, что может приводить к ошибкам OOM.

---------------------
Возвращаясь к началу: какие системы пострадают от приоритета анонимных страниц над файловым кешем? Где, по вашему мнению, стоит повысить vm.swappiness?

Давайте обсудим — это интересно!

P. S. поддержать лайком на Linkedin
5
Анализируя работу подсистемы памяти я часто обращаюсь к метрикам из /proc/vmstat - объем просканированных (pgscan) и украденных (pgsteal) страниц:

pgscan_kswapd
pgsteal_kswapd
pgscan_direct
pgsteal_direct


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

Что такое pgscan и pgsteal

* pgscan - сколько страниц было просканировано в поисках кандидатов на высвобождение;
* pgsteal - сколько из просканированных страниц удалось высвободить (украсть).
Scan/steal процессы могут быть запущены либо фоновым kswapd, либо напрямую приложением (direct).

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

А вот наличие direct может настораживать: похоже что kswapd не справляется и приложения явным образом требует предоставить ей памяти. Этот процесс уже происходит в foreground и чреват потерей производительности.

Как интерпретировать значения

* абсолютные значения - чем выше значения, тем чаще свободная память падает до минимума и система вынуждена принимать меры;
* соотношения pgsteal/pgscan - высокое значение (95%+) говорит, что большая часть просканированных страниц успешно освобождается (это хорошо). Низкое напротив - система тратит много ресурсов на сканирование, прежде чем найдет подходящие страницы для освобождения.

Как получить значения в grafana

node_exporter в помощь:
node_vmstat_pgscan_direct
node_vmstat_pgsteal_direct
node_vmstat_pgscan_kswapd
node_vmstat_pgsteal_kswapd


Итого

Резюмируя, если значения pgsteal, pgscan растут это сигнал, что память в системе переутилизирована и хорошо бы обратить на это внимание.

Решения проблемы в вакууме нет, все зависит от кейса: где-то будем докидывать памяти, где-то расширять swap, где-то анализировать потребление памяти приложением, а где-то хватит и лимитов на нее.
🔥11👍4
𝗚𝗶𝘁𝗟𝗮𝗯 выделяется среди других компаний своими открытыми репозиториями с множеством полезных гайдов и обсуждений 💬

В одном из таких материалов — заметке "𝗛𝗼𝘄 𝘁𝗼 𝘂𝘀𝗲 𝗳𝗹𝗮𝗺𝗲𝗴𝗿𝗮𝗽𝗵𝘀 𝗳𝗼𝗿 𝗽𝗲𝗿𝗳𝗼𝗿𝗺𝗮𝗻𝗰𝗲 𝗽𝗿𝗼𝗳𝗶𝗹𝗶𝗻𝗴" — автор объясняет, что такое 𝗳𝗹𝗮𝗺𝗲𝗴𝗿𝗮𝗽𝗵, зачем он нужен, как собирать данные и какие трудности могут возникнуть при анализе этих красно-желтых графиков.

Заметка также содержит практические примеры интерпретации 𝗳𝗹𝗮𝗺𝗲𝗴𝗿𝗮𝗽𝗵. Для сбора данных используется linux-perf: он добавляет оверхед, но подходит для старых версий ядер.

На ядрах 4.19+ лучше использовать eBPF-инструмент profile от bcc.
🔥7👍2
Когда система становится сложнее (добавляются новые технологии, версии ядер, типы дистрибутивов, машины, кластеры), необходимость в унификации и системном подходе к процессам заметно возрастает.

Профилирование приложений в таких условиях превращается в челендж: разные окружения, требования, версии и типы рантаймов усложняют задачу.

Предлагаю обменяться опытом:
* какой тулинг используете? Например, унифицированные инструменты вроде bcc (eBPF) или языкоориентированные (pprof, async-profiler);
* используете ли continuous profiling? (coroot, pyroscope, etc);
* в каких окружениях работаете (k8s, docker, чистый linux (windows?);
* как доставляете пререквизиты? доступ к symbols, frame pointer, linux-headers. Тащите все это добро проактивно или "по требованию?;
* как обходите ограничения по ИБ? Например, async-profiler требует RW-доступа к файловой системе контейнера, а требования ИБ настаивают на RO-доступе.

Будет интересно узнать, с какими сложностями сталкиваетесь и какие выбираете подходы!

——
Это же обсуждение на linkedin
🔥4
А вынесу из комментарий ответ по «проблемам» профилирования.

Унификация это то, к чему хочется стремиться. То есть одним инструментом профилировать весь зоопарк рантаймов.

На эту роль отлично подходят linux-perf или profile от BCC, но тут есть нюансы:

* многие начитавшись "вредных советов" отключают frame pointer и вырезают symbols при сборке бинарников, отчего профилирование становится бесполезным - на нем ничего не видно. Соответственно у каждого рантайма по своему включаются/отключаются эти опции;

* по умолчанию хочется использовать profile на eBPF, так как он дает сильно меньший оверхед, но для этого и linux ядра должны быть более менее свежие и скомпилированы они должны быть с соответствующими флагами, а еще на машинах должны быть подходящие версии linux-headers ;

* предыдущий пункт отчасти решает такая штука как libbpf, с помощью нее можно собрать бинарник profile, который уже в себе будет иметь все нужное(считай докер контейнер), но требования к свежей версии ядра никуда не деваются;

* далее проблемы на уровне контейнеризации, тот же libbpf profile (переносимый который) в нынешней реализации не container awareness, а значит умеет профилировать только процессы на уровне хоста, значит требуется куча зависимостей;

* а еще есть требования от ИБ, вот про тот же async-profiler и RO файловую систему. Можно деплоить Поды для профилирования на отдельные ноды, где будет все нужное, проводить тесты и деплоить обратно, но это мертворожденная история.

Вообщем проблем куча и их надо решать)
👍2🔥2
Investigation of a Cross-regional Network Performance Issue

Траблшутинг от Netflix: исследование причин низкой скорости сети между дата-центрами.

Подобные статьи люблю за то, что можно:
1. "подсмотреть", как инженеры строят гипотезы, проверяют их и находят решения;
2. узнать практические приёмы/фишки и применить в своей работе.

Из интересного

контейнерам в Netflix по умолчанию ограничивают пропускную способность сети (интересно на основе чего выбираются те или иные значения);

в окружениях за NAT пары ip:port на сервере и клиенте отличаются. Для идентификации одного TCP-стрима можно фильтровать пакеты по Sequence Numbers;

в ядре Linux 6.6+ изменился механизм расчёта TCP Receive Window:
— ранее использовался статический параметр net.ipv4.tcp_adv_win_scale.
— теперь применяются динамические расчёты на основе scaling_ratio, стартовое значение которого — 0.25.

Кроме того, упоминается поле tcp_sock->window_clamp:
Это максимальное значение окна приёма, которое может быть объявлено. Оно устанавливается как 0.25 от rcvbuf на основе начального значения scaling_ratio. Из-за этого размер окна ограничен этим значением и не может увеличиваться.


Звучит как баг, а не фича. Или это все работает немного не так;)

P.S. Подробнее вопрос Receive Window разбирается Cloudflare в Optimizing TCP for high WAN throughput while preserving low latency.

tags: #tcp #linux #kernel
🔥8
Как считается TCP Window Clamp

Ранее я упоминал статью Netflix Investigation of a Cross-regional Network Performance Issue. В ней разбирается деградация скорости TCP-соединений между дата-центрами и проблема возникла из-за изменения алгоритма расчёта TCP Receive Window в новых версиях ядра.

Как часто бывает, такие материалы оставляют больше вопросов, чем ответов — “know unknown” чистой воды.

Я покопался в исходниках Linux, чтобы лучше понять механизм подсчета TCP Window. Делюсь изысканиями:)

tags: #tcp #linux #kernel
🔥3👏2
Я отмечал ранее, что интерпретация CPU Usage — вещь не очевидная.
Высокие значения не всегда означают, что перегружен именно процессор — иногда проблема в скорости работы с памятью.

Понять во что упираемся помогает параметр — IPC (Instructions Per Cycle):
- высокий CPU Usage, высокий IPC: основная нагрузка идет на процессор;
- высокий CPU Usage, низкий IPC: узкое место в работе с памятью.

——

Окей, допустим мы поняли: надо оптимизировать память. С чего начать?

Отправной точкой может стать сборник статей Johnny's Software Lab: Memory Subsystem Optimizations.

Внутри:
- основы устройства памяти;
- советы по оптимизации кода, ОС и железа;
- техники для бенчмарков;
- и всякое другое.

Разбираемся, применяем на практике и делимся опытом;)
👍12🔥7
Breaking down CPU speed: How utilization impacts performance

Инженеры GitHub провели исследование, чтобы найти оптимальный баланс между загрузкой CPU и скоростью его работы.

Для этого они зеркалили продовый трафик в отдельное окружение и замеряли latency обработки операций при разной степени утилизации процессора. Задача была с одной стороны оставаться в рамках SLA, а с другой — не позволять ресурсам простаивать.

В результате, для их типичной рабочей нагрузки (IO-bound) и используемых процессоров (Intel), оптимальная утилизация CPU оказалась в районе 61%.

Интересно, что Б. Грегг в своей книге "Systems Performance" часто упоминает порог в 60% как точку, после которой ресурс (будь то диск или CPU) может стать узким местом в системе.

Цитата:
Netflix commonly uses ASGs that target a CPU utilization of 60%, and will scale up and down with the load to maintain that target.


P.S. Теперь дело "за малым": настроить процессы resource management и capacity planning, чтобы держать утилизацию в районе этих цифр. Изи)
👍18🤨1🤝1
Scaling in the Linux Networking Stack (scaling.txt)

Документ от разработчиков ядра Linux описывает пять техник, которые помогают повысить производительность сетевого стека в многоядерных системах.

Это:
* RSS: Receive Side Scaling
* RPS: Receive Packet Steering
* RFS: Receive Flow Steering
* Accelerated Receive Flow Steering
* XPS: Transmit Packet Steering

В нашей инфраструктуре мы уже давно и успешно используем RSS.

Это аппаратная технология, суть следующая.

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

Каждая RX-очередь привязана к конкретному ядру процессора.

Таким образом:
1. Обработка трафика распределяется между несколькими CPU, что позволяет эффективно использовать ресурсы;
2. Все пакеты одного соединения попадают в одну очередь. Это исключает проблему "out of order" пакетов.

Однако бывают ситуации, когда распределить обработку трафика между ядрами не получается, и отдельное ядро или группа ядер оказывается сильно перегруженной:

- одно конкретное соединение прокачивает кратно больший объем трафика, чем другие;
- RX-очередей в системе меньше, чем доступных CPU.

Мы, например, сталкивались с такими проблемами при использовании MetalLB в L2-режиме: весь трафик шел через одну машину MetalLB (мастер) и "приклеивался" к одной RX-очереди на Ingress Controller. Остальные ядра и RX-очереди при этом простаивали.

В подобных случаях может помочь другая техника, описанная в том же документе — RPS (Receive Packet Steering).

RPS — это программная реализация RSS. Она позволяет распределить RX-очереди по конкретным ядрам, выравнивая нагрузку между ними.

Но есть и минусы:
- Программная реализация создает дополнительную нагрузку на CPU, что проявляется в увеличении числа IRQ на графике загрузки процессора;
- Снижается "локальность" данных в кэшах процессора, что может повлиять на производительность.

(на скрине RPS включили после 16:00)

———

Тема сложная, и я не уверен, что до конца понимаю, как это все работает;)
Было бы интересно узнать, какие техники масштабирования трафика используете вы и как справляетесь с подобными проблемами.

Дальнейшее чтение:
- сам документ scaling.txt
- расшифровка доклада от Одноклассников, где ребята решали похожие проблемы;
- примерно тоже самое, но забугорный доклад по перформансу сетевого стека.

tags: #network #tuning #linux #кейс
👍13
Ingress NGINX Controller vs ClusterIP (IPtables): Балансировка.

Задумывались ли вы почему балансировка через Ingress NGINX Controller показывает более ровное распределение трафика по Подам в сравнении с ClusterIP (IPtables)?

Я до недавнего времени не очень, но "сигналы с мест" требовали разобраться.

Балансировка через ClusterIP (IPtables)

ClusterIP (далее svc) это сущность Kubernetes, позволяющая приземлять трафик в Под.

В свою очередь svc это просто набор IPtables правил (или правил аналогичных инструментов), "приклеивающий" запрос к конкретному Поду.

Примерно так:
iptables -t nat -A KUBE-SVC -m random --probability 0.33 -j DNAT --to <Pod1>
iptables -t nat -A KUBE-SVC -m random --probability 0.50 -j DNAT --to <Pod2>
iptables -t nat -A KUBE-SVC -j DNAT --to <Pod3>

Что можно интерпретировать как:
* Pod1 будет выбран в качестве апстрима в 33% случаев;
* далее в 50% Pod2;
* иначе апстримом станет Pod3.

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

Теория вероятностей.


Балансировка через Ingress NGINX Controller

Зная о том как функционирует svc, я по наивности полагал, что и Ingress Controller (далее IC) оперирует в качестве апстримов сущностью svc, что будет в итоге давать тот же "уровень" баланcировки.

Факты говорили об обратном, потому я пошел перепроверять.

Под каждый объект Ingress рендерится секция server{} шаблона nginx.tmpl (пруф):

{{ range $tcpServer := .TCPBackends }}
server {...}
{{ end }}


В ней директива proxy_pass указывает на некий upstream_balancer:

### Attention!!!
# We no longer create "upstream" section for every backend.
# Backends are handled dynamically using Lua....
...

balancer_by_lua_block {
balancer.balance()
}
...


В balancer.balance() и живет логика выбора апстрима, где peer == endpoint (считай Pod):

...
local peer = balancer:balance()
if not peer then
ngx.log(ngx.WARN, "no peer was returned, balancer: " .. balancer.name)
return
end
...


——
Да и как иначе IC сможет обеспечивать разные типы балансировки? А по умолчанию используется round-robin.
👍10👏31😐1