Масштабування Git в OpenAI
Створюємо систему контролю версій для роботи в масштабах агентів і водночас розвиваємо екосистему Git.
За останній рік розмір нашого монорепозиторію та вимоги до нього зростали приголомшливими темпами. Стало зрозуміло, що нам потрібно оновити систему контролю версій, щоб вона витримувала обсяг одночасної роботи, який уможливлює Codex.
Ми створили Pando — нову серверну частину для репозиторіїв Git, не перериваючи інженерної роботи й не змінюючи того, як наша команда використовує Git. Тепер Pando забезпечує всю розробку в монорепозиторії OpenAI. Pando створено для досягнення двох амбітних цілей. Перша — горизонтальне масштабування запису, оптимізоване для одночасних операцій push до багатьох гілок. Друга — максимальна пропускна здатність читання, щоб клієнти могли завантажувати дані досить швидко для повного використання доступної пропускної здатності мережі.
Ми досягли цих цілей, зберігши сумісність із протоколом і клієнтом Git, тож наші інженери й агенти змогли безперешкодно впровадити Pando у свої робочі процеси. У робочому середовищі Pando скоротила медіанну затримку push майже вп’ятеро. Тепер вона передає майже петабайт даних Git на день.
Тут ми розповімо, що спонукало нас створити нову систему контролю версій і чому ми вирішили зберегти сумісність із Git. Ми порівняємо нашу систему з поширеними підходами до масштабування Git і пояснимо, чому саме Pando здатна задовольнити потреби OpenAI в масштабуванні. Також обговоримо подальші плани й наш майбутній внесок у Git для підтримки роботи в масштабах агентів.
Codex змінив масштаб і темп розробки в OpenAI, давши інженерам змогу виконувати набагато більше роботи паралельно. Це означає більше гілок, операцій push і злиття, а також більше операцій fetch і клонування із середовищ розробки, дослідницьких завдань і CI.
Ці навантаження посилюють одне одного. Тепер один інженер має одразу кілька змін, готових до CI, а кожен push може запустити кілька завдань, що отримують той самий коміт.
Приблизно з кінця минулого року системи, які підтримують наш монорепозиторій, почали ледве справлятися з навантаженням. Ми стикалися з повільними операціями fetch і push та невдалими злиттями; надійність часом падала нижче 90%. Ми досягли межі самого Git чи способу, яким обслуговували наш монорепозиторій? Git виріс зі спільноти, яка обмінювалася патчами до ядра електронною поштою. Понад два десятиліття розробники адаптували свої інструменти до Git у міру того, як змінювалися способи створення програмного забезпечення. Коли у власному монорепозиторії ми почали відчувати труднощі, для нас було важливо й надалі спиратися на ці напрацювання.
Ми побачили спосіб зберегти звичний робочий процес, перебудувавши серверну частину для паралельної обробки push до різних гілок і одночасного передавання тієї самої історії багатьом клієнтам. Використання Git як основи нашого процесу контролю версій означало, що вдосконалення клієнта можуть бути корисними й за межами OpenAI. Ми хотіли передати ці вдосконалення з нашої копії Git(відкривається у новому вікні) до основного проєкту, щоб поділитися ними з ширшою спільнотою.
Масштабування системи контролю версій має два ключові напрями: багато клієнтів надсилають зміни до різних гілок, а багато інших — отримують ту саму історію. Pando оптимізує обидва.
По-перше, Pando координує оновлення кожної гілки незалежно. Оновлення незалежних гілок можуть відбуватися паралельно, а клієнт, який отримує гілку, завантажує історію, потрібну для відповідної ревізії. Компроміс полягає в тому, що клієнт, який переглядає кілька гілок, може бачити їхній стан на різні моменти часу.
По-друге, Pando повторно використовує пак-файли (формат Git для спільного стиснення кількох об’єктів) у різних операціях fetch, а не генерує окремий пак-файл для кожного клієнта. Клієнт може завантажити деякі вже наявні об’єкти, зате сервер може передавати ті самі байти багатьом клієнтам із мінімальними обчисленнями.
Ці рішення дають змогу масштабувати запис у різні гілки й обслуговувати читання з кешів. Щоб зрозуміти, як усе це працює разом, почнімо з того, як Git зберігає та передає історію.
Git має підтримувати синхронність двох складових репозиторію: об’єктів і посилань.
Репозиторій Git записує знімки файлів та історію, що їх пов’язує, у вигляді графа об’єктів. Об’єкт може бути блобом, деревом, комітом або тегом. Блоби містять вміст окремих файлів, а дерева пов’язують імена з блобами чи іншими деревами, утворюючи структуру каталогів. Коміти вказують на кореневе дерево та нуль або більше батьківських комітів, формуючи історію репозиторію.
Об’єкти ідентифікуються за хешем їхнього типу та вмісту. Об’єкти також незмінні: запис зміненого файлу створює новий блоб, тоді як незмінені файли й каталоги можуть повторно використовувати наявні об’єкти.
Гілки й теги — це посилання, або «refs», які вказують на коміти. Фіксація змін у гілці створює новий коміт і оновлює посилання гілки так, щоб воно вказувало на нього.
Об’єкти Git посилаються один на одного за ідентифікаторами: коміти — на дерева, а дерева — на інші дерева й блоби. Гілка refs/heads/main вказує на коміт C2, батьківським комітом якого є C1. C1 вказує на кореневе дерево T1, де parser.py посилається на блоб B1. C2 вказує на кореневе дерево T2, де parser.py посилається на блоб B2. В обох кореневих деревах docs/ посилається на те саме дерево T3. Його запис README.md вказує на блоб B3, а usage.md — на блоб B4.
Git зберігає об’єкти в «окремому» або «упакованому» вигляді. Окремі об’єкти зберігаються в індивідуальних файлах, які визначаються за ідентифікатором об’єкта. Упаковані об’єкти зберігаються разом у пак-файлі, що дає змогу зберігати подібні об’єкти як дельти відносно один одного.
Git може зберігати блоби як окремі файли або пакувати їх разом, кодуючи один блоб як дельту відносно іншого.
У Git об’єкт є досяжним із коміту, якщо Git може знайти його, переходячи за посиланнями від цього коміту через його батьківські коміти, дерева й блоби. Під час fetch клієнт повідомляє серверу, які коміти хоче отримати, а які вже має. Сервер знаходить об’єкти, досяжні з потрібних комітів, виключає ті, які, за його відомостями, клієнт уже має, і надсилає решту як пак-файл у тому самому форматі, який Git використовує на диску. Повне клонування починається без жодних об’єктів, тому потребує всіх об’єктів, досяжних з історії, що клонується.
Операція push передає об’єкти у зворотному напрямку, запитуючи в сервера оновлення одного або кількох посилань. Клієнт використовує набір оголошених сервером посилань, щоб не надсилати об’єкти, які на сервері вже є. Тому пакет, що надсилається, може залежати від наявних об’єктів: самого пакета недостатньо для відновлення нової історії.
Реплікація репозиторію Git означає забезпечення доступності і об’єктів, і посилань на кількох серверах. Перш ніж репліка зможе надавати нове значення посилання, вона має бути здатна передавати всі об’єкти, досяжні з цього значення. Сервіс також має координувати конкурентні оновлення посилань, тобто операції push, які намагаються перемістити ту саму гілку з тієї самої старої вершини на різні нові коміти. У системах, які встановлюють єдиний порядок усіх записів до репозиторію, операції push до незалежних гілок можуть змушені чекати одна на одну.
Проєктуючи Pando, ми оцінили традиційні підходи до масштабування контролю версій на основі Git. Один координує оновлення посилань між репліками репозиторію за допомогою трифазної фіксації (3PC — протоколу узгодження фіксації між кількома вузлами). Інший упорядковує оновлення через спільний журнал попереднього запису (WAL — журнал змін, які записуються до їх застосування) у блоб-сховищі.
У змодельованій нами архітектурі трифазної фіксації сервіс Git зберігає повну копію репозиторію на кількох репліках. Коли будь-який сервер отримує push, вхідний пак-файл копіюється й паралельно надсилається всім реплікам. Щоб опублікувати push, більшість реплік має прийняти оновлення посилання. Сервер, який керує цим процесом, є координатором. Процес потребує кількох раундів обміну повідомленнями між ним і кожною іншою реплікою.
Щоб прийняти оновлення посилання, кожна репліка блокує свою копію посилання й перевіряє, чи воно досі вказує на коміт, якого очікує клієнт. Потім координатор надсилає повідомлення про попередню фіксацію реплікам, які прийняли оновлення. Коли більшість підтверджує це повідомлення, координатор наказує їм виконати оновлення. Після цього будь-яка актуальна репліка може оголошувати нову вершину гілки зі своєї локальної копії репозиторію.
Зберігання повних реплік дає змогу обслуговувати читання локально й гарантує клієнтам узгоджений стан усіх посилань незалежно від вибраної репліки. У результаті посилання оновлюються по одному, а кожен push потребує кількох повних циклів обміну повідомленнями для узгодження оновлення між репліками.
Через очікування кворуму тривалість push залежить від продуктивності дисків реплік і затримок зв’язку. Зі зростанням обсягу запису репліки мають встигати обробляти і вхідні пакети, і трафік читання.
У змодельованій нами архітектурі на основі WAL сервіс Git використовує спільний маніфест у блоб-сховищі, щоб упорядковувати оновлення всього репозиторію. Коли сервер отримує push, він завантажує вхідний пак-файл до блоб-сховища й готує оновлення посилання у своїй локальній копії репозиторію. Щоб опублікувати push, він додає до спільного маніфесту в блоб-сховищі запис, який фіксує оновлення посилання й указує на пак-файл. Потім сервер Git оновлює маніфест операцією порівняння й заміни. Вона або атомарно замінює маніфест, або відхиляє зміну. Якщо за цей час інший клієнт оновив маніфест, сервер читає нові записи, перевіряє, чи його оновлення посилання досі коректне, і повторює спробу з уже завантаженим пак-файлом.
Клієнти можуть паралельно завантажувати свої пак-файли, але навіть push до незалежних гілок потребує оновлення того самого маніфесту. Час повного циклу успішної операції порівняння й заміни обмежує частоту, з якою репозиторій може публікувати новий маніфест. Блоб-сховище з низькою затримкою прискорює цей крок. Групування дає змогу одним маніфестом оновити кілька посилань одночасно, але потребує накопичення оновлень у черзі й збільшує затримку.
Блоб-сховище може паралельно приймати пак-файли, але оновлення спільного маніфесту все одно конкурують між собою. Самого збільшення потужності блоб-сховища недостатньо, щоб усунути це вузьке місце. Кожна репліка все одно має прочитати й проіндексувати пакети з блоб-сховища, перш ніж зможе надавати їх локально. Якщо вхідні записи надходять швидше, ніж виконується ця робота, репліки відстають, а клієнти довше чекають на отримання щойно надісланих комітів.
Так ми підійшли до Pando. Pando — це нова архітектура обслуговування репозиторіїв Git, створена для масштабування на велику кількість клієнтів, які одночасно виконують push. Ось як вона працює:
Ще до розробки Pando ми припускали, що зможемо задовольнити свої вимоги, не відмовляючись від Git. Однак довелося поступитися деякими умовами, які більшість систем Git вважають обов’язковими:
Узгодженість усього репозиторію: щоб надати узгоджений знімок усіх посилань на певний момент часу, потрібна координація в межах усього репозиторію. У системі з єдиним порядком усіх оновлень посилань операції push до незалежних гілок конкурують за одну точку координації.
Унікальні пак-файли для кожного клієнта: якщо два клієнти з різною локальною історією отримують ту саму гілку, серверу може знадобитися окремий пакет для кожного, щоб не надсилати вже наявні в них об’єкти. Формування цих відповідей потребує ресурсів процесора й дискового вводу-виводу, що ускладнює масштабування для великої кількості клієнтів, які читають дані.
Натомість Pando використовує послаблену модель узгодженості — причинну узгодженість — для масштабування запису та повторно використовує пак-файли для масштабування читання.
Коли клієнт виконує push до гілки, Pando зберігає вхідний пак-файл як незмінний блоб у блоб-сховищі. Щоразу, коли гілка змінюється, Pando записує у сховище метаданих версію посилання, що містить нову вершину гілки, пакет, який її надав, і всі попередні версії посилань, від яких залежить цей пакет. І блоб-сховище, і сховище метаданих підтримують горизонтальне масштабування.
Щоб отримати коміт або посилання з Pando, клієнт має відтворити набір версій посилань (пак-файлів), відновивши стан клієнта, який створив запитану версію посилання. Це забезпечує слабшу узгодженість усього репозиторію, водночас гарантуючи серіалізовність оновлень окремих посилань. Клієнти конфліктують лише тоді, коли одночасно виконують push до одного посилання. Клієнт, який читає кілька гілок, не може вважати, що ці дані відображають стан репозиторію на один і той самий момент часу.
Операція push до основної гілки додає інкрементний пакет з одним або кількома комітами. Кожна операція push до бічної гілки знову включає її попередні коміти, тож клієнт з оголошеною версією основної гілки може отримати бічну гілку одним пакетом.
Це одна з форм причинної узгодженості. Щоб отримати посилання, клієнт, який читає дані, бачить історію клієнта, який його створив. Під час push Pando контролює, яку спільну історію узгоджують клієнт і сервер, завдяки чому клієнти створюють пак-файли, які легше використовувати повторно.
Тому під час операцій Git Pando не надає стан усіх посилань на певний момент часу й не гарантує єдиного порядку їхніх оновлень у межах clone або fetch. Наприклад, припустімо, що гілки foo та bar оновлюються під час узгодження повного клонування. Під час узгодження один клієнт може побачити foo у версії 1, а bar — у версії 2, тоді як інший побачить foo у версії 2, а bar — у версії 1. На практиці користувачі й агенти не залежать від глобального порядку оновлень посилань, а покладаються на порядок оновлень окремого посилання.
У цій симуляції трьох моделей узгодженості ми налаштовуємо 1 000 клієнтів: кожен вибирає окрему гілку й багаторазово надсилає унікальні коміти. Ми вимірюємо загальну тривалість за реальним часом і затримку від push до fetch. Останній показник важливий для робочих навантажень, адже вимірює час повного циклу взаємодії користувачів і агентів під час співпраці або роботи з іншими системами, які залежать від контролю версій. Симуляція моделює обладнання різного класу — від стандартних до високопродуктивних віртуальних машин і блоб-сховищ. У цій симуляції ми послабили вимогу узгодженості всього репозиторію для трифазної фіксації.
Simulating Git Server Implementations
Follow each writer through repeated pushes, and separately measure when an independent reader can check out each commit.
Запустіть цю симуляцію, щоб порівняти Pando з двома іншими підходами. Виберіть інфраструктуру — від стандартного до високопродуктивного обладнання, кількість клієнтів та інші параметри, щоб побачити різницю.
У змодельованій системі трифазної фіксації затримка запису під навантаженням суттєво зростає через вичерпання пропускної здатності дисків. Водночас у системах із журналом попереднього запису з тієї ж причини значно зростає затримка читання: сервери не встигають читати журнал. Як у реальних тестах на нашому монорепозиторії, так і в цій симуляції Pando підтримує багато тисяч клієнтів, які можуть одночасно виконувати push без погіршення продуктивності під навантаженням.
Обробляючи push, Pando перевіряє вхідний пак-файл під час потокового передавання до блоб-сховища, не розпаковуючи його в локальний репозиторій. Коли клієнт починає fetch, Pando вибирає потрібні йому збережені пакети, і клієнт їх завантажує. Жодному серверу не потрібно застосовувати пак-файли до локального репозиторію чи виконувати ресурсомісткі операції розпакування й пакування. Оскільки сервери Pando переважно виконують узгодження та вибирають пак-файли, вони можуть працювати на стандартному обладнанні.
Завдяки цьому Pando може масштабуватися до меж можливостей клієнтів, мережі між клієнтами й Pando та сервісу блоб-сховища. За достатньої потужності системи кожен клієнт, який працює з незалежною гілкою, може передавати пакети з максимальною швидкістю свого з’єднання. Щоб обслуговувати більше клієнтів із такою швидкістю, ми можемо додавати сервери Pando й одночасно масштабувати сховище метаданих і блоб-сховище.
Pushing and Fetching a Branch in Pando
Pando stores the writer’s packfile in blob storage and records the ref version in metadata service. On fetch, Pando selects the packs the reader needs, and Express serves them.
Агент редагує файли, створює коміт локально й запускає git push. Ця команда формує пак-файл зі змінами для оновлення гілки. Pando отримує пакет і потоково передає його до блоб-сховища. Перевіривши вхідний push декодуванням за один прохід, Pando виконує транзакцію з кількома ключами, щоб записати версію посилання до сховища метаданих і атомарно оновити вказівник посилання. Завдяки цьому Pando гарантує узгодженість типу «читання власних записів». Уся ця операція запису потребує операцій читання та запису метаданих.
Коли інший клієнт отримує гілку, Pando вибирає набір збережених пакетів, враховуючи вже наявну в клієнта історію. Ця операція потребує читань метаданих, де — найкоротша відстань у версіях посилань між тим, що клієнт має, і тим, що хоче отримати. Сервер повертає URI пак-файлів, а клієнт завантажує їх через сервіс Express, який зберігає пак-файли в регіональних кешах.
Ця схема спирається на три основні сервіси архітектури Pando: надсилання, отримання та ущільнення.
Коли клієнт виконує push, звичайний сервер Git поводиться як гравець у покер, що розкриває всі карти: повідомляє клієнту про кожну свою гілку. Потім клієнт збирає коміти, дерева й блоби, щоб створити пак-файл, який не містить об’єктів, уже наявних на сервері за його відомостями.
Оголошення більшої кількості посилань може зменшити обсяг push. Водночас це може ускладнити повторне використання пакета, адже іншому клієнту може бракувати об’єктів, які відправник не включив.
Pando використовує інший підхід. Замість того щоб розкривати всі карти, вона оголошує клієнту лише вибраний набір гілок. На це є дві причини:
Надавати узгоджений стан усіх посилань — ресурсомістке завдання. За наявності мільйонів гілок лише оголошення посилань може перевищувати 100 МіБ. Для цього кожен сервер має або підтримувати актуальну таблицю відповідностей зі значними витратами на координацію, або виконувати запит до бази даних, вартість якого зростає з кількістю посилань.
Якщо надати клієнту лише частину посилань, він створить пакет, що залежатиме тільки від об’єктів, які сервер явно дозволив не включати. Відстеження залежностей дає Pando змогу повторно використовувати пак-файл, створений клієнтом під час одного push, і передавати його у відповідь на подальший fetch.
Що більше посилань оголошує сервер Git, то більше джерел мають клієнти для визначення об’єктів, які можна не надсилати під час push, і то меншими можуть бути пакети. Однак для повторного використання цих пак-файлів сервер має бути здатним підтвердити, що клієнти, які їх отримують, мають усі залежності. Інакше в пак-файлі може не бути потрібних клієнту об’єктів.
З іншого боку, якщо сервер Git не оголошує жодного посилання, клієнт створить пак-файл із повною історією репозиторію. Такий пак-файл може використати будь-який інший клієнт, але ціна висока: отримання будь-якої гілки фактично перетворюється на повне клонування.
Оголошення більшої кількості посилань зменшує пак-файли. Більшої частини скорочення розміру можна досягти, оголосивши лише одне посилання.
За замовчуванням, коли клієнт надсилає нову функціональну гілку, Pando оголошує як основу лише визначену основну гілку. Завдяки цьому розмір оголошення не змінюється зі зростанням кількості гілок у репозиторії, але такий підхід має свої компроміси. Коли інженер багаторазово виконує push до однієї гілки або створює одну функціональну гілку поверх іншої, клієнт мусить повторно надсилати все, чого немає в основній гілці.
Тому послідовні операції push до гілки збільшуються в обсязі, можуть містити надлишкові об’єкти й додатково навантажують мережевий канал клієнта. У нашому монорепозиторії медіанний розмір створеного так пак-файлу був меншим за 20 КіБ, а 99-й процентиль — меншим за 3 МіБ. Для нас це був розумний компроміс, адже клієнт, який уже має оголошену історію основної гілки, може отримати гілку одним цим пак-файлом.
Вибравши набір посилань для оголошення клієнту, Pando приймає push і оновлює сховище метаданих. Pando ділить оновлення метаданих на синхронну й асинхронну частини. Синхронне оновлення метаданих гарантує, що клієнти зможуть негайно отримати щойно оновлений коміт. При цьому записується нова версія посилання, а його вказівник оновлюється на цю версію. Зберігаються нова вершина, залежності, використані під час узгодження пак-файлу, і посилання на надісланий пак-файл.
Водночас у тій самій транзакції записуються асинхронні завдання, зокрема планування ущільнення. Для дуже великих операцій push синхронно записується лише частина комітів. Завдяки цьому кількість операцій читання та запису в сховищі метаданих залишається сталою незалежно від розміру push.
Важливо, що сервери Pando можуть незалежно приймати push до не пов’язаних між собою посилань. Єдиний ключ, за який виникає конкуренція, — це вказівник посилання. Ми захищаємо його атомарною операцією, подібною до порівняння й заміни. Ми можемо збільшувати пропускну здатність запису, додаючи сервери Pando, одиниці масштабування сховища метаданих або блоб-сховища.
Під час fetch клієнт надає серверу два набори ідентифікаторів комітів, відомі в протоколі як have та want. Звичайний сервер Git обчислює різницю між множиною об’єктів, досяжних із потрібних комітів, і множиною об’єктів, досяжних із наявних, стискає отримані об’єкти та надсилає результат клієнту. Хоча сервер Git із відкритим кодом має різноманітні локальні індекси для прискорення цієї операції, формування таких пак-файлів може бути ресурсомістким, і вони зазвичай погано піддаються кешуванню.
Натомість Pando використовує відповідності комітів, записані під час push, щоб знайти клієнтські have та want і визначити набір версій посилань, які містять ці коміти. Кожна з цих версій посилань пов’язана з пак-файлом, який Pando отримала раніше. На цій основі сервер визначає потреби клієнта й вибирає пак-файли, що охоплюють усі потрібні об’єкти.
Planning fetches in Pando
Pando compares what a client has with what it wants, then selects the stored packfiles needed—including any missing branch dependencies.
Fetch 27 trunk packs.
Select a branch to include its trunk dependencies.
Pando зіставляє наявні й потрібні коміти з протоколу Git із послідовністю версій посилань. Запит бічної гілки передбачає отримання історії аж до її залежностей від основної гілки.
Протокол Git дозволяє надсилати лише один пак-файл у відповіді HTTP на clone або fetch. Однак недооцінена можливість протоколу Git — packfile-uri — дає серверу змогу повертати посилання для окремого завантаження додаткових пакетів. Pando використовує виключно цей спосіб, а безпосередньо у відповіді передає порожній пак-файл. Для підтримки цієї можливості OpenAI внесла низку вдосконалень packfile-uri до основного проєкту.
Системи OpenAI працюють у багатьох географічних регіонах, тому в кожному з них ми підтримуємо кластери Pando з локальними кешами. Сервіс доставлення пакетів Express кешує дані нашого блоб-сховища, а окремий сервіс — дані сховища метаданих. Завдяки цим кешам Pando може обслуговувати запити за ідентифікатором коміту або спеціальні запити читання посилань повністю з кешованого стану відповідного кластера. Ми використовуємо узгоджене хешування для маршрутизації запитів пак-файлів до серверів Express, досягаючи частки влучань у кеш понад 99%.
Ця система кешів забезпечує надзвичайно високу пропускну здатність і може повністю використовувати мережевий канал між клієнтами та Pando. Ми перевірили: з URI пак-файлів і локальними кешами операції clone та fetch повністю завантажують канали 10 Гбіт/с навіть на звичайних віртуальних машинах хмарних провайдерів.
Для більшості серверів Git перепакування та збирання сміття необхідні, щоб підтримувати продуктивність репозиторію, який зростає. Обидві операції звільняють місце, але інтенсивно використовують обчислювальні ресурси та ввід-вивід.
У репозиторії, що швидко змінюється, як-от монорепозиторій OpenAI, недоцільно надсилати окремий URI пак-файлу для кожного push, виконаного після останнього fetch. Щодня клієнту довелося б завантажувати десятки тисяч пак-файлів, щоб оновити основну гілку.
Натомість Pando об’єднує пакети у фоновому режимі, зменшуючи ці накладні витрати. Схема подібна до ущільнення в журнально-структурованих деревах злиття (LSM-деревах). Кожен push створює пак-файл, який у Pando називається «L0» і є найменшою одиницею даних, яку вона може передати. З часом фонові обробники збирають ці пак-файли в покриття, що охоплюють послідовності версій посилань і зростають у геометричній прогресії від L0. Сьогодні наш монорепозиторій використовує покриття пак-файлів до рівня L4.
Ці покриття виконують дві функції. Коли покриття достатньо заповнюється, обробник ущільнення бере його в роботу й створює один пак-файл. Це підвищує ступінь стиснення завдяки реорганізації об’єктів усередині пак-файлу. Водночас це прискорює узгодження fetch, працюючи як список із пропусками для версій посилань. Одночасно скануючи індекси на кожному рівні, Pando може вибрати набір пакетів для клієнта за логарифмічний час. Ці покриття пак-файлів, перепаковані чи ні, дають змогу ефективніше планувати fetch, послаблюючи вимоги до пропускної здатності й затримки Pando.
Compaction in Pando
Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.
Ущільнення об’єднує пак-файли багатьох версій посилань у фоновому режимі, завдяки чому операції fetch стають ефективнішими й потребують менше файлів.
На відміну від багатьох серверів Git, Pando зберігає пак-файли на кожному рівні, не видаляючи їх і не прибираючи під час збирання сміття. Пакети всіх рівнів постійно використовуються для обслуговування клієнтів відповідно до have та want, указаних у запиті fetch. А для клієнтів, які використовують Pando як API, читання довільного об’єкта з пак-файлу зазвичай потребує менше даних із L0, ніж із L4, оскільки ймовірність його стиснення нижча.
Ущільнення в Pando значно скорочує загальний обсяг даних, що передаються клієнтам, а складність планування пакетів зростає сублінійно відносно того, наскільки відстав клієнт. Це особливо важливо для великих репозиторіїв, що швидко змінюються й, на нашу думку, стануть новою нормою розробки програмного забезпечення.
Ми створили Pando за допомогою Codex, пройшовши шлях від старту проєкту до запуску в робочому середовищі всього за 10 тижнів. Основу Pando спроєктував один інженер, реалізували команди агентів, а розгорнули кілька інженерів. Pando почала обслуговувати робочий трафік у квітні цього року й стала частиною ширшої системи інструментів контролю версій, які використовує OpenAI.
Перевіряючи архітектуру Pando, ми застосували багато різних підходів. Ми застосували свої принципи створення безпечнішої програмної основи. Оскільки проєкт створювався з нуля, ми вирішили реалізувати Pando на Rust і без застарілих залежностей. Ми написали набір тестів відповідності, щоб агенти могли перевіряти свої зміни на узгодженість зі стандартною поведінкою Git. Ми також розробили системи harness для детермінованого симуляційного тестування, щоб перевіряти інваріанти й властивості системи. Це дало змогу знаходити й усувати приховані стани гонитви та регресії в узгодженні й плануванні пакетів.
До створення цієї системи долучилося багато моделей. Коли з’являлися нові, ми використовували їх для підвищення продуктивності й надійності Pando. Використовуючи GPT‑6 Astra, агенти писали код на Lean і TLA+, щоб формалізувати «теорію Pando». За її допомогою ми знаходили й виправляли помилки в реалізації та доводили властивості системи. Кілька безперервних сеансів тривали сотні годин: у них виявляли та виправляли проблеми з крайовими випадками в Pando. Ми також цілодобово запускаємо агентів для змагального тестування Git і Pando, використовуючи моделі з можливостями Daybreak.
Спочатку ми розгорнули Pando як проксі для наскрізного читання й запису перед наявною системою контролю версій. Це дало змогу підтримувати синхронність Pando й попередньої системи та безпечно переводити трафік на Pando без простоїв.
Механізм кешування Pando також дає змогу усувати дублікати й об’єднувати запити перед їх пересиланням. Завдяки цьому наявні інструменти працювали стабільно, поки ми по черзі переводили системи на Pando. Що більше навантажень ми переносили на Pando, то більше їхні операції push наповнювали кеш Pando й підвищували надійність попередньої серверної частини.
Щоб оптимізувати продуктивність роботи з Pando, ми почали постачати наш публічний дистрибутив Git на комп’ютери розробників і в складі настільного застосунку ChatGPT. Для поетапного переведення клієнтів на Pando ми використали внутрішню обгортку над інтерфейсом командного рядка Git. Ми створили багато інших інструментів для обробки запитів на зміни й перевірки коду, а також API для автоматизації. Разом із трафіком Git ми перенесли сотні інтеграцій, які залежали від застарілих API, вебхуків і завантаження репозиторіїв.
Сам Codex допомагав розробникам і командам здійснити перехід. Ми створили навички для підготовки наявних робочих копій і спрямування агентів на використання нової системи. У результаті розробники могли просто попросити Codex перенести їхні середовища розробки й сервіси.
Перехід на Pando підвищив надійність злиття та скоротив медіанну затримку push майже на 80%.
Pando може передавати петабайт даних Git на день, використовуючи лише кілька процесорних ядер. За одну годину Pando передала понад 57 терабайтів пакетів Git, причому понад 99% цих байтів — із локального дискового кешу.
Повторне використання пакетів дає багатьом клієнтам змогу завантажувати ту саму історію репозиторію з мінімальними обчисленнями на обслуговувальних вузлах. Цей приклад стосується одного репозиторію, але Pando розрахована на підтримку значно більшої їх кількості. Зі зростанням попиту ми можемо незалежно масштабувати доставлення даних і узгодження Git.
Створення Pando на основі протоколу й клієнта Git допомогло нам виявити помилки та можливості покращення продуктивності самого Git.
Наприклад, Pando передає багаторазово використовувані пакети безпосередньо клієнтам, тому їх завантаження й індексування стали ключовими для швидкості та надійності системи. Ця робота виявила крайові випадки під час одночасних завантажень, очищення після перерваного передавання та відновлення вже завершених завантажень. Ми виправили ці сценарії, додали перевірки під час приймання наявного пакета для fetch і передали ці напрацювання до основного проєкту.
Нам також потрібно було розуміти, на що витрачаються час і пропускна здатність, тому ми додали трасування завантажень packfile-uri та підрахунок байтів під час генерації пакетів. Так стало простіше визначати, що сповільнює fetch: створення пакетів чи їх передавання.
У збірці Git від OpenAI(відкривається у новому вікні) клієнт може паралельно завантажувати й індексувати незалежні пакети. Ми додали поняття «явно вказаних наявних комітів», щоб сервер міг оголошувати поточну вершину гілки окремо від історії, яку клієнт під час push може не включати до пакета. У Pando це дає клієнтам змогу за допомогою --force-with-lease перевіряти, що функціональна гілка не змінилася, і формувати її пакет лише відносно основної гілки. Тоді будь-хто з цією історією основної гілки може отримати гілку, використовуючи той самий пакет.
Кілька з цих удосконалень уже ввійшли до основного проєкту Git, а багато інших доступні в нашому публічному репозиторії. Ми продовжуємо працювати зі спільнотою Git, щоб ці вдосконалення були корисними й поза Pando. Стежити за цією роботою можна в нашому публічному трекері патчів(відкривається у новому вікні).
Переосмисливши усталені підходи до масштабування репозиторіїв Git, ми створили Pando, що дає змогу масштабувати наш монорепозиторій без зміни звичних робочих процесів інженерів і агентів у Git. У нашому монорепозиторії Pando розширила можливості паралельної роботи, підвищила надійність злиття та скоротила час публікації змін коду. Окрім монорепозиторію, ми досліджуємо й інші способи використання Pando в OpenAI, зокрема для навантажень, де велика кількість агентів має спільно працювати в одному репозиторії Git.
Що більше програмного забезпечення створюють інженери й агенти, то більше команд за межами OpenAI стикатимуться з тими обмеженнями контролю версій, яких ми вже досягли. Наразі ми шукаємо способи поділитися з користувачами технологією, завдяки якій працює Pando.
Pando спирається на два десятиліття досвіду користувачів і адміністраторів репозиторіїв Git. Ми продовжимо розвивати Pando та співпрацювати зі спільнотою відкритого коду Git, щоб ділитися вдосконаленнями, корисними для ширшого кола користувачів Git.


