Debian 13 — помилки «Broken pipe»: коли це безпечний шум і коли це перша тривога (випадок №75)

Було корисно?

Ви спостерігаєте розгортання на Debian 13. Здається, усе гаразд — затримки стабільні, ліміти помилок спокійні — а тоді логи починають сипатися:
broken pipe. Деякі команди ставляться до цього як до дрібниці. Інші — як до аварії. Обидві іноді праві, і обидві часто помиляються.

«Broken pipe» — одна з тих помилок, що не каже, що саме зламалося. Вона каже коли ви дізналися про помилку:
ви спробували записати в з’єднання, яке інша сторона вже закрила. Справжнє питання — чому воно закрилося
і чи було це нормальним.

Що насправді означає «broken pipe» на Debian 13

На Debian 13, як і на будь‑якому сучасному Linux, «broken pipe» зазвичай — це ваш додаток, який виводить класичну UNIX‑ситуацію:
EPIPE (errno 32). Простими словами: ваш процес спробував виконати write() (або аналогічну операцію send)
у pipe або socket, на якому уже немає читача з іншого кінця.

Звідки приходить повідомлення

  • Додатки пишуть «Broken pipe», коли ловлять EPIPE і логують це (Go, Java, Python, Ruby, Nginx, Apache, клієнти PostgreSQL і т.д.).
  • Shell‑конвеєри іноді виводять це, коли кінцевий споживач завершується раніше (наприклад, head), а джерело продовжує писати.
  • Бібліотеки можуть перетворювати EPIPE в виняток (наприклад, java.io.IOException: Broken pipe, BrokenPipeError: [Errno 32] в Python).
  • Сигнали: за замовчуванням запис у закритий pipe може спричинити SIGPIPE, що завершує процес, якщо сигнал не оброблено/ігнорується. Багато мережевих серверів ігнорують SIGPIPE, щоб не падати, і натомість логують EPIPE.

Ключова операційна деталь: EPIPE — це виявлення записувачем, що пір відсутній. Це не доводить, що записувач має баг,
і не доводить, що мережа зламалася. Це доводить невідповідність життєвих циклів: одна сторона все ще пише, інша — уже завершилася.

Broken pipe vs. connection reset: чому це важливо

Інженери часто зводять «broken pipe» і «connection reset by peer» до одного. Вони пов’язані, але не тотожні.
«Connection reset» зазвичай — це ECONNRESET: peer (або middlebox) надіслав TCP RST, різко розриваючи з’єднання.
«Broken pipe» зазвичай — EPIPE, коли ви записуєте після того, як peer закрив з’єднання (FIN) і ви це опрацювали.

Ця тонкість важлива, бо FIN після відповіді — нормальна поведінка для багатьох клієнтів, тоді як хвиля RST може сигналізувати про тайм‑аут проксі,
навантаження ядра або те, що процес сервісу був жорстко вбито. Debian 13 не змінив TCP, але свіжіші версії демонів і нові дефолти можуть змінити те, як часто ви побачите ці повідомлення.

Одна цитата, яку я тримаю на ментальному табло: Werner Vogels (CTO Amazon) популяризував підхід надійності —
«Everything fails, all the time» (парафраз). «Broken pipe» часто — невелика поштова листівка з цієї реальності.

Безпечний шум проти першої тривоги

Коли «broken pipe» зазвичай безпечний

Ви можете знижувати пріоритет, якщо справджуються всі ці умови:

  • Корелює з відмовами клієнтів: користувачі йдуть зі сторінки, мобільні клієнти змінюють мережу, вкладки браузера закриваються.
  • Низький рівень помилок: невеликий відсоток запитів, стабільно з часом, без тренду зростання під навантаженням.
  • Виникає після того, як заголовки/тіло відповіді переважно відправлені: класична ситуація «клієнт закрив з’єднання під час відправлення відповіді».
  • На кінцях з довгими завантаженнями: великі файли, стрімінгові відповіді, SSE/websocket‑подібна поведінка без відповідних keepalive.
  • Метрики не показують шкоди: p95/p99 затримок, рівень 5xx, насичення та глибина черг — спокійні.

Приклад: Nginx логують broken pipe, бо браузер перестав зчитувати великий відповідь. Сервер не винен; ваш рівень логування просто чесний.

Коли «broken pipe» — ваша перша тривога

