OpenAI

2 ноември 2026 г.

Инженерство

Мащабиране на Git в OpenAI

Изграждане на контрол на версиите за мащаба на агентната работа и инвестиция в екосистемата на Git.

От Арън Фрил, Тейлър Блау и Тед Найман, членове на техническия екип

Зареждане…

През изминалата година размерът на нашето монохранилище и изискванията към него нараснаха с изумителни темпове. Беше ясно, че трябва да обновим системата си за контрол на версиите, за да поеме мащаба на паралелната работа, която Codex прави възможна.

Създадохме Pando — нова сървърна система за Git хранилища — без да прекъсваме инженерната работа или да променяме начина, по който екипът ни използва Git. Днес цялата разработка в монохранилището на OpenAI се осъществява чрез Pando. Pando е проектиран с две амбициозни цели: първо, хоризонтално мащабируеми записи, оптимизирани за едновременни операции push към много клонове. И второ, максимална производителност при четене, така че клиентите да изтеглят данни достатъчно бързо, за да използват изцяло наличната пропускателна способност на мрежата си.

Постигнахме тези цели, като запазихме съвместимостта с протокола и клиента на Git. Така инженерите и агентите ни можаха безпроблемно да включат Pando в съществуващите си работни процеси. В продукционна среда Pando намали медианната латентност на push почти 5 пъти. Сега системата предоставя почти петабайт Git данни на ден.

Тук ще разгледаме причините да изградим нова система за контрол на версиите и защо решихме да запазим съвместимостта с Git. Ще сравним системата си с обичайните подходи за мащабиране на Git и ще обясним защо Pando има уникалната способност да отговори на нуждите на OpenAI от мащабиране. Ще обсъдим и какво следва и как ще продължим да допринасяме към Git, за да поддържа работа в мащаба на агентите.

Защо ни беше нужна нова система за контрол на версиите

Codex промени мащаба и темпото на разработка в OpenAI, като позволи на инженерите да вършат много повече работа паралелно. Това означава повече клонове и операции push, повече сливания, както и повече операции fetch и clone от среди за разработка, изследователски задачи и системи за непрекъсната интеграция (CI).

Тези изисквания се натрупват. Вече един инженер може да има няколко промени, готови едновременно за CI, а всяка операция push може да задейства няколко задачи, които изтеглят един и същ комит.

Около края на миналата година системите, поддържащи нашето монохранилище, започнаха да не смогват на натоварването. Сблъсквахме се с бавни операции fetch и push и неуспешни сливания, като надеждността понякога падаше под 90%. Дали бяхме достигнали ограничение на самия Git, или на начина, по който обслужвахме монохранилището си? Git възникна в общност, която обменяше корекции за ядрото по имейл. Повече от две десетилетия разработчиците адаптират инструментите си около Git с промяната на начина, по който създават софтуер. Когато започнахме да срещаме затруднения в собственото си монохранилище, за нас беше важно да продължим да надграждаме тази работа.

Видяхме начин да запазим познатия работен процес, като преизградим сървърната система, за да обработва паралелно операции push към различни клонове и да предоставя една и съща история на много четящи клиенти едновременно. Използването на Git като основа на работния ни процес за контрол на версиите означаваше, че подобренията в клиента могат да бъдат полезни и извън OpenAI. Искахме да предложим тези подобрения от нашето копие на Git⁠(отваря се в нов прозорец) за включване в основния проект и така да ги споделим с по-широката общност.

Инженерни решения за Git в голям мащаб

Мащабирането на система за контрол на версиите има две основни измерения: много записващи клиенти, които изпращат промени към различни клонове, и много четящи клиенти, които изтеглят една и съща история. Pando е оптимизиран и за двете.

Първо, Pando координира актуализациите на всеки клон независимо. Актуализациите на несвързани клонове могат да се извършват паралелно, а клиентът, който изтегля клон, получава историята, необходима за съответната ревизия. Компромисът е, че клиент, който разглежда няколко клона, може да ги вижда в състояния от различни моменти.

Второ, Pando използва повторно пакетните файлове (формата на Git за съвместно компресиране на множество обекти) при различни операции fetch, вместо да генерира отделен пакетен файл за всеки клиент. Клиентът може да изтегли някои обекти, които вече има, но сървърът може да предостави същите байтове на много клиенти с малко изчисления.

Тези решения ни позволяват да мащабираме записите между клоновете и да обслужваме четенията от кешове. За да видим как се съчетават, нека започнем с начина, по който Git съхранява и прехвърля историята.

Основи на съхранението на обекти в Git

Git трябва да може да поддържа в синхрон две части на хранилището: обектите и референциите.

Git хранилището записва моментни снимки на файловете и свързващата ги история като граф от обекти. Обектът може да бъде блоб, дърво, комит или етикет. Блобовете съдържат съдържанието на отделен файл, а дърветата свързват имена с блобове или други дървета, за да образуват структура от директории. Комитите сочат към коренно дърво и нула или повече родителски комити, като образуват историята на хранилището.

