Сети и протоколы
Управление потоком и перегрузкой
Свежее TCP-соединение между Лондоном и Сиднеем не знает, насколько быстра линия. Оно начинает осторожно, удваивая скорость отправки каждый round-trip, пока не достигнет предела — затем отступает и пробует снова. Этот танец между отправителем и сетью позволяет TCP заполнить 10-гигабитный канал или вежливо разделить мегабитный.
Скользящее окно и управление потоком
После установки соединения каждый TCP-сегмент несёт размер окна — количество байтов, которые получатель готов принять сверх последнего подтверждённого байта. Отправитель поддерживает скользящее окно неподтверждённых байт в пути, ограниченное минимумом из рекламируемого окна получателя и собственного окна перегрузки. По мере прихода ACK окно скользит вперёд, позволяя отправлять новые байты. Если приложение получателя медленно опустошает буфер сокета, окно уменьшается; при полном заполнении буфера получатель рекламирует window=0 и отправитель делает паузу. Это сквозное управление потоком: получатель диктует темп, отправитель подчиняется.
MSS, масштабирование окна и SACK
Три опции, согласуемые при обмене SYN/SYN-ACK, существенно влияют на пропускную способность:
Maximum Segment Size (MSS): максимальный TCP-payload, который стек может обработать без IP-фрагментации. Типично: 1460 байт на Ethernet (MTU 1500 минус 20 IP + 20 TCP заголовки), 1220 байт на туннельных сетях.
Window Scaling (RFC 7323): 16-битное поле окна умножается на 2^scale, допуская окна до 1 ГиБ. Без этого ограничение 64 КиБ на линке 100 мс RTT ограничивает пропускную способность примерно 5 Мбит/с независимо от реальной скорости линка (bandwidth-delay product = 64 КиБ / 0,1 с ≈ 640 КиБ/с). Необходимо для высокополосных путей с большой задержкой.
Selective Acknowledgements (SACK, RFC 2018): получатель перечисляет точные диапазоны байт, полученных за пропуском, чтобы отправитель повторно передал только недостающие части, а не всё после пробела. На потерянном пути с большим RTT SACK может удвоить эффективную пропускную способность.
- MSS на Ethernet
- 1460 байт (MTU 1500)
- Максимум 16-битного окна
- 64 КиБ (без масштабирования)
- Максимум с масштабированием
- 1 ГиБ (2^30)
- Начальное окно Linux (IW)
- 10 MSS (~14,6 КБ)
- Bandwidth × RTT (BDP)
- 100 Мбит/с × 100 мс = 1,25 МБ
- BDP — минимальное нужное окно
- чтобы заполнить путь с большим BDP
Математика таймера повторной передачи (RFC 6298)
Если сегмент не подтверждён в течение retransmission timeout (RTO), отправитель пересылает его. RFC 6298 задаёт:
SRTT ← (1 − 1/8)·SRTT + (1/8)·new_RTT
RTTVAR ← (1 − 1/4)·RTTVAR + (1/4)·|SRTT − new_RTT|
RTO = SRTT + max(G, 4·RTTVAR)SRTT — сглаженный RTT, RTTVAR — дисперсия RTT (экспоненциально взвешенные скользящие средние). Первый RTO по умолчанию 1 с; после таймаута удваивается (экспоненциальный откат) до прихода сегмента или разрыва соединения. Алгоритм Карна запрещает измерять RTT по повторно переданным сегментам, так как ответный ACK неоднозначен.
Современный Linux использует RACK-TLP (RFC 8985) для более быстрого обнаружения потерь: RACK объявляет сегмент потерянным, когда позже отправленный сегмент подтверждён и истёк интервал переупорядочивания — без ожидания таймера RTO. TLP (Tail Loss Probe) повторно отправляет последний неподтверждённый сегмент через один RTT после последней отправки, чтобы избежать зависания на последнем пакете отправки.
Что означает рекламируемое окно получателя в заголовке TCP?
Slow start и congestion avoidance
Свежее соединение не знает, насколько быстра сеть. Slow start открывает congestion window экспоненциально (1, 2, 4, 8, … MSS за RTT) до тех пор, пока не произойдёт потеря или не достигнет slow-start threshold (ssthresh). После этого congestion avoidance увеличивает окно линейно (~1 MSS за RTT для Reno/CUBIC).
При потере алгоритм зависит от варианта:
- Reno: уменьшает окно вдвое, затем линейное увеличение.
- CUBIC (Linux по умолчанию с 2.6.19): уменьшает менее агрессивно, затем зондирует с кубической кривой — быстрее восстанавливает пропускную способность на путях с большим BDP.
- BBR: полностью игнорирует потери как сигнал перегрузки; использует RTT + измерения доставленных байт (разбирается в уроке о BBR).
Упорядочьте рост congestion window отправителя в TCP slow start:
- 1 Начало: cwnd = 10 MSS (IW по умолчанию в Linux)
- 2 После 1 RTT и ACK: cwnd удваивается до 20 MSS
- 3 После 2 RTT: cwnd удваивается до 40 MSS
- 4 Экспоненциальный рост продолжается до ssthresh или потери
- 5 При достижении ssthresh: переход в congestion avoidance (линейный +1 MSS/RTT)
- 6 При потере пакета: уменьшить cwnd согласно варианту управления перегрузкой
Почему для повторных передач используется экспоненциальный откат, а не постоянный таймер?
Проследите эпизод восстановления SACK после потери одного пакета в окне из 10 сегментов.
Почему это работает
Почему slow start начинается быстро. Название вводит в заблуждение: Linux 3.0+ (RFC 6928) устанавливает начальное congestion window (IW) равным 10 MSS (~14,6 КБ), а не 1 MSS. Это означает, что первый всплеск данных уже составляет 10 сегментов, выбранных под типичный размер HTTP-ответа на небольшой API-запрос. Slow start выглядит медленным только в сравнении с конечной пропускной способностью; по сравнению с классическим IW=1 это 10-кратное улучшение для первого RTT.
- 01Как Window Scaling (RFC 7323) влияет на достижимую пропускную способность на пути RTT 100 мс, 1 Гбит/с?
- 02Что такое RACK-TLP и почему это улучшение относительно классического обнаружения потерь на основе RTO?
- 03Почему соединение с высокими случайными потерями работает плохо с CUBIC, но не с BBR?
Управление потоком TCP использует скользящее окно: получатель рекламирует, сколько байт он может принять, отправитель никогда не превышает это значение. Три опции из SYN/SYN-ACK резко влияют на пропускную способность: MSS (размер сегмента), Window Scaling (расширяет 64 КиБ до 1 ГиБ окна) и SACK (точно определяет недостающие диапазоны, чтобы повторно передавались только потерянные сегменты). Управление перегрузкой начинает свежие соединения в slow start (экспоненциальный рост окна), затем переходит в congestion avoidance (линейный рост) после достижения ssthresh. CUBIC, стандартный с 2006 года, использует кубическую кривую восстановления после потерь. Таймер повторной передачи вычисляется из сглаженного RTT (RFC 6298) с экспоненциальным откатом. RACK-TLP, стандартный с Linux 5.x, обнаруживает потери быстрее, наблюдая за порядком ACK, а не ожидая срабатывания таймера.
встречается в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