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

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

🔹 Обо мне: alebedev.tech/about
🧑‍💻 Менторинг: alebedev.tech/mentoring
Download Telegram
Optimizing web servers for high throughput and low latency by Dropbox.tech

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

tags: #linux #performance #network #tuning
👍2
CPU Utilization is Wrong by Brendan Gregg

B. Gregg показывает почему метрика утилизации %CPU может вводить в заблуждение - она включает в себя не только время затраченное на полезную работу CPU, но и ожидание обращения к памяти.

Как решение предлагается ориентироваться на показатель IPC (instructions per cycle), доступный в perf stat, atop, perf коллектор в node_exporter, etc.

tags: #linux #performance #metrics #cpu
👍31
A Complete Guide of 'ss' Output Metrics by Mark Zhu

Самое подробное описание использования утилиты ss что я встречал.

tags: #network #linux #troubleshooting #tooling
👍2
Let's talk about resources isolation

Ветка на форуме proxmox, где топик-стартер подсвечивает недостатки существующих инструментов изоляции ресурсов в proxmox.

В частности отсутствие функционала "из коробки":
- vCPU pinning, что прямо или косвенно приводит к излишнему context-switching и L3 cache промахам;
- SMP-aware pinning - два логических ядра не всегда равны двум физическим (hyper threading) и при планировании тредов на процессор хорошо бы учитывать chiplets - задержка доступа к кешу между соседними ядрами может различаться в разы.

Автор подсвечивает пять уровней изоляции ресурсов виртуальной машины на гипервизоре и как их можно достичь:

1. CPU pinning тредов VM;
2. Освобождение vCPU от обработки IRQ;
3. Изоляция VM от userspace процессов гипервизора;
4. Изоляция VM от kernelspace процессов гипервизора;
5. Изоляция VM от других VM на гипервизоре (cgroups).

---

Тред пригодится в борьбе за производительность CPU-bound приложений в виртуальных окружениях.

#virualization #proxmox #linux #performance #tuning
👍1👏1
Написал в блог заметку о методологии "Thread State Analysis" (TSA).

Это когда о поведении системы судят по тому, в каком состоянии (state) пребывают её потоки: running, sleep, stopped и т.д.

TSA, как и другие высокоуровневые методологии (USE, RED), может помочь сориентироваться, определить направления для дальнейшего анализа и сократить общее время устранения проблем.

tags: #methodology #linux #troubleshooting
👍1🔥1
Есть такой ресурс sadservers.com, позиционирует себя как «leetcode для linux”.

На нём собраны задачки разной степени сложности из разряда «процесс PostgreSQL перестал работать, найди причину и устрани».

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

В качестве пробы пера записал видео с прохождением первой задачи с небольшими теоретическими отступлениями.
👍12
TCP Congestion Control в разных окружениях

Написал заметку как влияет потеря сетевых пакетов на пропускную способность TCP соединения.

p.s. катастрофически.

tags: #linux #tcp #performance
👍6
SadServers №15 | Middle-level | Tokyo

Траблшутинг в ленту!

Tokyo - пятнадцатая задача среднего (middle) уровня на sadservers.com:
- изучим состояние сокетов через ss;
- посмотрим на ретрансмиты в tcpdump;
- поправив правила netfilter (iptables);
- и наконец приведем в чувства webserver.

#sadservers #linux #devops #troubleshooting #sre #tcpdump
👍91
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
Мониторинг TCP: метрики Zero Window 🚀

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

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

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

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

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

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

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

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

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

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


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

tags: #tcp #linux
🔥163
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
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
Сетевой анализ с eBPF: измеряем Round Trip Time

Длительность (latency) — ключевой показатель производительности системы. На первый взгляд всё просто: рост задержки — признак деградации. Сложность в деталях...


Под катом:
* мои рассуждения о сложности интерпретации latency в современных системах;
* небольшой гайд по eBPF - напишем по шагам инструмент, который поможет отвечать на вопрос: "причина замедления в приложении или в инфраструктуре?".

Полезного чтения!

tags: #eBPF #Linux #SRE #TCP
👍19
photo_2025-01-20_08-36-01.jpg
60.5 KB
Ранее я писал о баге Haproxy: после рестарта треды не завершались, что приводило к их накоплению, память иссякала и приходил OOM Killer.