Обектите се идентифицират чрез хеш на техния тип и съдържание. Обектите са и неизменяеми: записването на променен файл създава нов блоб, докато непроменените файлове и директории могат да използват повторно съществуващи обекти.

Клоновете и етикетите са референции, или „refs“, които сочат към комити. Записването на промени в клон създава нов комит и актуализира референцията на клона, така че да сочи към него.

Клонът refs/heads/main сочи към комит C2, чийто родител е C1. C1 сочи към коренно дърво T1, в което parser.py се отнася към блоб B1. C2 сочи към коренно дърво T2, в което parser.py се отнася към блоб B2. И в двете коренни дървета docs/ се отнася към едно и също дърво — T3. Записът README.md в него сочи към блоб B3, а usage.md — към блоб B4.

Обектите в 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 съхранява обектите в „непакетирано“ или „пакетирано“ състояние. Непакетираните обекти се съхраняват в отделни файлове, определени от идентификатора на обекта. Пакетираните обекти се съхраняват заедно в пакетен файл, което позволява сходните обекти да се записват като разлики един спрямо друг.

Две версии на parser.py са показани в $GIT_DIR/objects. Като непакетирани обекти блоб A съдържа parse(), а блоб B съдържа както parse(), така и parse_file(). Пакетният файл съхранява A изцяло и представя B като инструкции за копиране на цялото A и добавяне на parse_file(). Съпътстващият файл *.idx определя местоположението на двата блоба в пакетния файл по идентификатор на обекта.

Git може да съхранява блобове като отделни файлове или да ги пакетира заедно, като кодира един блоб като разлика спрямо друг.

В Git един обект е достижим от комит, ако Git може да го намери, като проследи връзките от този комит през родителските му комити, дърветата и блобовете. По време на fetch клиентът съобщава на сървъра кои комити иска и кои вече има. Сървърът намира обектите, достижими от желаните комити, изключва тези, за които знае, че клиентът вече има, и изпраща останалите като пакетен файл в същия формат, който Git използва на диска. Пълното клониране започва без никакви обекти, затова са му нужни всички обекти, достижими от клонираната история.

Операцията push прехвърля обекти в обратната посока, като иска от сървъра да актуализира една или повече референции. Клиентът използва набора от обявени от сървъра референции, за да избегне изпращането на обекти, с които сървърът вече разполага. Затова изпращаният пакет може да зависи от съществуващи обекти: получаването само на пакета не е достатъчно за възстановяване на новата история.

Репликирането на Git хранилище означава да се поддържат достъпни както обектите, така и референциите на множество сървъри. Преди реплика да може да предостави новата стойност на референция, тя трябва да може да предостави обектите, достижими от тази стойност. Услугата трябва да координира и конкуриращи се актуализации на референции — тоест операции push, които се опитват да преместят един и същ клон от един и същ стар връх към различни нови комити. Системите, които налагат единен ред на всички записи в хранилището, могат да принудят операциите push към несвързани клонове да се изчакват взаимно.

Съществуващи подходи за разпределени Git услуги

При проектирането на Pando оценихме обичайните подходи за мащабиране на контрола на версиите с Git. Единият координира актуализациите на референции между реплики на хранилището чрез трифазно потвърждаване (3PC — протокол за координиране на потвърждаването между множество възли). Другият подрежда актуализациите чрез споделен журнал за предварителен запис (WAL — запис на промените, преди да бъдат приложени) в блоб хранилище.

Трифазно потвърждаване (3PC)

В моделирания от нас дизайн с трифазно потвърждаване Git услугата поддържа пълно копие на хранилището в няколко реплики. Когато който и да е сървър получи push, входящият пакетен файл се копира и изпраща паралелно до всички реплики. За да се публикува изпратеното чрез push, мнозинството от репликите трябва да приеме актуализацията на референцията. Сървърът, който управлява този процес, е координаторът. Процесът изисква няколко кръга на комуникация между него и всяка друга реплика.

За да приеме актуализация на референция, всяка реплика заключва своето копие на референцията и проверява дали тя все още сочи към комита, който клиентът очаква. След това координаторът изпраща съобщение за предварително потвърждаване до репликите, които приемат актуализацията. Щом мнозинството потвърди получаването на това съобщение, координаторът им указва да извършат актуализацията. След това всяка актуална реплика може да обявява новия връх на клона от локалното си копие на хранилището.

Поддържането на пълни реплики позволява четенията да се обслужват локално и гарантира, че клиентите получават съгласуван изглед на всички референции, независимо коя реплика изберат. В резултат референциите се актуализират една по една, а всяка операция push изисква няколко двупосочни обмена, за да се координира актуализацията между репликите.

Изчакването на кворум прави времето за push зависимо от дисковата производителност на репликите и латентността на комуникацията. С нарастването на обема на записите репликите трябва да смогват както с входящите пакети, така и с трафика за четене.

