OpenAI

2 ноември 2026 г.

Инженерство

Скалирање на Git во OpenAI

Градиме контрола на изворниот код за обемот на работа на агентите, а истовремено вложуваме во екосистемот на Git.

Автори: Арон Фрил, Тејлор Блау и Тед Најман, членови на техничкиот тим

Се вчитува...

Во изминатата година, големината на нашиот монорепозиториум и барањата што ги поставува растеа со неверојатно темпо. Беше јасно дека треба да го обновиме системот за контрола на изворниот код за да го скалираме до нивото на истовремена работа што го овозможува Codex.

Го изградивме Pando, нов заднински систем за Git-репозиториуми, без да ја прекинеме инженерската работа или да го смениме начинот на кој нашиот тим го користи Git. Pando сега го поддржува целиот развој во монорепозиториумот на OpenAI. Pando е осмислен да исполни две амбициозни цели: прво, хоризонтално скалабилни запишувања, оптимизирани за истовремено испраќање во многу гранки. И второ, максимален капацитет за читање, за клиентите да можат да преземаат податоци доволно брзо за целосно да го искористат достапниот мрежен пропусен опсег.

Ги постигнавме овие цели задржувајќи ја компатибилноста со протоколот и клиентот на Git, што им овозможи и на инженерите и на агентите непречено да го вклучат Pando во постојните работни текови. Во продукција, Pando го намали медијанското доцнење при испраќање речиси петпати. Сега испорачува речиси еден петабајт Git-податоци дневно.

Тука ќе ги објасниме причините за изградба на нов систем за контрола на изворниот код, како и зошто решивме да ја задржиме компатибилноста со Git. Ќе го споредиме нашиот систем со вообичаените пристапи за скалирање на Git и ќе објасниме зошто Pando има единствена способност да ги задоволи потребите на OpenAI за скалабилност. Ќе разгледаме и што следува и како ќе продолжиме да придонесуваме кон Git за да поддржи работа во обемот што го овозможуваат агентите.

Зошто ни беше потребен нов систем за контрола на изворниот код

Codex ги промени обемот и темпото на развој во OpenAI, овозможувајќи им на инженерите да извршуваат многу повеќе работа паралелно. Тоа значи повеќе гранки и испраќања, повеќе спојувања и повеќе преземања и клонирања од развојни околини, истражувачки задачи и системи за континуирана интеграција (CI).

Овие барања се надоврзуваат и го зголемуваат оптоварувањето. Еден инженер сега има неколку промени подготвени за CI истовремено, а секое испраќање може да активира неколку задачи што го преземаат истиот комит.

Кон крајот на минатата година, системите што го поддржуваат нашиот монорепозиториум почнаа тешко да го следат темпото. Се соочувавме со бавни преземања и испраќања, како и неуспешни спојувања, а сигурноста понекогаш паѓаше под 90%. Дали го достигнавме ограничувањето на самиот Git или на начинот на кој го опслужувавме нашиот монорепозиториум? Git произлезе од заедница што разменуваше закрпи за јадрото преку е-пошта. Повеќе од две децении, програмерите ги приспособуваат своите алатки околу Git, додека начинот на кој создаваат софтвер се менува. Кога почнавме да наидуваме на тешкотии во нашиот монорепозиториум, ни беше важно да можеме да продолжиме да се надоврзуваме на таа работа.

Видовме начин да го задржиме познатиот работен тек, а да го обновиме заднинскиот систем за паралелно да обработува испраќања во различни гранки и истовремено да ја доставува истата историја до многу клиенти што читаат. Користењето на Git како основа за нашиот работен тек за контрола на изворниот код значеше дека подобрувањата на клиентот можат да бидат корисни и надвор од OpenAI. Сакавме тие подобрувања од нашата копија на Git⁠(се отвора во нов прозорец) да ги внесеме во изворниот проект и да ги споделиме со пошироката заедница.

Инженерски решенија за Git во голем обем

Скалирањето систем за контрола на изворниот код има две суштински димензии: многу клиенти што испраќаат во различни гранки и многу клиенти што ја преземаат истата историја. Pando ги оптимизира и двете.

Прво, Pando ги координира ажурирањата на секоја гранка независно. Ажурирањата на неповрзани гранки можат да се одвиваат паралелно, а клиентот што презема гранка ја добива историјата потребна за таа ревизија. Компромисот е што клиент што разгледува повеќе гранки може да ги види во состојби од различни моменти.

