שיטות מומלצות לשימוש ב-BigQuery Storage Write API

במאמר הזה מפורטות שיטות מומלצות לשימוש ב-BigQuery Storage Write API. לפני שקוראים את המסמך הזה, כדאי לקרוא את הסקירה הכללית של BigQuery Storage Write API.

הגבלת קצב יצירת הסטרימינג

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

במקרה של סטרימינג שנוצר על ידי אפליקציה, מומלץ להימנע מביצוע קריאות ל-CreateWriteStream בתדירות גבוהה. באופן כללי, אם חורגים מ-40 עד 50 שיחות בשנייה, זמן האחזור של קריאות ה-API גדל באופן משמעותי (יותר מ-25 שניות). מוודאים שהאפליקציה יכולה לקבל הפעלה במצב התחלתי (cold start), מגדילים בהדרגה את מספר הזרמים ומגבילים את קצב השיחות של CreateWriteStream. אפשר גם להגדיר מועד אחרון ארוך יותר כדי לחכות לסיום השיחה, וכך למנוע שגיאה מסוג DeadlineExceeded. יש גם מכסה לטווח ארוך יותר על השיעור המקסימלי של קריאות ל-CreateWriteStream. יצירת זרמים היא תהליך שדורש הרבה משאבים, ולכן הדרך הכי טובה להימנע מחריגה מהמגבלה הזו היא לצמצם את קצב יצירת הזרמים ולנצל באופן מלא את הזרמים הקיימים.

ניהול מאגר חיבורים

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

כשמשתמשים בזרם ברירת המחדל, אפשר להשתמש בריבוב של Storage Write API כדי לכתוב לכמה טבלאות יעד עם חיבורים משותפים. ה-Multiplexing מאגד חיבורים כדי לשפר את קצב העברת הנתונים ואת ניצול המשאבים. אם בתהליך העבודה שלכם יש יותר מ-20 חיבורים בו-זמנית, מומלץ להשתמש בריבוב. ריבוב זמין ב-Java וב-Go. פרטים על הטמעה ב-Java מופיעים במאמר שימוש בריבוב. פרטים על הטמעה ב-Go זמינים במאמר בנושא שיתוף חיבורים (multiplexing). אם משתמשים במחבר Beam עם סמנטיקה של 'לפחות פעם אחת', אפשר להפעיל ריבוב באמצעות UseStorageApiConnectionPool. המחבר Managed Service for Apache Spark מופעל כברירת מחדל עם Multiplexing.

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

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

כל קריאה ל-AppendRows יוצרת אובייקט חדש של כתיבת נתונים. לכן, כשמשתמשים במקור נתונים שנוצר על ידי אפליקציה, מספר החיבורים תואם למספר מקורות הנתונים שנוצרו. בדרך כלל, חיבור יחיד תומך ברוחב פס של לפחות ‎1MBps. הגבול העליון תלוי בכמה גורמים, כמו רוחב הפס של הרשת, הסכימה של הנתונים ועומס השרת, אבל יכול להיות גבוה מ-10MBps.

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

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

ממשק Storage Write API מאפשר כתיבה רק לסוף הנוכחי של הזרם, שמשתנה ככל שמוסיפים נתונים. המיקום הנוכחי בסטרימינג מצוין כהיסט מתחילת הסטרימינג.

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

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

  • ALREADY_EXISTS (StorageErrorCode.OFFSET_ALREADY_EXISTS): השורה כבר נכתבה. אפשר להתעלם מהשגיאה הזו.
  • OUT_OF_RANGE‏ (StorageErrorCode.OFFSET_OUT_OF_RANGE): פעולת כתיבה קודמת נכשלה. מנסים שוב מהכתיבה האחרונה שהצליחה.

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

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

לא לחסום שיחות ב-AppendRows

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

עדיפות לבקשות גדולות יותר

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

טיפול בעדכוני סכימה

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

‫Storage Write API תומך בסכימות של טבלאות באופן הבא:

  • בקשת הכתיבה הראשונה כוללת את הסכימה.
  • כל שורת נתונים נשלחת כמאגר אחסון לפרוטוקולים בינארי. ‫BigQuery ממפה את הנתונים לסכימה.
  • אפשר להשמיט שדות שניתן להגדיר בהם ערך null, אבל אי אפשר לכלול שדות שלא קיימים בסכימה הנוכחית. אם שולחים שורות עם שדות נוספים, Storage Write API מחזיר StorageError עם StorageErrorCode.SCHEMA_MISMATCH_EXTRA_FIELD.

אם רוצים לשלוח שדות חדשים במטען הייעודי (payload), צריך קודם לעדכן את סכימת הטבלה ב-BigQuery. ממשק Storage Write API מזהה שינויים בסכימה אחרי זמן קצר, בסדר גודל של דקות. כש-Storage Write API מזהה את השינוי בסכימה, הודעת התגובה AppendRowsResponse מכילה אובייקט TableSchema שמתאר את הסכימה החדשה.

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

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

אפשר גם להגדיר את JsonStreamWriter להתעלם משדות לא ידועים בנתוני הקלט. כדי להגדיר את ההתנהגות הזו, קוראים ל-setIgnoreUnknownFields. ההתנהגות הזו דומה לאפשרות ignoreUnknownValues כשמשתמשים בגרסה הקודמת של tabledata.insertAll API. עם זאת, זה עלול לגרום לאובדן נתונים לא מכוון, כי שדות לא ידועים מושמטים בלי התראה.