Журнал за предварителен запис (WAL)

В моделирания от нас дизайн, базиран на WAL, Git услугата използва споделен манифест в блоб хранилище, за да подрежда актуализациите в цялото хранилище. Когато сървър получи push, той качва входящия пакетен файл в блоб хранилище и подготвя актуализацията на референцията в локалното си копие на хранилището. За да публикува изпратеното чрез push, той добавя запис в споделения манифест в блоб хранилището. Записът описва актуализацията на референцията и сочи към пакетния файл. След това Git сървърът актуализира манифеста чрез операция „сравни и замени“. Тя или заменя атомарно манифеста, или отхвърля промяната. Ако друг записващ клиент го е актуализирал междувременно, сървърът прочита междинните записи, проверява дали неговата актуализация на референцията все още е валидна и опитва отново с вече качения пакетен файл.

Записващите клиенти могат да качват пакетните си файлове паралелно, но дори операциите push към несвързани клонове изискват актуализация на един и същ манифест. Времето за пълен цикъл на успешна операция „сравни и замени“ ограничава честотата, с която хранилището може да публикува нов манифест. Блоб хранилище с ниска латентност ускорява тази стъпка. Групирането позволява един манифест да актуализира няколко референции едновременно, но за сметка на изчакване на актуализациите в опашка и по-висока латентност.

Блоб хранилището може да получава пакетни файлове паралелно, но актуализациите на споделения манифест продължават да се конкурират помежду си. Само увеличаването на капацитета на блоб хранилището не премахва това тясно място. Всяка реплика все пак трябва да прочете и индексира пакетите от блоб хранилището, преди да може да ги предоставя локално. Ако входящите записи изпреварват тази работа, репликите изостават и клиентите чакат по-дълго, за да изтеглят новоизпратените комити.

Архитектурата на Pando

Това ни отвежда до Pando. Pando е нова архитектура за обслужване на Git хранилища, създадена да се мащабира за много клиенти, които изпълняват push едновременно. Ето как работи:

Преосмисляне на ограниченията

Преди да разработим Pando, предполагахме, че можем да изпълним изискванията си, без да правим компромис с Git. Но трябваше да направим компромис с някои неща, които повечето Git системи приемат за задължителни:

  • Съгласуваност в цялото хранилище: За да се осигури съгласувана моментна снимка на всички референции, е нужна координация в цялото хранилище. В система, която подрежда всички актуализации на референции в обща последователност, операциите push към несвързани клонове се конкурират за една и съща точка на координация.

  • Уникални пакетни файлове за всеки клиент: Ако два клиента с различна локална история изтеглят един и същ клон, сървърът може да трябва да генерира различен пакет за всеки от тях, за да не изпраща обекти, които вече имат. Създаването на тези отговори изисква процесорно време и дискови входно-изходни операции, което затруднява мащабирането при голям брой четящи клиенти.

Вместо това Pando използва модел с по-слаба съгласуваност (причинна съгласуваност), за да мащабира записите, и използва повторно пакетните файлове, за да мащабира четенията.

Причинна съгласуваност (CC)

Когато клиент изпълни push към клон, Pando съхранява входящия пакетен файл като неизменяем блоб в блоб хранилище. При всяко преместване на върха на клона Pando записва в хранилище за метаданни версия на референцията, съдържаща новия връх, пакета, който го е предоставил, и всички предишни версии на референции, от които пакетът зависи. И блоб хранилището, и хранилището за метаданни могат да се мащабират хоризонтално.

Изтеглянето на комит или референция от Pando изисква към клиента да се възпроизведе набор от версии на референции (пакетни файлове), за да се възстанови състоянието на клиента, създал изтегляната версия. Това осигурява по-слаба форма на съгласуваност в цялото хранилище, но гарантира сериализируемост на актуализациите на отделните референции. Конфликт между клиенти възниква само когато изпълняват push към една и съща референция едновременно. Клиент, който чете няколко клона, не може да приеме, че тези четения отразяват един и същ момент за цялото хранилище.

Вляво три последователни пакета на основния клон съдържат комитите A, B и C; D и E; и F. Вдясно основният и тематичният клон се разделят при C. Основният клон напредва до G, а тематичният — през D, E и F. Последният пакет на тематичния клон съдържа D, E и F. Пунктиран контур показва, че D и E присъстват и в предишния пакет на тематичния клон. Синята стрелка показва зависимостта на новия пакет от основния клон при G. Родителят на F в Git е E.

Операцията push към основния клон добавя инкрементален пакет с един или повече комити. Всяка операция push към страничен клон включва отново предишните му комити, така че клиент с обявената версия на основния клон да може да изтегли страничния клон с един пакет.