Второ, Pando повторно ги користи пакет-датотеките (форматот на Git за заедничко компресирање на повеќе објекти) при различни преземања, наместо да создава посебна пакет-датотека за секој клиент. Клиентот може да преземе некои објекти што веќе ги има, но серверот може да ги доставува истите бајти до многу клиенти со малку пресметки.

Овие избори ни овозможуваат да ги скалираме запишувањата низ гранките и да ги опслужуваме читањата од кеш. За да видиме како се надополнуваат, да почнеме од начинот на кој 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 меѓусебно се поврзуваат преку своите ID-а: комитите со дрвја, а дрвјата со други дрвја и блобови. Гранката refs/heads/main покажува кон комитот C2, чиј родител е C1. C1 покажува кон коренското дрво T1, каде што parser.py упатува на блобот B1. C2 покажува кон коренското дрво T2, каде што parser.py упатува на блобот B2. Во двете коренски дрвја, docs/ упатува на истото дрво, T3. Неговиот запис README.md покажува кон блобот B3, а usage.md кон блобот B4.

Git ги складира објектите во „непакувана“ или „пакувана“ состојба. Непакуваните објекти се складираат во поединечни датотеки идентификувани со ID на објектот. Пакуваните објекти се складираат заедно во пакет-датотека, што овозможува сличните објекти да се зачуваат како разлики еден во однос на друг.

Две верзии на parser.py се прикажани во $GIT_DIR/objects . Како непакувани објекти, блобот A содржи parse(), а блобот B ги содржи parse() и parse_file(). Пакет-датотеката го складира A во целост, а B го претставува како инструкции за копирање на целиот A и додавање на parse_file(). Придружната датотека *.idx ги лоцира двата блоба во пакет-датотеката според ID на објектот.

Git може да ги складира блобовите како посебни датотеки или да ги пакува заедно, кодирајќи еден блоб како разлика во однос на друг.

Во Git, објект е достижен од комит ако Git може да го најде следејќи ги врските од тој комит преку неговите родители, дрвја и блобови. При преземање, клиентот му кажува на серверот кои комити ги бара и кои веќе ги има. Серверот ги наоѓа објектите достижни од бараните комити, ги исклучува оние за кои знае дека клиентот веќе ги има и ги испраќа останатите како пакет-датотека во истиот формат што Git го користи на диск. Целосното клонирање започнува без ниту еден објект, па му се потребни сите објекти достижни од историјата што се клонира.

Испраќањето пренесува објекти во спротивната насока, барајќи од серверот да ажурира една или повеќе референци. Клиентот го користи множеството референци што ги објавува серверот за да избегне испраќање објекти што серверот веќе ги има. Затоа пакетот што се испраќа може да зависи од постојни објекти: примањето само на пакетот не е доволно за реконструкција на новата историја.

Реплицирањето Git-репозиториум значи одржување на достапноста и на објектите и на референците на повеќе сервери. Пред една реплика да може да ја опслужува новата вредност на референца, мора да може да ги достави објектите достижни од таа вредност. Услугата треба да ги координира и конкурентните ажурирања на референци, односно испраќањата што се обидуваат да ја поместат истата гранка од истиот стар врв кон различни нови комити. Системите што наметнуваат единствен редослед на сите запишувања во репозиториум можат да ги принудат испраќањата во неповрзани гранки да чекаат едно на друго.

Постојни обрасци за дистрибуирани Git-услуги

При дизајнирањето на Pando ги оценивме конвенционалните пристапи за скалирање на контролата на изворниот код со Git: еден што ги координира ажурирањата на референците меѓу репликите на репозиториумот со трифазно потврдување (3PC, протокол за координирање потврда на повеќе јазли) и еден што ги подредува ажурирањата преку заеднички дневник за претходно запишување (WAL, запис на промените пред нивната примена) во складиште за блобови.

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

Во дизајнот со трифазно потврдување што го моделиравме, Git-услугата чува целосна копија на репозиториумот на неколку реплики. Кога кој било сервер прима испраќање, дојдовната пакет-датотека се копира и паралелно се испраќа до сите реплики. За испраќањето да се објави, мнозинството реплики мора да го прифатат ажурирањето на референцата. Серверот што управува со ова е координаторот, а постапката бара повеќе кругови комуникација меѓу него и секоја друга реплика.

