עובדים קבועים

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

עובד קבוע הוא תהליך ותיק שפועל על ידי שרת Bazel, שפועל כwrapper מסביב לכלי עצמו, (בדרך כלל מהדר) או "הכלי" עצמו. כדי להפיק תועלת מהעובדים הקבועים, הכלי צריך לתמוך ברצף של אוספים, והממשק צריך לתרגם בין ה-API של הכלי לבין פורמט הבקשה/תגובה שמתואר למטה. ניתן לקרוא לאותו עובד עם או בלי הסימון ב---persistent_worker באותו בניין, והוא אחראי להתחלת ולדבר כראוי עם הכלי, וכן לכיבוי עובדים ביציאה. לכל מופע של עובד מוקצה (אבל לא מועבר) ספרייה נפרדת בכפוף ל-<outputBase>/bazel-workers.

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

עובדים קבועים מטמיעים מספר שפות, כולל Java, Scala, Kotlin ועוד.

תוכניות שמשתמשות בזמן ריצה של NodeJS יכולות להשתמש בספריית העזרה @bazel/worker כדי להטמיע את פרוטוקול העובד.

שימוש בעובדים קבועים

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

bazel build //my:target --strategy=Javac=worker,local

שימוש בשיטת העבודה של העובדים במקום בשיטה המקומית יכול להאיץ את מהירות האיסוף באופן משמעותי, בהתאם ליישום. כשמדובר ב-Java, יצירה של גרסאות build יכולה להימשך פי 2 עד 4 פעמים, לפעמים אפילו יותר. הידור בזל הוא פי 2.5 מהר יותר בין העובדים. למידע נוסף, עיינו בקטע "בחירת מספר העובדים"

אם יש לכם גם סביבת build מרוחקת שתואמת לסביבת Build מקומית, תוכלו להשתמש באסטרטגיית הדינמיקה הניסיונית, שכוללת גם ביצוע מרחוק וגם ביצוע של עובד. כדי להפעיל את האסטרטגיה הדינמית, יש להעביר את הדגל --experimental_spawn_scheduler. האסטרטגיה הזו מאפשרת לעובדים לפעול באופן אוטומטי, כך שאין צורך לציין את השיטה worker, אבל עדיין אפשר להשתמש ב-local או ב-sandboxed כחלופה.

בחירת מספר עובדים

מספר המכונות שמוגדרות כברירת מחדל לכל משתמש הוא 4, אבל אפשר לשנות את ההגדרה הזו באמצעות הסימון worker_max_instances. יש הבחנה בין שימוש טוב במעבדים הזמינים לבין הכמות של הידור JIT ונתוני ההיטים שאתם מקבלים. עם יותר עובדים, יעדים רבים יותר ישלםו עלויות הפעלה של הרצת קוד שלא נכנס לג'קוזי ויגיעו למטמון קר. אם יש לכם מספר קטן של יעדים ליצור, עובד אחד יכול לתת את האיזון הטוב ביותר בין מהירות האיסוף לשימוש במשאבים (לדוגמה, אפשר לעיין בבעיה מס' 8586. הסימון ב-worker_max_instances מגדיר את המספר המקסימלי של מופעים של עובדים לכל קבוצה ושל סוגי דגלים (ראו בהמשך), כך שבמערכת מעורבת אפשר להשתמש בכמות זיכרון גדולה אם לא תשנו את ערך ברירת המחדל. לגבי גרסאות מצטברות, היתרון של מכונות מרובות הוא קטן עוד יותר.

