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

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

🔹 Обо мне: alebedev.tech/about
🧑‍💻 Менторинг: alebedev.tech/mentoring
Download Telegram
TCP Retransmission May Be Misleading (2023) by Arthur Chiao

Про типы TCP ретрансмитов в linux, как наблюдать и влиять на них.

tags: #linux #network #tcp
👍1
TCP Puzzlers by Dave Pacheco

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

* отключение питания сервера
* перезагрузка сервера
* внештатный обрыв соединения.

Авто подсвечивает не совсем очевидные моменты, о которых хорошо бы знать.

tags: #tcp #network
👍2
TCP Congestion Control в разных окружениях

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

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

tags: #linux #tcp #performance
👍6
💥 Выступил на perfconf#10 с неделю назад💥

C докладом:

"Когда код идеален, но система тормозит. Скрытые враги производительности"

(немного пафосно звучит, но что поделать😉)

Рассказывал почтенной публике о влиянии TCP Retransmits и CPU Throttling на производительность приложений — как, зачем и что с этим делать.

Такой ликбез с небольшим deep dive в нюансы.

По ощущениям получилось вполне неплохо, и думаю, стоит повторять такие упражнения в будущем😊

По традиции — спасибо всем причастным! 🙌

P.S. Несколько фото и презентация прилагаются 📸💻

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

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

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

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

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

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

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

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

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

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

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

tags: #network #tcp #theory
👍7
Мониторинг 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
Сетевой анализ с eBPF: измеряем Round Trip Time

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


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

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

tags: #eBPF #Linux #SRE #TCP
👍19
Алгоритмы управления потоком (Flow Control) в TCP служат для предотвращения перегрузки сети и потерь данных.

Исследования в этой области не прекращаются и на сегодня нам доступно множество вариантов:

* Reno (1986)
* New Reno (1999)
* CUBIC (2004)
* FAST TCP (2005)
* BBRv1 (2016)
* BBRv2 (2019)
* BBRv3 (2023)
* ...

По умолчанию в Linux используется CUBIC. Однако создатели BBR (Google) выкладывают любопытные исследования, где резюмируют:

BBR enables big throughput improvements on high-speed, long-haul links...
BBR enables significant reductions in latency in last-mile networks that connect users to the internet...


Так может нам просто переехать на новые рельсы?

Хотя кажется правильнее поставить вопрос по другому: в каких случаях какой алгоритм может быть предпочтительнее?

————

Алгоритмы Flow Control можно условно разделить на два типа:
1. Loss-based (ориентированы на потери пакетов): Reno, NewReno, CUBIC
2. Delay-based (ориентированы на изменения RTT): FAST TCP, BBRv*

Основная цель любой реализации Flow Control — максимально эффективно использовать пропускную способность канала, сохраняя баланс между скоростью передачи данных и предотвращением перегрузок.

Скорость регулируется через Congestion Window (окно перегрузки) — сколько данных можно отправить без получения подтверждения.

Разница между подходами к контролю перегрузки заключается в методах её определения.

Loss-based (CUBIC)

Алгоритмы этого типа оценивают перегрузку по потерям пакетов.

Пришел дублирующий ACK или сработал Retransmission Timeout (RTO)? Значит есть потери и следовательно канал перегружен - снижаем скорость.
Затем ориентируясь на поступающие ACK, скорость увеличивается, пока не обнаружатся новые потери.

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

Delay-based (BBR)

В Delay-based алгоритмах, таких как BBR, перегрузка оценивается на основе изменения задержек:
* минимальный RTT (RTT_min) принимается за эталон;
* если текущий RTT (RTT_now) превышает RTT_min, алгоритм предполагает, что канал перегружен, и снижает скорость передачи данных.

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

————

CUBIC проигрывает BBR в сетях с высоким RTT, например, в интернете. Это происходит из-за медленного роста скорости после обнаружения потерь: ACK приходят с задержкой.

Внутри дата-центров, где RTT низкий, CUBIC должен справляться лучше - быстрые ACK ускоряют рост скорости передачи данных.

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

Вообщем как обычно надо быть осторожее!

Почитать:
- https://blog.apnic.net/2017/05/09/bbr-new-kid-tcp-block/
- https://book.systemsapproach.org/congestion.html
- https://tcpcc.systemsapproach.org/

tags: #network #tcp
🔥20👍3🤝1
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 (собрание материалов о TCP из канала)

📦 Алгоритмы контроля перегрузки

- TCP Congestion Control в разных окружениях — как алгоритмы перегрузки работают при потере пакетов. Практические наблюдения.
- BBR vs CUBIC — выбираем подходящий под наше окружение алгоритм.
- Мониторинг TCP: метрики Zero Window — что такое Zero Window, как он сигнализирует о перегрузке получателя, и почему это важно мониторить.
- Как считается TCP Window Clamp — копаемся в исходниках Linux и разбираемся в механизмах TCP Window;

📈 Ретрансмиты

- Мое выступление на Perf Conf №10 — влияние потерь пакетов на производительность приложений.
- TCP Retransmission May Be Misleading — классификация типов ретрансмитов и их мониторинг
- TCP ретрансмиты и их направления — пишем eBPF код для визуализации направления ретрансмитов в Grafana;

🔗 TCP соединения

- Сетевой анализ с eBPF: измеряем Round Trip Time — пишем eBPF код для мониторинга Round Trip Time в Grafana;
- TCP Puzzlers — интерпретируй закрытие соединений правильно.
- A Complete Guide of 'ss' Output Metrics — полный разбор метрик утилиты ss.

📚 Разное

- Зависшие соединения в Haproxy и механизм работы TCP keepalive — читаем код ядра и устраняем проблемы зависших соединений.
- Рассуждения о размерах очередей и какие трейдофы у больших значений;
- Investigation of a Cross-regional Network Performance Issue — траблшутинг медленной сети между дата-центрами после обновления ядра Linux
👍21❤‍🔥1
(Не) очевидные особенности настроек 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
Готовлюсь к внутреннему митапу с докладом о потерях сетевого трафика: как их диагностировать и устранять.

В основе GIF-анимация, иллюстрирующая путь и остановки входящего сетевого пакета в ядре Linux.


Очереди в ядре Linux

* RX queue. Первая остановка: очередь сетевой карты (см. ethtool -l eth0)
* QDisc. Приоритизация, модификация и многое другое возможны с помощью дисциплины очередей. Calico, Cilium и подобные ребята перехватывает пакеты именно здесь (eBPF)
* Input Packet Queue. Очередь перед стеком протоколов.

📍Для новых соединений
* SYN queue. Очередь, где SYN-сегменты дожидаются ACK;
* Accept queue. Приложение через accept()подтверждает, что соединение установлено - зеленый свет для обмена данными.

📍Для уже установленных соединений
* Out Of Order queue. При нарушении очередности (sequence number больше ожидаемого), пакет помещается в нее, до восстановления правильного порядка;
* Recv queue. TCP-буфер сокета, из него приложение читает данные системным вызовом read().

———
Подробнее я описывал весь процесс в двух частях: один, два.

P.S. Кстати, поддержать канал теперь можно на Бусти или просто донатом!

#network #tcp #kernel
🔥18👍9