За да прифати ажурирање на референца, секоја реплика ја заклучува својата копија на референцата и проверува дали таа сè уште покажува кон комитот што клиентот го очекува. Потоа координаторот испраќа порака за претпотврда до репликите што прифатиле. Откако мнозинството ќе го потврди приемот на пораката за претпотврда, координаторот им наложува да го извршат ажурирањето. Потоа секоја ажурирана реплика може да го објави новиот врв на гранката од својата локална копија на репозиториумот.

Чувањето целосни реплики овозможува локално опслужување на читањата и гарантира дека клиентите добиваат конзистентен приказ на сите референци, без оглед на тоа која реплика ќе ја изберат. Како последица, референците се ажурираат една по една, а секое испраќање бара повеќе циклуси на комуникација за координација на ажурирањето меѓу репликите.

Чекањето кворум значи дека испраќањето зависи од перформансите на дисковите на репликите и од доцнењето во комуникацијата. Како што расте обемот на запишувања, репликите мора да го следат темпото и на дојдовните пакети и на сообраќајот за читање.

Дневник за претходно запишување (WAL)

Во дизајнот заснован на WAL што го моделиравме, Git-услугата користи заеднички манифест во складиште за блобови за да ги подреди ажурирањата низ целиот репозиториум. Кога серверот прима испраќање, ја поставува дојдовната пакет-датотека во складиште за блобови и го подготвува ажурирањето на референцата во својата локална копија на репозиториумот. За да го објави испраќањето, додава запис во заедничкиот манифест зачуван во складиштето за блобови, кој го евидентира ажурирањето на референцата и покажува кон пакет-датотеката. Потоа Git-серверот го ажурира манифестот со операција „спореди и замени“. Таа атомски го заменува манифестот или ја одбива промената. Ако друг клиент го ажурирал во меѓувреме, серверот ги чита записите додадени во тој период, проверува дали неговото ажурирање на референцата е сè уште важечко и се обидува повторно со веќе поставената пакет-датотека.

Клиентите што запишуваат можат паралелно да ги поставуваат своите пакет-датотеки, но дури и испраќањата во неповрзани гранки бараат ажурирање на истиот манифест. Времето за целосен циклус на успешна операција „спореди и замени“ ограничува колку често репозиториумот може да објавува нов манифест. Складиште за блобови со мало доцнење го забрзува овој чекор. Групирањето овозможува еден манифест да ажурира повеќе референци истовремено, но по цена на ставање на ажурирањата во редица и зголемено доцнење.

Складиштето за блобови може паралелно да прима пакет-датотеки, но ажурирањата на заедничкиот манифест сè уште се натпреваруваат меѓусебно. Самото зголемување на капацитетот на складиштето за блобови не го отстранува тоа тесно грло. Секоја реплика и понатаму треба да ги прочита и индексира пакетите од складиштето за блобови пред да може да ги опслужува локално. Ако дојдовните запишувања се побрзи од таа обработка, репликите заостануваат, а клиентите чекаат подолго за да ги преземат новоиспратените комити.

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

Тука доаѓаме до Pando. Pando е нова архитектура за опслужување Git-репозиториуми, со цел да се скалира за многу клиенти што испраќаат истовремено. Еве како функционира:

Преиспитување на ограничувањата

Пред да го развиеме Pando, претпоставувавме дека можеме да ги исполниме нашите барања без да правиме компромиси со Git. Сепак, моравме да направиме компромиси со некои од работите што повеќето Git-системи ги сметаат за неопходни:

  • Конзистентност на целиот репозиториум: Обезбедувањето конзистентна снимка на сите референци во еден момент бара координација низ целиот репозиториум. Во систем што ги подредува сите ажурирања на референците заедно, испраќањата во неповрзани гранки се натпреваруваат за една точка на координација.

  • Посебни пакет-датотеки за секој клиент: Ако два клиента со различни локални истории ја преземаат истата гранка, серверот можеби ќе треба да создаде различен пакет за секој од нив за да не испраќа објекти што веќе ги имаат. Создавањето на овие одговори троши процесорски ресурси и дисковни влезно-излезни операции, што го отежнува скалирањето за голем број клиенти што читаат.

Наместо тоа, Pando нуди модел со помалку строга конзистентност (каузална конзистентност) за скалирање на запишувањата и повторно ги користи пакет-датотеките за скалирање на читањата.

Каузална конзистентност (CC)