תרשים זה מציג את זמני האיסוף של נקודת האחיזה ב-Bazel (יעד //src:bazel) בתחנת עבודה של Intel Xeon 3.5 GHz Linux עם 6 ליבות, עם זיכרון RAM בנפח 64GB. לכל הגדרה של עובד מתבצעות חמש גרסאות build נקיות, והמערכת משתמשת בממוצע של ארבע הארבע האחרונות.

תרשים של שיפורי הביצועים של גרסאות build נקיות

איור 1. תרשים של שיפורי ביצועים של גרסאות build נקיות.

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

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

שילוב של מקורות Java בלבד (//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar) אחרי שינוי של מחרוזת מחרוזת פנימית ב-AbstractContainerizingSedap.javascript מאפשר האצה של פי 3 (ממוצע של 20 גרסאות build מצטברות בעזרת build אחד לבניית חימום):

תרשים של שיפורי ביצועים של גרסאות מצטברות

איור 2. תרשים של שיפורי ביצועים של גרסאות מצטברות.

המהירות תלויה בשינוי. מהירות גבוהה של גורם 6 נמדדת במצב הנתון כאשר משתנה קבוע קבוע.

שינוי עובדים קבועים

אתם יכולים להעביר את הדגל --worker_extra_flag כדי לציין דגלים של סטארט-אפ לעובדים, לפי נוסחה. לדוגמה, העברה של --worker_extra_flag=javac=--debug מפעילה את ניפוי הבאגים עבור Javac בלבד. ניתן להגדיר סימון אחד בלבד של שימוש לכל שימוש בסימון הזה, ורק עבור תכונה אחת בלבד. לא רק העובדים יוצרים כל אחד מהם בנפרד, אלא גם וריאציות שונות בסימונים של הסטארט-אפ שלהם. כל שילוב של דגלים מתנוססים וסטארט-אפים משולב בWorkerKey, ואפשר ליצור עבור כל WorkerKey עד worker_max_instances עובדים. בקטע הבא מוסבר איך ההגדרה של הדגל יכולה לציין התרעות לגבי הגדרה.

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

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

הסימון --worker_quit_after_build מועיל בעיקר לניפוי באגים וליצירת פרופילים. הדגל הזה מאלץ את כל העובדים לצאת מהבנייה. תוכלו גם להעביר את --worker_verbose כדי לקבל יותר פלט מה העובדים עובדים. הדגל הזה מופיע בשדה verbosity ב-WorkRequest, כך שההטמעה של עובדים יכולה להיות גם יותר מפורטת.

העובדים מאחסנים את היומנים שלהם בספריית <outputBase>/bazel-workers, לדוגמה, /tmp/_bazel_larsrc/191013354bebe14fdddae77f2679c3ef/bazel-workers/worker-1-Javac.log. שם הקובץ כולל את מזהה העובד ואת הסיסמה. מאחר שאפשר להגדיר יותר מקוד אחד (WorkerKey) לכל אתר, יכול להיות שיוצגו יותר מ-worker_max_instances קובצי יומן לשירה מסוימת.

עבור גרסאות build של Android, אפשר לעיין בפרטים בדף הביצועים של Android Build.

הטמעת עובדים קבועים

במאמר יצירת עובדים קבועים מוסבר איך יוצרים עובדים.

בדוגמה הזו מוצגת ההגדרה של Starlark לעובד שמשתמש ב-JSON:

args_file = ctx.actions.declare_file(ctx.label.name + "_args_file")
ctx.actions.write(
    output = args_file,
    content = "\n".join(["-g", "-source", "1.5"] + ctx.files.srcs),
)
ctx.actions.run(
    mnemonic = "SomeCompiler",
    executable = "bin/some_compiler_wrapper",
    inputs = inputs,
    outputs = outputs,
    arguments = [ "-max_mem=4G",  "@%s" % args_file.path],
    execution_requirements = {
        "supports-workers" : "1", "requires-worker-protocol" : "json" }
)

לפי ההגדרה הזו, השימוש הראשון בפעולה הזו יתחיל בביצוע שורת הפקודה /bin/some_compiler -max_mem=4G --persistent_worker. לאחר מכן, בקשה ליצור את Foo.java תיראה כך:

הערה: במפרט מאגר הנתונים של הפרוטוקול נעשה שימוש ב- "snake case" (request_id). פרוטוקול ה-JSON משתמש ב- " Capel case" (requestId). במסמך הזה, נשתמש בנרתיק Camel בדוגמאות של JSON, אבל לא מדובר בנחשים כשמדברים על השדה, בלי קשר לפרוטוקול.

{
  "arguments": [ "-g", "-source", "1.5", "Foo.java" ]
  "inputs": [
    { "path": "symlinkfarm/input1", "digest": "d49a..." },
    { "path": "symlinkfarm/input2", "digest": "093d..." },
  ],
}

העובד מקבל זאת ב-stdin בפורמט JSON מופרד בשורה חדשה (כי requires-worker-protocol מוגדר כ-JSON). לאחר מכן, העובד מבצע את הפעולה ושולח WorkResponse בפורמט JSON ל-Bazel ב-stdout. לאחר מכן, בזל מנתחת את התגובה הזו וממירה אותה באופן ידני ל-WorkResponse Proto. כדי לתקשר עם העובד המשויך באמצעות פרוטוקול פרו-בינארי מקודד במקום JSON, requires-worker-protocol יהיה proto באופן הבא:

  execution_requirements = {
    "supports-workers" : "1" ,
    "requires-worker-protocol" : "proto"
  }

אם לא תכללו את requires-worker-protocol בדרישות הביצוע, Bazel יבחר כברירת מחדל בתקשורת של העובד כדי להשתמש בפרוטוקול.

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

כל עובד יכול לעבד עכשיו רק בקשה אחת בכל פעם. התכונה הניסיונית Multiplex worker מאפשרת להשתמש במספר שרשורים אם הכלי הבסיסי מחולק לשרשורים וה-wrapper מוגדר כדי להבין זאת.

בדוח ה-GitHub הזה, אפשר לראות דוגמאות של רכיבי wrapper של עובדים לדוגמה ב-Java וגם ב-Python. אם אתם עובדים ב-JavaScript או ב-TypeScript, תוכלו להיעזר בחבילת @bazel/worker ובדוגמה לעבודה של Nodejs.

איך עובדים משפיעים על ארגז חול?

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

כדי לאפשר שימוש תקין במטמון מהדר עם עובדים, מועבר תקציר עם כל קובץ קלט. לכן, המהדר או ה-wrapper יכולים לבדוק אם הקלט עדיין תקף ללא צורך לקרוא את הקובץ.

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

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

קריאה נוספת

מידע נוסף על עובדים קבועים זמין בכתובת: