Когда я в последний раз писал о нашем конвейере распространения DNS в 2022 году, Zone Builder обрабатывал около 250 изменений DNS-записей в секунду. Сегодня он принимает около 700 — почти в три раза больше, а также примерно 100 других событий, таких как изменения настроек zone. Тем не менее, количество событий никогда не было настоящей проблемой. Проблема заключалась в том, какая работа требовалась для некоторых из этих событий и как эта работа растет вместе с размером зоны. Некоторые зоны теперь содержат более 20 миллионов записей, и мы ожидаем, что они будут продолжать расти до сотен миллионов.
Каждое событие запускает таймер, который измеряет, сколько времени требуется для преобразования этого изменения в данные, которые могут использовать наши авторитетные DNS-серверы. Эту работу мы называем сборкой. Сборка считывает текущее состояние базы данных, преобразует его в формат, используемый на периферии, и записывает любые изменения в Quicksilver.
В 2022 году сборки для отдельных записей были в среднем в 150 раз быстрее полных сборок. На тестовой зоне с одним миллионом записей сборки отдельных записей были быстрее в 4250 раз. Эту статью мы завершили очевидным следующим шагом: сделать то же самое для настроек зон.
Почему Zone Builder просто не пересобирает ту настройку, которая изменилась?
Он и должен это делать, и именно к этому мы в итоге пришли. Однако доказательство того, что это безопасно, потребовало большей части работы. Событие записи определяет, что именно изменилось. Событие зоны может исходить от множества продуктов Cloudflare, может не указывать причину изменения зоны и может изменять данные, совместно используемые несколькими зонами с одинаковым именем. Выбор слишком большого объема работы тратит ресурсы впустую. Выбор слишком малого объема может оставить устаревшие или неверные данные DNS на периферии.
Мы создали целевой планировщик сборок, который выбирает наименьшую сборку, безопасность которой мы можем доказать, и новый диспетчер задач, который объединяет работу для одной и той же зоны. Изменение настроек зон больше не приводит к пересборке зон с десятками миллионов записей. Вместе они сократили пиковое потребление памяти на 85%, а дополнительный запас по производительности позволил нам безопасно сократить время ожидания, которое конвейеру всегда требовалось для обеспечения отказоустойчивости, уменьшив вдвое время от изменения DNS до записи данных в Quicksilver.
Как изменения DNS достигают периферии
DNS-зона — это совокупность записей и настроек для домена. Cloudflare использует многоэтапный конвейер для преобразования изменений в этой зоне в данные, доступные по всей нашей глобальной сети.
- Клиент изменяет DNS-запись через API DNS-записей или панель управления, либо другой сервис Cloudflare изменяет настройку зоны.
- Изменение сохраняется в базе данных.
- Изменение в базе данных или другая система генерирует событие.
- DNS Kafka Aggregator объединяет события из нескольких топиков Kafka и отправляет их в Zone Builder.
- Zone Builder считывает необходимые данные зоны и преобразует их в ключи, понятные нашим авторитетным DNS-серверам.
- Zone Builder сравнивает эти ключи с Quicksilver, нашим распределенным хранилищем «ключ-значение». Он записывает только то, что изменилось, а Quicksilver распространяет эти изменения по сети Cloudflare.
Это упрощенный взгляд. Zone Builder не копирует строки базы данных напрямую на периферию. Он объединяет записи с настройками зоны, применяет правила обслуживания DNS и организует результат так, чтобы наши авторитетные DNS-серверы могли быстро его прочитать.
Почему полные сборки перестали масштабироваться
У исходного Zone Builder было одно полезное свойство: ему не нужно было знать, почему зона изменилась. Он воссоздавал желаемое состояние из текущего состояния базы данных.
- Считать текущую зону и ее настройки.
- Считать каждую DNS-запись в зоне.
- Преобразовать эти записи в ключи Quicksilver.
- Сравнить новые ключи с уже сохраненными.
- Удалить устаревшие ключи, записать измененные ключи и обновить метаданные зоны.
Финальная запись содержит только измененные ключи. Тем не менее, полная сборка по-прежнему считывает, кодирует и сравнивает каждую запись, удерживая всю зону в памяти во время этого процесса. Это недорого для небольшой зоны. Для наших крупнейших зон, содержащих десятки миллионов записей, зона вообще не помещается в память. Ниже я объясняю, как мы с этим справляемся.
Настройки зон усложнили задачу таргетинга. Universal SSL — один из примеров. Его включение или отключение обновляет метаданные и может изменить записи авторизации центра сертификации (CAA) на вершине зоны, в «голом» домене, таком как example.com. Записи CAA сообщают центрам сертификации, разрешено ли им выдавать сертификат для домена.
Полной сборке нужно было лишь знать, что зона изменилась. Целевая сборка должна отвечать на три вопроса:
- Какие DNS-имена могут измениться?
- Могут ли измениться общие метаданные или список опубликованных зон?
- Какое предыдущее состояние необходимо удалить из Quicksilver?
Безопасные ответы на эти вопросы потребовали решения пяти проблем, о которых по порядку рассказывается в оставшейся части этой статьи:
- События должны были сообщать, что именно изменилось.
- Каждое изменение должно было сопоставляться с наименьшей сборкой, которая все еще остается корректной.
- Сборки должны были учитывать данные, совместно используемые каждой зоной с одинаковым именем.
- База данных могла измениться между событием и сборкой.
- Объединение работы не должно было приводить к потере сообщения Kafka.
Сделать так, чтобы события сообщали об изменениях
Мы изменили производителя событий так, чтобы он включал измененные поля и настройки зоны с предыдущими и новыми значениями, когда они доступны. Затем мы проследили каждую настройку через код, генерирующий вывод DNS, и классифицировали то, на что она может повлиять.
Не каждое событие было столь специфичным. Некоторые функции базы данных обновляют только временную метку, фактически говоря: «что-то в этой зоне изменилось». Иногда это безобидная бухгалтерия. Иногда это означает, что данные изменились в другом месте и их нужно прочитать заново.
Такие случаи выглядят в событии абсолютно одинаково. Пока мы не сможем идентифицировать вызывающий объект и его назначение, обновление только по временной метке все равно вызывает полную сборку. То же правило применяется к недостающим деталям, неизвестным событиям или любым изменениям, которые могут повлиять на записи в любой точке зоны. Планировщик никогда не превращает неопределенность в уменьшение объема работы.
Выбор наименьшей безопасной сборки
Вывод Quicksilver имеет две части, которые здесь важны. Ключи записей содержат ответы DNS для одной зоны и имени. Меньший ключ общих метаданных сообщает нашим авторитетным DNS-серверам, как использовать эту зону, включая то, где искать специальные записи, такие как серверы имен и подстановочные знаки. Некоторые события изменяют только ключи записей; другие должны обновлять и то, и другое.
С учетом этого различия планировщик может выбрать один из пяти типов сборки:
- Только ресурсы: пересборка записей в определенных DNS-именах, которые мы называем ресурсами, без изменения метаданных зоны. Обычные изменения A, AAAA, TXT и CNAME обычно идут по этому пути.
- Ресурсы и метаданные: пересборка определенных имен и общих метаданных. По этому пути идут изменения серверов имен и подстановочных знаков.
- Вершина и метаданные: пересборка вершины зоны и метаданных. Такие настройки, как Universal SSL, могут использовать этот путь.
- Очистка (Purge): удаление зоны, которая больше не должна публиковаться.
- Полная зона: пересборка каждой записи, когда нельзя доказать безопасность меньшего объема работы.
Правило простое: каждый ключ, записываемый целевой сборкой, должен быть байт-в-байт идентичен тому, что записала бы полная сборка. Если мы не можем это доказать, мы выполняем полную сборку.
Удаление старого состояния работает так же, как и для сборок на основе записей в 2022 году. События записей включают удаленные записи и предыдущую версию обновленных. Затем Zone Builder сканирует Quicksilver на предмет существующих ключей под каждым перестраиваемым именем, удаляя все те, которые не были записаны новой сборкой.
Полные сборки по-прежнему необходимы, когда изменение может затронуть записи в любой точке зоны или когда зону необходимо опубликовать впервые.
Существует одно осознанное исключение. Полная сборка удерживает в памяти одновременно каждую запись из базы данных и каждый существующий ключ Quicksilver для зоны, а наши крупнейшие зоны не помещаются целиком. Выше фиксированного порога количества записей Zone Builder никогда не переходит к полной сборке. Вместо этого он перестраивает вершину, метаданные и каждое имя, определенное событиями в очереди. Мы идем на этот компромисс, потому что полная сборка таких зон не может завершиться, и, пока она повторяется, она задерживает каждое другое изменение в своем разделе Kafka. Многие случаи, для которых в противном случае потребовалась бы полная сборка, связаны с несколькими зонами, разделяющими одно имя, и блокировки зон снижают этот риск, предотвращая добавление того же имени в другую учетную запись. Для редких оставшихся случаев, таких как включение зоны в другой сети, мы можем вручную перестраивать отдельные имена в зоне.
Когда одно имя означает более одной зоны
Quicksilver хранит ключи записей каждой зоны отдельно. Он также хранит один ключ метаданных, общий для каждой зоны Cloudflare с этим доменным именем. Этот общий ключ действует как каталог: он сообщает нашим авторитетным DNS-серверам, какие внутренние идентификаторы зон они могут использовать при ответе на запрос.
Почему одно имя может иметь несколько идентификаторов зон? Во время подключения клиентов, их отключения и других изменений для одного и того же публичного домена могут одновременно существовать несколько объектов зон Cloudflare. Мы используем политику приоритетов, чтобы выбрать небольшой ограниченный набор для публикации.
Рассмотрим четыре внутренних зоны для example.com: Зона 101, Зона 202, Зона 303 и Зона 404. Ключ метаданных в настоящее время перечисляет Зоны 101, 202 и 303. Зона 404 существует, но она не выбрана. Номера идентифицируют отдельные объекты зон Cloudflare; клиенты по-прежнему видят одно доменное имя.
Изменение TXT-записи для www.example.com в Зоне 202 затрагивает только ключи записей Зоны 202, поэтому ключ метаданных не изменяется. Изменения серверов имен и подстановочных знаков отличаются, так как ключ метаданных записывает информацию о них. Такие сборки должны обновлять как записи зоны, так и ключ метаданных.
Во время выполнения Zone Builder считывает текущий список снова. Если он записывает ключ метаданных, он перестраивает весь ключ на основе этого свежего представления, а не исправляет старую копию. Он записывает записи вызвавшей событие зоны перед метаданными, которые ссылаются на них.
Планирование на основе события, проверка перед записью
Событие описывает то, что произошло в прошлом. Сборка может ожидать прибытия новых изменений в базе данных и других событий, поэтому планирование происходит дважды. Сначала событие создает консервативный список работ. Непосредственно перед выполнением Zone Builder считывает свежий снимок публикации: текущие настройки зоны, должен ли этот Zone Builder публиковать зону в своей сети и какие идентификаторы зон с тем же именем принадлежат ключу метаданных. Здесь сеть — это отдельная изолированная копия Quicksilver для одной среды. Каждый экземпляр Zone Builder выполняет запись в одну сеть, и зона может быть включена в одних сетях и не включена в других. Например, зона может обслуживаться из глобальной сети Cloudflare, а также из китайской сети.
Свежее состояние может изменить результат четырьмя способами:
- Целевая сборка может превратиться в полную сборку, если в зоне появилась функция, требующая работы со всей зоной.
- Сборка может превратиться в очистку (purge), если зону больше не следует публиковать в этой сети.
- Сборка может быть пропущена, если вызвавшая ее зона больше не принадлежит ключу метаданных.
- Сборка метаданных может восстановить общий ключ из текущего набора кандидатов.
Свежее чтение выполняется из копий базы данных только для чтения, называемых репликами, поэтому сборки не конкурируют с операциями записи клиентов. Доставка Kafka и репликация базы данных происходят независимо: событие может поступить до того, как изменения в базе данных станут видны на реплике. Агрегатор DNS Kafka задерживает события на время, достаточное для покрытия обычной задержки, а финальная проверка базы данных отклоняет чтение, если эта реплика все еще отстает.
Объединение изменений в одном планировщике
Работа с записями и работа со всей зоной ранее использовали разные планировщики. Каждый мог объединять свою собственную работу, но не работу из другого планировщика. Мы заменили их единым планировщиком. Планировщик решает, что должно быть построено, а диспетчер планировщика решает, когда это может быть запущенно.
Когда перед началом сборки поступает несколько событий для одной зоны, планировщик объединяет их работу:
- Изменяется A-запись для www.example.com, что требует обработки этого имени.
- Изменяется подстановочный знак (wildcard) для *.example.com, что требует обработки этого имени и общих метаданных.
- Планировщик объединяет оба имени и обновление метаданных в одну сборку.
Слияние только добавляет работу. Если какое-либо событие требует полной зоны, объединенная сборка становится полной сборкой. Очистка переопределяет любое перестроение, поскольку ее желаемый результат заключается в том, что зона больше не публикуется.
Планировщик отслеживает две вещи по отдельности: работу DNS, которая должна быть выполнена, и сообщения Kafka, ожидающие подтверждения. Повторная попытка сохраняет оба элемента. Даже когда полная сборка или очистка больше не нуждаются в отдельных именах записей, сообщения остаются привязанными до тех пор, пока результат не будет безопасно записан.
Поддержание движения Kafka
Одна медленная сборка может задержать несвязанных клиентов, даже когда рабочие потоки доступны. Kafka делит сообщения на упорядоченные разделы. Для каждого раздела она хранит одно зафиксированное смещение: точку, до которой каждое сообщение считается завершенным. Перемещение этого смещения за незавершенную работу может привести к потере изменений клиента после перезапуска.
Мы используем мягкую фиксацию (soft committing), которую я ранее описывал в своем глубоком погружении в Secondary DNS. Сборка может завершиться не по порядку и быть помеченной как завершенная в памяти, но фиксированная позиция в Kafka продвигается вперед только после того, как все более ранние сообщения также будут завершены.
На диаграмме сообщение 2 все еще выполняется, поэтому сообщения с 3 по n не могут быть зафиксированы. Если Zone Builder перезапускается, эти завершенные в памяти задачи исчезают, и Kafka воспроизводит сообщения со 2 по n. Мы сознательно выбираем повторную работу вместо потери изменений.
Целевые планы уменьшают время, в течение которого самое старое сообщение остается незавершенным. Планировщик также предотвращает ситуацию, когда одна занятая зона захватывает все рабочие потоки. После построения зоны ее следующая сборка ожидает интервала перестроения для конкретной зоны, который различается в зависимости от тарифного плана Cloudflare. Изменения, поступающие тем временем, объединяются в эту следующую сборку. Планировщик обычно соблюдает этот интервал, но позволяет старой очереди работ выполниться, прежде чем блокировать раздел Kafka на слишком долгое время. Блокировки зон и задержки повторных попыток по-прежнему применяются, поэтому сбойная сборка не может выполняться в непрерывном цикле.
Безопасный выпуск
Меньшая сборка полезна только в том случае, если она выдает те же DNS-данные. Мы добавили тесты на эквивалентность для путей имен серверов, подстановочных знаков и вершин (apex). Каждый тест выполняет полную сборку и целевую сборку из одного состояния базы данных, фиксирует значения, зависящие от времени, и побайтово сравнивает каждый ключ Quicksilver, принадлежащий целевому тарифному плану. Тесты планировщика и диспетчера охватывают классификацию, зоны с одинаковыми именами, слияние, очистку и эскалацию.
Во время развертывания мы также запускали полную сборку параллельно с каждой целевой сборкой и сравнивали результат. Это было ресурсоемко по замыслу, так как каждый раз считывалась вся зона целиком, поэтому мы использовали этот подход лишь до тех пор, пока зеркальное сравнение, описанное ниже, не дало нам такую же уверенность.
Тесты не могут воспроизвести любую комбинацию клиентов, поэтому каждый релиз также проходит через зеркальный Zone Builder. Он потребляет производственный поток Kafka и считывает состояние производственной базы данных, но выполняет запись в отдельную среду Quicksilver, которая не обслуживает клиентский трафик.
Модуль сравнения проверяет 20% выборку зон на предмет пропущенных ключей, лишних ключей и отличающихся значений. Эти две среды не всегда создают зону в один и тот же момент времени, поэтому временное расхождение не является автоматической регрессией. Мы ищем новые типы различий, которые сохраняются после того, как оба конвейера догоняют друг друга. Это дает нам уверенность в том, что целевые сборки дают тот же результат, что и продакшн.
Мы создали DNS Mirror Dashboard, чтобы группировать такие различия и сохранять примеры для расследования. Разумеется, она работает на базе Cloudflare Developer Platform: принимает результаты, Durable Objects поддерживают состояние, а Cloudflare Access защищает ее. Каждая из критических групп на скриншоте ниже была проверена и подтверждена как безопасная перед релизом. Частота различий — это доля сравниваемых зон, в которых продакшн и зеркало разошлись как минимум по одному ключу в момент сравнения. Большинство из них — это временные различия, которые исчезают, как только оба конвейера синхронизируются, поэтому мы следим за двумя вещами: чтобы этот показатель оставался низким и чтобы не появлялось новых видов различий.
После того как результаты зеркалирования остались стабильными, мы раскатили планировщик на основе хэша имени зоны. Хэширование имени, а не идентификатора зоны гарантировало, что все зоны с одинаковым именем попадают на один и тот же планировщик, поэтому два планировщика никогда не записывали один и тот же ключ метаданных. Мы увеличили процент для бесплатных зон, а затем повторили этот процесс для платных зон.
Результаты
За 24 часа мы зафиксировали, что 86.81% сборок состоят только из ресурсов, 2.54% — из ресурсов и метаданных, 2.30% — из вершин и метаданных, а 1.30% — это очистки. В общей сложности 92.95% сборок позволили избежать пересборки всей зоны. На долю полных сборок пришлось оставшиеся 7.04%.
Раньше оба типа сборок метаданных были полными сборками, даже для крупных зон. Вместе на них приходится 4.84% сборок. В сочетании с 7.04% тех сборок, которые по-прежнему являются полными, это составляет 11.88%, то есть количество полных сборок сократилось примерно на 40%. Их доля по количеству также преуменьшает объем предотвращенной работы, поскольку они включают в себя самые крупные зоны. Оставшиеся полные сборки занимают в среднем 0.35 секунды, так как события, которые все еще вызывают их (например, первая публикация зоны), в основном затрагивают новые зоны с небольшим количеством записей.
В 2022 году мы также планировали систему пакетирования (батчинга). Унифицированный планировщик делает ее ненужной: он не ждет формирования пакетов и объединяет события только тогда, когда для той же зоны уже поставлена в очередь работа. Раньше длительные полные сборки задерживали зафиксированное смещение (offset) для своего раздела. Теперь, когда их стало меньше, мы потребляем Kafka по мере поступления событий, вместо того чтобы отставать и нагонять упущенное всплесками до 1,550 сообщений в секунду.
Мы также измерили задержку завершения сборки: время от изменения DNS-записи или настройки зоны до завершения записи Zone Builder в Quicksilver. Это не включает распространение Quicksilver по нашей сети, которое различается в зависимости от местоположения и не зависит от данной работы. Продакшн работал на старых планировщиках, а зеркало — на унифицированном планировщике с теми же событиями за те же 24 часа. Среды идентичны, за исключением кода: они потребляют одни и тем же события Kafka, читают из одной базы данных и записывают эквивалентные данные в Quicksilver. Это делает сравнение контролируемым и параллельным. Цифры объединяют все типы сборок и тарифные планы, поэтому они не являются гарантией задержки для отдельной зоны. Значительная доля этого времени приходится на описанный ранее интервал пересборки, а не на саму работу по сборке. В каждой точке измерения p99 — это время, в течение которого завершились 99% событий. Типичные задержки здесь практически не изменились, поскольку большинство сборок и так были небольшими. Разница заключается в «хвосте»: самый большой всплеск p99 упал с 14.6 минут до 1.21 минуты, то есть в 12 раз. На графике используется логарифмическая шкала.
Целевые сборки также гораздо реже загружают целиком крупные зоны. Совокупный рабочий объем памяти (working-set memory), активно используемый развертыванием, сократился в среднем с 49 ГиБ до 7 ГиБ, что составляет уменьшение на 86%. Пиковые значения снизились с 79 ГиБ до 12 ГиБ, то есть на 85%.
Таковы были непосредственные результаты работы нового планировщика. Они также позволили нам пересмотреть два защитных механизма. Первый — это интервал пересборки, который мешает одной перегруженной зоне постоянно пересобираться и отнимать ресурсы у всех остальных. Поскольку сборки стали дешевле, а гораздо меньшее их число считывает зоны целиком, мы смогли безопасно сократить этот интервал на большинстве тарифных планов. Второй — это время, в течение которого DNS Kafka Aggregator удерживает каждое событие, чтобы убедиться, что реплики базы данных догнали основные узлы перед тем, как мы начнем из них читать. Как только наши базы данных начали отклонять чтение с отставших реплик, этот буфер стало возможным безопасно уменьшить. В совокупности это сократило медианную задержку завершения сборки вдвое: с примерно 7.7 секунд до примерно 3.3 секунд. На графике ниже первый шаг вниз — это изменение в DNS Kafka Aggregator. Красная полоса отмечает релиз Zone Builder с более короткими интервалами пересборки. Этот релиз внедрялся на наших серверах постепенно, поэтому задержка падает в два этапа, а не одновременно.
Линии p90 и p99 на этом графике менее точны, чем кажутся. Гистограмма имела интервалы (бакеты) в 1, 5, 10 и 30 секунд, и процентиль, попадающий между двумя границами, оценивается так, будто задержки распределены равномерно по этому диапазону. Большинство задержек находится в нижней части своего диапазона, поэтому эти оценки получаются завышенными: p99 считывается близко к 30 секундам, а p90 — выше 7 секунд. Точное среднее значение, не зависящее от бакетов, подтверждает сокращение вдвое. С тех пор мы перешли на нативную гистограмму, которая автоматически выбирает мелкозернистые бакеты. Она определяет p90 примерно на уровне 5.5 секунд, а p99 — примерно на уровне 12 секунд.
Почему полные сборки все еще существуют
В 2022 году мы заявляли, что планируем полностью избавиться от полных сборок. Теперь мы понимаем, что это нереалистично и нецелесообразно. Зоне, публикуемой впервые, требуется запись каждой записи. Изменение, которое может затронуть записи в любом месте зоны, требует проверки каждой записи. Событие, в котором не указано, что именно изменилось, все равно приводит к полной сборке, поскольку планировщик никогда не превращает неопределенность в меньший объем работы. Для небольшой зоны полная сборка к тому же обходится дешево.
По мере того как мы будем узнавать, что именно меняют другие события, мы продолжим переносить их в целевые сборки. Тем не менее, главный результат уже достигнут: события, которые раньше запускали пересборку наших крупнейших зон, больше этого не делают, а оставшиеся полные сборки в основном инициируются такими событиями, как первая публикация, которые затрагивают новые небольшие зоны.
Хотите узнать, что происходит после того, как Zone Builder записывает изменения? Читайте о том, как Quicksilver распространяет конфигурацию на каждый дата-центр Cloudflare за считанные секунды.