Кога клиент испраќа во гранка, Pando ја зачувува дојдовната пакет-датотека како непроменлив блоб во складиште за блобови. При секое поместување на гранката, Pando запишува во складиште за метаподатоци верзија на референцата што ги содржи новиот врв, пакетот што го доставил и сите претходни верзии на референци од кои зависи пакетот. И складиштето за блобови и складиштето за метаподатоци можат хоризонтално да се скалираат.

За преземање комит или референца од Pando, на клиентот треба да му се испрати низа верзии на референци (пакет-датотеки), со што се реконструира состојбата на клиентот што ја создал верзијата на референцата што се презема. Ова обезбедува послаба форма на конзистентност низ целиот репозиториум, но гарантира серијализабилност на ажурирањата на поединечни референци. Клиентите доаѓаат во конфликт само кога истовремено испраќаат во иста референца. Клиент што чита повеќе гранки не може да претпостави дека тие читања ја опишуваат состојбата на репозиториумот во еден ист момент.

Лево, три последователни пакети од главната гранка ги содржат комитите A, B и C; D и E; и F. Десно, главната и тематската гранка се раздвојуваат кај C. Главната гранка напредува до G, а тематската преку D, E и F. Најновиот пакет од тематската гранка ги содржи D, E и F. Испрекинатата рамка означува дека D и E се присутни и во претходниот пакет од тематската гранка. Сината стрелка ја означува зависноста на новиот пакет од главната гранка кај G. Родителот на F во Git е E.

Испраќањето во главната гранка додава инкрементален пакет со еден или повеќе комити. Секое испраќање во споредна гранка повторно ги вклучува нејзините претходни комити, така што клиент што ја има објавената верзија на главната гранка може да ја преземе целата тема во еден пакет.

Ова е форма на каузална конзистентност. За да преземе референца, клиентот што чита ја добива историјата на клиентот што ја запишал. При испраќање, Pando контролира за која заедничка историја се согласуваат клиентот и серверот, со што обезбедува клиентите да создаваат пакет-датотеки што полесно се споделуваат.

Затоа, при Git-операции Pando не обезбедува состојба на сите референци во еден ист момент, ниту редослед на ажурирањата на референците при клонирање или преземање. На пример, да претпоставиме дека гранките foo и bar се ажурираат додека се договара целосно клонирање. При договарањето, еден клиент може да ја види foo во верзија 1 и bar во верзија 2, а друг клиент да ја види foo во верзија 2 и bar во верзија 1. Во практика, корисниците и агентите не зависат од глобалниот редослед на ажурирањата на референците, туку од редоследот на ажурирањата на една референца.

Споредба на пристапите

Во оваа симулација на трите модели на конзистентност поставуваме 1.000 клиенти, од кои секој избира посебна гранка и потоа постојано испраќа уникатни комити. Ги мериме вкупното изминато време и доцнењето од испраќање до преземање. Втората мерка е важна за работните оптоварувања, бидејќи го мери времето на целиот циклус потребен за корисниците и агентите да соработуваат или да користат други системи што се потпираат на контрола на верзии. Симулацијата моделира хардвер во опсег од стандардни до висококласни виртуелни машини и системи за складирање блобови. Во оваа симулација ги ублаживме барањата за конзистентност на целиот репозиториум кај трифазното потврдување.

Simulating Git Server Implementations

Follow each writer through repeated pushes, and separately measure when an independent reader can check out each commit.

Three-phase commit

Ready
Simulated time
0 s
Mean push to fetch
—
Writers done
0 / 1,000

Each square represents one writer. All selected architectures run the same workload. Squares lighten as pushes are acknowledged and turn blue when a writer finishes. The chart shows writers finished over simulated time; independent readers can continue fetching afterward. Use arrow keys to select a writer, Home or End to jump, and Escape to clear.

0 / 10,000 pushes acknowledged · 0 fetched

Select a square to inspect that writer.

Object-store WAL

Ready
Simulated time
0 s
Mean push to fetch
—
Writers done
0 / 1,000

Each square represents one writer. All selected architectures run the same workload. Squares lighten as pushes are acknowledged and turn blue when a writer finishes. The chart shows writers finished over simulated time; independent readers can continue fetching afterward. Use arrow keys to select a writer, Home or End to jump, and Escape to clear.

0 / 10,000 pushes acknowledged · 0 fetched

Select a square to inspect that writer.

Pando

Ready
Simulated time
0 s
Mean push to fetch
—
Writers done
0 / 1,000

Each square represents one writer. All selected architectures run the same workload. Squares lighten as pushes are acknowledged and turn blue when a writer finishes. The chart shows writers finished over simulated time; independent readers can continue fetching afterward. Use arrow keys to select a writer, Home or End to jump, and Escape to clear.

