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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

הרכבה מחדש של מקורות Java בלבד (//src/main/java/com/google/devtools/build/lib/bazel:BazelServer_deploy.jar) לאחר שינוי של מחרוזת מחרוזת פנימית ב-AbstractContaineringSandboxedSpawn.JavaScript פי 3 מהר יותר (בממוצע של 20 גרסאות 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 מועיל בעיקר לניפוי באגים וליצירת פרופילים. הסימון הזה מאלץ את כל העובדים לצאת מהעבודה לאחר סיום ה-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 תיראה כך:

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 במחסן. לאחר מכן, Bazel מנתחת את התגובה הזו וממירה אותה באופן ידני לפרוטוקול WorkResponse. כדי לתקשר עם העובד המשויך באמצעות Protobuf מקודד בינארי במקום JSON, requires-worker-protocol יוגדר ל-proto באופן הבא:

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

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

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

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

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

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

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

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

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

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

קריאה נוספת

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