Сети и протоколы
Bufferbloat и перегрузка
Кто-то в команде на видеозвонке, и голос постоянно прерывается — но speed test показывает 300 Мбит/с, зелёный. В это время в фоне идёт резервное копирование в облако. Пропускная способность в норме. Задержка — нет. У этого есть название.
Буфер, который съел вашу задержку
Сетевой буфер существует для сглаживания всплесков: когда пакеты приходят быстрее, чем канал их отправляет, они ждут в очереди, а не отбрасываются. В умеренных количествах — это нормально. Bufferbloat — это сбой от избытка.
Граница интернета — кабельные модемы DOCSIS, LTE-модемы, домашние роутеры — это место, где быстрая сторона (гигабитная LAN) встречается с медленной (тарифный аплинк). Это узкое место, и вендоры перестарались с буферами именно там. Модем может держать 100–500 мс буферизованных данных. При насыщении каждый пакет, стоящий за полной очередью резервной копии, ждёт полную глубину этого буфера.
Симптом узнаётся мгновенно, как только знаешь о нём: idle ping — 20 мс, кто-то начинает загрузку, и ping растёт до 300–500 мс. Throughput выглядит отлично — канал полностью загружен — но каждый интерактивный пакет (DNS-запрос, SYN, видеокадр) стоит за bulk-передачей. Звонки прерываются, игры лагают, страницы зависают — и всё это пока speed test зелёный.
Почему TCP нуждается в отбрасывании пакетов
Глубинная причина — несоответствие между буферами и управлением перегрузкой. Потерь-ориентированный TCP — CUBIC, стандарт Linux годами — не имеет другого способа узнать, что канал заполнен. Он продолжает увеличивать окно передачи до тех пор, пока пакет не отбрасывается, воспринимает потерю как «труба полна» и откатывается.
Правильно настроенный буфер отбрасывает пакет рано, когда очередь ещё мелкая, так что TCP получает сигнал перегрузки при ещё низкой задержке. Огромный буфер поглощает пакет вместо этого. TCP не видит потери, предполагает, что есть место, и отправляет ещё данных — которые гигантский буфер тоже поглощает. Окно раздувается, пока буфер не заполняется полностью, на сотни миллисекунд. Вендоры думали, что «никогда не терять пакеты» — безопасный выбор. Это прямо противоположно истине: скрывая сигнал перегрузки, избыточная буферизация гарантирует глубокую очередь.
- Глубина буфера (DOCSIS / LTE)
- 100–500 мс данных в очереди
- Ping idle vs насыщение (без AQM)
- 20 мс → 300–500 мс
- Ping насыщение с fq_codel / CAKE
- держится ниже ~30 мс
- CUBIC через GEO (~600 мс RTT)
- голодает — окна отправлены до возврата потери
- Восстановление throughput BBR на GEO
- большая часть ёмкости канала, независимо от потерь
- RTT LEO (Starlink, орбита ~550 км)
- ~50–60 мс — CUBIC работает как наземный
Почему это продолжается
Bufferbloat известен с 1980-х годов, но он везде до сих пор. Три причины:
- Неверный стимул. Вендоры воспринимали отбрасывание пакетов как дефект и перестарались с буферами, чтобы «избегать потерь» — неверное исправление, потому что потеря и есть сигнал.
- Невидимая метрика. Домашние пользователи измеряют throughput, не задержку под нагрузкой. Speed test работает на иначе-простаивающем канале и никогда не видит bloat. Проблема проявляется только когда интерактивное приложение борется с bulk-передачей.
- Задержка развёртывания. Исправление поставляется в прошивке. Нужен роутер с Smart Queue Management (SQM), а много операторского оборудования никогда не получает это обновление.
Само исправление несложное.
Active Queue Management
Active Queue Management (AQM) — в маркетинге Smart Queue Management на домашних роутерах — отбрасывает или ECN-маркирует пакеты рано и честно, до того как очередь вырастает, восстанавливая сигнал перегрузки, который нужен TCP.
- fq_codel (RFC 8290) — flow-queued CoDel. CoDel («Controlled Delay») отслеживает время ожидания пакетов в очереди, не их количество; как только время ожидания превышает цель (~5 мс) слишком долго, начинается отбрасывание. Flow-queue разделяет потоки на отдельные подочереди, чтобы один bulk-upload не заглушал latency-sensitive поток.
- PIE (RFC 8033) — Proportional Integral controller Enhanced. Оценивает задержку очереди и отбрасывает с вероятностью, настроенной на удержание задержки около цели. PIE — это AQM, обязательный для DOCSIS 3.1, поэтому он в современных кабельных модемах.
- CAKE — Common Applications Kept Enhanced. fq_codel плюс встроенное формирование полосы, честность per-host и компенсация framing DOCSIS/ATM. Его обычно выбирают в домашних роутер-проектах (OpenWrt).
Общая идея: ограничивать задержку очереди, не длину очереди. При полном насыщении канал с CAKE или fq_codel держит ping ниже ~30 мс вместо раздувания до 500 мс.
Почему это работает
Почему SQM нужно формировать ниже линейной скорости. AQM может управлять только очередью, которой владеет. Если узкое место находится внутри модема ISP, очередь вашего роутера никогда не заполняется — пакеты проходят через него и накапливаются в другом месте, где у вас нет контроля. Поэтому SQM намеренно формирует egress до ~90–95% реальной скорости аплинка. Это перемещает узкое место обратно в роутер, где fq_codel или CAKE им управляет. Вы теряете крупицу пиковой пропускной способности в обмен на очередь, которую реально можете дисциплинировать — почти всегда правильный компромисс для интерактивного домохозяйства.
Когда проблема не в буфере: BBR vs CUBIC
Управление перегрузкой также ломается на путях с большим RTT, и там ответ — другой алгоритм, а не другая очередь.
LEO-спутник (Starlink, орбита ~550 км, ~50–60 мс RTT) ведёт себя как наземный канал — потерь-ориентированный CUBIC работает нормально. GEO-спутник (~36 000 км орбита, ~600 мс RTT) — нет. При петле обратной связи 600 мс к тому времени, как сигнал о потере возвращается к отправителю, несколько полных окон уже переданы. Потерь-ориентированное управление реагирует слишком поздно; CUBIC разгоняется медленно и никогда не заполняет канал — он голодает.
BBR (Bottleneck Bandwidth and Round-trip propagation time) полностью обходит потери. Он активно зондирует пропускную способность и минимальный RTT пути, строит модель узкого места и ведёт отправки по этой модели. Поскольку он не ждёт потери, задержка обратной связи в 600 мс больше не калечит его, и BBR восстанавливает большую часть ёмкости GEO-канала. Та же независимость от потерь делает BBR сильным на любом потерявшем пути — сотовая связь, перегруженный Wi-Fi.
Эти два аспекта встречаются у сотовой вышки: мобильные операторы развёртывают AQM в стиле CAKE на радиоячейке, чтобы буфер для пользователей, делящих одну ячейку, оставался дисциплинированным. Паттерн для запоминания: BBR + AQM + маленькие буферы выигрывает на high-latency или lossy путях; CUBIC + разумно настроенный буфер нормален на наземных проводных каналах.
Модем буферизует 300 мс данных и 'никогда не отбрасывает пакеты.' Почему это делает задержку хуже, а не лучше?
В домашней сети видеозвонки прерываются при каждой загрузке резервной копии. Выберите исправление.
Удалённый сотрудник жалуется, что видеозвонки зависают каждый день после обеда. Speed test идеален. Пройди диагностику.
Throughput BBR на GEO-спутнике
1/3- 01Объясни bufferbloat и почему он сохраняется, несмотря на то что известен с 1980-х.
- 02Что делает Active Queue Management иначе, чем plain FIFO-буфер? Назови три AQM-алгоритма.
- 03Почему потерь-ориентированный CUBIC голодает на GEO-спутниковом канале, а BBR восстанавливает большую часть throughput?
Bufferbloat — это избыточная буферизация на сетевой границе: DOCSIS-модемы, LTE-модемы, домашние роутеры держат 100–500 мс данных в очереди. Потерям-ориентированный TCP вроде CUBIC нуждается в раннем отбрасывании пакетов для обнаружения перегрузки; огромный буфер поглощает потерю, поэтому TCP раздувает окно пока ping не раздуется с 20 мс до 300–500 мс при зелёном throughput. Сохраняется потому что вендоры гнались за «без потерь», пользователи измеряют throughput а не задержку, и исправление живёт в прошивке. Active Queue Management — fq_codel (RFC 8290), PIE (RFC 8033), CAKE — отбрасывает или маркирует рано и честно, удерживая queueing delay ниже ~30 мс даже при насыщении; SQM формирует ниже линейной скорости чтобы владеть узкой очередью. На путях с большим RTT ответ — другой алгоритм: BBR зондирует пропускную способность и RTT вместо ожидания потери, восстанавливая throughput на GEO-спутнике где CUBIC голодает.
встречается в162
- Путь запроса: семь остановок от сокета до ответаjunior
- Accept и парсинг: от очереди ядра до типизированного запросаmiddle
- Маршрутизация и middleware: что выполняется и в каком порядкеmiddle
- Обработчик и ответ: от бизнес-логики до байтов на проводеmiddle
- Стриминг и backpressure: когда клиент читает медленнее, чем вы пишетеsenior
- Таймауты и хвостовая задержка: бюджеты, дедлайны и ловушка fan-outsenior
- Middleware и DI: два паттерна, формирующие любой backendjunior
- Пишем middleware: сигнатуры, next() и три модели фреймворковmiddle
- Инверсия управления: как зависимости добираются до классаmiddle
- Скоупы и время жизни DI: singleton, request, transientmiddle
- DI как шов для тестов: фейки, моки и граница, которая важнаsenior
- DI-контейнеры в продакшене: графы разрешения, циклы и когда не стоитsenior
- Блокирующий vs неблокирующий I/O: два способа ждатьjunior
- Event loop: один поток, упорядоченные фазыmiddle
- Что блокирует цикл: CPU-работа и синхронные вызовыmiddle
- Вынос CPU-работы: worker threads и пул libuvmiddle
- Backpressure и ограниченная конкурентностьsenior
- Пропускная способность под нагрузкой: хвостовая задержка и насыщениеsenior
- Зачем пул: цена создания соединенияjunior
- Размер пула: почему больше не значит быстрееmiddle
- Взятие и таймауты: очередь ожидания — настоящий дроссель задержкиmiddle
- Стратегии retry: backoff, jitter и thundering herdmiddle
- Наблюдаемость, production-инциденты и дизайн для глобального масштабаsenior
- Задачи, микрозадачи и scheduler.yield()middle
- Точность таймеров, троттлинг и фоновая работаmiddle
- Event loop Node.js: фазы, nextTick и задержка циклаsenior
- Стратегии рендеринга: SSG, SSR, ISR, streaming и гидратацияjunior
- SSG, SSR, ISR, streaming и RSC — как работает каждая стратегияmiddle
- Цена гидратации: selective, progressive, острова, resumabilitymiddle
- Core Web Vitals: что измеряют LCP, INP и CLSjunior
- LCP: четыре фазы, одна доминирующая стоимостьmiddle
- INP: input delay, processing, presentationmiddle
- Lab vs field: почему они расходятся и как использовать каждыйmiddle
- Трейдоффы метрик, RUM-атрибуция и цикл CI+полеsenior
- Общая картина: от URL до LCP до INP как эстафетаjunior
- Восемь слоёв трассировки: от service worker до второй навигацииmiddle
- Пять канонических поломок: где производство стабильно ломаетсяsenior
- Метод трёх треков: чтение трасс и построение системы мониторингаsenior
- Что такое индекс и как он ускоряет запросыjunior
- Leading-column rule: почему порядок столбцов в composite-индексе важенmiddle
- Partial, expression и covering-индексыmiddle
- Типы индексов: GIN, GiST, BRIN, Hash, Bloom и HOT-обновленияmiddle
- Index-only scan, Visibility Map и INCLUDEsenior
- Типичные сбои в продакшне и аудит индексовsenior
- Упражнение по проектированию индексов: стратегия полнотекстового поискаsenior
- EXPLAIN и планы выполнения: что решает планировщик и почемуjunior
- Типы сканирования: Seq, Index, Bitmap, Index-Onlymiddle
- Алгоритмы соединения и каскад ошибок оценки строкmiddle
- pg_statistic, ANALYZE и производственная наблюдаемостьmiddle
- Расширенная статистика: исправление ошибок оценки для коррелированных колонокsenior
- Кеш планов, настройка константных стоимостей и внутренности планировщикаsenior
- Производственные режимы отказа и стабильность плановsenior
- Connection pool: зачем амортизировать стоимость backend Postgresjunior
- Режимы PgBouncer: session, transaction и statementmiddle
- Размер пула: формула (ядра × 2) + шпинделей и двухуровневый стекmiddle
- Исчерпание пула и idle-in-transaction: сценарий отказа в 3 ночиmiddle
- Миграция на transaction mode: план развёртывания и prepared statements в PgBouncer 1.21middle
- Процессная модель Postgres и почему увеличение max_connections снижает производительностьsenior
- Ландшафт пулеров 2026, serverless connection storms и полная таксономия отказовsenior
- ADD COLUMN: мгновенно в PG 11+ против перезаписи в старом Postgresjunior
- Режим отказа очереди блокировок: почему мгновенный DDL может заморозить базуmiddle
- Безопасные DDL-паттерны: NOT VALID, CONCURRENTLY и исправления небезопасных операцийmiddle
- Таксономия сбоев миграций и дисциплина продакшнаsenior
- Выбор ключа шарда: стратегии hash, range, list и directorymiddle
- Ко-локация и Citus: инвариант, делающий шардирование пригодным к использованиюmiddle
- Режим отказа hot shard: обнаружение, изоляция и долгосрочная политикаmiddle
- Онлайн-решардинг, 2PC и операционная стоимость шардированияsenior
- Семь актов: от CREATE TABLE до Citusjunior
- Акты 1–3 в глубину: схема, индексы и статистика планировщикаmiddle
- Акты 4–6 в глубину: MVCC bloat, connection pooling и безопасные миграцииmiddle
- Акт 7 в глубину: шардинг, co-location и семиуровневый каскад трейдоффовmiddle
- Наблюдаемость, антипаттерны и производственный триажsenior
- Что такое три сигнала: метрики, логи, трейсыjunior
- Метрики и cardinality: cost-модель time-series databasemiddle
- Логи и объём: cost-модель структурного логированияmiddle
- Трейсы и сэмплирование: cost-модель distributed tracingmiddle
- Join-ключи и exemplar''''ы: как три сигнала становятся компонуемымиmiddle
- Observability 2.0: широкие события и сдвиг стоимостиsenior
- Режимы сбоя и инженерная практика: cardinality budget''''ы, PII и сэмплированиеsenior
- Зачем нужны структурные логи: дневник против таблицыjunior
- Схема продакшн-лога: поля, которые несёт каждая строкаmiddle
- Log levels и маршрутизация алертовmiddle
- Стратегии sampling и стоимость логовmiddle
- PII-редакция и log injectionsenior
- Propagation trace-контекста в логахsenior
- OTel Logs Data Model и audit-логи как подсистемаsenior
- Сигналы OTel, Semantic Conventions и проводной формат OTLPmiddle
- Авто-инструментирование и ручные спаны: правило 80/20 в OTelmiddle
- Collector OTel: receivers, processors, exporters и паттерны развёртыванияmiddle
- Стратегии сэмплирования: head, tail и parent-basedmiddle
- Vendor-нейтральность, eBPF-инструментирование, Operator и OTel в браузере и serverlesssenior
- Эксплуатация OTel Collector: надёжность, version skew, режимы отказа и управлениеsenior
- RED и USE: два чек-листа, одна дисциплина триажаjunior
- Инструментация RED в Prometheus: счётчики, гистограммы и дисциплина cardinalitymiddle
- USE на Linux: CPU, память, диск, сеть и PSImiddle
- Golden signals, структура дашборда и auto-RED в service meshmiddle
- Cardinality как драйвер затрат: label, PII, exemplars и семплированиеmiddle
- Native histograms, SLO и паттерны production-сбоевmiddle
- Выбор SLI и SLO-целей: отношения, не ощущенияmiddle
- Multi-window multi-burn-rate-алертинг: почему AND лучше ORmiddle
- Error budget policy, latency SLO и составные journeysmiddle
- Iceberg SLI, математика составного SLO и SLA vs SLOsenior
- Flame graph: читаем картинку, которая показывает, куда ушло времяjunior
- Sampling vs instrumentation profiling: почему 99 Гц побеждает в productionmiddle
- Типы профилей: CPU, память, off-CPU, mutex — какой когда братьmiddle
- Continuous profiling: always-on flame graphs с eBPF и корреляцией trace-idmiddle
- Как flame graph строится из сэмплов и как использовать его в productionmiddle
- Linux perf, внутренности eBPF, PGO и ограничения sampling''''аsenior
- Profiling в production: безопасность, war stories, OTel profiles и дизайн инфраструктурыsenior
- Debugging-воронка: SLO → RED → trace → profilejunior
- Архитектура OTel: один SDK, четыре сигнала, один wire-форматmiddle
- Экономия на observability: удерживаем затраты в пределах 5% inframiddle
- Масштаб, безопасность и ROI наблюдаемых системsenior
- Сначала профиль: измерь куда реально уходит времяjunior
- Закон Амдала и self-time: потолок любого ускорения, которое ты можешь выпуститьmiddle
- Измерительный цикл: микробенч, макробенч, prod-профиль, эффект наблюдателяmiddle
- Чтение флейм-графов: формы, профайлеры по языкам и 60-секундный сканmiddle
- Статистические baseline''''ы: почему один запуск — не измерениеmiddle
- История профайлеров и ловушки микробенчей: от Кнута до GWPsenior
- Hardware counters, профили холодного старта и безопасность профилейsenior
- Непрерывное профилирование в масштабе: затраты, CI-гейты, корреляция с трейсами и антипаттерныsenior
- Что делает путь горячим: симптом против причиныjunior
- Пять форм hotspot''''а: CPU, аллокации, кэш, лок, syscallmiddle
- Чтение parent и child chains: где применять правкуmiddle
- JIT deopt, цикл fix-and-verify и PR-time профилированиеmiddle
- Аппаратные счётчики и Intel TMA: диагностика подкатегорийsenior
- False sharing и горячие пути нативных мостовsenior
- Горячие пути в production: безопасность, хвостовая латентность и происхождение инструментовsenior
- Иерархия памяти: почему расстояние важнее числа операцийjunior
- Row-major vs column-major: порядок доступа и разрыв в 9xjunior
- Branch prediction: 10–30 циклов штрафа за неожиданный ifmiddle
- Hardware prefetcher, TLB и memory-level parallelismsenior
- Основы GC: за что рантайм берёт налогjunior
- Алгоритмы GC: поколенческая гипотеза, concurrent marking и write barriermiddle
- GC tradeoffs: пауза, throughput, память и давление аллокацийmiddle
- Настройка GC: пейсинг, форма кучи и наблюдаемость аллокацийmiddle
- Внутреннее устройство GC: tri-color инвариант, write barriers и глубокое погружение в рантаймыsenior
- GC в production: наблюдаемость, безопасность, edge cases и управление флотомsenior
- N+1: одна логическая операция, много round-trip''''овjunior
- Семейства фиксов: JOIN, IN, preload и DataLoadermiddle
- Обнаружение N+1: query logs, APM traces и CI gatesmiddle
- DataLoader: батчинг по дереву резолверовmiddle
- Кросс-протокольный N+1: HTTP fan-out и Redis MGETmiddle
- N+1 в масштабе: исчерпание пула, изменения планов и денормализацияsenior
- Batching: амортизируй фиксированную цену каждой операцииjunior
- Окно батчинга: размер и время ожиданияmiddle
- Batching в Kafka и Postgresmiddle
- io_uring и наблюдаемость пакетированияmiddle
- От Nagle до io_uring: эволюция пакетированияmiddle
- Backpressure, изоляция сбоев и безопасность батчей в продакшенеsenior
- Что на самом деле стоит bundle: download, parse, compile, executejunior
- Core Web Vitals: LCP, INP и CLSmiddle
- Code splitting: route-level, component-level, vendor splittingmiddle
- Tree shaking и compression: удаляем то, что не используемmiddle
- Third-party scripts: тихий убийца бюджетаmiddle
- CI enforcement и RUM: делаем бюджеты рабочимиmiddle
- V8 JIT-пайплайн, HTTP-приоритеты и безопасность bundlesenior
- Цикл performance: дисциплина, а не проектjunior
- Классификация и исправление: сопоставление family bottleneck с методамиmiddle
- Observability-стек и CI gates: ловить регрессии до выпускаmiddle
- От инцидента к enforcement: SLO burn до верифицированного исправления за 35 минутmiddle
- Культура, экономика и масштаб performancesenior