Темы



Мост к Git-инфраструктуре для масштабирования работы агентов

Коротко сгенерировано ИИ по тексту статьи
  • В Azure и собственных ЦОД уже хранятся 99,99999% репозиториев GitHub; оставшиеся самые загруженные сдерживает задержка около 25 мс.
  • Переход от кворума всего репозитория к кворуму отдельных ссылок дал на внутренних испытаниях в 3–4 раза большую пропускную способность записи.
  • Виртуальная реплика записи сохраняет данные в Azure Blob Storage, а эфемерные реплики чтения позволяют отдельно масштабировать обработку чтений.

Почему это важно: Это важно, потому что рост трафика агентов и ограничения расширения ЦОД требовали переносить данные и повышать пропускную способность поэтапно.

За последний год, пока трафик Git рос в геометрической прогрессии, 99,99999% репозиториев Git на GitHub стали храниться одновременно в двух местах: в наших собственных центрах обработки данных и в Azure. Рассказываем, как кворумы для отдельных ссылок помогут перенести остальные репозитории, а объектное хранилище позволит масштабировать чтение независимо.

Mona sits on a raised green block beside another block displaying a circular refresh-style icon.

21 октября 2026 года
| 8 минут

За последний год, пока трафик Git рос в геометрической прогрессии, 99,99999% репозиториев Git на GitHub стали храниться одновременно в двух местах: в наших собственных центрах обработки данных и в Azure. Оставшиеся репозитории — одни из самых загруженных на платформе, и их удерживает лишь одно препятствие: задержка в 25 мс на круговой обмен данными.

В предыдущей публикации в блоге мы изложили два принципа, лежащих в основе архитектуры хранилища нового поколения:

  • Минимизировать координацию
  • Отделить хранилище от вычислительных ресурсов

В этой статье мы рассмотрим промежуточный этап между старым и новым решениями. Расскажем, как изменение способа сериализации записей в кворуме позволит перенести в Azure самые загруженные репозитории, а также как этот же шаг открывает путь к репликам, не хранящим собственного постоянного состояния.

Как мы к этому пришли

Сроки и ход миграции определялись переходом к агентной разработке: трафик быстро рос, возможности электроснабжения наших центров обработки данных и прилегающего региона имели жесткий предел, а физические ограничения цепочки поставок мешали расширению. В начале миграции оставшихся мощностей с учетом всех возможных вариантов расширения хватало, чтобы безопасно перенести данные, но не настолько, чтобы делать это не спеша.

Мы уже создали прототипы новых архитектурных подходов к масштабированию Git в облачной инфраструктуре. Но менять одновременно и архитектуру хранилища, и платформу размещения на фоне такого роста было слишком рискованно. Учитывая наши масштабы и критическую важность сервиса для сообщества, нам нужно было тщательно выстроить последовательность работ: сначала перенести данные, а затем развивать архитектуру после их переезда.

Операции Git относятся к сервисам уровня Tier 0: это ключевая функция GitHub, от данных репозиториев зависит почти всё на платформе. Поэтому в первую очередь нужно было перенести данные, сервисы и все их зависимости. Первая копия репозитория появилась в регионе Azure Central US в августе 2025 года. Сегодня у большинства репозиториев две из пяти реплик находятся в Azure, а на Azure приходится около половины трафика Git. Большую часть этого времени Azure принимала на себя растущий трафик, тогда как нагрузка на наши собственные центры обработки данных оставалась стабильной.

В ходе миграции трафик следует за данными. У большинства репозиториев есть пять реплик — для баланса между долговечностью, доступностью и масштабированием. Чтобы минимизировать задержку чтения, Spokes использует приоритетную маршрутизацию и направляет запросы к реплике в том же регионе и зоне доступности. По мере переноса реплик в Azure растет доля трафика, который можно обслуживать оттуда. Когда переезжает последняя реплика, старый регион полностью перестает получать запросы. Каждый репозиторий проходит этот процесс:

Ни один репозиторий не останется позади

Что же насчет оставшихся 0,00001% репозиториев?

