פיצול מונוריפוזיטורי למאגרים נפרדים: git subtree ו-splitsh

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

היכולות שלנו:

שאלות נפוצות

העבודות האחרונות

  • פיתוח אפליקציית ווב עבור FEEDME
    פיתוח אפליקציית ווב עבור FEEDME
    1359
  • פיתוח חנות מקוונת לחברת FURNORO
    פיתוח חנות מקוונת לחברת FURNORO
    1330
  • עיצוב לוגו לחברת B2B Advance
    עיצוב לוגו לחברת B2B Advance
    776
  • פיתוח אפליקציית ווב עבור Enviok
    פיתוח אפליקציית ווב עבור Enviok
    1077
  • פיתוח לוגו חברת AIDER
    פיתוח לוגו חברת AIDER
    1022
  • פיתוח CRM עבור Chasseurs
    פיתוח CRM עבור Chasseurs
    1113

Split של מונורפו: מדוע נשברים הבנייה וההרשאות

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

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

צוואר הבקבוק השני — הוצאת הריפו והפרדת הרשאות. היסטוריה מלאה לאורך שנים שוקלת עשרות ג'יגה-בייט, ומכונת מפתח חדשה או סוכן בנייה מבזבזים עליה יותר זמן מאשר על העבודה עצמה.

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

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

בהמשך נסביר כיצד פיצול מונורפו מטפל בכל שלוש נקודות הצמיחה — מהכנת העץ וההרשאות ועד דרך לשמר את ההיסטוריה.

מה נותן הפיצול: מהירות בנייה ושחרורים בלתי תלויים

במונורפו כל שינוי מפעיל בנייה של כל העץ: תיקון בשירות אחד גורר התקנת תלויות ובדיקות עבור כל השאר. לאחר הפיצול הצינור בונה רק את הריפו של השירות שהשתנה — זמן הבנייה יורד מעשרות דקות לכמה דקות, ותור הבניות מפסיק להיות צוואר בקבוק שכולם מחכים בו בבת אחת.

שחרורים בלתי תלויים — תוצאה של אותו גבול. תג בריפו של שירות מפעיל רק את הצינור שלו, ולכן נעלם סוג הכשלים שבו "צוות אחד שבר את הענף המשותף וחסם את השחרור של כולם".

כל צוות משחרר את חלקו לפי לוח הזמנים שלו, ותאימות גרסאות נקבעת בחוזים ובגרסאות תלויות, ולא בענף משותף שכולם חייבים לשמור ירוק.

צמצום זמן השחזור נובע מאותה תכונה: rollback נוגע בריפו אחד ובתג אחד, ולא בבנייה מחדש של כל בסיס הקוד. אזור הנזק בעת תקלה מוגבל לשירות — שחרור כושל מוחזר לאחור תוך דקות, בלי לחכות ששאר הצוותים יחזירו את הענף המשותף למצב תקין.

גם היקף הגישה מצטמצם: הרשאות ניתנות ברמת הריפו, ולא על כל בסיס הקוד, ולכן קבלן או צוות חדש רואה רק את הספריות שלו, הסודות שלו וכללי ההגנה על הענפים שלו. תחומי האחריות של הצוותים הופכים ברורים — בעל הריפו אחראי לבנייה, לשחרור ולכוננות שלו, ולא מתמוסס בזרם הכללי של שינויים זרים.

שיטות split של מונורפו: השוואה ומגבלות

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

ההיסטוריה בכל גישה עוברת עם ייחוס ותאריכים — היא הולכת לאיבוד רק ב"כיווץ" מפורש של קומיטים. לכן המגבלות נבחרות לא לפי נפח ההיסטוריה, אלא לפי תדירות השינויים שילכו לשני הכיוונים.

גישה להעברת תת-פרויקט מתי מתאימה מגבלות הגישה
פיצול מלא לריפוים נפרדים הצוותים ומחזורי השחרור מתפצלים, הקוד המשותף מפסיק להיות משותף בנייה חוזרת דורשת הגדרה ידנית, קישורים רוחביים ותלויות משותפות נקרעים
הוצאה חלקית של תת-פרויקט עם שיקוף (git subtree) תת-הפרויקט נחוץ גם בתוך המונורפו וגם בחוץ, וצפויה תרומה חזרה היסטוריית קומיטים כפולה, סנכרון ידני, ובעת תיקונים משני הצדדים נוצרים קונפליקטים
הוצאת היסטוריה חד-פעמית (splitsh, git filter-repo) נחוץ snapshot לצוות חיצוני או לפרסום זרימה חד-כיוונית: שינויים מבחוץ חוזרים כפאצ'ים

לפני הבחירה בודקים היסטוריה וקישורים על clone לבדיקה: רק כך רואים מה הכלי עושה עם קומיטים של מיזוג וחתימות.

