צמצום הגודל של עדכוני OTA

בדף הזה מתוארים שינויים שנוספו ל-AOSP כדי לצמצם שינויים מיותרים בקבצים בין גרסאות build. מיישמי מכשירים שמנהלים מערכות בנייה משלהם יכולים להשתמש במידע הזה כהנחיה לצמצום הגודל של העדכונים שלהם דרך האוויר (OTA).

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

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

מערכת בנייה יכולה ליצור תיקונים גדולים שלא לצורך בכמה דרכים. כדי לפתור את הבעיה הזו, ב-Android 8.0 ואילך, הוטמעו תכונות חדשות כדי להקטין את גודל הטלאי לכל קובץ diff. השיפורים שהובילו להקטנת הגודל של חבילות העדכון דרך האוויר (OTA) כוללים:

  • שימוש ב-ZSTD, אלגוריתם דחיסה כללי ללא אובדן נתונים לעדכונים של תמונות מלאות במכשירים שאינם A/B. אפשר להתאים אישית את ZSTD כדי לקבל יחסי דחיסה גבוהים יותר על ידי הגדלת רמת הדחיסה. רמת הדחיסה מוגדרת במהלך יצירת ה-OTA ואפשר להגדיר אותה באמצעות העברת הדגל --vabc_compression_param=zstd,$COMPRESSION_LEVEL
  • הגדלת גודל חלון הדחיסה שמשמש במהלך OTA. אפשר להגדיר את הגודל המקסימלי של חלון הדחיסה על ידי התאמה אישית של פרמטר ה-build בקובץ .mk של המכשיר. המשתנה הזה מוגדר כ- PRODUCT_VIRTUAL_AB_COMPRESSION_FACTOR := 262144
  • שימוש ב-Puffin recompression, כלי דטרמיניסטי לתיקון של deflate streams, שמטפל בפונקציות הדחיסה וההשוואה ליצירת עדכוני OTA של בדיקות A/B.
  • שינויים בשימוש בכלי ליצירת דלתא, למשל באופן השימוש בספרייה bsdiff לדחיסת תיקונים. ב-Android 9 ואילך, הכלי bsdiff בוחר את אלגוריתם הדחיסה שיניב את תוצאות הדחיסה הטובות ביותר לתיקון.
  • שיפורים ב- update_engine הביאו לצריכת זיכרון נמוכה יותר כשמחילים תיקונים לעדכוני מכשירים מסוג A/B.

בקטעים הבאים נדון בבעיות שונות שמשפיעות על הגודל של עדכוני OTA, בפתרונות שלהן ובדוגמאות להטמעה ב-AOSP.

סדר הקבצים

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

פתרון: כשמשתמשים בכלים כמו find ו-make עם פונקציית התו הכללי, צריך למיין את הפלט של הפקודות האלה לפני שמשתמשים בהן. כשמשתמשים ב-$(wildcard) או ב-$(shell find) בקובצי Android.mk, צריך למיין אותם גם כן. חלק מהכלים, כמו Java, ממיינים את הקלט, ולכן לפני שממיינים את הקבצים, צריך לוודא שהכלי שבו משתמשים לא עשה זאת כבר.

דוגמאות: תוקנו הרבה מקרים במערכת הבנייה המרכזית באמצעות פקודת המאקרו all-*-files-under המובנית, שכוללת את all-cpp-files-under (כי כמה הגדרות היו מפוזרות בקובצי makefile אחרים). פרטים נוספים זמינים במאמרים הבאים:

בניית ספרייה

בעיה: שינוי הספרייה שבה נוצרים הפריטים יכול לגרום להבדלים בקבצים הבינאריים. רוב הנתיבים ב-build של Android הם נתיבים יחסיים, ולכן __FILE__ ב-C/C++ לא מהווה בעיה. עם זאת, סמלי הניפוי באגים מקודדים את נתיב השם המלא כברירת מחדל, והערך .note.gnu.build-id נוצר מגיבוב של הקובץ הבינארי לפני ההסרה, ולכן הוא ישתנה אם סמלי הניפוי באגים ישתנו.

הפתרון: ב-AOSP, נתיבי ניפוי הבאגים הם יחסיים. פרטים נוספים זמינים ב-CL: https://android.googlesource.com/platform/build/+/6a66a887baadc9eb3d0d60e26f748b8453e27a02.

חותמות זמן

הבעיה: חותמות הזמן בפלט של ה-build גורמות לשינויים מיותרים בקובץ. סביר להניח שזה יקרה במיקומים הבאים:

  • __DATE__/__TIME__/__TIMESTAMP__ פקודות מאקרו בקוד C או C++.
  • חותמות זמן שמוטמעות בארכיונים מבוססי-ZIP.

פתרונות/דוגמאות: כדי להסיר את חותמות הזמן מהפלט של הבנייה, צריך לפעול לפי ההוראות שבהמשך בנושא __DATE__/__TIME__/__TIMESTAMP__ ב-C/C++‎ וחותמות זמן מוטמעות בארכיונים.

‫__DATE__/__TIME__/__TIMESTAMP__ ב-C/C++

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

חותמות זמן מוטמעות בארכיונים (zip, ‏ jar)