Проблему решали костылем — директива hard-stop-after принудительно завершает треды после рестарта.

Но Haproxy не сдается и наносит ответный удар!

Причины еще предстоит выяснить, поэтому это скорее "заметка с полей"


Симптомы схожи: утечка памяти.

Но сбой наступает когда (это гипотеза) заканчивается память для TCP-буферов (net.ipv4.tcp_mem) - ядро с переменным успехом пытается освободить память для новых / существующих соединений, что приводит к затруднению в сетевых взаимодействиях.

На скрине такой период отмечен красным прямоугольником.

# sysctl net.ipv4.tcp_mem
net.ipv4.tcp_mem = 90435 120582 180870


Где 180870 - максимальное значение (в страницах памяти) под все TCP сокеты в системе, что равно ~ 706MB.

Оказалось, что система насыщается "повисшими" соединениями, чьи буферы сокетов содержат данные:
# ss -ntOai | awk '{for(i=1;i<=NF;i++)if($i~/^lastsnd:/){split($i,a,":");print a[2], $2, $4, $5}}' | sort -n | tail 

#lastsnd # Recv-Q #Src #Dst
234423668 157355 10.11.12.4:57354 10.11.6.123:80
235316436 302417 10.11.12.4:56232 10.11.6.124:80
238200680 301585 10.11.12.4:37940 10.11.6.124:80
238726828 300103 10.11.12.4:58944 10.11.6.124:80
243816724 297015 10.11.12.4:51700 10.11.6.125:80
251456440 302959 10.11.12.4:52324 10.11.6.125:80
252237780 302464 10.11.12.4:47786 10.11.6.123:80
257868244 163453 10.11.12.4:41568 10.11.6.125:80
259905196 300433 10.11.12.4:40202 10.11.6.123:80
261307944 214022 10.11.12.4:54888 10.11.6.123:80 # это ~ 72 часа

где:
* lastsnd - время с последней отправки данных, в милисекундах;
* Recv-Q - объем не прочитанных данных, в байтах.

А раз есть не прочитанные данные, значит таймер TCP keepalive не взводится:
static void tcp_keepalive_timer (struct timer_list *t)
{
...
/* It is alive without keepalive 8) */
if (tp->packets_out || !tcp_write_queue_empty(sk))
goto resched;

...

resched:
inet_csk_reset_keepalive_timer (sk, elapsed);
goto out;

...

out:
bh_unlock_sock(sk);
sock_put(sk);
}

Был бы повод, а костыль найдется!

Ребята из CloudFlare писали в свое время статью When TCP sockets refuse to die, где в виде решения предлагалось использовать опцию сокета TCP_USER_TIMEOUT:
...it specifies the maximum amount of time in milliseconds that transmitted data may remain unacknowledged, or buffered data may remain untransmitted (due to zero window size) before TCP will forcibly close the corresponding connection and return **ETIMEDOUT** to the application...


В свою очередь Haproxy поддерживает ее через tcp-ut.

Посмотрим, как себя покажет.

tags: #tcp #linux #kernel #troubleshooting #кейс
👍21🔥2
(Не) очевидные особенности настроек TCP сокетов. Часть 1.

Задача: затюнить размеры сокетов у Nginx, чтобы без потерь переживать всплески трафика.

Уточним размер буфера чтения:
# ss -ntlmO | grep ':80 '
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* skmem:(r0,rb131072,t0,tb16384,f0,w0,o0,bl0,d0)

Нас интересует значение rb - 131072 байт.

Окей, допустим мы хотим сделать его равным 6291456 байт (~6мб).

В Nginx за размер буфер приема отвечает параметр rcvbuf, см. док .

Вносим изменения в конфиг и перезапускаем Nginx:
# grep listen /etc/nginx/nginx.conf
listen 80 rcvbuf=6291456;
# systemctl restart nginx


Проверяем:
# ss -ntlmO | grep ':80 '
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* skmem:(r0,rb425984,t0,tb16384,f0,w0,o0,bl0,d0)


425984 не похоже на 6291456 ;)

———

# sysctl -a | grep rmem
net.core.rmem_default = 212992
net.core.rmem_max = 212992
net.ipv4.tcp_rmem = 4096 131072 18874368
...