סדר העבודות: מביקורת היסטוריה ועד מסירת סכמות

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

  1. ביקורת היסטוריה וענפים פעילים. עוברים על הספריות והמיזוגים, מסמנים ענפים ארוכי-טווח ותגי שחרור — כך נקבעים הגבולות של הריפוים העתידיים.
  2. תכנון סכמת הריפוים. קובעים מה מוצאים בנפרד, איזה קוד נשאר משותף, כיצד קשורות הגרסאות ומי אחראי לכל חלק.
  3. העברה על עותק של הריפו. מריצים git subtree או splitsh על clone: ההיסטוריה עוברת עם ייחוס ותאריכים, והריפו המקורי ממשיך להיבנות.
  4. בדיקת שלמות. משווים מספר קומיטים, ענפים ותגים, בודקים תוכן לפי checksums ובונים את הפרויקט מהריפו החדש.
  5. הגדרת בנייה. מעבירים משימות בנייה לכתובות חדשות, מגדירים טריגרים והרשאות גישה, כדי שהשחרור יעבור בלי צעדים ידניים.
  6. מסירת נהלים והדרכה. מוסרים את סכמת הריפוים, תיאור משימות הבנייה וכללי העבודה עם ענפים, ועוברים עליהם עם הצוות.

אם הריפו המשותף ממשיך לחיות, מתארים בנפרד כיצד תיקונים חוזרים אליו וכיצד זה מנוטר.

ערכת מסירה: קונפיגורציות, סכמות, נהלים

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

  • קונפיגורציות פיצול — סט מסננים ל-git subtree split ומניפסט ל-splitsh: קידומות, ענפים, כללי העברת נתיבים. ריצה חוזרת מופעלת בפקודה אחת.
  • סכמות תלויות — מפת מודולים וספריות משותפות: מה עבר, מה נשאר במונורפו ואיפה עכשיו מקור האמת לכל חלק משותף.
  • מדיניות גישה — מטריצת הרשאות לפי ריפוים וענפים: מי מפרסם שינויים ישירות, מי רק מציע אותם, ולמי יש זכות ל-rollback כפוי.
  • תשתית בדיקה — תרחיש שמריץ את הפיצול על ענף בדיקה, משווה עצי קבצים לפני ואחרי, ועוצר אם סט הנתיבים השתנה.
  • תיעוד — הוראות לעבודה יומיומית: כיצד למשוך שינויים מהמקור המשותף וכיצד להעביר תיקון מריפו אחד לאחר.
  • נוהל שחרור שינויים — סדר גרסאות ותגים, הרגע שבו הריפו הופך לעצמאי, והליך הבנייה החוזרת.
  • יומן מיגרציה — טבלת התאמה בין נתיבים ישנים לחדשים ורשימת שינויים שאחריהם ההיסטוריה בריפוים מתפצלת.

מקרה בוחן: כיצד השתנו הבנייה וההרשאות לאחר הפיצול

מצב התחלתי

צוות המוצר החזיק הכול בעץ אחד Git: שירותי פלטפורמה, ספריות משותפות, frontend וקובצי קונפיגורציה. בנייה מלאה נמשכה כחצי שעה — מערכת הבנייה בנתה מחדש את כל המודולים בכל תיקון, וקובץ התלויות המשותף איפס את המטמון. גישה לקוד ניתנה בשלמותה או לא ניתנה בכלל, ולכן שותפים חיצוניים שלחו פאצ'ים שהיה צריך להחיל ידנית.

מה הועבר

את המונורפו השאירו כנקודת בנייה, ואל החוץ הוציאו שני ריפוים דרך git subtree split תוך שמירה על היסטוריית קומיטים וייחוס. עבור עצים גדולים יותר אותו מעבר מבוצע מהר יותר על ידי splitsh, אבל כאן הספיק המנגנון המובנה ולילה אחד של הכנה.

ייבוא חוזר הפכו לקבוע: ענף מהריפו שהוצא נמשך לעץ הראשי ועובר סקירה, ולכן תיקונים של שותפים לא הולכים לאיבוד בפיצול הבא.

מדדים לפני ואחרי

הבנייה הצטמצמה מחצי שעה לכמה דקות: נבנה מחדש רק השירות שנגעו בו, ולא כל העץ ולא מודולים משותפים. קונפליקטים בשחרור ירדו מחמישה-שישה לאחד לשחרור — תיקונים בספריות משותפות כבר לא מתנגשים באותו קומיט לפני הוצאה. שיעור הכשלים בגלל קוד זר ירד מחלק חמישי למקרים בודדים: לכל צוות יש עץ משלו ובדיקות משלו.

במה git subtree שונה מ-splitsh ומה לבחור

שתי הדרכים מעבירות היסטוריה, אבל מתנהלות איתה אחרת. git subtree — מנגנון מובנה: הוא מוציא ספריית משנה לענף נפרד בלי לאבד קומיטים, ויודע למשוך שינויים בחזרה. Splitsh — כלי נפרד: עובר על העץ, בוחר את הנתיבים הדרושים ויוצר סט קומיטים חדש, ואז שולח אותו לריפו היעד כשיקוף.

ההבדל ניכר במה שנשאר בריפו היעד. Subtree מוסיף קומיטים טכניים של מיזוג בכל החלפה: ההיסטוריה שלמה, אבל הענף מצטבר בנקודות חיבור טכניות. Splitsh בונה מחדש קומיטים תוך שמירה על המחבר, התאריך וההודעה — ופלט הוא רצועה ליניארית.