ב-Android 7.0 תוקנה הבעיה של חותמות זמן מוטמעות בארכיוני zip, על ידי הוספת -X לכל השימושים בפקודה zip. הפעולה הזו מסירה את ה-UID/GID של הבונה ואת חותמת הזמן המורחבת של Unix מקובץ ה-ZIP.

כלי חדש, ziptime (שנמצא ב /platform/build/+/android16-release/tools/ziptime/), מאפס את חותמות הזמן הרגילות בכותרות של קובצי ה-ZIP. פרטים נוספים מופיעים בקובץ ה-README.

כלי signapk מגדיר חותמות זמן לקובצי ה-APK, שיכולות להיות שונות בהתאם לאזור הזמן של השרת. פרטים נוספים זמינים ב-CL https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028.

כלי signapk מגדיר חותמות זמן לקובצי ה-APK, שיכולות להיות שונות בהתאם לאזור הזמן של השרת. פרטים נוספים זמינים ב-CL https://android.googlesource.com/platform/build/+/6c41036bcf35fe39162b50d27533f0f3bfab3028.

מחרוזות גרסה

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

פתרון: מסירים את מספר ה-build ממחרוזת הגרסה של ה-APK.

דוגמאות:

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

אם dm-verity מופעל במכשיר, כלי ה-OTA יזהו באופן אוטומטי את הגדרות ה-verity ויפעילו את חישוב ה-verity במכשיר. כך אפשר לחשב בלוקים של verity במכשירי Android, במקום לאחסן אותם כבייטים גולמיים בחבילת ה-OTA. בלוקים של Verity יכולים להשתמש בכ-16MB למחיצה של 2GB.

עם זאת, חישוב האמינות במכשיר יכול להימשך זמן רב. במיוחד, יכול להיות שיעבור הרבה זמן עד שקוד תיקון השגיאות של העברה יפעל. במכשירי Pixel, התהליך בדרך כלל נמשך עד 10 דקות. במכשירים בסיסיים, התהליך עשוי להימשך זמן רב יותר. אם רוצים להשבית את החישוב של אימות במכשיר, אבל עדיין להפעיל את dm-verity, אפשר לעשות זאת על ידי העברת --disable_fec_computation לכלי ota_from_target_files כשיוצרים עדכון OTA. הדגל הזה משבית את החישוב של אימות במכשיר במהלך עדכוני OTA. היא מקצרת את זמן ההתקנה של OTA, אבל מגדילה את גודל חבילת ה-OTA. אם במכשיר לא מופעלת התכונה dm-verity, להעברת הדגל הזה אין השפעה.

כלי בנייה עקביים

הבעיה: הכלים שיוצרים קבצים מותקנים צריכים להיות עקביים (אותו קלט צריך תמיד להפיק את אותו פלט).

פתרונות/דוגמאות: נדרשו שינויים בכלי הבנייה הבאים:

שימוש בכלי להשוואת גרסאות

במקרים שבהם אי אפשר למנוע שינויים בקבצים שקשורים לבנייה, ב-AOSP יש כלי להשוואת בנייה, target_files_diff.py, שאפשר להשתמש בו כדי להשוות בין שני חבילות קבצים. הכלי הזה מבצע השוואה רקורסיבית בין שני build, לא כולל שינויים נפוצים בקבצים שקשורים ל-build, כמו

  • שינויים צפויים בפלט של הבנייה (לדוגמה, בגלל שינוי במספר הבנייה).
  • שינויים שנובעים מבעיות מוכרות במערכת הבנייה הנוכחית.

כדי להשתמש בכלי להשוואת גרסאות, מריצים את הפקודה הבאה:

target_files_diff.py dir1 dir2

dir1 ו-dir2 הן ספריות בסיס שמכילות את קובצי היעד שחולצו לכל build.

שמירה על עקביות בהקצאת החסימות

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

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

כדי לפתור את הבעיה הזו, ב-Android 7.0 Google הרחיבה את כלי make_ext4fs כדי לשמור על הקצאת בלוקים עקבית בכל הגרסאות. הכלי make_ext4fs מקבל דגל -d base_fs אופציונלי שמנסה להקצות קבצים לאותם בלוקים כשיוצרים תמונת ext4. אפשר לחלץ את קובצי מיפוי הבלוקים (כמו קובצי המיפוי base_fs) מקובץ ה-ZIP של קובצי היעד של build קודם. לכל מחיצת ext4 יש קובץ .map בספרייה IMAGES (לדוגמה, IMAGES/system.map מתאים למחיצה system). אחר כך אפשר לבצע צ'ק-אין לקובצי base_fs האלה ולציין אותם באמצעות PRODUCT_<partition>_BASE_FS_PATH, כמו בדוגמה הזו:

  PRODUCT_SYSTEM_BASE_FS_PATH := path/to/base_fs_files/base_system.map
  PRODUCT_SYSTEM_EXT_BASE_FS_PATH := path/to/base_fs_files/base_system_ext.map
  PRODUCT_VENDOR_BASE_FS_PATH := path/to/base_fs_files/base_vendor.map
  PRODUCT_PRODUCT_BASE_FS_PATH := path/to/base_fs_files/base_product.map
  PRODUCT_ODM_BASE_FS_PATH := path/to/base_fs_files/base_odm.map

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

לא לעדכן אפליקציות

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