В нашей нынешней архитектуре у репозитория, размещенного более чем в одном регионе, ниже предельная пропускная способность записи. Для фиксации записи кворуму Spokes достаточно большинства реплик, однако перед продолжением он ждет ответа от каждой работоспособной реплики (до истечения тайм-аута). Поэтому медленные реплики не отстают, а задерживают каждую запись. На практике это означает, что задержка каждой записи определяется самой медленной работоспособной репликой. Поскольку каждая запись удерживает блокировку всего репозитория до фиксации, именно она также задает предел пропускной способности. Реплика в целевом регионе Azure находится примерно в 1 000 милях от наших центров обработки данных, а значит, каждый круговой обмен по сети занимает около 25 мс, причем для записи требуется несколько таких обменов. Репозиторий, уже работающий почти на пределе пропускной способности, не сможет выдерживать такую задержку при каждой сериализованной записи — после каждой отправки данных он будет отставать всё сильнее.

Здесь вступает в дело наш первый принцип — минимизация координации. В данном случае он означает: не сериализовать то, что в этом не нуждается. Требования Git к согласованности применяются к каждой ссылке по отдельности. Как только сервер принимает обновление ссылки, ни один клиент не должен увидеть после этого ее старое значение — только это значение или более новое. Это правило действует и при перемотке вперед, и при принудительной отправке изменений, и при удалении. Однако в большинстве случаев обновления разных ссылок не зависят друг от друга, поэтому их можно применять независимо.

Поэтому мы меняем область действия кворума: вместо всего репозитория он будет охватывать отдельные ссылки.

Если удерживать блокировку всего репозитория на протяжении всего обновления ссылки, включая сетевые обмены, кворум превращается в единую неупорядоченную очередь запросов, в которой одновременно выполняется только одна запись. Каждая запись в этой очереди занимает столько же времени, сколько отвечает самая медленная работоспособная реплика. С ростом частоты записей растет и очередь, а каждая следующая запись ждет дольше предыдущей.

При сериализации на уровне отдельных ссылок друг друга должны ждать только записи, конкурирующие за одну и ту же ссылку. На практике большинство записей в репозиторий затрагивают разные ссылки — например, когда агенты и разработчики отправляют изменения в собственные ветки. Такие записи больше не ждут друг друга. Каждая запись по-прежнему испытывает задержку при обмене данными между регионами, однако записи в разные ссылки могут выполняться одновременно. Это устраняет единое узкое место репозитория. Сравним, как при сопоставимой нагрузке в двух подходах накапливаются очереди и возникают тайм-ауты:

Эти изменения повышают предел пропускной способности записи в репозиторий примерно пропорционально числу одновременно обновляемых ссылок и позволяют перенести в Azure последние оставшиеся репозитории. В ходе внутренних испытаний на реалистичном трафике записи в разные ссылки стабильно обеспечивали в 3–4 раза большую пропускную способность при меньшей задержке.

Масштабирование чтения независимо от записи

В большинстве репозиториев чтение значительно преобладает над записью: на одну запись приходится от 100 до 1 000 операций чтения. Сегодня операции чтения и записи обращаются к одним и тем же репликам, поэтому конкурируют за одни и те же ресурсы и не могут масштабироваться независимо.

Отделение хранилища от вычислительных ресурсов меняет ситуацию. Когда данные репозитория надежно сохраняются в Azure Blob Storage и обновляются с каждой новой записью, мы можем начать отделять трафик чтения от кворума записи и независимо масштабировать вычислительные ресурсы для каждого из них. Например, чтению помог бы гораздо больший объем кеша в памяти, чем мы выделяем сегодня, но эта память нужна и для компактизации. После разделения каждому процессу можно будет задать собственную конфигурацию.

Первый шаг — включить объектное хранилище в кворум, но в особом качестве, которое мы называем виртуальной репликой. Вычислительный узел, представляющий эту реплику, — наш первый полностью эфемерный узел. Он служит мостом к объектному хранилищу: передает поступающие записи и не обслуживает запросы на чтение. Поскольку этот узел входит в кворум, подтверждение записи отправляется только после того, как она надежно сохранена в объектном хранилище.

Это немного увеличивает задержку каждой записи, но, как и в случае с круговым обменом между регионами, сериализация на уровне отдельных ссылок не дает этому стать узким местом для пропускной способности. Виртуальная реплика записи также объединяет транзакции для непересекающихся ссылок в единый набор операций ввода-вывода по сети с объектным хранилищем. Поэтому загруженный репозиторий выполняет один круговой обмен для множества параллельных записей, а не по одному на каждую запись. Пока что виртуальная реплика записи также выполняет компактизацию, а реплики чтения наследуют уже компактированное состояние.