Това е форма на причинна съгласуваност. За да изтегли референция, четящият клиент вижда историята на записващия клиент, който я е създал. По време на push Pando контролира каква обща история договарят клиентът и сървърът, като гарантира, че клиентите създават пакетни файлове, които могат по-лесно да се споделят.

Затова Pando не предоставя състояние на всички референции към един и същ момент по време на Git операции, нито общ ред на актуализациите на референции в рамките на clone или fetch. Да предположим например, че клоновете foo и bar се актуализират по време на договарянето на пълно клониране. По време на договарянето един клиент може да вижда foo във версия 1 и bar във версия 2, а друг — foo във версия 2 и bar във версия 1. На практика потребителите и агентите не зависят от общия ред на актуализациите на всички референции, а от реда на актуализациите на отделна референция.

Сравнение на подходите

В тази симулация на трите модела на съгласуваност настройваме 1000 клиента, всеки от които избира отделен клон и многократно изпраща уникални комити чрез push. Измерваме общото изминало време и латентността от push до fetch. Вторият показател е важен за работните натоварвания, защото измерва времето за пълен цикъл, когато потребители и агенти си сътрудничат или използват други системи, които разчитат на контрол на версиите. Симулацията моделира инфраструктура от стандартен до висок клас — виртуални машини и системи за блоб съхранение. В тази симулация сме отслабили изискванията за съгласуваност в цялото хранилище при трифазното потвърждаване.

Симулиране на реализации на Git сървъри

Проследете всеки записващ клиент през поредица от изпращания и измерете отделно кога независим четящ клиент може да извлече работно копие на всеки комит.

Трифазно потвърждаване

Готово
Симулирано време
0 s
Средно време от изпращане до изтегляне
—
Приключили записващи клиенти
0 / 1,000

Всеки квадрат представлява един записващ клиент. Всички избрани архитектури изпълняват едно и също работно натоварване. Квадратите изсветляват с потвърждаването на изпращанията и стават сини, когато записващият клиент приключи. Графиката показва броя на приключилите записващи клиенти с напредването на симулираното време; независимите четящи клиенти могат да продължат да изтеглят и след това. Използвайте клавишите със стрелки, за да изберете записващ клиент, Home или End, за да отидете в началото или края, и Escape, за да изчистите избора.

0 / 10,000 потвърдени изпращания · 0 изтеглено

Изберете квадрат, за да разгледате съответния записващ клиент.

WAL в обектно хранилище

Готово
Симулирано време
0 s
Средно време от изпращане до изтегляне
—
Приключили записващи клиенти
0 / 1,000

Всеки квадрат представлява един записващ клиент. Всички избрани архитектури изпълняват едно и също работно натоварване. Квадратите изсветляват с потвърждаването на изпращанията и стават сини, когато записващият клиент приключи. Графиката показва броя на приключилите записващи клиенти с напредването на симулираното време; независимите четящи клиенти могат да продължат да изтеглят и след това. Използвайте клавишите със стрелки, за да изберете записващ клиент, Home или End, за да отидете в началото или края, и Escape, за да изчистите избора.

0 / 10,000 потвърдени изпращания · 0 изтеглено

Изберете квадрат, за да разгледате съответния записващ клиент.

Pando

Готово
Симулирано време
0 s
Средно време от изпращане до изтегляне
—
Приключили записващи клиенти
0 / 1,000

Всеки квадрат представлява един записващ клиент. Всички избрани архитектури изпълняват едно и също работно натоварване. Квадратите изсветляват с потвърждаването на изпращанията и стават сини, когато записващият клиент приключи. Графиката показва броя на приключилите записващи клиенти с напредването на симулираното време; независимите четящи клиенти могат да продължат да изтеглят и след това. Използвайте клавишите със стрелки, за да изберете записващ клиент, Home или End, за да отидете в началото или края, и Escape, за да изчистите избора.

0 / 10,000 потвърдени изпращания · 0 изтеглено

Изберете квадрат, за да разгледате съответния записващ клиент.

Напредък на записващите клиенти до проверка на изтеглянията

  • Трифазно потвърждаване
  • WAL в обектно хранилище
  • Pando
05001,00000 s
  • Изчакване
  • Изпращане
  • Всички изпращания са потвърдени
0 sПо-късно

Синтетични учебни модели, а не тестове за производителност в реална среда. Средното време от изпращане до изтегляне се измерва за изпращанията към тематични клонове — от първия опит, през повторните опити, до проверката на съответния комит от независим четящ клиент. То включва изчакването за свободен слот за четящ клиент, но изключва компилирането и тестовете. Когато сливанията са включени, времето за приключване на записващия клиент включва необходимите изтегляния и сливането в основния клон.

Настройки на експеримента

Подготовка на симулацията…

Резултати

Пуснете сравнението, за да видите записаните резултати.

