walgit: Git-сервер без базы данных по архитектуре Cursor Continuity

walgit, это Git-сервер, написанный на Rust и распространяемый как один бинарник. У него нет базы данных и нет лидера: единственное, что имеет значение, лежит в объектном хранилище (S3-совместимом или GCS). Развёртывание, это бинарник плюс TOML-конфиг с адресом бакета и токеном; после запуска первый же push с новым именем репозитория сам создаёт этот репозиторий. Сервер умеет smart HTTP fetch/push (протоколы v0 и v2), клоны через bundle-uri, отдающиеся как статические файлы, Git LFS, веб-интерфейс для просмотра репозиториев, JSON API со своим SDK, политику пушей на уровне отдельного репозитория и вебхуки. Каждая машина, на которой запущен walgit, просто одноразовый кэш; настоящий репозиторий, это бакет. По словам проекта, если убить все запущенные инстансы разом, потеряется только «тепло» кэша и ничего больше.
Проблема, которую решает архитектура, в устройстве git. Данные репозитория упакованы в крупные бинарные packfile, уложенные так, чтобы занимать мало места, а не чтобы читаться по порядку: любая git-операция превращается в случайные обращения по гигабайтам данных. На локальном диске с файлом в кэше страниц это работает быстро, а через сетевую файловую систему, катастрофически медленно, из-за чего попытки «просто положить репозитории на NFS» проваливались у всех крупных хостингов, кто их пробовал. Схема, которая выжила в промышленном масштабе, Spokes от GitHub: она держит настоящие репозитории на локальных NVMe-дисках (чтобы сам git делал работу быстро) и реплицирует их на уровне packfile со строгой консистентностью. Цена этой строгости, трёхфазный коммит по фиксированному набору реплик, отдельная база данных, которая знает, на каких машинах лежит каждый репозиторий, и целый парк серверов, за каждым из которых нужно индивидуально следить.
Cursor в своём посте «Git at any scale» предложила архитектуру Continuity, которая меняет эту экономику: журнал упреждающей записи (write-ahead log) в объектном хранилище становится единственным источником истины, а любая копия репозитория на диске, просто кэш. Push сохраняется как неизменяемый объект в бакете и становится видимым только тогда, когда крошечный файл-манифест переписывается операцией compare-and-swap (CAS). Именно этот CAS и есть весь консенсус, без выборов лидера, без кворума, без ведущего узла. Принять push может любой инстанс, но выиграть гонку из двух одновременных попыток может только один. Реплика, которая никогда раньше не видела репозиторий, читает журнал, и репозиторий у неё уже есть. Чтение остаётся консистентным без какой-либо координации между узлами, потому что перед каждым чтением сервер сначала спрашивает хранилище, не изменилось ли что-то (условный GET, обычно возвращающий HTTP 304, «без изменений»). Компактификацию (упаковку истории заново) выполняет только тот узел, у кого есть лизинг на неё, и публикует результат в журнал, остальные реплики просто скачивают уже сжатые пакеты, а не пересобирают их сами. А поскольку журнал и есть истина, у репозитория есть полная история происхождения: каждый push и каждая компактификация воспроизводимы на любой момент времени.
walgit берёт архитектуру Continuity как есть и добавляет то, что нужно для монорепозитория на маленьких машинах: удалённое чтение по HTTP range-запросам для репозитория, чьи пакеты никогда не поместятся на диск инстанса; хранение коммитов и деревьев локально, тогда как блобы остаются в бакете (так называемый history pack); и вынос байтов клонирования полностью за пределы сервера, свежие клоны и подтягивание изменений отдаются как статические файлы прямо бакетом или CDN через bundle-uri.
Сама структура хранения репозитория внутри бакета устроена так: под путём repos/
Механика push: обработчик receive-pack индексирует входящий пакет (git index-pack с флагами --fix-thin --rev-index во временном каталоге), проверяет связность истории и политику доступа, загружает пакет, его индекс и запись журнала в бакет, а затем переписывает манифест через CAS. Если CAS конфликтует и сервер получает HTTP 412, он перечитывает манифест, заново проверяет каждую ссылку на старое значение и повторяет попытку. Несколько одновременных push в один репозиторий на одном инстансе группируются в один общий CAS. Клиент видит успех push только после того, как бакет подтвердил запись.
Механика чтения: один условный GET манифеста; ответ 304, сервер отдаёт из локальной копии, ответ 200, применяет новые записи журнала. То, что именно значит «применить», зависит от типа запроса: для ссылок достаточно снапшота и журнала, для обслуживания клиента, набор пакетов в том объёме, который может удержать конкретная машина (мелкие пакеты и history pack локально, слишком большой базовый пакет читается по диапазонам), для полной компактификации нужны все данные локально, а для веб-интерфейса на репозитории, который не помещается на диск, работает удалённый читатель объектов. Скачивание пакетов выполняется на отдельном рантайме и никогда не блокирует запросы к ссылкам.
Размещение данных задаётся конфигурацией, а не выводится автоматически: секция [placement] с масками serve/maintain определяет, для каких репозиториев конкретный хост выполняет работу с объектами хранилища (запросы уровня ссылок работают на любом хосте). На одной машине можно оставить настройки по умолчанию; при нескольких машинах монорепозиторий кладут на хост с SSD (режим кэша cache.mode = "disk"), остальное, на маленькие узлы, а маршрутизацию делают по пути /
Сборка требует Rust (версия закреплена в rust-toolchain.toml), protoc и Node.js версии 24 вместе с pnpm для веб-интерфейса; есть и альтернативные пути сборки через Nix или Podman/Containerfile. На стороне клиента для работы установщика и bearer-токен-аутентификации нужен git версии не ниже 2.46: именно с этой версии git-credential helper умеет отвечать на запрос authtype=Bearer, а при получении настоящей ошибки 401 сам стирает сохранённый токен и подсказывает, откуда взять новый. Установка на машину разработчика сводится к одной идемпотентной команде через curl, которая сохраняет токен в файл, читаемый только текущим пользователем.
Тестирование описано так: быстрый «герметичный» набор тестов (юнит-тесты плюс быстрые интеграционные) выполняется меньше чем за минуту и использует хранилище в памяти вместе с настоящим git; end-to-end набор с настоящим git против запущенного сервера занимает около 20 секунд; отдельно есть симуляция с намеренной инъекцией сбоев, падения узлов, сетевые разделения, устаревшие чтения, и контрактные тесты хранилища против локального rustfs. Проект распространяется под лицензией MIT.
Ключевые факты
- walgit, Git-сервер в один бинарник без базы данных и без значимого локального состояния: все данные хранятся в бакете S3 или GCS, а каждая машина с walgit, лишь одноразовый кэш.
- Реализует архитектуру Continuity, которую Cursor описала в посте «Git at any scale»: журнал упреждающей записи в объектном хранилище, источник истины, push становится виден только после CAS-перезаписи манифеста, без выборов лидера и кворумов.
- Поддерживает Git LFS, bundle-uri (клоны отдаются как статика прямо из бакета или CDN), веб-интерфейс, JSON API со своим SDK, вебхуки и политику пушей на уровне отдельного репозитория.
- Работает даже когда репозиторий больше диска сервера: удалённое чтение по HTTP range-запросам, локально держатся только коммиты и деревья, блобы остаются в бакете.
- Требования: git не ниже версии 2.46 для bearer-токен-аутентификации, Node.js 24 для сборки веб-интерфейса; лицензия MIT.
Почему это важно
Хостинг git традиционно упирался в устройство самих packfile: данные упакованы компактно, но не по порядку чтения, из-за чего любая операция превращается в случайные обращения по гигабайтам, терпимо на локальном диске и катастрофично по сети. Попытки просто вынести репозитории на сетевую файловую систему проваливались у всех, кто их пробовал; схема, которая выжила в промышленном масштабе (Spokes у GitHub), держит репозитории на локальных NVMe и реплицирует их со строгой консистентностью, платя за это трёхфазным коммитом, отдельной базой размещения и парком серверов, за которыми нужно следить поштучно. Архитектура Continuity, которую описала Cursor, меняет эту экономику: единственный источник истины, журнал в объектном хранилище, а любая локальная копия, просто кэш, который не жалко потерять. walgit, открытая Rust-реализация этой идеи, доведённая до состояния, когда она работает даже на машинах меньше самого репозитория.
Кому это важно
В первую очередь, инженерам инфраструктуры и DevOps-командам, которые сами хостят git для больших монорепозиториев и хотят GitHub-подобный сервис без необходимости держать целый парк «домашних животных»-серверов с локальными NVMe и базой размещения. Полезно и тем, кто хочет масштабировать git-хостинг горизонтально дешёвыми одноразовыми машинами, добавляя и убирая инстансы без какой-либо синхронизации между ними, а также командам, которым нужен self-hosted git с LFS, вебхуками и политиками доступа без готового SaaS-решения.
Как это применить
Развёртывание, один бинарник плюс TOML-конфиг с адресом S3- или GCS-бакета и токеном доступа; после запуска push с новым именем репозитория сам создаёт этот репозиторий, ничего заранее регистрировать не нужно. Дополнительные машины, указывающие на тот же бакет, начинают обслуживать те же репозитории без какой-либо координации между собой. Размещение работы с объектами задаётся явно через конфигурацию (секция [placement]): на одной машине оставляют настройки по умолчанию, при нескольких, монорепозиторий кладут на хост с SSD в режиме дискового кэша, а остальные репозитории обслуживают маленькие узлы, маршрутизируя запросы по пути владелец/имя. Роли сервера (serve, maintain, events) тоже настраиваются отдельно, что позволяет развести раздачу git-трафика и фоновую компактификацию по разным машинам.
Можно ли доверять
Источник, собственный README проекта на GitHub, то есть самоописание авторов, а не независимая оценка. В тексте нет номера релиза, changelog или даты выпуска, нет бенчмарков производительности и нет прямого сравнения с GitHub, GitLab или Gitea, только качественное описание архитектуры. Имя автора в самом тексте не названо (виден лишь путь репозитория на GitHub). При этом проект описывает довольно подробный набор тестов, быстрый юнит-набор до минуты, end-to-end тесты около 20 секунд и отдельную симуляцию с намеренной инъекцией сбоев (падения узлов, сетевые разделения, устаревшие чтения), что говорит в пользу инженерной зрелости, но подтверждается только словами самого проекта. Лицензия, MIT, других юридических или финансовых деталей о том, кто поддерживает проект, в тексте нет.
Риски и подводные камни
Без номера версии и даты релиза сложно судить, насколько проект уже обкатан в проде за пределами авторов. Корректность всей схемы держится на гарантиях consistency у самого объектного хранилища (compare-and-swap манифеста, условный GET), если бакет-провайдер их не даёт в полном объёме, предположения архитектуры могут не выполняться. Конфликтующие push обрабатываются через повторную попытку после HTTP 412, то есть под высокой конкуренцией за один репозиторий возможны ретраи и рост задержки. Для клиентской bearer-аутентификации нужен git не ниже версии 2.46, на более старых клиентах штатный механизм токенов не заработает. И главное, сложность из локальной репликации не исчезла, а переместилась в стоимость обращений к бакету: сам проект признаёт, что каждое протокольное решение оценивается по числу round-trip к объектному хранилищу, а не считается «бесплатным», при этом независимых цифр по реальной производительности в сравнении с GitHub, GitLab или Gitea в тексте нет.