0 / 10,000 pushes acknowledged · 0 fetched

Select a square to inspect that writer.

Writer progress through verified fetches

  • Three-phase commit
  • Object-store WAL
  • Pando
05001,00000 s
  • Waiting
  • Pushing
  • All pushes acknowledged
0 sLater

Synthetic teaching models, not production benchmarks. Mean push to fetch covers topic pushes, from the first attempt through retries until an independent reader verifies that commit. It includes waiting for a reader slot and excludes builds and tests. With merges enabled, writer completion includes the required fetches and trunk merge.

Experiment settings

Preparing simulation…

Results

Play the comparison to see the recorded results.

Synthetic teaching models, not production benchmarks. Mean push to fetch covers topic pushes, from the first attempt through retries until an independent reader verifies that commit. It includes waiting for a reader slot and excludes builds and tests. With merges enabled, writer completion includes the required fetches and trunk merge.

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

Моделираниот систем со трифазно потврдување покажува значително поголемо доцнење при запишување под оптоварување, кога дисковите се заситуваат. Во меѓувреме, системите со дневник за претходно запишување покажуваат значително поголемо доцнење при читање од истата причина: серверите тешко успеваат навреме да го читаат дневникот. И во реалните тестови на нашиот монорепозиториум и во оваа симулација, Pando поддржува повеќе илјади клиенти што можат истовремено да испраќаат без да ги нарушат перформансите под оптоварување.

При обработка на испраќање, Pando ја проверува дојдовната пакет-датотека додека ја пренесува во складиштето за блобови, без да ја отпакува во локален репозиториум. Кога клиент започнува преземање, 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.

Stream the push

  • Packfile bytes
  • Metadata
  • Request / acknowledgment
  • Packfile URIs

Агент уредува датотеки, создава комит локално и ја извршува командата git push, која создава пакет-датотека со тие промени за да ажурира гранка. Pando го прима пакетот и го пренесува во складиштето за блобови. Откако ќе го провери дојдовното испраќање со декодирање во едно поминување, Pando користи трансакција со повеќе клучеви за да запише верзија на референцата во нашето складиште за метаподатоци и атомски да го ажурира покажувачот на референцата. Ова му овозможува на Pando да обезбеди конзистентност од типот „читање на сопствените запишувања“. Целата оваа операција за запишување бара O(1)O(1) читања и запишувања на метаподатоци.

Кога друг клиент ја презема гранката, Pando избира множество зачувани пакети користејќи ја историјата што клиентот веќе ја има. Оваа операција бара O(log⁡Δ)O(\log \Delta) читања на метаподатоци, при што Δ\Delta е најкраткото растојание, мерено во верзии на референци, меѓу она што клиентот го има и она што го бара. Серверот враќа URI-адреси на пакет-датотеките, а клиентот ги презема преку услугата Express, која ги чува пакет-датотеките во регионални кешови.

Овој дизајн се потпира на трите основни услуги во архитектурата на Pando: испраќање, преземање и компакција.

Скалирање на испраќањата

Кога клиент испраќа, конвенционалниот Git-сервер е како покер-играч што ги покажува сите карти: му ја открива на клиентот секоја гранка што ја има. Клиентот потоа ги собира комитите, дрвјата и блобовите за да создаде пакет-датотека што ги исклучува објектите за кои знае дека серверот веќе ги има.

Објавувањето повеќе референци може да го намали обемот на испраќањето. Но може и да ја отежни повторната употреба на пакетот, бидејќи на друг клиент можеби му недостигаат објектите што испраќачот ги изоставил.

Pando има поинаков пристап. Наместо да ги покаже сите карти, на клиентот му објавува само избрано множество гранки. Ова се прави од две причини:

  1. Обезбедувањето конзистентен приказ на сите референци е скапо. Со милиони гранки, само списокот на објавени референци може да надмине 100 MiB. За тоа, секој сервер мора или да одржува ажурирано мапирање, што бара значителни ресурси за координација, или да изврши пребарување во базата чиј обем расте со бројот на референци.

  2. Кога на клиентот му се дава само подмножество референци, тој создава пакет што зависи само од она што серверот изречно му дозволува да го исклучи. Следењето на зависностите му овозможува на Pando повторно да ја користи пакет-датотеката што клиентот ја создава при едно испраќање, така што ја доставува како одговор на подоцнежно преземање.