Среднее значение tcp_rmem соответствует размеру буфера до изменений, а новое (425984) не совпадает ни с чем.

Документация к rcvbuf говорит, что настройка соответствует опции сокета SO_RCVBUF:
# man 7 socket

SO_RCVBUF
Sets or gets the maximum socket receive buffer in bytes. The kernel doubles this value (to allow space for bookkeeping overhead) when it is set using setsockopt(2), and this doubled value is returned by getsockopt(2). The default value is set by the /proc/sys/net/core/rmem_default file, and the maximum allowed value is set by the /proc/sys/net/core/rmem_max file...


1. ядро будет удваивать переданное значение (что и увидим в`rb`)
2. дефолтное (начальное) равно rmem_default;
3. максимум задается через rmem_max.

Причины удвоения (хотя там могут быть разные варианты) стоит искать в "man 7 tcp" и в "net.ipv4.tcp_adv_win_scale". Или в подробной статье от CloudFlare.


Окей, произведем расчеты: 425984 / 2 = 212992.

Похоже, что мы уперлись в net.core.rmem_max:
# sysctl net.core.rmem_max=6291456
net.core.rmem_max = 6291456
# nginx -s reload
# ss -ntlmO | grep '0.0.0.0:80 '
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* skmem:(r0,rb12582912,t0,tb16384,f0,w0,o0,bl0,d0)


Получаем искомое rb в 12582912 (6mb * 2).

Промежуточный итог: если мы выставляем руками размеры буферов на уровне приложение (опция SO_RCVBUF), следует учесть это и в net.core.rmem_max.

Следующая часть.

tags: #tcp #linux #kernel
🔥15
(Не) очевидные особенности настроек TCP сокетов. Часть 2.

Начало тут.

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

Потому разберемся с настройкой net.ipv4.tcp_rmem и узнаем оказывает ли на него влияние net.core.rmem_max:
# man 7 tcp
...
tcp_rmem (since Linux 2.4)
This is a vector of 3 integers: [min, default, max]. These parameters are used by TCP to regulate receive buffer sizes. ...

min minimum size of the receive buffer used by each TCP socket. The default value is the system page size. (On Linux 2.4, the default value is 4 kB, lowered to PAGE_SIZE bytes in low-memory systems.)...

default the default size of the receive buffer for a TCP socket. This value overwrites the initial default buffer size from the generic global net.core.rmem_default defined for all protocols...

max the maximum size of the receive buffer used by each TCP socket. This value does not override the global net.core.rmem_max. This is not used to limit the size of the receive buffer declared using SO_RCVBUF on a socket...


Перефразирую:
- дефолтное значение tcp_rmem перезаписывает net.core.rmem_default — это мы заметили в самом начале, до использования rcvbuf;
- максимальное значение tcp_rmem НЕ перезаписывает net.core.rmem_max и НЕ используется при выставлении SO_RCVBUF.

Делаем промежуточные вывод, что:
1. net.core.rmem_max задает жесткий лимит на размер TCP буфера;
2. tcp_rmem не участвует в игре при выставлении SO_RCVBUF.

Проверим!

Начнем с конца:
# sysctl -a | grep rmem
net.core.rmem_default = 212992
net.core.rmem_max = 6291456
net.ipv4.tcp_rmem = 4096 131072 18874368

### Выставляем максимальное значение tcp_rmem ниже rmem_max
# sysctl net.ipv4.tcp_rmem="4096 131072 851968"
net.ipv4.tcp_rmem = 4096 131072 851968

# grep listen /etc/nginx/nginx.conf
listen 80 rcvbuf=6291456;

# systemctl restart nginx

# ss -ntlmO | grep '0.0.0.0:80 '
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* skmem:(r0,rb12582912,t0,tb16384,f0,w0,o0,bl0,d0)


Действительно, максимальный tcp_rmem не сыграл.

Теперь проверим, что net.core.rmem_max задает жесткий лимит над размером TCP сокетов:
#  sysctl -a | grep rmem
net.core.rmem_default = 212992
net.core.rmem_max = 6291456
net.ipv4.tcp_rmem = 4096 131072 851968

### Делаем net.core.rmem_max ниже чем дефолтный tcp_rmem, значение которого поднимем