Слід ставитись до цього як до сигналу аварії, якщо хоча б одна з цих умов істинна:

  • Стрибки частоти під час деплою: вказують на churn з’єднань, повільний старт, проблеми з readiness або невдале зливання старих подів.
  • Збігається з тайм‑аути: тайм‑аути upstream, тайм‑аути проксі чи idle timeouts балансувальника.
  • З’являється при тиску пам’яті/CPU: сервер гальмує, клієнти здаються, записи в мертві сокети призводять до EPIPE.
  • Скупченість на конкретних пірах: одна зона доступності, один NAT‑шлюз, один шар проксі, один endpoint зі зберіганням.
  • З’являється в внутрішніх RPC: збій сервіс‑до‑сервісу рідко буває «поведінкою користувача».
  • Корелює з TCP retransmits/resets: вказує на втрату пакетів, проблеми MTU, conntrack‑тиск або неполадки middlebox.

Жарт №1: «Broken pipe» — це лог‑еквівалент вашого колеги, який каже «нам треба поговорити» — може бути нічого, але ви не спокійні, поки не дізнаєтесь.

Операційне правило

Якщо «broken pipe» здебільшого в edge‑трафіку і здоров’я сервісу зелене, це шум, який варто приборкати.
Якщо це east‑west трафік, що зростає, або супроводжується тайм‑аутами/5xx — це симптом. Шукайте причину, а не рядок у логах.

Факти та історія, які стануть в пригоді в постмортемі

  1. «Broken pipe» походить із UNIX‑pipe: запис у pipe без читача історично викликав SIGPIPE; фраза старша за сучасні TCP‑сервіси.
  2. EPIPE — це errno 32 у Linux: тому ви бачите «Errno 32» в Python та подібних.
  3. За замовчуванням SIGPIPE завершує процес: багато серверів явно ігнорують SIGPIPE, щоб пережити відключення клієнта.
  4. TCP має два поширені режими завершення: акуратне закриття (FIN) проти різкого скидання (RST). Різні сигнатури помилки — різна відповідальність.
  5. HTTP/1.1 keep‑alive підвищив помітність: персистентні з’єднання збільшили поверхню для idle‑таймаутів і напівзакритих станів.
  6. Reverse‑проксі часто логують «broken pipe»: вони між клієнтами та upstream і перші помічають, коли одна зі сторін відходить.
  7. Middlebox люблять таймаути: балансувальники, NAT‑шлюзи, фаєрволи й проксі часто застосовують idle‑таймери, які додатки можуть ігнорувати.
  8. Linux може повідомляти про помилки сокета з запізненням: запис може успішно пройти локально й лише пізніше повідомити про збій; тому момент виникнення виглядає «випадковим».
  9. «Клієнт перервав» — не завжди користувач: health checks, синтетичний моніторинг, сканери та агресивні SDK також відкривають і кидають з’єднання.

План швидкої діагностики

Це порядок дій, що швидко знаходить реальні вузькі місця. Оптимізовано під on‑call реальність: вам потрібен напрямок за 10 хвилин, а не дисертація за 10 годин.

Перше: класифікуйте, де це відбувається (edge чи внутрішнє)

  • Edge / публічний HTTP: починайте з логів проксі (Nginx/Apache/Envoy), патернів відмов клієнтів та тайм‑аутів.
  • Внутрішнє RPC / база даних: поводьтеся з цим як з інцидентом надійності, поки не доведено протилежне.
  • Shell‑скрипти / cron: часто випадкові поведінки конвеєрів, але можуть ховати реальні проблеми з частковим виводом.

Друге: корелюйте з тайм‑аути та насиченням

  • Перевірте 499/408/504 (або еквіваленти) та лічильники upstream‑тайм‑аутів.
  • Перевірте CPU steal, runnable queue, тиск пам’яті, IO wait, затримки диска.
  • Перевірте мережеві retransmits/resets.

Третє: підтвердьте, хто закрив першим

  • Захоплення пакетів на хвилину на ураженому вузлі (так, навіть у 2025 році tcpdump усе ще заробляє гроші).
  • Інспекція налаштувань keepalive на клієнті, проксі, балансувальнику та сервері.
  • Пошук поведінки при деплої/дренуванні: readiness‑гейти, зливання з’єднань, graceful shutdown.

Четверте: визначте клас виправлення

  • Шум: тонке налаштування логування, зменшення трасувань, семплінг, покращити видимість клієнтських обривів.
  • Несумісність тайм‑аутів: вирівняти idle‑таймаути і keepalive; налаштувати буферизацію проксі.
  • Перевантаження: додати потужності, зменшити роботу на запит, виправити повільний I/O, впровадити backpressure.
  • Краші: виправити OOM, segfault, хвилі рестартів і погані практики деплою.