בנוסף יש מגבלה קשיחה: subtree דורש ספריית משנה שלמה, בעוד splitsh מסנן לפי רשימת נתיבים ושורד פריסה שבה קבצי המודול מפוזרים על פני העץ.

ייבוא חוזר — נקודת מחלוקת עקרונית. Subtree תוכנן להחלפה דו-כיוונית: תיקונים מהריפו שהוצא חוזרים במיזוג של ספריית משנה, וקונפליקטים נפתרים כרגילים. Splitsh עובד בכיוון אחד: השיקוף רק מקבל, והזרימה חזרה נבנית בנפרד — עם כלל מפורש איזה מקור נחשב ראשי.

הבחירה המעשית היא כזו. מעט ריפוים והחלפה דו-כיוונית חיה — subtree סוגר את המשימה בלי תלויות חיצוניות. עשרות ספריות משנה, רצועה ליניארית ויכולת שחזור מקונפיגורציה — splitsh צפוי יותר. תרחיש ביניים: splitsh לחיתוך, subtree או החלפת פאצ'ים לייבוא חוזר.

ספקות טכניים: היסטוריה, גישות, ייבוא חוזר

ההתנגדות הנפוצה ביותר היא שבעת הוצאת תת-פרויקט היסטוריית הקומיטים תתכווץ לקומיט טכני אחד ועבודת הצוות תתאפס.

זה לא נכון: git subtree split בונה מחדש היסטוריה רק לפי הספרייה שצוינה, תוך שמירה על מחברים, תאריכים והודעות, ו-splitsh-lite עושה אותו דבר דרך מטמון עצים — פיצול חוזר אחרי קומיטים חדשים מחושב בשניות, כי חלקים שחושבו קודם עוברים שימוש חוזר.

ההתנגדות השנייה — השבתת צוותים ואובדן הרשאות גישה. הספרייה עוברת כענף נפרד, הבנייה בריפו החדש עולה במקביל, והזרימה הראשית עוברת רק אחרי ריצה ירוקה. הרשאות ניתנות לפי אותן קבוצות, והריפו הקודם בזמן ההשוואה פתוח לקריאה בלבד.

האם היסטוריית הקומיטים באמת נשמרת?

משווים לפני ההעברה: מספר קומיטים לפי נתיב, hashes של השינויים האחרונים, ייחוס ותאריכים. פער נראה כבר בבדיקה הזו, ולא אחרי שבוע מהמעבר, כשכבר אי אפשר לשחזר את ההיסטוריה.

מה קורה עם צינורות הבנייה?

תיאורי הבנייה עוברים יחד עם הקוד, והטריגרים עוברים לריפו החדש. הצינור הישן ממשיך לעבוד עד רגע המעבר — rollback נמשך דקות ולא דורש לבנות מחדש את סכמת הבנייה.

כיצד בנוי ייבוא חוזר של שינויים?

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

נדון במשימה: מה להכין לשיחה הראשונה

כדי שהערכת הפיצול של מונורפו תישען על עובדות ולא על הנחות, לשיחה הראשונה מספיק לאסוף נתוני פתיחה. פיצול דרך git subtree ו-splitsh פותר משימות שונות: הראשון שומר היסטוריה ומאפשר לסנכרן שינויים חזרה, השני חותך ספרייה עם היסטוריה וייחוס מלאים במעבר אחד.

מה לבחור — תלוי במבנה המודולים, בקשרים ביניהם ובאופן שבו בנויים הבנייה והאספקה. לכן מתחילים לא מהכלי, אלא מהריפו שלכם.

מה להכין לדיון במשימה:

  • מבנה הריפו: ספריות שורש, היכן נמצאות ספריות משותפות וקבצים טכניים, כיצד מודולים מפנים זה לזה;
  • רשימת צוותים ותחומי אחריות: מי עובד עם אילו ספריות ולמי נחוץ ריפו נפרד;
  • דרישות להיסטוריה: האם לשמר קומיטים, מחברים, ענפים ותגים, והאם חשובה רציפות מספור השחרורים;
  • קשרים לבנייה ולאספקה: אילו משימות בנייה, סביבות וארטיפקטים תלויים בריפו הנוכחי;
  • תלויות בין מודולים: חבילות משותפות, תת-מודולים, נתיבים מקומיים — כל מה שיהפוך לחיצוני לאחר הפיצול;
  • דרישות גישה: מי ומה אמור לראות לאחר ההוצאה, והאם נדרש בידוד קוד בין צוותים;
  • סדר ומגבלות: איזה מודול מוציאים ראשון, ומה לא אמור להיעצר בתהליך.

עם סט נתוני הפתיחה הזה נחזור עם הערכת עבודות ותוכנית פיצול: רצף הוצאת המודולים, נקודות rollback, סדר תיקון הבנייה והרשאות הגישה. תארו את המשימה ושתלחו את מה שכבר קיים — את השאר נדייק בשיחה.