Колку повеќе референци објавува Git-серверот, толку повеќе извори имаат клиентите што испраќаат за исклучување објекти, што им овозможува да создаваат помали пакети. Сепак, за повторна употреба на овие пакет-датотеки, серверот мора да може да докаже дека клиентите што преземаат ја имаат секоја од зависностите. Во спротивно, пакет-датотеката може да изостави објекти што му се потребни на клиентот.

Од друга страна, ако Git-серверот не објави ниту една референца, клиентот би создал пакет-датотека со целата историја на репозиториумот. Таа пакет-датотека може да ја користи кој било друг клиент, но со значителен недостаток: преземањето на која било гранка практично станува целосно клонирање.

Крива што покажува како објавените референци влијаат врз големината на пакет-датотеката: од целосната историја кога нема објавени референци, преку објавување на главната гранка, до само новите објекти кога се објавени сите референци.

Објавувањето повеќе референци резултира со помали пакет-датотеки. Најголемиот дел од намалувањето може да се постигне со објавување само една референца.

Стандардно, кога клиент испраќа нова гранка за функционалност, Pando објавува само една одредена главна гранка како основа. Така, големината на објавениот список останува иста додека во репозиториумот се додаваат гранки, но тоа носи и компромиси. Кога инженер повеќепати испраќа во иста гранка или надоврзува една гранка за функционалност врз друга, клиентот мора повторно да испрати сè што е надвор од главната гранка.

Поради тоа, последователните испраќања во гранката стануваат поголеми, можат да содржат непотребно повторени објекти и трошат дополнителен пропусен опсег на клиентот. Во нашиот монорепозиториум, медијанската големина на вака создадените пакет-датотеки беше под 20 KiB, а 99-тиот перцентил беше под 3 MiB. За нас, ова беше разумен компромис, бидејќи клиент што веќе ја има објавената историја на главната гранка може да ја преземе гранката со таа една пакет-датотека.

Откако ќе го избере множеството референци што ќе му ги објави на клиентот, Pando го прифаќа испраќањето и го ажурира складиштето за метаподатоци. Pando го дели ажурирањето на метаподатоците на синхрон и асинхрон дел. Синхроното ажурирање на метаподатоците обезбедува клиентите веднаш да можат да го преземат новоажурираниот комит. Се запишува нова верзија на референцата и се ажурира мапирањето на покажувачот кон неа, при што се зачувуваат новиот врв, зависностите употребени при договарањето на пакет-датотеката и врска до испратената пакет-датотека.

Истовремено, во истата трансакција се запишуваат и асинхрони задачи, како закажување компакција. Кај многу големи испраќања, само дел од комитите се запишуваат синхроно. Така ни е потребен само константен број читања и запишувања во складиштето за метаподатоци, без оглед на големината на испраќањето.

Важно е што серверите на Pando можат независно да прифаќаат испраќања во неповрзани референци. Единствениот клуч за кој може да има натпревар е покажувачот на референцата, кој го заштитуваме со атомска операција слична на „спореди и замени“. Можеме да го зголемиме капацитетот за запишување со додавање сервери на Pando, единици за скалирање на складиштето за метаподатоци или складишта за блобови.

Преземање и клонирање со полна мрежна брзина

Кога клиент извршува преземање, му доставува на серверот две множества ID-а на комити, познати во протоколот како have и want. Конвенционалниот Git-сервер ја пресметува разликата меѓу множествата објекти достижни од постојните и од бараните комити, ги компресира потребните објекти и му го испраќа резултатот на клиентот. Иако Git-серверот со отворен код има различни локални индекси за забрзување на оваа операција, пресметувањето на овие пакет-датотеки може да биде скапо, а тие генерално тешко се кешираат.

Наместо тоа, Pando ги користи мапирањата на комитите запишани при испраќањата за да ги пронајде 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.

Already heldFetchMissing branch dependency

Fetch 27 trunk packs.

Select a branch to include its trunk dependencies.

Pando ги пресликува постојните и бараните комити од протоколот Git во низа верзии на референци. Барањето споредна гранка подразбира барање и на историјата до нејзините зависности во главната гранка.

Протоколот Git дозволува да се испрати само една пакет-датотека во HTTP-одговорот при клонирање или преземање. Сепак, една недоволно ценета можност на протоколот Git, packfile-uri, му дозволува на серверот да врати врски за одделно преземање дополнителни пакети. Pando ја користи исклучиво оваа можност, а во самиот одговор испраќа празна пакет-датотека. За да ја поддржи оваа можност, OpenAI придонесе со повеќе подобрувања на packfile-uri во изворниот проект.