Практичні завдання: команди, виводи, рішення (12+)

Це ті завдання, які я насправді виконую на Debian‑хостах. Кожне має три частини: команда, що означає вивід, і яке рішення приймати далі.
Команди виконуються з root або sudo, де потрібно.

Завдання 1: Знайти точну формулювання помилки і який сервіс її видає

cr0x@server:~$ sudo journalctl -S -2h -p warning..alert | grep -i -E "broken pipe|EPIPE|SIGPIPE" | tail -n 20
Dec 31 09:12:41 api-01 nginx[1234]: *918 writev() failed (32: Broken pipe) while sending to client, client: 203.0.113.10, server: _, request: "GET /download.bin HTTP/1.1"
Dec 31 09:13:02 api-01 app[8871]: ERROR send failed: Broken pipe (os error 32)

Значення: Тепер ви знаєте, чи це Nginx, додаток, sidecar чи щось інше.
Nginx «while sending to client» кричить про клієнтський abort; помилки на рівні додатку можуть стосуватися downstream (БД, кеш, внутрішній RPC).
Рішення: Розділіть шлях розслідування: edge‑трафік для Nginx; трасування залежностей для помилок додатку.

Завдання 2: Перевірити, чи збігаються деплоя чи рестарти з піком

cr0x@server:~$ sudo journalctl -S -6h -u nginx -u app.service --no-pager | grep -E "Started|Stopping|Reloaded|SIGTERM|exited" | tail -n 30
Dec 31 08:59:58 api-01 systemd[1]: Reloaded nginx.service - A high performance web server and a reverse proxy server.
Dec 31 09:00:01 api-01 systemd[1]: Stopping app.service - API Service...
Dec 31 09:00:03 api-01 systemd[1]: app.service: Main process exited, code=killed, status=15/TERM
Dec 31 09:00:03 api-01 systemd[1]: Started app.service - API Service.

Значення: churn з’єднань під час reload/restart може породжувати EPIPE, коли клієнти в польоті втрачають upstream.
Рішення: Якщо помилки зосереджені навколо рестартів, перевірте graceful shutdown, дренування з’єднань, readiness і retry‑політики проксі.

Завдання 3: Шукати статуси abort клієнта у проксі

cr0x@server:~$ sudo awk '$9 ~ /^(499|408|504)$/ {c[$9]++} END{for (k in c) print k, c[k]}' /var/log/nginx/access.log
499 317
504 12
408 41

Значення: Багато 499 зазвичай означають, що клієнт закрив раніше (конвенція Nginx). 504 вказує на тайм‑аут upstream.
408 означає тайм‑аут запиту (клієнт занадто повільний або тайм‑аут читання заголовка).
Рішення: Якщо 499 домінує при стабільних 5xx — ймовірно безпечний шум; якщо 504 зростає разом з EPIPE — шукайте латентність/перевантаження upstream.

Завдання 4: Визначити, який endpoint породжує broken pipes

cr0x@server:~$ sudo awk '$9==499 {print $7}' /var/log/nginx/access.log | sort | uniq -c | sort -nr | head
  211 /download.bin
   63 /reports/export
   31 /api/v1/stream
   12 /favicon.ico

Значення: Довгі завантаження й експорти — класичні магніти для клієнтських abort.
Рішення: Якщо зосереджено в великих відповідях, розгляньте буферизацію, підтримку range, відновлювані завантаження і семплінг логів для 499/EPIPE.

Завдання 5: Перевірити шаблони часу відповіді upstream (сервер повільний?)

cr0x@server:~$ sudo awk '{print $(NF-1)}' /var/log/nginx/access.log | awk -F= '{print $2}' | sort -n | tail -n 5
12.991
13.105
13.442
14.003
15.877

Значення: Припускає, що ви логуєте щось на зразок upstream_response_time=....
Високі хвости латентності означають, що клієнти можуть тайм‑аутитись або здаватися, спричиняючи EPIPE, коли ви нарешті пишете.
Рішення: Якщо хвости високі, переключайтесь на перевірку CPU, пам’яті та I/O; також перевірте, чи проксі‑тайм‑аути не дуже агресивні.

Завдання 6: Підтвердити тиск системи (CPU, load, пам’ять) під час події

cr0x@server:~$ uptime
 09:14:22 up 23 days,  4:11,  2 users,  load average: 18.91, 19.22, 17.80