Создав виртуальную реплику записи, мы можем по мере необходимости запускать для репозитория любое количество виртуальных реплик чтения. Каждая такая реплика прогревает свой кеш из Azure Blob Storage и начинает обслуживать запросы на чтение, не присоединяясь к кворуму. Источником истины для нее служит объектное хранилище, а локальное состояние сверяется с ним в фоновом режиме.

Этот шаг открывает возможности для существенного масштабирования чтения. Во время внутренних испытаний наша распределенная система нагрузочного тестирования регулярно выявляла пределы нынешней архитектуры. При тестировании виртуальных реплик чтения система сначала упиралась в собственные ограничения. Однако этот подход создает и несколько проблем, которые будут решены на следующих этапах:

  1. Виртуальные реплики чтения сначала сверяют свои данные с объектным хранилищем в фоновом режиме. Это подходит для некоторых типов запросов на чтение, но не для всех. Чтобы обслуживать все типы чтения, нужно перенести эту сверку в основной поток обработки и получать только состояние ссылок и данные, изменившиеся с момента последней синхронизации реплики.
  2. Мы постепенно повышаем пропускную способность записи, переходя к модели с отдельными ссылками, однако кворум по-прежнему ограничивает дальнейшее увеличение.

Как еще больше повысить пропускную способность записи?

Что дальше

Нынешний GitHub построен на кворуме Spokes, и за прошедшие годы Spokes превратился в нечто гораздо большее, чем координатор кворума. Это также интеллектуальный уровень маршрутизации и балансировки нагрузки, единый API, с помощью которого внутренние клиенты обращаются к данным репозиториев и выполняют с ними операции — более 100 миллиардов в день, —, а также набор фоновых систем, которые мы называем «сборщиками». Они отвечают за размещение и восстановление репозиториев, а также за перебалансировку между узлами хранилища.

Подавляющее большинство функций сервиса Spokes применимо к новой архитектуре не хуже, чем к существующей. Но мы также понимаем, что протокол фиксации изменений, который помог GitHub достичь миллиарда репозиториев, не позволит нам добраться до следующего миллиарда и превзойти этот рубеж.

Работа еще не завершена. После этих изменений следующим ограничением станет пропускная способность записи. Каждая запись по-прежнему ждет реплики, каждая из которых хранит полную копию репозитория, хотя в действительности нужно упорядочивать лишь небольшую часть данных записи. Объекты Git неизменяемы и именуются по своему содержимому, поэтому их можно хранить, не дожидаясь согласия остальных участников. Остается лишь небольшая часть данных, и главная сложность — упорядочить ее как можно быстрее, не перенеся узкое место в другое место системы. Спроектировать это на доске — одно дело. Внедрить решение в системе с миллиардом репозиториев, в которые разработчики и агенты круглосуточно вносят изменения, — совсем другое. Чтобы справиться с этой задачей, нам пришлось переосмыслить и эффекты второго порядка: например, где хранить долговечное состояние ссылок и как масштабировать компактизацию и сборку мусора.

Трафик от агентов не снижается, и каждый из этих шагов дает репозиториям дополнительный запас пропускной способности сразу после внедрения, не заставляя ждать завершения всей работы.

На следующей неделе: На GitHub Universe и в заключительной статье этой серии мы расскажем о конечном варианте архитектуры, о том, как он повлияет на пропускную способность записи, какие проблемы второго порядка нам пришлось решить и почему где-то в стеке всё еще остается небольшая группа консенсуса.

Благодарим Brian Celenza за вклад в эту статью.

Авторы

Eleanor White

Старший инженер-программист

Victoria Dye

Ведущий инженер-программист

Похожие публикации

Узнайте больше о GitHub

Docs

Документация

Всё необходимое, чтобы освоить GitHub, собрано в одном месте.

GitHub

GitHub

Создавайте будущее на GitHub — платформе, где каждый и отовсюду может создать что угодно.

Customer stories

Истории клиентов

Познакомьтесь с компаниями и инженерными командами, которые создают проекты с помощью GitHub.

GitHub Universe 2026

GitHub Universe 2026

Присоединяйтесь к нам 28–29 октября в Сан-Франциско или онлайн на GitHub Universe — нашем главном мероприятии для разработчиков, объединяющем людей, агентов и код со всего мира.

Мы тоже рассылаем новости

Получайте советы, технические руководства и лучшие практики в нашей рассылке для разработчиков, которая выходит раз в две недели.

Ваш адрес электронной почты

© GitHub Engineering