Meningkatkan skala Git di OpenAI
Membangun kontrol kode sumber untuk skala kerja agen sambil berinvestasi dalam ekosistem Git.
Selama setahun terakhir, ukuran monorepo kami dan tuntutan terhadapnya meningkat dengan sangat pesat. Jelas bahwa kami perlu merombak sistem kontrol kode sumber agar mampu menangani skala pekerjaan serentak yang dimungkinkan oleh Codex.
Kami membangun Pando, backend baru untuk repositori Git, tanpa mengganggu pekerjaan rekayasa apa pun atau mengubah cara tim kami menggunakan Git. Kini Pando menopang seluruh pengembangan di monorepo OpenAI. Pando dirancang untuk memenuhi dua sasaran ambisius: pertama, penulisan yang dapat ditingkatkan skalanya secara horizontal, dioptimalkan untuk push serentak ke banyak cabang. Kedua, memaksimalkan throughput baca agar klien dapat mengunduh data cukup cepat untuk menggunakan seluruh bandwidth jaringan yang tersedia.
Kami mencapai sasaran ini sambil mempertahankan kompatibilitas dengan protokol dan klien Git, sehingga engineer dan agen kami dapat mengadopsi Pando dengan lancar ke dalam alur kerja yang sudah ada. Di lingkungan produksi, Pando memangkas median latensi push hingga hampir seperlimanya. Kini Pando mengirimkan hampir satu petabyte data Git per hari.
Di sini, kami akan membahas motivasi di balik pembangunan sistem kontrol kode sumber baru, beserta alasan kami memilih mempertahankan kompatibilitas dengan Git. Kami akan membandingkan sistem kami dengan pendekatan umum untuk meningkatkan skala Git, dan menjelaskan keunggulan khusus Pando dalam memenuhi kebutuhan skalabilitas OpenAI. Kami juga membahas langkah selanjutnya dan bagaimana kami akan terus berkontribusi pada Git untuk mendukung pekerjaan dalam skala agen.
Codex telah mengubah skala dan laju pengembangan di OpenAI, memungkinkan engineer mengerjakan jauh lebih banyak pekerjaan secara paralel. Artinya, ada lebih banyak cabang dan push, lebih banyak merge, serta lebih banyak fetch dan clone dari lingkungan pengembangan, tugas riset, dan CI.
Tuntutan ini saling melipatgandakan. Kini satu engineer memiliki beberapa perubahan yang siap untuk CI sekaligus, dan setiap push dapat memicu beberapa tugas untuk mengambil commit yang sama.
Mulai sekitar akhir tahun lalu, sistem yang menopang monorepo kami mulai kesulitan mengimbangi kebutuhan. Kami mengalami fetch dan push yang lambat serta kegagalan merge, dengan keandalan yang terkadang turun di bawah 90%. Apakah kami telah mencapai batas Git itu sendiri, atau batas cara kami melayani monorepo? Git tumbuh dari komunitas yang bertukar patch kernel melalui email. Selama lebih dari dua dekade, developer telah menyesuaikan alat mereka dengan Git seiring perubahan cara mereka membangun perangkat lunak. Saat kami mulai menemui hambatan di monorepo sendiri, penting bagi kami untuk tetap dapat melanjutkan pengembangan di atas hasil kerja tersebut.
Kami melihat cara mempertahankan alur kerja yang sudah familier itu sambil membangun ulang backend untuk menangani push ke cabang berbeda secara paralel dan menyajikan riwayat yang sama kepada banyak pembaca secara bersamaan. Menjadikan Git sebagai fondasi alur kerja kontrol kode sumber kami berarti peningkatan pada klien dapat bermanfaat juga di luar OpenAI. Kami ingin menyumbangkan peningkatan dari salinan Git kami(terbuka di jendela baru) ke proyek induk, agar manfaatnya dapat dirasakan komunitas yang lebih luas.
Peningkatan skala sistem kontrol kode sumber memiliki dua dimensi penting: banyak penulis melakukan push ke cabang berbeda, dan banyak pembaca mengambil riwayat yang sama. Pando dioptimalkan untuk keduanya.
Pertama, Pando mengoordinasikan pembaruan setiap cabang secara independen. Pembaruan pada cabang yang tidak saling terkait dapat berjalan paralel, dan pembaca yang mengambil suatu cabang mendapatkan riwayat yang dibutuhkan untuk revisi tersebut. Konsekuensinya, pembaca yang melihat beberapa cabang dapat melihat keadaan cabang-cabang tersebut pada titik waktu yang berbeda.
Kedua, Pando menggunakan ulang packfile (format Git untuk mengompresi beberapa objek bersama-sama) di berbagai operasi fetch, alih-alih membuat packfile tersendiri untuk setiap klien. Klien mungkin mengunduh beberapa objek yang sudah dimilikinya, tetapi server dapat menyajikan byte yang sama kepada banyak klien dengan sedikit komputasi.
Pilihan ini memungkinkan kami meningkatkan skala penulisan lintas cabang dan melayani pembacaan dari cache. Untuk memahami keterkaitan keduanya, mari mulai dari cara Git menyimpan dan mentransfer riwayat.
Git harus mampu menjaga sinkronisasi dua bagian repositori: objek dan referensi.
Repositori Git mencatat snapshot file dan riwayat yang menghubungkannya sebagai graf objek. Objek dapat berupa blob, tree, commit, atau tag. Blob menyimpan isi satu file, sedangkan tree mengaitkan nama dengan blob atau tree lain untuk membentuk struktur direktori. Commit menunjuk ke tree akar serta nol atau lebih commit induk, sehingga membentuk riwayat repositori Anda.
Objek diidentifikasi melalui hash dari jenis dan isinya. Objek juga tidak dapat diubah: pencatatan file yang berubah akan membuat blob baru, sedangkan file dan direktori yang tidak berubah dapat menggunakan ulang objek yang ada.
Cabang dan tag adalah referensi, atau “ref”, yang menunjuk ke commit. Melakukan commit pada cabang akan membuat commit baru dan memperbarui ref cabang agar menunjuk ke commit tersebut.
Objek Git saling merujuk berdasarkan ID: commit merujuk ke tree, dan tree merujuk ke tree serta blob. Cabang refs/heads/main menunjuk ke commit C2, yang induknya adalah C1. C1 menunjuk ke tree akar T1, tempat parser.py merujuk ke blob B1. C2 menunjuk ke tree akar T2, tempat parser.py merujuk ke blob B2. Dalam kedua tree akar, docs/ merujuk ke tree yang sama, T3. Entri README.md di dalamnya menunjuk ke blob B3, dan usage.md menunjuk ke blob B4.
Git menyimpan objek dalam keadaan “terpisah” atau “dikemas”. Objek terpisah disimpan dalam file tersendiri yang diidentifikasi melalui ID objeknya. Objek yang dikemas disimpan bersama dalam packfile, sehingga objek serupa dapat disimpan sebagai delta terhadap objek lain.
Git dapat menyimpan blob sebagai file terpisah atau mengemasnya bersama, dengan mengodekan satu blob sebagai delta terhadap blob lain.
Dalam Git, objek dapat dijangkau dari suatu commit jika Git dapat menemukannya dengan mengikuti tautan dari commit tersebut melalui induk, tree, dan blob-nya. Selama fetch, klien memberi tahu server commit mana yang diinginkan dan mana yang sudah dimilikinya. Server menemukan objek yang dapat dijangkau dari commit yang diinginkan, mengecualikan objek yang diketahuinya sudah dimiliki klien, lalu mengirim sisanya sebagai packfile dalam format yang sama dengan yang digunakan Git pada disk. Clone penuh dimulai tanpa objek apa pun, sehingga membutuhkan semua objek yang dapat dijangkau dari riwayat yang dikloning.
Push mentransfer objek ke arah sebaliknya dengan meminta server memperbarui satu atau beberapa ref. Klien menggunakan kumpulan ref yang diumumkan server untuk menghindari pengiriman objek yang sudah dimiliki server. Karena itu, pack yang dikirim dapat bergantung pada objek yang sudah ada: menerima pack saja tidak cukup untuk merekonstruksi riwayat baru.
Mereplikasi repositori Git berarti menjaga ketersediaan objek dan referensi di beberapa server. Sebelum replika dapat menyajikan nilai baru suatu referensi, replika harus mampu menyediakan objek yang dapat dijangkau dari nilai tersebut. Layanan juga perlu mengoordinasikan pembaruan referensi yang saling bersaing, yaitu push yang mencoba memindahkan cabang yang sama dari ujung lama yang sama ke commit baru yang berbeda. Sistem yang memberlakukan satu urutan untuk semua penulisan ke repositori dapat membuat push ke cabang yang tidak saling terkait harus saling menunggu.
Saat merancang Pando, kami mengevaluasi pendekatan konvensional untuk meningkatkan skala kontrol kode sumber menggunakan Git: pendekatan yang mengoordinasikan pembaruan ref antar-replika repositori dengan commit tiga fase (3PC, protokol untuk mengoordinasikan commit di beberapa node), dan pendekatan yang mengurutkan pembaruan melalui write-ahead log bersama (WAL, catatan perubahan yang ditulis sebelum diterapkan) di penyimpanan blob.
Dalam desain commit tiga fase yang kami modelkan, layanan Git menyimpan salinan lengkap repositori pada beberapa replika. Saat server mana pun menerima push, packfile yang masuk disalin dan dikirim ke semua replika secara paralel. Agar push dapat dipublikasikan, mayoritas replika harus menerima pembaruan ref. Server yang mengelola proses ini disebut koordinator, dan prosesnya membutuhkan beberapa putaran komunikasi antara koordinator dan setiap replika lainnya.
Untuk menerima pembaruan referensi, setiap replika mengunci salinan ref-nya dan memeriksa bahwa ref tersebut masih menunjuk ke commit yang diharapkan klien. Koordinator kemudian mengirim pesan pra-commit ke replika yang menerima pembaruan. Setelah mayoritas mengonfirmasi pesan pra-commit, koordinator meminta mereka melakukan pembaruan. Setiap replika yang sudah mutakhir kemudian dapat mengumumkan ujung cabang baru dari salinan lokal repositorinya.
Menyimpan replika lengkap memungkinkan pembacaan dilayani secara lokal dan menjamin klien mendapatkan tampilan yang konsisten atas semua referensi, apa pun replika yang dipilih. Konsekuensinya, referensi diperbarui satu per satu, dan setiap push membutuhkan beberapa komunikasi bolak-balik untuk mengoordinasikan pembaruan ref antar-replika.
Karena harus menunggu kuorum, proses push dipengaruhi oleh kinerja disk replika dan latensi komunikasi. Seiring meningkatnya volume penulisan, replika harus mengimbangi pack yang masuk sekaligus trafik pembacaan.
Dalam desain berbasis WAL yang kami modelkan, layanan Git menggunakan manifes bersama di penyimpanan blob untuk mengurutkan pembaruan di seluruh repositori. Saat server menerima push, server mengunggah packfile yang masuk ke penyimpanan blob dan menyiapkan pembaruan ref di salinan lokal repositorinya. Untuk memublikasikan push, server menambahkan entri ke manifes bersama di penyimpanan blob yang mencatat pembaruan ref dan menunjuk ke packfile. Server Git kemudian memperbarui manifes menggunakan operasi compare-and-swap. Operasi ini mengganti manifes secara atomik atau menolak perubahan tersebut. Jika penulis lain telah memperbaruinya sementara itu, server membaca entri yang ditambahkan selama jeda tersebut, memeriksa bahwa pembaruan ref-nya masih valid, lalu mencoba lagi menggunakan packfile yang sudah diunggah.
Penulis dapat mengunggah packfile secara paralel, tetapi bahkan push ke cabang yang tidak saling terkait harus memperbarui manifes yang sama. Waktu bolak-balik untuk operasi compare-and-swap yang berhasil membatasi seberapa sering repositori dapat memublikasikan manifes baru. Penyimpanan blob berlatensi rendah membuat langkah ini berjalan cepat. Pemrosesan secara batch memungkinkan satu manifes memperbarui beberapa ref sekaligus, tetapi mengharuskan pembaruan mengantre dan meningkatkan latensi.
Penyimpanan blob dapat menerima packfile secara paralel, tetapi pembaruan pada manifes bersama tetap saling bersaing. Meningkatkan kapasitas penyimpanan blob saja tidak mengatasi hambatan tersebut. Setiap replika tetap perlu membaca dan mengindeks pack dari penyimpanan blob sebelum dapat menyajikannya secara lokal. Jika penulisan yang masuk lebih cepat daripada proses tersebut, replika akan tertinggal dan klien harus menunggu lebih lama untuk mengambil commit yang baru dikirim melalui push.
Di sinilah Pando hadir. Pando adalah arsitektur baru untuk melayani repositori Git, dengan tujuan meningkatkan skala hingga mampu menangani banyak klien yang melakukan push secara bersamaan. Berikut cara kerjanya:
Sebelum mengembangkan Pando, kami menduga bahwa kebutuhan kami dapat dipenuhi tanpa mengorbankan Git. Namun, kami perlu melonggarkan beberapa hal yang dianggap sebagai persyaratan oleh sebagian besar sistem Git:
Konsistensi di seluruh repositori: Menyediakan snapshot semua ref yang konsisten pada satu titik waktu membutuhkan koordinasi di seluruh repositori. Dalam sistem yang mengurutkan semua pembaruan ref secara terpusat, push ke cabang yang tidak saling terkait berebut akses pada satu titik koordinasi.
Packfile unik untuk setiap klien: Jika dua klien dengan riwayat lokal yang berbeda mengambil cabang yang sama, server mungkin perlu membuat pack berbeda untuk masing-masing klien agar tidak mengirim objek yang sudah mereka miliki. Pembuatan respons ini menghabiskan sumber daya CPU dan I/O disk, sehingga sulit ditingkatkan skalanya untuk melayani banyak pembaca.
Sebagai gantinya, Pando menyediakan model konsistensi yang lebih longgar (konsistensi kausal) untuk meningkatkan skala penulisan, dan menggunakan ulang packfile untuk meningkatkan skala pembacaan.
Saat klien melakukan push ke suatu cabang, Pando menyimpan packfile yang masuk sebagai blob yang tidak dapat diubah di penyimpanan blob. Setiap kali cabang berubah, Pando mencatat versi ref di penyimpanan metadata. Versi ini berisi ujung cabang yang baru, pack yang menyediakannya, serta versi ref sebelumnya yang menjadi dependensi pack tersebut. Penyimpanan blob dan penyimpanan metadata sama-sama dapat ditingkatkan skalanya secara horizontal.
Mengambil commit atau referensi dari Pando memerlukan pengiriman ulang serangkaian versi ref (packfile) ke klien, untuk merekonstruksi keadaan klien yang membuat versi ref yang sedang diambil. Ini memberikan bentuk konsistensi yang lebih lemah di seluruh repositori, sembari menjamin serializabilitas untuk pembaruan pada setiap ref. Konflik antarklien hanya terjadi saat mereka melakukan push ke ref yang sama secara bersamaan. Klien yang membaca beberapa cabang tidak dapat menganggap bahwa hasil pembacaan tersebut menggambarkan satu titik waktu yang sama di seluruh repositori.
Push ke trunk menambahkan pack inkremental yang berisi satu atau beberapa commit. Setiap push ke cabang samping kembali menyertakan commit sebelumnya dari cabang topik tersebut, sehingga klien dengan versi trunk yang diumumkan dapat mengambil cabang topik dalam satu pack.
Ini adalah bentuk konsistensi kausal. Untuk mengambil suatu referensi, pembaca mengamati riwayat penulis yang menghasilkannya. Selama push, Pando mengendalikan riwayat bersama yang disepakati klien dan server, sehingga klien menghasilkan packfile yang lebih mudah digunakan bersama.
Konsekuensinya, Pando tidak menyediakan keadaan semua referensi pada satu titik waktu selama operasi Git, maupun urutan pembaruan ref dalam operasi clone atau fetch. Sebagai contoh konkret, anggap cabang foo dan bar sama-sama diperbarui selama negosiasi clone penuh. Selama negosiasi, satu klien mungkin melihat foo pada versi 1 dan bar pada versi 2, sementara klien lain melihat foo pada versi 2 dan bar pada versi 1. Dalam praktiknya, pengguna dan agen tidak bergantung pada urutan global pembaruan ref, melainkan pada urutan pembaruan satu referensi.
Dalam simulasi ketiga model konsistensi ini, kami mengonfigurasi 1.000 klien agar masing-masing memilih cabang yang berbeda, lalu berulang kali melakukan push commit unik. Kami mengukur total waktu yang berlalu serta latensi dari push hingga fetch. Metrik kedua penting bagi beban kerja karena mengukur waktu bolak-balik yang dibutuhkan pengguna dan agen untuk berkolaborasi atau menggunakan sistem lain yang bergantung pada kontrol versi. Simulasi ini memodelkan perangkat keras mulai dari kelas standar hingga premium untuk mesin virtual dan sistem penyimpanan blob. Dalam simulasi ini, kami melonggarkan konsistensi di seluruh repositori untuk commit tiga fase.
Simulating Git Server Implementations
Follow each writer through repeated pushes, and separately measure when an independent reader can check out each commit.
Jalankan simulasi ini untuk membandingkan Pando dengan dua pendekatan lain. Pilih infrastruktur dari perangkat keras standar hingga premium, jumlah klien, dan pengaturan lainnya untuk melihat perbandingannya.
Sistem commit tiga fase yang dimodelkan menunjukkan latensi tulis yang jauh lebih tinggi saat dibebani karena disknya mencapai kapasitas maksimum. Sementara itu, sistem write-ahead log menunjukkan latensi baca yang jauh lebih tinggi karena alasan yang sama, saat server kesulitan mengimbangi pembacaan jurnal. Baik dalam pengujian nyata pada monorepo kami maupun dalam simulasi ini, Pando mendukung ribuan klien yang dapat melakukan push secara bersamaan tanpa menurunkan kinerja saat dibebani.
Saat menangani push, Pando memvalidasi packfile yang masuk sambil mengalirkannya ke penyimpanan blob, tanpa membongkarnya ke repositori lokal. Saat klien memulai fetch, Pando memilih pack tersimpan yang dibutuhkan pembaca, lalu pembaca mengunduhnya. Tidak ada server yang perlu menerapkan packfile ke repositori lokal atau melakukan operasi pembongkaran dan pengemasan yang memakan banyak sumber daya. Karena tugas utama server Pando adalah negosiasi dan pemilihan packfile, server ini dapat berjalan pada perangkat keras standar.
Ini memungkinkan Pando meningkatkan skalanya hingga batas kapasitas klien, jaringan antara klien dan Pando, serta layanan penyimpanan blob. Dengan kapasitas yang memadai, setiap klien yang bekerja pada cabang independen dapat mentransfer pack secepat koneksinya masing-masing. Untuk melayani lebih banyak klien pada kecepatan tersebut, kami dapat menambahkan server Pando serta meningkatkan skala penyimpanan metadata dan blob secara bersamaan.
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.
Agen mengedit file dan membuat commit secara lokal, lalu menjalankan git push, yang membuat packfile berisi perubahan tersebut untuk memperbarui cabang. Pando menerima pack dan mengalirkannya ke penyimpanan blob. Setelah memvalidasi push yang masuk dengan dekode satu lintasan, Pando menggunakan transaksi multikunci untuk mencatat versi ref di penyimpanan metadata kami dan memperbarui penunjuk ref secara atomik. Ini memungkinkan Pando menyediakan konsistensi “baca hasil penulisan sendiri”. Seluruh operasi penulisan ini membutuhkan pembacaan dan penulisan metadata.
Saat klien lain mengambil cabang tersebut, Pando memilih sekumpulan pack tersimpan berdasarkan riwayat yang sudah dimiliki klien. Operasi ini membutuhkan pembacaan metadata, dengan sebagai jarak terpendek dalam satuan versi ref antara yang dimiliki klien dan yang diinginkannya. Server mengembalikan URI packfile, dan klien mengunduhnya melalui layanan Express, yang menyimpan packfile dalam cache regional.
Desain ini mengandalkan tiga layanan inti arsitektur Pando: push, fetch, dan pemadatan.
Saat klien melakukan push, server Git konvensional ibarat pemain poker yang memperlihatkan semua kartunya: server mengungkapkan setiap cabang yang dimilikinya kepada klien. Klien lalu mengumpulkan commit, tree, dan blob untuk membuat packfile yang tidak menyertakan objek yang diketahuinya sudah dimiliki server.
Mengumumkan lebih banyak ref dapat memperkecil ukuran push. Namun, ini juga dapat membuat pack lebih sulit digunakan ulang karena klien lain mungkin belum memiliki objek yang tidak disertakan penulis.
Pando mengambil pendekatan berbeda. Alih-alih memperlihatkan semua kartunya, Pando hanya mengumumkan sejumlah cabang terpilih kepada klien. Ada dua alasan untuk ini:
Menyediakan tampilan yang konsisten untuk setiap ref membutuhkan banyak sumber daya. Dengan jutaan cabang, pengumuman ref saja dapat melebihi 100 MiB. Untuk menyediakannya, setiap server harus menjaga pemetaan tetap mutakhir, yang membutuhkan overhead koordinasi besar, atau menjalankan kueri basis data yang bebannya meningkat seiring jumlah ref.
Dengan hanya memberikan sebagian ref kepada klien, klien akan menghasilkan pack yang hanya bergantung pada objek yang secara eksplisit diizinkan server untuk tidak disertakan. Pelacakan dependensi memungkinkan Pando menggunakan ulang packfile yang dihasilkan klien dalam satu push sebagai respons untuk fetch berikutnya.
Makin banyak ref yang diumumkan server Git, makin banyak sumber yang dapat digunakan klien saat push untuk mengecualikan objek, sehingga pack yang dihasilkan lebih kecil. Namun, untuk menggunakan ulang packfile ini, server harus dapat membuktikan bahwa klien yang melakukan fetch memiliki semua dependensinya. Jika tidak, packfile mungkin tidak menyertakan objek yang dibutuhkan klien.
Sebaliknya, jika server Git tidak mengumumkan ref sama sekali, klien akan menghasilkan packfile yang berisi seluruh riwayat repositori. Packfile tersebut dapat digunakan oleh klien lain mana pun, tetapi konsekuensinya besar: mengambil cabang apa pun pada dasarnya menjadi clone penuh.
Mengumumkan lebih banyak ref menghasilkan packfile yang lebih kecil. Sebagian besar pengurangan ukuran dapat dicapai dengan mengumumkan satu ref saja.
Secara default, saat klien melakukan push cabang fitur baru, Pando hanya mengumumkan satu cabang trunk yang ditetapkan sebagai dasar. Ini menjaga ukuran pengumuman tetap sama meskipun jumlah cabang repositori bertambah, tetapi ada konsekuensinya. Saat engineer melakukan push berkali-kali ke satu cabang, atau menumpuk cabang fitur di atas cabang fitur lain, klien harus mengirim ulang semua yang ada di luar trunk.
Akibatnya, push cabang berikutnya menjadi lebih besar, mungkin berisi objek yang redundan, dan menggunakan bandwidth klien tambahan. Di monorepo kami, median ukuran packfile yang dihasilkan dengan cara ini di bawah 20 KiB, dan persentil ke-99 di bawah 3 MiB. Bagi kami, kompromi ini masuk akal karena pembaca yang sudah memiliki riwayat trunk yang diumumkan dapat mengambil cabang tersebut dengan satu packfile itu saja.
Setelah memilih kumpulan ref untuk diumumkan kepada klien, Pando menerima push dan memperbarui penyimpanan metadatanya. Pando membagi pembaruan metadata menjadi bagian sinkron dan asinkron. Pembaruan metadata sinkron memastikan klien dapat segera mengambil commit yang baru diperbarui. Proses ini menulis versi ref baru dan memperbarui pemetaan penunjuk ref ke versi baru tersebut, dengan menyimpan ujung cabang baru, dependensi yang digunakan untuk menegosiasikan packfile, serta tautan ke packfile yang dikirim melalui push.
Pada saat yang sama, pekerjaan asinkron, seperti penjadwalan pemadatan, dicatat dalam transaksi yang sama. Untuk push yang sangat besar, hanya sebagian commit yang ditulis secara sinkron. Dengan demikian, kami hanya membutuhkan jumlah pembacaan dan penulisan yang konstan ke penyimpanan metadata, berapa pun ukuran push.
Yang penting, server Pando dapat menerima push ke ref yang tidak saling terkait secara independen. Satu-satunya kunci yang diperebutkan adalah penunjuk ref, yang kami lindungi dengan operasi atomik serupa compare-and-swap. Kami dapat meningkatkan throughput tulis dengan menambahkan server Pando, unit penskalaan penyimpanan metadata, atau penyimpanan blob.
Saat klien melakukan fetch, klien memberikan dua kumpulan ID commit kepada server, yang dalam protokol dikenal sebagai have dan want. Pada server Git konvensional, server menghitung selisih himpunan antara objek yang dapat dijangkau dari have dan objek yang dapat dijangkau dari want, mengompresinya, lalu mengirim hasilnya kepada klien. Meskipun server Git sumber terbuka memiliki beragam indeks lokal untuk mempercepat operasi ini, pembuatan packfile tersebut dapat membutuhkan banyak komputasi dan umumnya sulit di-cache.
Sebagai gantinya, Pando menggunakan pemetaan commit yang dicatat saat push untuk mencari have dan want klien, lalu memetakannya ke kumpulan versi ref yang berisi commit tersebut. Setiap versi ref tersebut terhubung ke packfile yang sebelumnya diterima Pando. Server menggunakan informasi ini untuk menentukan kebutuhan klien, dengan memilih packfile yang mencakup kumpulan objek yang dibutuhkan klien.
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 memetakan have dan want dalam protokol Git ke urutan versi ref. Meminta cabang samping berarti juga meminta riwayat trunk hingga dependensi cabang tersebut.
Protokol Git hanya mengizinkan satu packfile dikirim dalam respons HTTP pada operasi clone atau fetch. Namun, fitur protokol Git yang kurang mendapat perhatian, packfile-uri, memungkinkan server mengembalikan tautan untuk mengunduh pack tambahan secara terpisah. Pando sepenuhnya menggunakan mekanisme ini dan hanya menyertakan packfile kosong langsung dalam respons. Untuk mendukung fitur ini, OpenAI menyumbangkan sejumlah peningkatan packfile-uri ke proyek induk.
Sistem OpenAI beroperasi di banyak wilayah geografis, sehingga kami mengelola klaster Pando dengan cache lokal di setiap wilayah. Layanan pengiriman pack, Express, menyimpan cache dari penyimpanan blob kami, dan layanan terpisah menyimpan cache dari penyimpanan metadata. Cache ini memungkinkan Pando melayani permintaan ID commit atau pembacaan khusus suatu ref sepenuhnya dari keadaan yang tersimpan dalam cache di klaster tersebut. Kami menggunakan hashing konsisten untuk merutekan permintaan packfile ke server Express, dengan tingkat keberhasilan cache di atas 99%.
Sistem cache ini menyediakan throughput yang sangat tinggi, sehingga mampu menggunakan seluruh kapasitas jaringan antara klien dan Pando. Dengan URI packfile dan cache lokal ini, kami telah memastikan bahwa operasi clone dan fetch dapat menggunakan seluruh kapasitas koneksi jaringan 10 Gbps, hanya dengan mesin virtual standar dari penyedia cloud.
Pada sebagian besar server Git, pengemasan ulang dan pembersihan objek yang tidak terpakai diperlukan untuk menjaga kinerja repositori yang terus berkembang. Kedua langkah ini membebaskan ruang penyimpanan, tetapi membutuhkan komputasi dan I/O yang intensif.
Pada repositori yang berubah cepat seperti monorepo OpenAI, tidak praktis untuk mengirim URI packfile tersendiri untuk setiap push yang terjadi sejak fetch terakhir. Setiap hari, klien perlu mengunduh puluhan ribu packfile untuk memutakhirkan trunk-nya.
Sebagai gantinya, Pando menggabungkan pack di latar belakang untuk mengurangi overhead ini, dengan desain yang mirip pemadatan pada log-structured merge-tree. Setiap push menghasilkan packfile yang disebut Pando sebagai “L0”, yaitu unit data terkecil yang dapat dikirim Pando. Seiring waktu, worker latar belakang mengumpulkan packfile ini ke dalam kelompok cakupan yang merentang sepanjang urutan versi ref, dengan ukuran yang meningkat secara geometris dari L0. Saat ini, monorepo kami menggunakan kelompok cakupan packfile hingga L4.
Kelompok cakupan ini memiliki dua fungsi. Setelah suatu kelompok cakupan cukup penuh, worker pemadatan akan mengambil alih kelompok tersebut dan menghasilkan satu packfile. Ini meningkatkan rasio kompresi dengan menata ulang objek di dalam packfile. Selain itu, kinerja negosiasi fetch juga meningkat karena kelompok cakupan ini berfungsi sebagai skip list atas versi ref. Dengan memindai indeks pada setiap tingkat secara bersamaan, Pando dapat memilih sekumpulan pack untuk dikirim kepada klien dalam waktu logaritmik. Kelompok cakupan packfile ini, baik yang sudah dikemas ulang maupun belum, memungkinkan perencanaan fetch yang lebih efisien, sehingga mengurangi kebutuhan throughput maupun latensi Pando.
Compaction in Pando
Small packfiles are grouped into larger covers across levels, improving compression and speeding up fetches.
Pemadatan menggabungkan packfile dari banyak versi ref di latar belakang, sehingga fetch menjadi lebih efisien dengan lebih sedikit file.
Berbeda dengan banyak server Git, packfile dipertahankan di setiap tingkat, bukan dibuang atau dihapus melalui pembersihan objek tak terpakai. Pack di setiap tingkat terus digunakan untuk melayani klien, berdasarkan have dan want yang mereka ajukan dalam fetch. Bagi klien yang menggunakan Pando sebagai API, membaca objek sembarang dari packfile cenderung membutuhkan lebih sedikit data dari L0 daripada L4, karena objek tersebut lebih kecil kemungkinannya dikompresi.
Pemadatan di Pando secara signifikan mengurangi total ukuran transfer ke klien sekaligus membuat beban perencanaan pack meningkat secara sublinear terhadap seberapa jauh klien tertinggal. Ini sangat penting untuk repositori besar yang berubah cepat, yang kami perkirakan akan menjadi kelaziman baru dalam pengembangan perangkat lunak.
Kami membangun Pando dengan Codex, dari awal proyek hingga produksi, hanya dalam 10 minggu. Inti Pando dirancang oleh satu engineer, diimplementasikan oleh tim-tim agen, dan diterapkan oleh segelintir engineer. Pando mulai melayani trafik produksi pada April tahun ini, dan merupakan bagian dari sistem alat pengelolaan kontrol kode sumber yang lebih besar di OpenAI.
Kami menggunakan beragam pendekatan untuk memvalidasi desain Pando. Kami menerapkan prinsip kami untuk membangun fondasi perangkat lunak yang lebih aman. Karena proyek ini dimulai dari nol, kami memilih mengimplementasikan Pando dalam Rust tanpa dependensi sistem lama. Kami menulis rangkaian uji kesesuaian agar agen dapat menguji perubahan mereka terhadap perilaku standar Git. Kami juga mengembangkan harness pengujian simulasi deterministik untuk memeriksa invarian dan sifat sistem. Dengan ini, kami dapat menemukan dan mengatasi kondisi balapan yang sulit terdeteksi serta regresi dalam negosiasi dan perencanaan pack.
Banyak model berkontribusi pada sistem ini; ketika model baru tersedia, kami menggunakannya untuk meningkatkan kinerja dan keandalan Pando. Dengan GPT‑6 Astra, agen menulis Lean dan TLA+ untuk memformalkan “teori Pando”, yang kami gunakan untuk menemukan dan memperbaiki bug dalam implementasi serta membuktikan sifat-sifat sistem. Beberapa sesi berkelanjutan berjalan selama ratusan jam untuk menemukan dan memperbaiki kasus tepi di Pando. Kami juga menjalankan agen sepanjang waktu untuk melakukan pengujian adversarial terhadap Git dan Pando menggunakan model berkemampuan Daybreak.
Pada penerapan awal, Pando berfungsi sebagai proksi yang meneruskan pembacaan dan penulisan ke sistem kontrol kode sumber kami yang sudah ada. Ini memungkinkan kami menjaga sinkronisasi Pando dan sistem sebelumnya, sehingga trafik dapat dialihkan ke Pando dengan aman tanpa waktu henti.
Sebagai bagian dari desain cache-nya, Pando juga dapat menghapus duplikasi dan menggabungkan permintaan sebelum meneruskannya melalui proksi. Ini memungkinkan kami menjaga alat yang sudah ada tetap berjalan baik sambil memindahkan sistem ke Pando satu per satu. Saat makin banyak beban kerja dipindahkan ke Pando, push dari beban kerja tersebut mengisi cache Pando dan meningkatkan keandalan backend kami sebelumnya.
Untuk mengoptimalkan kinerja saat menggunakan Pando, kami mulai mendistribusikan distribusi publik Git kami ke mesin developer dan di dalam aplikasi desktop ChatGPT. Untuk memigrasikan klien ke Pando secara bertahap, kami menggunakan pembungkus internal untuk CLI Git. Kami membuat banyak alat lain untuk menangani permintaan perubahan dan peninjauan kode, serta API untuk otomatisasi. Kami memigrasikan ratusan integrasi yang bergantung pada API lama, webhook, dan unduhan repositori, bersamaan dengan trafik Git.
Codex sendiri membantu developer dan tim melakukan peralihan. Kami membuat skill untuk menyiapkan checkout yang sudah ada dan memandu agen menggunakan sistem baru. Hasilnya, developer cukup meminta Codex untuk memigrasikan lingkungan pengembangan dan layanan mereka.
Peralihan ke Pando meningkatkan keandalan merge dan mengurangi median latensi push hampir 80%.
Pando dapat mengirimkan satu petabyte data Git per hari hanya dengan segelintir inti CPU. Pando telah menyajikan lebih dari 57 terabyte pack Git dalam satu jam, dengan lebih dari 99% byte tersebut berasal dari cache disk lokal.
Penggunaan ulang pack memungkinkan banyak klien mengunduh riwayat repositori yang sama dengan sedikit komputasi pada node yang melayaninya. Contoh ini menunjukkan satu repositori, tetapi Pando dirancang untuk mendukung jauh lebih banyak. Seiring meningkatnya kebutuhan, kami dapat meningkatkan skala pengiriman data dan negosiasi Git secara independen.
Membangun Pando di atas protokol dan klien Git membantu kami mengidentifikasi bug serta peluang untuk meningkatkan kinerja Git itu sendiri.
Misalnya, Pando menyajikan pack yang dapat digunakan ulang langsung kepada klien. Karena itu, pengunduhan dan pengindeksan pack tersebut menjadi bagian penting dalam membuat sistem yang cepat dan andal. Pekerjaan ini mengungkap kasus tepi pada unduhan serentak, pembersihan setelah transfer terputus, dan pelanjutan unduhan yang sebenarnya sudah selesai. Kami memperbaiki alur tersebut, menambahkan validasi saat menerima pack yang sudah ada untuk fetch, dan menyumbangkan hasilnya kembali ke proyek induk.
Kami juga perlu mengetahui penggunaan waktu dan bandwidth, sehingga kami menambahkan pelacakan unduhan packfile-uri serta penghitungan byte untuk pembuatan pack. Dengan begitu, lebih mudah diketahui apakah fetch yang lambat menghabiskan waktu untuk membuat pack atau mentransfernya.
Dalam build Git OpenAI(terbuka di jendela baru), klien dapat mengunduh dan mengindeks pack independen secara paralel. Kami menambahkan konsep “have eksplisit” agar server dapat mengumumkan ujung terkini suatu cabang secara terpisah dari riwayat yang boleh tidak disertakan dalam pack oleh klien yang melakukan push. Di Pando, ini memungkinkan klien menggunakan --force-with-lease untuk memeriksa bahwa cabang fitur belum berubah dan membuat pack hanya dengan trunk sebagai dasar. Siapa pun yang memiliki riwayat trunk tersebut kemudian dapat mengambil cabang menggunakan pack yang sama.
Beberapa peningkatan ini sudah tersedia di proyek induk Git, dan banyak lainnya ada di repositori publik kami. Kami terus bekerja sama dengan komunitas Git agar peningkatan ini bermanfaat juga di luar Pando. Anda dapat mengikuti pekerjaan tersebut di pelacak patch publik kami(terbuka di jendela baru).
Dengan meninjau ulang anggapan umum tentang cara meningkatkan skala repositori Git, Pando memungkinkan kami meningkatkan skala monorepo tanpa mengubah alur kerja Git yang sudah digunakan engineer dan agen kami. Di monorepo kami, Pando memungkinkan lebih banyak pekerjaan berjalan paralel, meningkatkan keandalan merge, dan mengurangi waktu yang dibutuhkan untuk memublikasikan perubahan kode. Selain untuk monorepo, kami juga menjajaki penggunaan Pando lainnya di OpenAI, termasuk beban kerja yang memerlukan banyak agen berkolaborasi pada repositori Git bersama.
Seiring bertambahnya perangkat lunak yang dihasilkan engineer dan agen, makin banyak tim di luar OpenAI akan menghadapi batasan kontrol kode sumber yang kami alami. Saat ini, kami sedang menjajaki cara untuk membagikan teknologi yang mendasari Pando kepada pengguna kami.
Pando dibangun di atas pengalaman dua dekade dari pengguna dan pengelola repositori Git. Kami akan terus mengembangkan Pando sambil bekerja sama dengan komunitas sumber terbuka Git untuk membagikan peningkatan yang bermanfaat bagi pengguna Git secara lebih luas.