Значення: Load average значно вищий за кількість CPU (або за норму для цього хоста) вказує на контеншн.
Рішення: Якщо load високий, перевірте, чи це CPU‑насичення, IO wait чи backlog runnable.

cr0x@server:~$ free -h
               total        used        free      shared  buff/cache   available
Mem:            31Gi        29Gi       420Mi       1.2Gi       1.6Gi       1.0Gi
Swap:          2.0Gi       1.8Gi       200Mi

Значення: Низький доступний обсяг пам’яті плюс важкий swap — як ви отримуєте «все повільно, клієнти кидають, запис EPIPE».
Рішення: Якщо відбувається свопінг, ставтеся до broken pipe як до симптома. Зупиніть витік пам’яті, додайте пам’яті, зменшіть конкурентність або виправте стратегію кешування.

Завдання 7: Перевірити OOM‑kill (тиха фабрика broken‑pipe)

cr0x@server:~$ sudo journalctl -k -S -6h | grep -i -E "oom|killed process|out of memory" | tail -n 20
Dec 31 09:00:02 api-01 kernel: Out of memory: Killed process 8871 (app) total-vm:4123456kB, anon-rss:1987654kB, file-rss:0kB, shmem-rss:0kB

Значення: Якщо ядро вбиває ваш додаток, клієнти бачитимуть розриви; upstream отримає broken pipe, коли спробує записати назад.
Рішення: Перестаньте трактувати EPIPE як лог‑незначущість; виправте ліміти пам’яті, витоки або фан‑аут запитів, що викликає спайки.

Завдання 8: Інспектувати TCP‑resets і retransmits (мережевий детектор істини)

cr0x@server:~$ sudo nstat -az | egrep "TcpExtTCPSynRetrans|TcpRetransSegs|TcpOutRsts|TcpInRsts"
TcpExtTCPSynRetrans             18                 0.0
TcpRetransSegs                  4201               0.0
TcpOutRsts                      991                0.0
TcpInRsts                       874                0.0

Значення: Зростання retransmits і RSTs може вказувати на втрату пакетів, перевантаження, проблеми conntrack або застосування idle‑таймаутів middlebox.
Рішення: Якщо RSTs/retransmits зростають разом з EPIPE, захопіть трафік і перегляньте таймаути middlebox; також перевірте помилки NIC.

Завдання 9: Перевірити дропи/помилки на NIC і рівні ядра

cr0x@server:~$ ip -s link show dev eth0
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc fq_codel state UP mode DEFAULT group default qlen 1000
    RX:  bytes packets errors dropped  missed   mcast
    9876543210 1234567      0   12451       0   12345
    TX:  bytes packets errors dropped carrier collsns
    8765432109 2345678      0    9421       0       0

Значення: Дропи на рівні інтерфейсу не завжди фатальні, але сильно корелюють з retransmits і «випадковими» розривами.
Рішення: Якщо дропи ростуть, перевірте qdisc/backlog‑налаштування, interrupt moderation, насичення CPU хоста або мережеву конгестію upstream.

Завдання 10: Подивитись, в яких станах знаходяться з’єднання (чи тоне сервер у напівзакритих сокетах?)

cr0x@server:~$ ss -s
Total: 2134
TCP:   1821 (estab 712, closed 943, orphaned 7, timewait 122, ports 0)
Transport Total     IP        IPv6
RAW       0         0         0
UDP       31        27        4
TCP       878       831       47
INET      909       858       51
FRAG      0         0         0

Значення: Багато timewait може бути нормою для короткоживучих з’єднань, але великі стрибки можуть означати проблеми з keepalive або повторним використанням портів.
Рішення: Якщо estab велике і стабільне — можливо повільні клієнти; якщо closed/orphaned стрибки — розслідуйте різкі завершення та краші.

cr0x@server:~$ ss -tan state time-wait '( sport = :443 )' | head
State      Recv-Q Send-Q Local Address:Port Peer Address:Port
TIME-WAIT  0      0      10.0.1.10:443     198.51.100.25:51322
TIME-WAIT  0      0      10.0.1.10:443     203.0.113.77:44118

Значення: Підтверджує розподіл портів і станів.
Рішення: Якщо TIME‑WAIT надмірні і викликають виснаження портів на клієнтах чи NAT, підвищуйте повторне використання через дизайн (keepalive), а не через kernel‑хакі.

Завдання 11: Перевірити невідповідність тайм‑аутів (приклад Nginx)

