Включили IPv6 — часть клиентов стала ходить медленнее: разбор happy eyeballs
Добавили AAAA-запись на сервер, порадовались «теперь мы современные, поддерживаем IPv6» — и через день в мониторинге всплеск p95 по времени ответа. Не у всех, а у заметной части клиентов. Логика подсказывает, что IPv6 должен быть как минимум не хуже IPv4. На практике всё ровно наоборот для тех, у кого IPv6 до конечного пользователя работает криво — и разбираться приходится не в вашей сети, а в механизме, который решает, по какому протоколу клиент вообще будет к вам стучаться.
Содержание
- Почему сама AAAA-запись уже меняет поведение клиента
- Как работает happy eyeballs (RFC 8305)
- Где на самом деле ломается IPv6 у клиента
- Почему это до сих пор аукается
- Как продиагностировать, что происходит именно у вас
- Мониторинг: считать IPv6 отдельно от IPv4
- Безопасный rollout: TTL, canary и быстрый откат
Почему сама AAAA-запись уже меняет поведение клиента
Пока у домена есть только A-запись, резолвер клиента получает один список IPv4-адресов, и весь путь до сервера — в зоне вашей ответственности и ответственности провайдеров транзита. Как только вы добавляете AAAA, у клиента при резолве появляется выбор: IPv4 или IPv6. Современные ОС и браузеры сами решают, какой протокол пробовать первым, опираясь на политику предпочтений (RFC 6724 — Default Address Selection) и результаты предыдущих попыток подключения.
Здесь и кроется ловушка: то, что ваш сервер прекрасно отвечает по IPv6 из вашего дата-центра, ничего не говорит о качестве IPv6-пути у конкретного клиента. Путь пакета зависит от провайдера клиента, от того, как у него настроен IPv6-транзит, от пиринга между вашим аплинком и его аплинком — та же тема, что в разборах асимметричной маршрутизации и того, почему маршрут не совпадает с географией. До AAAA-записи эта клиентская проблема была невидима — трафик просто не мог пойти по IPv6. После неё проблема клиента становится вашей: часть клиентов теперь пробует путь, который у них объективно хуже.
С точки зрения протокола у вас ничего не «сломалось». IPv6 работает. Проблема целиком на стороне клиента — но пользователь видит тормозящий сайт и связывает это с вашим последним деплоем, а не с состоянием своего роутера или IPv6-туннеля у провайдера.
Как работает happy eyeballs (RFC 8305)
Happy eyeballs придумали именно для того, чтобы наличие «плохого» IPv6 не убивало пользовательский опыт. Логика, описанная в RFC 8305 (Happy Eyeballs Version 2), в общих чертах такая:
- Клиент резолвит имя одновременно по A и AAAA — два параллельных DNS-запроса, а не последовательных.
- Получив адреса, клиент сортирует их по приоритету — обычно IPv6 ставится первым, если у хоста в принципе есть работающая IPv6-связность.
- Клиент запускает TCP-подключение к первому адресу (как правило, IPv6).
- Если за короткий интервал (Connection Attempt Delay, спецификация называет порядок в несколько сотен миллисекунд, точное число каждая реализация тюнит сама) соединение не установилось, клиент, не дожидаясь полного таймаута, параллельно запускает попытку к следующему адресу — обычно IPv4.
- Побеждает адрес, чей TCP-хендшейк завершился первым. Остальные попытки сбрасываются.
Ключевая идея — попытки идут внахлёст, а не строго последовательно с ожиданием таймаута. Это отличает happy eyeballs от наивной логики «попробовать IPv6, и если не вышло за десятки секунд — IPv4», которая была нормой в ранних реализациях и до сих пор встречается в урезанном сетевом стеке: во встроенных библиотеках, в самописных HTTP-клиентах, которые резолвят и подключаются последовательно, а не через параллельную сортировку адресов.
У клиента с полноценным современным стеком добавление AAAA в худшем случае стоит доли секунды — время, за которое несостоявшаяся IPv6-попытка успевает уступить место IPv4. Но «в худшем случае» — не гарантия, а зависит от деталей реализации конкретного клиента и от того, насколько именно «плохой» у него IPv6.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверГде на самом деле ломается IPv6 у клиента
Happy eyeballs эффективно спасает от одного класса проблем — полностью неработающего IPv6 (нет маршрута, мгновенный RST). Но есть более коварные случаи, где IPv6 «как бы работает», просто плохо, — и там даже правильная реализация не спасает полностью:
- Легаси-туннели. 6to4 (RFC 3056) и Teredo — автоматическое туннелирование IPv6 поверх IPv4, в своё время массово включённое по умолчанию на домашних роутерах и в Windows. Пакет по такому туннелю добавляет задержку (инкапсуляция, лишний хоп через relay-сервер, который может быть далеко и перегружен), а иногда релеи вообще недоступны. Соединение формально *устанавливается* — TCP хендшейк завершается, просто на порядок медленнее IPv4. Если такой путь всё же успевает выиграть гонку, пользователь получает медленное, но «победившее» IPv6-соединение — и вся сессия идёт по нему.
- Провайдер выдал IPv6, но плохо его маршрутизирует. Адрес нативный и полноценный, но IPv6-транзит у оператора куплен у другого апстрима, чем IPv4, пиринг для IPv6 не докуплен на тех же точках обмена — и путь идёт через лишние хопы, которых для IPv4 давно нет. Тот же класс проблем, что в статье про пиринг и транзит простыми словами, только применительно к отдельному стеку протоколов у конкретного оператора.
- MTU и PMTU Discovery. IPv6 не позволяет промежуточным маршрутизаторам фрагментировать пакеты — только отправителю, через ICMPv6 Packet Too Big. Если ICMPv6 где-то фильтруется, PMTUD ломается тихо: маленькие пакеты (сам хендшейк) проходят, соединение быстро выигрывает гонку happy eyeballs, а крупные пакеты с полезной нагрузкой зависают. Happy eyeballs про эту проблему ничего не знает — она проявляется уже после того, как гонка завершена.
- CPE-роутеры с кривой IPv6-реализацией. Баги в firewall-правилах, в DHCPv6-PD, в обработке Router Advertisement — от эпизодических зависаний до полной потери связности при смене префикса после перезагрузки роутера.
Часть этих проблем временная и локальная у оператора, а не у вас. Если сервер отдаёт контент нормально и по IPv4, и по IPv6 в независимых измерениях, а жалобы приходят только от подсети одного провайдера — чинить нужно не инфраструктуру, а либо ждать, пока провайдер поправит транзит, либо временно исключить этих клиентов из IPv6-пути.
Почему это до сих пор аукается
Ранние реализации (до широкого распространения RFC 6555, а затем RFC 8305) часто действовали последовательно: попробовать первый адрес, дождаться таймаута TCP SYN — это могло быть долго — и только потом пробовать следующий. Если первым оказывался «мёртвый» IPv6-адрес, пользователь ждал таймаут целиком. Отсюда и долго живущая в индустрии рекомендация «не включайте IPv6, пока не уверены, что не сломаете часть аудитории» — обоснованная для стека тех лет.
Сегодня основные браузерные движки и современные ОС реализуют параллельные попытки по актуальной спецификации. Но полагаться, что *весь* трафик идёт через такие клиенты, не стоит:
- часть трафика — не браузеры, а API-клиенты, вебхуки, серверные интеграции на самописных HTTP-библиотеках, где happy eyeballs может быть не реализован вообще, а логика подключения — простой перебор адресов в порядке, как их вернул резолвер;
- библиотеки и рантаймы разных языков и версий поддерживают параллельные попытки не одинаково полно — где-то это делает системный резолвер, где-то нужно явно включать опцию в HTTP-клиенте;
- в корпоративных и встраиваемых окружениях (IoT, легаси-ПО, старые мобильные ОС) до сих пор встречаются реализации без нормального happy eyeballs.
Практический вывод: нельзя исходить из «happy eyeballs у всех работает штатно, поэтому AAAA безопасна по определению». Часть аудитории — не браузер с последним движком, а что-то менее заботливое к пользовательскому опыту.
Как продиагностировать, что происходит именно у вас
Прежде чем что-то откатывать, стоит локализовать проблему — это IPv6 у клиента, PMTUD-блэкхол или что-то на вашей стороне.
Проверка с сервера или тестовой машины по каждому протоколу отдельно:
# принудительно по IPv6
curl -6 -o /dev/null -s -w "time_connect: %{time_connect}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n" https://example.com/
# принудительно по IPv4
curl -4 -o /dev/null -s -w "time_connect: %{time_connect}\ntime_starttransfer: %{time_starttransfer}\ntime_total: %{time_total}\n" https://example.com/
Если time_connect по IPv6 сопоставим с IPv4, а time_starttransfer сильно больше — похоже на PMTUD-проблему (соединение установилось, данные зависли), а не на медленный хендшейк.
Проверка MTU/PMTUD (ищем, где именно пакет с DF-битом перестаёт проходить):
ping6 -M do -s 1400 example.com
ping6 -M do -s 1452 example.com
Крупные пакеты не проходят, мелкие проходят — прямое подтверждение проблемы с ICMPv6 Packet Too Big где-то на пути.
Трассировка отдельно по каждому протоколу, тот же приём, что при разборе несовпадения маршрута с географией:
mtr -6 example.com
mtr -4 example.com
Полезны и публичные онлайн-инструменты категории IPv6-connectivity-тестов: сайт открывается в браузере пользователя, параллельно опрашивает сервер по IPv4-only, IPv6-only и по обоим адресам, и показывает, какой путь выиграл гонку и с какой задержкой. Такой тест стоит прогнать не только с рабочего места (там обычно нормальный IPv6), а попросить пользователей из проблемного региона/оператора прислать результат.
На уровне веб-сервера научитесь различать v4- и v6-клиентов уже в логах. В nginx это делается через формат $remote_addr (адрес в квадратных скобках — IPv6):
map $remote_addr $ip_family {
default "v4";
~^\[?[0-9a-fA-F:]+\]?$ "v6";
}
Дальше $ip_family выносится отдельным полем в структурированный лог и агрегируется в системе метрик.
Мониторинг: считать IPv6 отдельно от IPv4
Главная методическая ошибка после включения IPv6 — смотреть на общий p95/p99 по всем клиентам сразу. Если доля IPv6-трафика небольшая, деградация у этой доли тонет в общем графике и не триггерит алерты, а жалобы продолжают приходить.
Что стоит явно разделить:
- Долю IPv6-трафика от общего — counter по
$ip_familyиз логов или метрик балансировщика/CDN. Её рост после включения AAAA — ожидаемое событие, само по себе не проблема. - Латентность отдельно по v4 и v6 — минимум время до первого байта и общее время ответа, в разбивке по протоколу. Если p50 у IPv6 близок к p50 IPv4, а p95/p99 сильно хуже — похоже на долгий хвост из клиентов с плохим транзитом или туннелями.
- Долю неудачных подключений по протоколу. Если есть RUM (метрики из браузера через Resource Timing API — поля вроде
connectStart/connectEnd), видно то, что видит пользователь, а не только то, что видно на сервере: сервер не узнает о клиентах, которые не смогли достучаться и откатились без запроса. - Разбивку по автономным системам (ASN) клиента, если есть доступ к GeoIP/ASN-базе в логах. Проблемы с IPv6 почти всегда концентрируются у нескольких конкретных операторов, а не размазаны равномерно — разрез по ASN отличает «у нас системная проблема» от «у оператора X плохой транзит, и это не наша зона ответственности».
Отдельно стоит следить за «хвостовым» эффектом: p50 по IPv6 может быть прекрасным, а p95/p99 заметно хуже именно из-за небольшой доли клиентов с туннелями и кривым транзитом — они не показывают ошибок, просто работают медленнее. По одной агрегированной цифре это почти не видно, только по разбивке на процентили и протоколы.
Безопасный rollout: TTL, canary и быстрый откат
Включать IPv6 сразу на весь домен без пути назад рискованно: проблема проявляется не у вас, а у случайного подмножества клиентов, о качестве чьего IPv6-транзита вы заранее ничего не знаете.
- Снизьте TTL заранее, до добавления AAAA. Если запись годами стояла с TTL в час и больше, откат будет медленным. За сутки-двое выставьте TTL порядка 300 секунд — достаточно для быстрого отката и не настолько мало, чтобы перегружать DNS-инфраструктуру.
example.com. 300 IN A 203.0.113.10
example.com. 300 IN AAAA 2001:db8::10
- Выкатывайте сначала на поддомен (
v6test.example.com), соберите метрики на реальном разнообразии клиентов, прежде чем трогать продовый домен.
- Если инфраструктура позволяет — раскатывайте постепенно, не на 100% сразу: часть DNS-провайдеров и балансировщиков умеет отдавать AAAA не всем резолверам одновременно (weighted/policy-based routing), что держит долю IPv6-трафика управляемой на старте.
- Держите рубильник под рукой. Самый быстрый и надёжный откат — удалить или занулить AAAA-запись. При низком TTL это расходится по большей части резолверов за несколько минут, и клиенты с проблемным IPv6 возвращаются на привычный IPv4-путь без вмешательства в код приложения.
- Не откатывайте всё при первой же жалобе. Одна жалоба от одного пользователя одного оператора — повод для диагностики по ASN, а не для отката для всех. Массовое ухудшение p95/p99 именно у IPv6-сегмента, подтверждённое метриками за день-два (чтобы захватить пиковую нагрузку и разных клиентов), — уже повод для отката или сегментации.
- Проверяйте PMTUD отдельно от happy eyeballs. Даже если гонка всегда выигрывается быстрым протоколом, зависания на передаче данных из-за фильтрации ICMPv6 не пройдут сами — убедитесь, что файрвол пропускает ICMPv6 Packet Too Big (type 2) в обе стороны, и что MTU на интерфейсах и туннелях настроен консервативно, а не просто «по умолчанию 1500» без проверки.
Нужен сервер под эту задачу?
Разверните VPS MAATRIX за пару минут: NVMe, AMD EPYC, root-доступ, локации UK, США, Франция и РФ. Оплата картой РФ и по СБП.
Арендовать серверНужны сами нейросети для контента?
Генерируйте изображения, видео и озвучку нейросетями на falapi.io — десятки моделей в одном окне. Оплата картой РФ и по СБП.
Частые вопросы
Можно ли вообще не включать IPv6, раз с ним столько нюансов?
Можно, но доля пользователей с IPv6-only или IPv6-preferred подключением продолжает расти, а часть мобильных операторов уже годами гоняет трафик преимущественно по IPv6 с NAT64 для доступа к IPv4-only ресурсам — такой путь тоже добавляет задержку и точку отказа. Полное отсутствие AAAA — не бесплатный выбор, просто его цена не так заметна в мониторинге.
Happy eyeballs гарантированно выберет самый быстрый протокол?
Нет — он выбирает тот, чей TCP-хендшейк завершился первым в рамках заданных таймаутов. Соединение, которое устанавливается быстро, но потом «висит» из-за PMTUD, happy eyeballs не отличит от нормального — это решается отдельно, проверкой ICMPv6 и MTU.
Достаточно ли добавить AAAA только для веб-домена, а API оставить на IPv4?
Разумная стратегия для первого этапа: веб-трафик в массе идёт через браузеры с современным happy eyeballs, а API-клиенты и серверные интеграции чаще используют упрощённые HTTP-библиотеки без параллельных попыток подключения. Разделение доменов позволяет выкатывать IPv6 туда, где риск ниже.
Как понять, что проблема не в вашей сети, а в транзите клиента?
Сравните time_connect из curl -4 и curl -6 с независимой машины и с публичных площадок тестирования IPv6-связности в разных регионах. Если оттуда оба протокола дают сопоставимые цифры, а жалобы приходят от конкретной подсети — проблема на стороне провайдера клиента, и практичный ход — временно исключить эту аудиторию из IPv6-пути через сегментацию DNS, либо просто подождать, пока провайдер починит транзит.
Обсудить статью, задать вопрос или начать новую тему
Есть вопрос по этой статье, идея для обсуждения или просто хотите поделиться опытом? Сообщество MAATRIX ждёт. Для общения, пожалуйста, зарегистрируйтесь в нашем личном кабинете.
Перейти в сообщество →