Синтетични учебни модели, а не тестове за производителност в реална среда. Средното време от изпращане до изтегляне се измерва за изпращанията към тематични клонове — от първия опит, през повторните опити, до проверката на съответния комит от независим четящ клиент. То включва изчакването за свободен слот за четящ клиент, но изключва компилирането и тестовете. Когато сливанията са включени, времето за приключване на записващия клиент включва необходимите изтегляния и сливането в основния клон.

Стартирайте тази симулация, за да сравните Pando с два други подхода. Изберете инфраструктура от стандартен до висок клас, брой клиенти и други настройки, за да видите как се представят различните подходи.

Моделираната система с трифазно потвърждаване показва значително по-висока латентност при запис под натоварване, когато дисковете достигнат капацитета си. По същата причина системите с журнал за предварителен запис показват значително по-висока латентност при четене, тъй като сървърите не смогват да четат от журнала. Както в реални тестове с нашето монохранилище, така и в тази симулация Pando поддържа много хиляди клиенти, които могат да изпълняват push едновременно, без да влошават производителността под натоварване.

При обработката на push Pando валидира входящия пакетен файл, докато го предава поточно към блоб хранилището, без да го разопакова в локално хранилище. Когато клиент започне fetch, Pando избира нужните му съхранени пакети и клиентът ги изтегля. Нито един сървър не трябва да прилага пакетни файлове към локално хранилище или да изпълнява ресурсоемки операции по разопаковане и пакетиране. Тъй като сървърите на Pando основно извършват договаряне и подбор на пакетни файлове, те могат да работят на стандартен хардуер.

Това позволява на Pando да се мащабира до границите на възможностите на клиентите, мрежата между тях и Pando и услугата за блоб съхранение. При достатъчен капацитет всеки клиент, който работи по независим клон, може да прехвърля пакети толкова бързо, колкото позволява собствената му връзка. За да обслужваме повече клиенти с тази скорост, можем да добавяме Pando сървъри и заедно с тях да мащабираме хранилищата за метаданни и блобове.

Изпращане и изтегляне на клон в Pando

Pando съхранява пакетния файл на записващия клиент в хранилището за BLOB обекти и записва версията на референцията в услугата за метаданни. При изтегляне Pando избира пакетите, необходими на четящия клиент, а Express ги предоставя.

Поточно предаване на изпращаните данни

  • Байтове на пакетния файл
  • Метаданни
  • Заявка / потвърждение
  • URI адреси на пакетните файлове

Агент редактира файлове, създава комит локално и изпълнява git push, което създава пакетен файл с тези промени, за да актуализира клон. Pando получава пакета и го предава поточно към блоб хранилището. След като валидира входящата операция push чрез еднопроходно декодиране, Pando използва транзакция с множество ключове, за да запише версия на референцията в хранилището ни за метаданни и атомарно да актуализира указателя на референцията. Това позволява на Pando да гарантира съгласуваност от типа „четеш това, което си записал“. Цялата операция по запис изисква O(1)O(1) четения и записи на метаданни.

Когато друг клиент изтегля клона, Pando избира набор от съхранени пакети според историята, с която клиентът вече разполага. Тази операция изисква O(log⁡Δ)O(\log \Delta) четения на метаданни, където Δ\Delta е най-краткото разстояние, измерено във версии на референции, между това, което клиентът има, и това, което иска. Сървърът връща URI адреси на пакетни файлове, а клиентът ги изтегля чрез услугата Express, която съхранява пакетни файлове в регионални кешове.

Този дизайн разчита на трите основни услуги в архитектурата на Pando: push, fetch и сбиване.

Мащабиране на операциите push

Когато клиент изпълнява push, обичайният Git сървър прилича на покер играч, който показва всичките си карти — разкрива на клиента всеки клон, с който разполага. След това клиентът събира комитите, дърветата и блобовете, за да създаде пакетен файл, който изключва обектите, за които знае, че сървърът вече има.

Обявяването на повече референции може да намали обема на push. Но може и да затрудни повторното използване на пакета, защото на друг клиент може да му липсват обекти, които записващият клиент е пропуснал.

Pando използва различен подход. Вместо да показва всичките си карти, той обявява на клиента само избран набор от клонове. Причините са две:

  1. Предоставянето на съгласуван изглед на всички референции е ресурсоемко. При милиони клонове само данните за обявените референции могат да надхвърлят 100 MiB. За целта всеки сървър трябва или да поддържа актуално съответствие, което изисква значителни разходи за координация, или да изпълнява заявка към база данни, чиято сложност расте с броя на референциите.

  2. Когато на клиента се предостави само подмножество от референциите, той генерира пакет, който зависи единствено от това, което сървърът изрично му позволява да изключи. Проследяването на зависимостите позволява на Pando да използва повторно пакетния файл, генериран от клиент при push, като го предостави в отговор на последваща операция fetch.

Колкото повече референции обявява Git сървърът, толкова повече източници имат изпращащите клиенти, въз основа на които да изключват обекти и да създават по-малки пакети. Повторното използване на тези пакетни файлове обаче изисква сървърът да може да докаже, че изтеглящите клиенти разполагат с всяка от зависимостите. В противен случай в пакетния файл може да липсват обекти, нужни на клиента.