cr0x@server:~$ sudo nginx -T 2>/dev/null | egrep "keepalive_timeout|proxy_read_timeout|proxy_send_timeout|send_timeout" | head -n 20
keepalive_timeout 65;
send_timeout 30s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;

Значення: Якщо send_timeout менший за типовий час відповіді для великих завантажень, ви побачите broken pipes.
Якщо proxy_read_timeout занадто малий, upstream‑запити будуть перервані, а проксі може скинути з’єднання.
Рішення: Вирівняйте тайм‑аути по всьому ланцюгу (клієнт ↔ LB ↔ проксі ↔ додаток). Підвищуйте таймаути тільки коли маєте ресурси.

Завдання 12: Інспектувати ліміти systemd і поведінку при kill

cr0x@server:~$ sudo systemctl show app.service -p TimeoutStopUSec -p KillSignal -p KillMode -p Restart -p RestartSec
TimeoutStopUSec=30s
KillSignal=SIGTERM
KillMode=control-group
Restart=on-failure
RestartSec=2s

Значення: Якщо вашому додатку потрібно 90 секунд на дренування, а systemd дає 30 — ви будете отримувати розриви під час деплою.
Рішення: Налаштуйте graceful shutdown: підвищіть TimeoutStopUSec, реалізуйте drain‑ендпоінт і переконайтесь, що проксі перестає направляти трафік першим.

Завдання 13: Підтвердити, хто закрив першим, коротким tcpdump

cr0x@server:~$ sudo tcpdump -i eth0 -nn -s 0 -c 50 'tcp port 443 and (tcp[tcpflags] & (tcp-fin|tcp-rst) != 0)'
tcpdump: verbose output suppressed, use -v[v]... for full protocol decode
listening on eth0, link-type EN10MB (Ethernet), snapshot length 262144 bytes
09:13:02.112233 IP 203.0.113.10.51544 > 10.0.1.10.443: Flags [F.], seq 12345, ack 67890, win 64240, length 0
09:13:02.112450 IP 10.0.1.10.443 > 203.0.113.10.51544: Flags [R], seq 67890, win 0, length 0

Значення: Ви бачите FIN від клієнта, а потім reset від сервера (або навпаки). Це істина про те, хто поклав слухавку.
Рішення: Якщо клієнти надсилають FIN рано — ймовірно їхні abort/тайм‑аути; якщо сервер надсилає RST — дивіться на закриття проксі/додатку, краші або агресивні тайм‑аути.

Завдання 14: Якщо задіяно сховище, перевірити латентність I/O (повільний диск змушує клієнтів кинути)

cr0x@server:~$ iostat -xz 1 3
Linux 6.12.0 (api-01)  12/31/2025  _x86_64_  (8 CPU)

avg-cpu:  %user   %nice %system %iowait  %steal   %idle
          22.10    0.00    5.20   18.30    0.00   54.40

Device            r/s     w/s   rkB/s   wkB/s  avgrq-sz avgqu-sz   await r_await w_await  svctm  %util
nvme0n1         90.0   120.0  8200.0  5400.0     92.0     7.80   38.20   41.10   35.90   0.80  17.00

Значення: Високі await і %iowait можуть гальмувати обробку запитів. Клієнти тайм‑аутять, потім ви пишете: EPIPE.
Рішення: Якщо латентність диска зростає разом з broken pipes, виправляйте IO‑шлях: оптимізуйте запити, кешуйте, використовуйте швидше сховище або вводьте backpressure — не лише підвищуйте таймаути.

Три корпоративні міні‑історії з практики

1) Інцидент через невірне припущення: «Broken pipe завжди — це клієнтський abort»

Середня SaaS‑компанія оновила флот Debian і помітила більше рядків «broken pipe» в API‑логах. На‑колі лідер відмахнувся:
«Це просто люди закривають вкладки». Це було правдоподібно; трафік був переважно від браузерів, графіки спочатку виглядали добре.

За два дні партнерська інтеграція почала падати. Не браузери — машини. Пакетний процес партнера відкривав з’єднання, надсилав payload
і чекав відповіді. Його job повторювалася при помилці, зростала конкуренція. Тим часом API‑сервер гальмував через фонову
компактацію, яка наситила диск I/O. Запити стояли в черзі. Партнер перевищив клієнтський тайм‑аут і закрив сокети.

API згодом записував відповіді у вже неіснуючі з’єднання. Логи заповнилися EPIPE. Помилка не була хворобою сама по собі;
вона була симптомом латентності, достатньої, щоб спрацював клієнтський тайм‑аут. Коли ретраї почали наростати, черга погіршила стан, і графіки вже не були «добре».