Системите на OpenAI работат во многу географски региони, па во секој од нив одржуваме кластери на Pando со локални кешови. Услугата за испорака на пакети Express го кешира нашето складиште за блобови, а посебна услуга го кешира складиштето за метаподатоци. Овие кешови му овозможуваат на Pando да ги опслужува барањата за ID на комит или за посебно читање референца целосно од кешираната состојба во тој кластер. Користиме конзистентно хеширање за насочување на барањата за пакет-датотеки кон серверите Express, при што над 99% од барањата се задоволуваат од кеш.

Овој систем од кешови обезбедува исклучително голем капацитет, доволен целосно да ја искористи мрежната врска меѓу клиентите и Pando. Со овие URI-адреси на пакет-датотеки и локални кешови потврдивме дека, користејќи само стандардни виртуелни машини од облачни провајдери, операциите за клонирање и преземање целосно ги искористуваат мрежните врски од 10 Gbps.

Компакција и време до првиот бајт

За повеќето Git-сервери, препакувањето и чистењето на неискористените објекти се неопходни за одржување на перформансите на репозиториум што расте. И двата чекора ослободуваат простор, но бараат многу пресметки и влезно-излезни операции.

Во репозиториум што брзо се менува, како монорепозиториумот на OpenAI, не е изводливо да се испраќа посебна URI-адреса на пакет-датотека за секое испраќање по последното преземање. Секој ден, клиентот би требало да преземе десетици илјади пакет-датотеки за да ја ажурира својата главна гранка.

Наместо тоа, Pando ги обединува пакетите во заднина за да го намали дополнителното оптоварување, со дизајн сличен на компакцијата кај лог-структурираните дрвја за спојување. Секое испраќање создава пакет-датотека што Pando ја нарекува „L0“, најмалата единица податоци што Pando може да ја пренесе. Со текот на времето, заднинските процеси ги собираат овие пакет-датотеки во покривни групи што опфаќаат низа верзии на референци и растат геометриски почнувајќи од L0. Денес, нашиот монорепозиториум користи покривни групи на пакет-датотеки до ниво L4.

Овие покривни групи имаат двојна намена. Кога една покривна група доволно ќе се пополни, процес за компакција ја презема и создава една пакет-датотека. Со реорганизирање на објектите во пакет-датотеката се подобрува степенот на компресија. Но се забрзува и договарањето на преземањето, бидејќи групата служи како листа со прескокнување низ верзиите на референците. Со истовремено пребарување низ индексите на секое ниво, Pando може да избере множество пакети за испраќање до клиент во логаритамско време. Овие покривни групи на пакет-датотеки, препакувани или не, овозможуваат поефикасно планирање на преземањата, намалувајќи ги барањата и за капацитет и за доцнење на Pando.

Compaction in Pando

Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.

Compaction in progressPublished cover

Компакцијата ги обединува во заднина пакет-датотеките од многу верзии на референци, овозможувајќи поефикасно преземање со помалку датотеки.

За разлика од многу Git-сервери, пакет-датотеките се задржуваат на секое ниво и не се отфрлаат ниту се отстрануваат при чистење. Пакетите од сите нивоа постојано се користат за опслужување на клиентите, врз основа на 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. Деноноќно користиме и агенти за тестирање на безбедноста на Git и Pando преку симулирани напади, со модели што ги имаат способностите на Daybreak.

Воведување на Pando

Првично го воведовме Pando како посредник за читање и запишување кон нашиот постоен систем за контрола на изворниот код. Така можевме да ги одржуваме Pando и претходниот систем синхронизирани и безбедно да го пренасочуваме сообраќајот кон Pando без прекини во работата.

Како дел од својот дизајн за кеширање, Pando може и да ги отстранува дупликатите и да ги обединува барањата пред да ги проследи. Тоа ни овозможи постојните алатки да работат стабилно додека ги префрлавме системите на Pando еден по еден. Како што префрлавме повеќе работни оптоварувања на Pando, нивните испраќања го пополнуваа кешот на Pando и ја подобруваа сигурноста на претходниот заднински систем.

