OpenAI

2 בנובמבר 2026

הנדסה

הרחבת הקיבולת של Git ב־OpenAI

בניית מערכת בקרת קוד להיקפי עבודה אג'נטיים, תוך השקעה באקוסיסטם של Git.

מאת ארון פריל, טיילור בלאו וטד ניימן, חברי הצוות הטכני

בטעינה…

בשנה האחרונה, מאגר הקוד המאוחד שלנו והדרישות ממנו גדלו בקצב מסחרר. היה ברור שעלינו לחדש את מערכת בקרת הקוד שלנו כדי לתמוך בהיקף העבודה המקבילית ש־Codex מאפשר.

בנינו את Pando, תשתית צד שרת חדשה למאגרי Git, בלי להפריע לעבודת ההנדסה ובלי לשנות את האופן שבו הצוות שלנו משתמש ב־Git. כיום Pando עומד מאחורי כל פעילות הפיתוח במאגר הקוד המאוחד של OpenAI. Pando תוכנן לעמוד בשתי מטרות שאפתניות: הראשונה, כתיבה שניתן להרחיב אופקית, עם אופטימיזציה לדחיפות מקביליות לענפים רבים. והשנייה, למקסם את תפוקת הקריאה, כך שלקוחות יוכלו להוריד נתונים מהר מספיק כדי לנצל במלואו את רוחב הפס הזמין להם.

עמדנו במטרות האלה תוך שמירה על תאימות לפרוטוקול וללקוח של Git, וכך אפשרנו למהנדסים ולסוכנים שלנו לשלב את Pando בצורה חלקה בתהליכי העבודה הקיימים שלהם. בסביבת הייצור, Pando קיצר את השהיית הדחיפה החציונית כמעט פי 5. כיום הוא מספק כמעט פטה־בייט של נתוני 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 מפנים זה לזה לפי המזהים שלהם: קומיטים מפנים לעצים, ועצים מפנים לעצים ולבלובים. הענף 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 יכול למצוא אותו באמצעות מעקב אחר קישורים מאותו קומיט דרך ההורים, העצים והבלובים שלו. במהלך אחזור, הלקוח מודיע לשרת אילו קומיטים הוא מבקש ואילו כבר נמצאים ברשותו. השרת מוצא את האובייקטים הנגישים מהקומיטים המבוקשים, משמיט את אלה שידוע לו שכבר נמצאים אצל הלקוח, ושולח את היתר כקובץ חבילה באותו פורמט שבו 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 שולט בהיסטוריה המשותפת שעליה מסכימים הלקוח והשרת, וכך מבטיח שהלקוחות ייצרו קובצי חבילה שקל יותר לשתף.

כתוצאה מכך, Pando אינו מספק מצב של כל ההפניות בנקודת זמן אחת במהלך פעולות Git, וגם לא סדר של עדכוני הפניות בתוך פעולת שכפול או אחזור. לדוגמה, נניח שהענפים 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, יחידות הרחבה למאגר המטא־נתונים או מאגרי בלובים.

ביצועי אחזור ושכפול במלוא מהירות הקו

כשלקוח מבצע אחזור, הוא מספק לשרת שתי קבוצות של מזהי קומיטים, המוכרות בפרוטוקול כרשומות 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 לטפל בבקשות למזהה קומיט או בקריאה מיוחדת של הפניה, בהתבסס אך ורק על המצב השמור במטמון של אותו אשכול. אנחנו משתמשים בגיבוב עקבי כדי לנתב בקשות לקובץ חבילה לשרתי 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, התחלנו להפיץ את ההפצה הציבורית שלנו של 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