Виправлення не було «приглушити broken pipe‑логи». Виправлення — пріоритизувати foreground I/O, перемістити компактацію поза пік,
додати rate limiting і узгодити клієнтські тайм‑аути зі SLO сервісу. Після цього broken pipes впали до низького, нудного базового рівня.

2) Оптимізація, що відбилася боком: агресивне налаштування keepalive

У великому підприємстві, де проксі було більше, ніж співробітників, хтось вирішив «оптимізувати» обробку з’єднань.
Ідея: тримати з’єднання відкритими довше, щоб зменшити TLS‑handshake і CPU. Тому підвищили keepalive у реверс‑проксі й додатку.

Балансувальник попереду не отримав оновлення. У нього був коротший idle‑тайм. LB тихо кидав прості idle‑з’єднання, а проксі намагався
їх переиспользувати. Іноді повторно використане з’єднання вже було мертвим. Наступний запис викликав reset або broken pipe, залежно від того,
хто помітив першим.

Гірше: довший keepalive означав більше idle‑сокетів. Використання дескрипторів файлів повзло вгору. Під час піків трафіку проксі досяг fd‑ліміту,
почав відмовлятися від accept, клієнти ретраїли. Тепер був і зайвий churn, і виснаження ресурсів. Класичний приклад вирішення не тієї проблеми.

Рішення було простим і правильним: узгодити idle‑таймаути LB ↔ проксі ↔ додаток, обмежити keepalive‑запити і встановити адекватні fd‑ліміти.
CPU піднявся трохи. Частота інцидентів знизилась значно. Це обґрунтований компроміс.

3) Нудна, але правильна практика, що врятувала день: грамотний graceful shutdown і дисципліна дренування

Платформа в фінансовій сфері працювала на Debian‑стеку за реверс‑проксі. У них була культура «без героїзму»: кожен сервіс мав
документовану поведінку при завершенні і вимушений процес дренування. Звучало бюрократично — поки не стало рятівним.

Одного дня потрібен був ребут через оновлення ядра. Ребути — місце, де «broken pipe» любить народжуватися: з’єднання падають посеред відповіді,
клієнти ретраять, upstream сипляться. Але цього разу команда слідувала чек‑листу. Ноди відводилися (або видалялися з ротації),
трафік дренувався, довгі запити дозволялися закінчитись, і лише потім сервіси зупинялися.

У логах «broken pipe» ледь помітився. Ще важливіше — клієнтські помилки не виросли. Команда пішла додому вчасно.
Єдиний драматизм — хтось заявив, що процес «занадто повільний», що люди кажуть перед тим, як швидке рішення коштує вихідних.

Урок: дренування з’єднань і дотримання вікон graceful shutdown запобігає великій кількості шуму EPIPE і реальною болю для користувачів.
Це не гламурно. Це працює.

Жарт №2: «швидкий деплой», який ігнорує дренування, — це як спроба швидко пробігти фарфорову фабрику: технічно вражає, фінансово дивно.

Типові помилки: симптом → причина → виправлення

1) Симптом: Nginx логує «writev() failed (32: Broken pipe) while sending to client»

Причина: Клієнт від’єднався під час відповіді (вкладка закрита, мобільна мережа змінилася, нетерплячий SDK).
Іноді спрацьовує через повільну відповідь сервера.

Виправлення: Якщо це нормальний базовий рівень — зменште рівень логування/семплінг для цього класу; якщо сплеск корелює з латентністю — спочатку виправляйте повільність.

2) Симптом: додаток логує EPIPE при записі в upstream (Redis/БД/внутрішній HTTP)

Причина: Upstream закрив з’єднання через idle‑таймаут, перевантаження, досягнення max clients або рестарт; або ваш клієнт повторно використав застаріле з’єднання.

Виправлення: Узгодьте keepalive і idle‑таймери, реалізуйте ретраї з джитером (обережно) і перевірте метрики насичення upstream.

3) Симптом: сплески broken pipe саме під час деплоїв

Причина: Немає graceful shutdown, занадто коротке вікно завершення, readiness змінюється запізніло, проксі все ще маршрутує трафік на ті інстанси, що дренуються.

Виправлення: Додайте фазу дренування: виведіть з ротації, перестаньте приймати нову роботу, завершіть in‑flight запити, потім зупиняйте. Налаштуйте systemd таймаути.