От друга страна, ако Git сървърът не обяви нито една референция, клиентът ще генерира пакетен файл с пълната история на хранилището. Този пакетен файл може да се използва от всеки друг клиент, но цената е висока: изтеглянето на който и да е клон на практика се превръща в пълно клониране.

Крива, показваща как обявените референции влияят върху размера на пакетния файл: от пълната история, когато няма обявени референции, през обявяване на основния клон, до само новите обекти, когато са обявени всички референции.

Обявяването на повече референции води до по-малки пакетни файлове. По-голямата част от намалението може да се постигне с обявяването само на една.

По подразбиране, когато клиент изпраща нов клон за функционалност, Pando обявява като основа само един определен основен клон. Това запазва размера на обявяваните данни независимо от нарастващия брой клонове в хранилището, но е свързано с компромиси. Когато инженер изпълнява push многократно към един клон или изгражда клон за функционалност върху друг такъв клон, клиентът трябва да изпраща отново всичко извън основния клон.

Това увеличава обема на последователните операции push към клона, които може да съдържат излишни обекти, и използва допълнителен мрежов ресурс на клиента. В нашето монохранилище медианният размер на създадените по този начин пакетни файлове беше под 20 KiB, а 99-ият персентил — под 3 MiB. За нас това беше разумен компромис, защото четящ клиент, който вече има обявената история на основния клон, може да изтегли клона само с този един пакетен файл.

След като избере кои референции да обяви на клиента, Pando приема операцията push и актуализира хранилището си за метаданни. Pando разделя актуализацията на метаданните на синхронна и асинхронна част. Синхронната актуализация на метаданните гарантира, че клиентите могат веднага да изтеглят току-що актуализирания комит. Тя записва нова версия на референцията и актуализира указателя ѝ към тази версия, като съхранява новия връх, зависимостите, използвани при договарянето на пакетния файл, и връзка към изпратения пакетен файл.

В същата транзакция се записват и асинхронни задачи, например планиране на сбиване. При много големи операции push само част от комитите се записват синхронно. Така е необходим само константен брой четения и записи в хранилището за метаданни, независимо от размера на push.

Важно е, че сървърите на Pando могат независимо да приемат операции push към несвързани референции. Единственият ключ, за който има конкуренция, е указателят на референцията. Защитаваме го чрез атомарна операция от типа „сравни и замени“. Можем да увеличаваме производителността при запис, като добавяме Pando сървъри, единици за мащабиране на хранилището за метаданни или блоб хранилища.

Fetch и clone с максималната скорост на мрежата

Когато клиент изпълнява fetch, той предоставя на сървъра два набора от идентификатори на комити, известни в протокола като have и want. Обичайният Git сървър изчислява разликата между множеството обекти, достижими от желаните комити, и множеството обекти, достижими от наличните комити, компресира я и изпраща резултата на клиента. Макар Git сървърът с отворен код да разполага с различни локални индекси за ускоряване на тази операция, генерирането на тези пакетни файлове може да е ресурсоемко, а кеширането им обикновено е трудно.

Вместо това Pando използва съответствията за комитите, записани при push, за да намери подадените от клиента have и want и да ги свърже с набор от версии на референции, съдържащи тези комити. Всяка от тези версии на референции сочи към пакетен файл, получен по-рано от Pando. Сървърът използва това, за да определи от какво се нуждае клиентът, и избира пакетните файлове, които покриват набора от необходимите обекти.

Планиране на изтеглянията в Pando

Pando сравнява наличното при клиента със заявеното, след което избира необходимите съхранени пакетни файлове, включително всички липсващи зависимости на клоновете.

Вече наличноИзтеглянеЛипсваща зависимост на клона

Изтегляне на 27 пакета на основния клон.

Изберете клон, за да включите зависимостите му от основния клон.

Pando съпоставя наличните и желаните комити в протокола Git с последователност от версии на референции. Заявката за страничен клон включва и историята до зависимостите му от основния клон.

Протоколът Git позволява изпращането само на един пакетен файл в HTTP отговора при clone или fetch. Една подценявана възможност на протокола Git обаче — packfile-uri — позволява на сървъра да връща връзки за отделно изтегляне на допълнителни пакети. Pando използва изцяло този механизъм и изпраща празен пакетен файл в самия отговор. За да поддържа тази възможност, OpenAI допринесе с редица подобрения на packfile-uri в основния проект.

Системите на OpenAI работят в много географски региони, затова във всеки от тях поддържаме Pando клъстери с локални кешове. Услугата за доставка на пакети Express кешира данните от блоб хранилището ни, а отделна услуга кешира хранилището за метаданни. Тези кешове позволяват на Pando да обслужва заявки за идентификатор на комит или специално четене на референция изцяло от кешираното състояние в съответния клъстер. Използваме консистентно хеширане, за да насочваме заявките за пакетни файлове към сървърите на Express, като над 99% от заявките намират данните в кеша.