# sysctl net.core.rmem_max=212992
net.core.rmem_max = 212992
# sysctl net.ipv4.tcp_rmem="4096 524288 851968"
net.ipv4.tcp_rmem = 4096 524288 851968

### Убираем директиву rcvbuf
# grep listen /etc/nginx/nginx.conf
listen 80;

# systemctl restart nginx
# ss -ntlmO | grep '0.0.0.0:80 '
LISTEN 0 511 0.0.0.0:80 0.0.0.0:* skmem:(r0,rb524288,t0,tb16384,f0,w0,o0,bl0,d0)


Размер буфера выставляется равным дефолтному tcp_rmem (кстати без удвоений), который больше net.core.rmem_max.

——
Особо пытливым для перепроверки можно обратиться к исходникам:
1. net.core.rmem_max участвует либо в обработке опции SO_RCVBUF:
...
case SO_RCVBUF:
...
__sock_set_rcvbuf(sk, min_t(u32, val, READ_ONCE(sysctl_rmem_max)));


2. либо в определении начального TCP окна:
...
space = max_t(u32, space, READ_ONCE(sock_net(sk)->ipv4.sysctl_tcp_rmem[2]));
space = max_t(u32, space, READ_ONCE(sysctl_rmem_max));
space = min_t(u32, space, *window_clamp);
*rcv_wscale = clamp_t(int, ilog2(space) - 15,
0, TCP_MAX_WSCALE);


что все таки не бьется с документацией.

Выходит обманывают нас разработчики или я так интерпретирую тексты :)

Окончательные выводы:
1. net.core.rmem_max играет только при ручном выставлении размеров сокетов (SO_RCVBUF);
2. net.ipv4.tcp_rmem напротив, не участвует в SO_RCVBUF, зато позволяет использовать автоподстройку, что в большинстве случаев будет более гибким решением.

В итоге решение задачи может быть следующим:
# sysctl net.ipv4.tcp_rmem="4096 1048576 12582912"


tags: #tcp #linux #kernel
👍23
Об IPC
(конспект по книге Performance Analysis and Tuning on Modern CPUs)

Я уже писал о показателе Instructions Per Cycle (IPC) (например тут и тут). Сейчас разберём детали глубже.

Instruction Per Cycle (IPC) — это среднее количество инструкций, завершённых за один такт процессора:

IPC = Retired Instructions / Core Cycles

Определим ключевые понятия.

Инструкции делятся на executed и retired.

- Executed инструкции уже выполнены, но результат ещё не записан в память. Они могут выполняться вне порядка (out of order) и быть отменены, например, из-за miss branch prediction;

- Retired инструкции полностью завершены, то есть и выполнены и их результаты записаны (committed). Отменить их уже нельзя.

Executed напрямую не отслеживаются, а для retired есть отдельный счётчик:
perf stat -e instructions -- ./a.exe

2173414 instructions # 0.80 insn per cycle


Cycles (циклы) процессора бывают двух видов:
- core;
- reference.

Разница важна при динамическом изменении частоты процессора:
1. если CPU работает на штатной частоте: core = reference.
2. если CPU разогнан: core > reference.

Core Cycles отражают реальную текущую, когда Reference Cycles базовую (по паспорту) частоту процессора.
perf stat -e cycles,ref-cycles -- ./a.exe

43340884632 cycles # 3.97 GHz <= Core Cycles
37028245322 ref-cycles # 3.39 GHz <= Reference Cycles


Следовательно IPC показывает единицу A) выполненной B) полезной работы в текущий момент.

IPC не зависит от изменения тактовой частоты, так как всегда рассчитывается на один цикл.


Факторы, ограничивающие IPC
(перечислены в случайном порядке, список неполный):

- скорость памяти и cache misses;
- архитектура процессора: скалярность, загрузка слотов пайплайна;
- тип и сложность инструкций;
- branch misprediction (пенальти на ошибку по 10–25 ns);
- ...

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

На практике этот максимум недостижим: процессор может одновременно выполнять только определённые типы инструкций. Например, в 6-wide архитектуре за такт можно провести четыре операции сложения/вычитания, одну загрузку и одну запись, но не шесть загрузок одновременно.

to be continued...

#cpu #theory
👍16🔥9👎1