4) Симптом: Broken pipe + 504 gateway timeouts

Причина: Upstream занадто повільний або заблокований (CPU, I/O, contention за локи), проксі має надто короткий тайм‑аут, або наявне чергування.

Виправлення: Підвищуйте таймаут лише після перевірки ресурсного запасу. Краще виправляти латентність: зменшити роботу, кешувати, оптимізувати запити, додати потужності.

5) Симптом: Broken pipe з’являється після ввімкнення стрімінгу HTTP‑відповідей

Причина: Стрімінг робить клієнтські abortи видимими; також проксі може буферизувати незаплановано або тайм‑аутити idle‑стріми.

Виправлення: Налаштуйте стрімінг свідомо: відключіть буферизацію там, де потрібно, надсилайте періодичні keepalive‑байти, налаштуйте таймаути проксі.

6) Симптом: Shell‑команда виводить «Broken pipe» у конвеєрі

Причина: Нижча команда виходить раніше (наприклад, head), верхня продовжує писати і потрапляє на SIGPIPE/EPIPE.

Виправлення: Зазвичай ігнорувати. Якщо ламає скрипти, перенаправте stderr або використайте інструменти, які коректно зупиняють джерело (або обробляють SIGPIPE).

7) Симптом: Багато broken pipe після зміни налаштувань keepalive

Причина: Несумісність таймаутів між шарами; повторне використання мертвих з’єднань; idle‑таймаут балансувальника коротший за проксі/додаток.

Виправлення: Документуйте і узгодьте idle‑таймаути по кінцях; перевірте через захоплення пакетів; розгортаючи зміни робіть поступово.

8) Симптом: Broken pipe плюс OOM‑kill або хвилі рестартів

Причина: Процес помирає під час обслуговування запитів; піри продовжують писати в мертві сокети.

Виправлення: Виправте ліміти пам’яті/витоки, зменшіть конкурентність, задайте адекватний backoff рестартів і впровадьте hooks для graceful termination.

Контрольні списки / покроковий план

Контрольний список A: Визначити, чи це шум чи пожежа

  1. Де це логовано? Логи проксі на краю vs внутрішні логи сервісів vs пакетні скрипти.
  2. Чи змінюється частота? Стабільний базовий рівень часто — шум; тренд зростання — попередження.
  3. Є супутні симптоми? 5xx, 504, ріст черг, CPU steal, swap, затримки I/O, retransmits.
  4. Чи концентрується проблема? Один endpoint, один upstream, один вузол, одна AZ — досліджуйте цей зріз.
  5. Чи збігається з деплоєм/рестартом? Якщо так — спочатку виправляйте дренування та поведінку завершення.

Контрольний список B: Безпечно вирівняти таймаути і keepalive

  1. Зробіть інвентар таймаутів на кожному хопі: клієнт, LB, реверс‑проксі, сервер додатка, клієнти залежностей.
  2. Виберіть ціль: upstream має тайм‑аутити після downstream, а LB не має бути найкоротшим, якщо це не навмисно.
  3. Idle keepalive‑таймаути повинні бути консистентні; якщо ні — очікуйте повторного використання мертвих з’єднань.
  4. Розгорніть зміни на невеликій підмножині; моніторьте resets/retransmits і rate EPIPE.
  5. Документуйте «контракт», щоб наступна оптимізація не зламала його.

Контрольний список C: Зробити деплоя такими, щоб вони не викликали broken pipes

  1. Переконайтесь, що інстанс перестає отримувати новий трафік перед зупинкою процесу.
  2. Реалізуйте readiness, що змінюється раніше (припинити рекламувати ready) і liveness, що не флапає.
  3. Переконайтесь, що systemd (або оркестратор) дає достатнє вікно для завершення in‑flight запитів.
  4. Логуйте фази завершення: «draining started», «no longer accepting», «in‑flight=…», «shutdown complete».
  5. Тестуйте деплой під навантаженням і вимірюйте помилки, видимі клієнту, а не лише рядки в логах.

Контрольний список D: Зменшити шум broken‑pipe, не осліпнувши

  1. Тегуйте логи контекстом (endpoint, upstream, байти відправлено, час запиту).
  2. Семплюйте повторювані EPIPE помилки; зберігайте повні деталі для 5xx і тайм‑аутів.
  3. Майте метрику лічильника EPIPE за компонентом; алерт на зміну швидкості, а не на існування.
  4. Зберігайте невеликий сирий лог‑семпл для судово‑технічного розслідування.

FAQ