Тази система от кешове осигурява изключително висока производителност, достатъчна да използва пълния мрежов капацитет между клиентите и Pando. С тези URI адреси на пакетни файлове и локални кешове потвърдихме, че дори само със стандартни виртуални машини от облачни доставчици операциите clone и fetch използват изцяло мрежови връзки със скорост 10 Gbps.

Сбиване и време до първия байт

При повечето Git сървъри повторното пакетиране и почистването на неизползвани обекти са необходими, за да се запази производителността на нарастващото хранилище. И двете операции освобождават място, но изискват много изчислителни ресурси и входно-изходни операции.

В бързо променящо се хранилище като монохранилището на OpenAI не е практично да се изпраща отделен URI адрес на пакетен файл за всяка операция push след последното изтегляне. Всеки ден клиентът би трябвало да изтегля десетки хиляди пакетни файлове, за да актуализира основния си клон.

Вместо това Pando обединява пакети във фонов режим, за да намали тези разходи, като използва дизайн, подобен на сбиването в лог-структурирани дървета със сливане (LSM дървета). Всяка операция push създава пакетен файл, който Pando нарича „L0“ — най-малката единица данни, която системата може да предава. С течение на времето фонови процеси събират тези пакетни файлове в покриващи групи, които обхващат последователност от версии на референции и нарастват геометрично спрямо L0. Днес нашето монохранилище използва покриващи групи от пакетни файлове до ниво L4.

Тези групи имат двойно предназначение. Когато една група се запълни достатъчно, процес за сбиване я поема и създава един пакетен файл. Това подобрява степента на компресия чрез пренареждане на обектите в пакетния файл. Но подобрява и производителността при договаряне на fetch, като действа като списък с прескачане върху версиите на референциите. Като обхожда едновременно индексите на всяко ниво, Pando може да избере набор от пакети за изпращане към клиента за логаритмично време. Тези покриващи групи от пакетни файлове, независимо дали са препакетирани, позволяват по-ефективно планиране на fetch и намаляват изискванията към Pando както за производителност, така и за латентност.

Сбиване в Pando

Малките пакетни файлове се групират в по-големи покрития на различни нива, което подобрява компресията и ускорява изтеглянията.

Извършва се сбиванеПубликувано покритие

Сбиването обединява във фонов режим пакетни файлове от много версии на референции, което позволява по-ефективно изтегляне с по-малко файлове.

За разлика от много Git сървъри, тук пакетните файлове се запазват на всяко ниво и не се изхвърлят или премахват при почистване на неизползвани обекти. Пакетите на всяко ниво постоянно се използват за обслужване на клиенти според подадените при fetch have и want. А за клиентите, които използват Pando като API, четенето на произволен обект от пакетен файл обикновено изисква по-малко прочетени данни от L0, отколкото от L4, защото е по-малко вероятно обектът да е компресиран.

Сбиването в Pando значително намалява общия обем данни, изпращани към клиентите, и позволява сложността на планирането на пакетите да расте сублинейно спрямо изоставането на клиента. Това е особено важно за големи и бързо променящи се хранилища, които очакваме да се превърнат в новата норма при разработката на софтуер.

Изграждането на Pando

Създадохме Pando с Codex — от началото на проекта до продукционна среда само за 10 седмици. Ядрото на Pando беше проектирано от един инженер, реализирано от екипи от агенти и внедрено от няколко инженери. Pando започна да обслужва продукционен трафик през април тази година и е част от по-голяма система от инструменти за управление на контрола на версиите, използвани в OpenAI.

Използвахме разнообразни подходи, за да валидираме дизайна на Pando. Приложихме принципите си за изграждане на по-сигурни софтуерни основи⁠. Тъй като започвахме от нулата, избрахме да реализираме Pando на Rust и без наследени зависимости. Написахме набор от тестове за съответствие, за да могат агентите да проверяват промените си спрямо стандартното поведение на Git. Разработихме и рамки за детерминистично симулационно тестване, с които да проверяваме инварианти и свойства на системата. Това ни позволи да откриваме и ограничаваме трудно забележими състезателни състояния и регресии при договарянето и планирането на пакетите.

Много модели допринесоха за тази система. С появата на нови модели ги използвахме, за да подобряваме производителността и надеждността на Pando. С GPT‑6 Astra агентите писаха код на Lean и TLA+, за да формализират „теория на Pando“, която използвахме за откриване и отстраняване на грешки в реализацията и за доказване на свойства на системата. Няколко непрекъснати сесии продължиха стотици часове, откривайки и отстранявайки проблеми в гранични случаи в Pando. Използваме и денонощно работещи агенти с модели с възможности за Daybreak, за да подлагат Git и Pando на тестове за сигурност чрез симулирани атаки.

