אם מורידים אובייקט גדול ב-Cloud Storage באופן סדרתי דרך חיבור יחיד, יכול להיות שרוחב הפס הזמין ברשת לא ינוצל. כדי לשפר את קצב ההורדה של אובייקטים גדולים באמצעות ספריות הלקוח של Cloud ל-Rust, אפשר לחלק אובייקט לפלחים של טווחי בייטים שנקראים פסים (stripes) ולקרוא את הפלחים האלה בו-זמנית.
במאמר הזה נסביר איך להפיץ אובייקטים גדולים לבדיקה באמצעות הרכבת אובייקטים, להוריד טווחים של בייטים בו-זמנית באמצעות ReadRange::segment ולכתוב פסים לקובץ מקומי בהיסטים קבועים. אם אתם משתמשים חדשים בספריית הלקוח, קודם קראו את המאמר שימוש ב-Cloud Storage עם Rust.
לפני שמתחילים
לפני שמריצים את הקוד במסמך הזה, צריך להשלים את הדרישות המוקדמות הבאות:
- יוצרים או בוחרים פרויקט ב-Google Cloud שהחיוב מופעל בו.
- יוצרים קטגוריה של Cloud Storage לבדיקה ייעודית בפרויקט.
- מגדירים את Application Default Credentials בסביבה שלכם.
- מקצים לחשבון המשתמש תפקיד בניהול זהויות והרשאות גישה (IAM) עם הרשאה לקרוא, לכתוב, ליצור ולמחוק אובייקטים בקטגוריה, כמו אדמין לניהול אחסון (
roles/storage.admin).
יחסי תלות
מוסיפים את ספריית הלקוח של Cloud Storage ואת ה-crates התומכים לקובץ Cargo.toml:
cargo add google-cloud-storage google-cloud-gax anyhow bytes futures
cargo add tokio --features macros,rt-multi-thread
יצירת אובייקטים גדולים לבדיקה
כדי לבדוק הורדות מחולקות בלי להעלות גיגה-בייטים של נתונים ברשת, מעלים אובייקט seed קטן ומשתמשים בפעולת ה-compose של Cloud Storage כדי לשרשר עותקים בצד השרת. בתוך פונקציה אסינכרונית של seed שמקבלת את client, control ו-bucket_name בפורמט projects/_/buckets/BUCKET_ID, מבצעים את הפעולות הבאות:
כדי להפנות לאובייקטים קיימים בבקשות הרכבה, מייבאים את
SourceObject:מעלים אובייקט ראשוני בנפח 1 MiB בשם
1MiB.txt:כדי ליצור אובייקט בגודל 32 MiB בשם
32MiB.txt, משרשרים 32 עותקים של1MiB.txtבשרת:כדי ליצור אובייקט בנפח 1 GiB בשם
1GiB.txt, משרשרים 32 עותקים של32MiB.txt:כדי ליצור אובייקטים לבדיקה בחזקות של שתיים מ-
1GiB.txt(2GiB.txt,4GiB.txt,8GiB.txt,16GiB.txtו-32GiB.txt), חוזרים על פעולת ההרכבה:
תיאום הורדות מקבילות של רצועות
כדי להוריד אובייקט במקביל, שולחים שאילתה לגבי הגודל והגנרציה שלו, מקצים מראש את קובץ היעד המקומי ויוצרים עתיד אחד לכל פס. בתוך פונקציית download שמקבלת את client, control, bucket_name, object_name, stripe_size ו-destination, מבצעים את הפעולות הבאות:
אחזור המטא-נתונים של האובייקט באמצעות
StorageControl::get_objectכדי לבדוק את הגודל הכולל של האובייקט ולנעול את הקריאות הבאות לדור יחיד:יוצרים את קובץ היעד ומקצים לו מראש את הגודל המדויק של האובייקט, כדי שמשימות ה-worker יוכלו לכתוב בהיסטים עצמאיים של בייטים:
מחשבים את ההיסט של כל פס באמצעות
stripe_sizeבתורlimitויוצריםwrite_stripeעתידי לכל פלח, ומוסיפים פס חלקי בסוף אם גודל האובייקט לא מתחלק באופן שווה ב-limit:מריצים את כל ה-futures של Stripe בו-זמנית עם
futures::future::join_allומפיצים את כל השגיאות:
הורדה וכתיבה של פס בודד
כל משימת write_stripe קוראת טווח בייטים יחיד מ-Cloud Storage וכותבת את הנתונים שהתקבלו במיקום המתאים ב-file handle המקומי המשותף. כדי להטמיע את write_stripe, מבצעים את השלבים הבאים:
מייבאים את
ReadRangeומגדירים את הפונקציהwrite_stripe:יוצרים כלי ליצירת בקשות
ReadObjectבאמצעותStorage::read_object, ושולחים את הבקשה אחרי שמגדירים את אפשרויות היצירה וטווח הקריאה בשלבים הבאים:כדי להצמיד את כל הפסים לאותו דור של אובייקטים ולמנוע קריאות לא עקביות אם תהליך אחר מחליף את האובייקט, צריך להפעיל את
set_generationבבונה הבקשות לפני שמפעילים אתsend:כדי להגביל את הקריאה לטווח הבייטים של ה-stripe, צריך להפעיל את
set_read_rangeעםReadRange::segment(offset, limit)בבונה הבקשות:מעבירים בסטרימינג קטעים מהקורא וכותבים כל קטע לקובץ היעד ב-
current_posבתוךtokio::task::spawn_blocking. בספרייה הרגילה של Rust אין שיטת כתיבה לפי מיקום שמתאימה בפלטפורמות שונות, אז בדוגמה הזו משתמשים ב-FileExt::write_all_atב-Unix וב-FileExt::seek_writeב-Windows:
שיקולי ביצועים
יש כמה גורמים שקובעים את קצב העברת הנתונים האפקטיבי של הורדות מחולקות:
- מהירות הדיסק המקומי. כתיבת מקטעים לאחסון מקומי איטי יכולה להפוך לצוואר בקבוק. שימוש באחסון SSD מקומי עם תפוקה גבוהה עוזר לשמור על קצב קלט/פלט של הדיסק שגבוה מקצב הקריאה ברשת.
- קיבולת המעבד (CPU) של המכונה הווירטואלית. Cloud Storage מצפין את נתוני האובייקטים במעבר, כך שהלקוח מפענח כל קבוצת נתונים שהוא מקבל. מוודאים שבמכונה הווירטואלית יש מספיק מעבדי vCPU כדי לטפל בפענוח TLS בו-זמנית בין פסים.
- המיקום של הקטגוריה והמכונה הווירטואלית. הורדה בין אזורים גורמת לזמן אחזור ברשת WAN ומגבילה את קצב הנתונים. אם אפשר, כדאי למקם את המכונה הווירטואלית באותו אזור שבו נמצאת הקטגוריה.
אתם יכולים גם להתנסות בכוונון של stripe_size כדי להימנע מהשארת פס שאריות קטן מאוד או מיצירת יותר מדי בקשות טווח זעירות.
השלמת הקוד
בדוגמה הבאה משולבות הפונקציות seed, download ו-write_stripe במודול שלם:
כשמריצים את download על אובייקט בגודל 32MiB עם stripe_size בגודל 1MiB, הפלט דומה לפלט הבא, שמדווח על גודל ההורדה, הזמן שחלף, מספר הפסים ורוחב הפס בפועל:
Completed 32.00 MiB download in 412.5ms, using 32 stripes, effective bandwidth = 77.58 MiB/s
הסרת המשאבים
כדי להימנע מצבירת חיובים בחשבון Google Cloud על אובייקטים לבדיקה שנוצרו על ידי seed, בסיום הבדיקה מחקו את האובייקטים לבדיקה ומחקו את הקטגוריה לבדיקה. צריך להסיר גם את קובץ היעד המקומי.