1) Чи є «broken pipe» багом Debian 13?

Майже ніколи. Це стандартна UNIX/Linux‑помилка, що демонструється додатками. Debian 13 може змінити версії та дефолти, що змінює видимість, але не фізику.

2) Чому я бачу «BrokenPipeError: [Errno 32]» в Python?

Python піднімає BrokenPipeError, коли запис вражає EPIPE. Це зазвичай означає, що peer закрив сокет (клієнт скасував, upstream тайм‑аутнувся або проксі обірвав).

3) Чому це частіше з Nginx або reverse‑проксі?

Проксі сидять посередині. Вони постійно пишуть клієнтам і upstream, і перші помічають, коли одна зі сторін зникає.
Вони також більш явно логують такі умови, ніж багато додатків.

4) Чи варто вимикати SIGPIPE?

Багато мережевих серверів ігнорують SIGPIPE, щоб один клієнтський розрив не вбив процес. Це нормально.
Не вимикайте його сліпо у випадкових інструментах; зрозумійте, чи ви хочете аварію (fail fast) або повертати помилку (обробляти її).

5) Чи означає «broken pipe» корупцію даних?

Саме по собі — ні. Це означає, що запис не дійшов до читача. Якщо ви стрімите відповідь, клієнт отримав часткову відповідь.
Для аплоадів або внутрішніх RPC потрібні ідемпотентність і ретраї, щоб уникнути часткового стану.

6) Чому я бачу це в shell‑конвеєрах при використанні head?

Тому що head виходить після отримання достатньої кількості рядків. Верхній процес продовжує писати і вдаряється в закритий pipe.
Це нормально; перенаправте stderr, якщо це дратує у скриптах.

7) Як визначити, хто закрив першим — клієнт чи сервер?

Використайте коротке захоплення пакетів (напрямок FIN/RST) або корелюйте логи: клієнтські коди abort (наприклад, 499) vs тайм‑аути upstream (504) vs рестарти сервісів.
Коли сумніваєтеся — tcpdump на 60 секунд краще за суперечки.

8) Чи варто просто піднімати всі таймаути, щоб зупинити це?

Ні. Підвищення таймаутів може приховати перевантаження і збільшити утримання ресурсів (більше завислих запитів, більше пам’яті, більше сокетів).
Виправляйте латентність або узгоджуйте таймаути свідомо; не крутить ручки вправо бездумно.

9) Чому це спалахує під час бекапів або великих експортувань?

Довгі відповіді підсилюють нетерплячість і мережеву невизначеність. Повільний I/O (диск, об’єктне сховище, БД) збільшує час відповіді, клієнти тайм‑аутять, потім записи вдаряються в EPIPE.

10) Чи безпечно фільтрувати «broken pipe» з логів?

Іноді. Якщо ви довели, що це здебільшого клієнтські abortи і маєте метрики для змін частоти, семплінг або фільтрація може зменшити шум.
Не фільтруйте, якщо це пов’язано з тайм‑аутами upstream, внутрішніми викликами або подіями деплою.

Висновок: кроки, що зменшують шум і ризик

«Broken pipe» на Debian 13 — ні катастрофа, ні привод для відвертої байдужості. Це підказка, що одна сторона розриву розмови пішла раніше.
Ваше завдання — вирішити, чи цей ранній вихід був нормальною поведінкою, невідповідністю таймаутів чи системою під стресом.

Практичні наступні кроки, які окупаються:

  1. Класифікуйте за джерелом: proxy edge vs додаток vs внутрішні залежності.
  2. Корелюйте з тайм‑аути і насиченням: якщо зростає латентність або ресурси під тиском — йдіть за тим спочатку.
  3. Узгодьте таймаути по‑всьому ланцюгу: клієнт ↔ LB ↔ проксі ↔ додаток і документуйте контракт.
  4. Виправте дренування при деплої: graceful shutdown дешевший за судові логи.
  5. Приборкайте шум без осліплення: семплуйте повторювані EPIPE логи, але алертьте на зміни швидкості і супутні симптоми (504/5xx/restarts).

Мета не в тому, щоб усунути кожен рядок «broken pipe». Мета — зробити так, щоб ті, що лишилися, були нудними, зрозумілими і не були першою главою для випадку №76.

← Попередня
Proxmox LXC — мережа не працює: чекліст veth/tap, який справді знаходить причину
Наступна →
Dovecot: Maildir проти mdbox — оберіть сховище, яке не підведе вас пізніше

Залишити коментар