За да ги оптимизираме перформансите при користење на Pando, почнавме да ја испорачуваме нашата јавна дистрибуција на Git на програмерските компјутери и во десктоп-апликацијата ChatGPT. За постепено префрлање на клиентите на Pando, користевме интерна обвивка околу интерфејсот на Git за командна линија. Создадовме и многу други алатки за обработка на барања за промени, преглед на код и API-а за автоматизација. Заедно со Git-сообраќајот, префрливме и стотици интеграции што зависеа од наследени API-а, веб-куки и преземања на репозиториуми.

И самиот Codex им помогна на програмерите и тимовите да преминат на новиот систем. Создадовме вештини за подготовка на постојните работни копии и за насочување на агентите да го користат новиот систем. Како резултат, програмерите можеа едноставно да побараат од Codex да ги префрли нивните развојни околини и услуги.

Преминувањето на Pando ја подобри сигурноста на спојувањето и го намали медијанското доцнење при испраќање за речиси 80%.

Pando може да испорача еден петабајт Git-податоци дневно со само неколку процесорски јадра. Pando има испорачано повеќе од 57 терабајти Git-пакети во еден час, при што над 99% од тие бајти биле од локалниот дисковен кеш.

Повторната употреба на пакетите им овозможува на многу клиенти да ја преземаат истата историја на репозиториумот со малку пресметки на јазлите што ги опслужуваат. Овој пример прикажува еден репозиториум, но Pando е дизајниран да поддржува многу повеќе. Како што расте побарувачката, можеме независно да ги скалираме испораката на податоци и договарањето со Git.

Придонес кон проектот Git

Градењето на Pando врз протоколот и клиентот на Git ни помогна да откриеме грешки и можности за подобрување на перформансите на самиот Git.

На пример, Pando им доставува пакети за повторна употреба директно на клиентите, па преземањето и индексирањето на тие пакети стана клучен дел од обезбедувањето брз и сигурен систем. Таа работа откри гранични случаи при истовремени преземања, чистење по прекинати преноси и продолжување преземања што веќе биле завршени. Ги поправивме тие делови од кодот, додадовме проверки при прифаќање постоен пакет за преземање и ги внесовме подобрувањата во изворниот проект.

Требаше да видиме и каде се трошат времето и пропусниот опсег, па додадовме следење на преземањата преку packfile-uri и броење бајти при создавање пакети. Така полесно се утврдува дали бавното преземање го троши времето на создавање пакети или на нивен пренос.

Во верзијата на Git од OpenAI⁠(се отвора во нов прозорец), клиентот може паралелно да презема и индексира независни пакети. Го додадовме концептот „експлицитно наведени постојни комити“, за серверот да може да го објави тековниот врв на гранката одделно од историјата што клиентот што испраќа смее да ја изостави од пакетот. Во Pando, ова им овозможува на клиентите да користат --force-with-lease за да проверат дека гранката за функционалност не е поместена и да го изградат пакетот само врз основа на главната гранка. Потоа, секој што ја има таа историја на главната гранка може да ја преземе гранката користејќи го истиот пакет.

Неколку од овие подобрувања веќе се во изворниот проект Git, а многу други се во нашиот јавен репозиториум. Продолжуваме да соработуваме со заедницата на Git за овие подобрувања да бидат корисни и надвор од Pando. Таа работа можете да ја следите во нашиот јавен систем за следење закрпи⁠(се отвора во нов прозорец).

Што следува

Со преиспитување на вообичаените сфаќања за скалирање на Git-репозиториуми, Pando ни овозможува да го скалираме нашиот монорепозиториум без да ги менуваме работните текови со Git што нашите инженери и агенти веќе ги користат. Во нашиот монорепозиториум, Pando овозможи поголем степен на паралелизам, ја подобри сигурноста на спојувањето и го намали времето потребно за објавување промени во кодот. Надвор од нашиот монорепозиториум, истражуваме и други примени на Pando во OpenAI, вклучително и работни оптоварувања каде што голем број агенти треба да соработуваат во заеднички Git-репозиториум.

Како што инженерите и агентите создаваат повеќе софтвер, сè повеќе тимови надвор од OpenAI ќе се соочуваат со ограничувањата на контролата на изворниот код на кои наидовме ние. Во моментов истражуваме начини да ја споделиме со нашите корисници технологијата што го овозможува Pando.

Pando се надоврзува на две децении искуство на корисниците и операторите на Git-репозиториуми. Ќе продолжиме да го развиваме Pando и да соработуваме со заедницата за отворен код на Git за да ги споделуваме подобрувањата што им користат на корисниците на Git пошироко.

Автор

OpenAI