Внедряването на Pando

Първоначално внедрихме Pando като прокси, което препраща четенията и записите към съществуващата ни система за контрол на версиите. Това ни позволи да поддържаме Pando и предишната система в синхрон, за да прехвърляме безопасно трафика към Pando без прекъсване на работата.

Като част от механизма си за кеширане Pando може също да премахва дублиращи се заявки и да ги обединява, преди да ги препрати. Така запазихме нормалната работа на съществуващите си инструменти, докато прехвърляхме системите към Pando една по една. С прехвърлянето на повече работни натоварвания към Pando техните операции push запълваха кеша му и подобряваха надеждността на предишната ни сървърна система.

За да оптимизираме производителността при работа с Pando, започнахме да предоставяме публичната си дистрибуция на Git на машините на разработчиците и в настолното приложение ChatGPT. За поетапното прехвърляне на клиентите към Pando използвахме вътрешна обвивка около интерфейса на Git за команден ред. Създадохме много други инструменти за обработка на заявки за промени, преглед на код и API за автоматизации. Заедно с Git трафика прехвърлихме стотици интеграции, които зависеха от наследени API, уебхукове и изтегляния на хранилища.

Самият Codex помогна на разработчиците и екипите да преминат към новата система. Създадохме умения за подготовка на съществуващите работни копия и за насочване на агентите към използване на новата система. В резултат разработчиците можеха просто да помолят Codex да прехвърли средите им за разработка и услугите им.

Преминаването към Pando подобри надеждността на сливанията и намали медианната латентност на push с почти 80%.

Pando може да предоставя петабайт Git данни на ден само с няколко процесорни ядра. Pando е предоставял над 57 терабайта Git пакети за един час, като над 99% от тези байтове са дошли от локалния дисков кеш.

Повторното използване на пакети позволява на много клиенти да изтеглят една и съща история на хранилището с малко изчисления на обслужващите възли. Този пример показва едно хранилище, но Pando е проектиран да поддържа много повече. С нарастването на търсенето можем независимо да мащабираме доставката на данни и договарянето по протокола Git.

Принос към проекта Git

Изграждането на Pando върху протокола и клиента на Git ни помогна да открием грешки и възможности за подобряване на производителността на самия Git.

Например Pando предоставя пакети за повторна употреба директно на клиентите, затова изтеглянето и индексирането им се превърнаха в ключов фактор за бързината и надеждността на системата. Тази работа разкри гранични случаи при едновременни изтегляния, почистване след прекъснати трансфери и възобновяване на вече завършили изтегляния. Коригирахме обработката на тези случаи, добавихме проверки при приемането на съществуващ пакет за fetch и предоставихме тези промени на основния проект.

Трябваше също да виждаме къде отиват времето и мрежовият ресурс. Затова добавихме проследяване на изтеглянията чрез packfile-uri и броячи на байтове при генерирането на пакети, за да е по-лесно да установим дали бавната операция fetch губи време в генериране на пакети, или в прехвърлянето им.

В версията на Git от OpenAI⁠(отваря се в нов прозорец) клиентът може да изтегля и индексира независими пакети паралелно. Добавихме понятието „изрично обявени налични комити“, за да може сървърът да обявява текущия връх на клон отделно от историята, която изпращащият клиент може да пропусне в пакета си. В Pando това позволява на клиентите да използват --force-with-lease, за да проверят, че клонът за функционалност не се е променил, и да изградят пакета му само спрямо основния клон. След това всеки, който разполага с тази история на основния клон, може да изтегли клона със същия пакет.

Няколко от тези подобрения вече са включени в основния Git проект, а много други са в публичното ни хранилище. Продължаваме да работим с общността на Git, за да направим тези подобрения полезни и извън Pando. Можете да следите тази работа в нашата публична система за проследяване на корекциите⁠(отваря се в нов прозорец).

Какво следва

Като преосмисля утвърдените подходи за мащабиране на Git хранилища, Pando ни позволява да мащабираме нашето монохранилище, без да променяме работните процеси с Git, които инженерите и агентите ни вече използват. В нашето монохранилище Pando позволи повече паралелна работа, подобри надеждността на сливанията и съкрати времето за публикуване на промени в кода. Освен за монохранилището проучваме и други приложения на Pando в OpenAI, включително задачи, при които голям брой агенти трябва да си сътрудничат в споделено Git хранилище.

С увеличаването на софтуера, създаван от инженери и агенти, все повече екипи извън OpenAI ще се сблъскват с ограниченията в контрола на версиите, до които достигнахме ние. В момента проучваме начини да споделим с потребителите си технологията, която стои зад Pando.

Pando стъпва върху две десетилетия опит на потребителите и администраторите на Git хранилища. Ще продължим да развиваме Pando и да работим с общността на Git с отворен код, за да споделяме подобренията, които са полезни за по-широкия кръг потребители на Git.

Автор

OpenAI