אתם יכולים להשתמש בשיטות המומלצות שמופיעות כאן כחומר עזר קצר של הדברים שחשוב לזכור כשמפתחים אפליקציה שמשתמשת ב-Datastore. אם רק התחלתם להשתמש ב-Datastore, יכול להיות שהדף הזה הוא לא המקום הכי טוב להתחיל בו כי הוא לא מלמד את העקרונות הבסיסיים של אופן השימוש ב-Datastore. למשתמשים חדשים מומלץ להתחיל עם המדריך למתחילים ב-Datastore.
כללי
- תמיד צריך להשתמש בתווים בתקן UTF-8 בשמות של מרחבי שמות, שמות של סוגים, שמות של מאפיינים ושמות של מפתחות בהתאמה אישית. תווים שאינם UTF-8 שמשמשים בשמות האלה עלולים להפריע לפונקציונליות של Datastore. לדוגמה, תו שאינו UTF-8 בשם של מאפיין יכול למנוע יצירה של אינדקס שמשתמש במאפיין.
- אין להשתמש בקו נטוי (
/) בשמות של סוגים או בשמות של מפתחות בהתאמה אישית. השימוש בלוכסנים בשמות האלה עלול להפריע לפונקציונליות עתידית. - לא מומלץ לאחסן מידע רגיש במזהה פרויקט Cloud. יכול להיות שמזהה הפרויקט ב-Cloud יישמר גם אחרי שהפרויקט יסתיים.
- כשיטה מומלצת לתאימות לכללי הפרטיות, אנחנו ממליצים לא לאחסן מידע רגיש בשמות של ישויות ב-Datastore או בשמות של מאפייני ישויות.
קריאות ל-API
- כדאי להשתמש בפעולות אצווה לקריאות, לכתיבות ולמחיקות במקום בפעולות בודדות. פעולות אצווה יעילות יותר כי הן מבצעות כמה פעולות עם אותה תקורה כמו פעולה אחת.
- אם עסקה נכשלת, חשוב לנסות לבטל את העסקה. החזרה למצב הקודם מצמצמת את זמן האחזור של ניסיון חוזר לבקשה אחרת שמתחרה על אותם משאבים בעסקה. חשוב לדעת שגם פעולת החזרה לגרסה קודמת עלולה להיכשל, ולכן צריך להתייחס אליה כאל ניסיון בלבד.
- אם אפשר, כדאי להשתמש בשיחות אסינכרוניות במקום בשיחות סינכרוניות.
שימוש בקריאות אסינכרוניות מצמצם את ההשפעה של זמן האחזור. לדוגמה, נניח שיש אפליקציה שצריכה את התוצאה של
lookup()סינכרוני ואת התוצאות של שאילתה לפני שהיא יכולה להציג תגובה. אם ל-lookup()ולשאילתה אין תלות בנתונים, אין צורך להמתין באופן סינכרוני עד ש-lookup()יסתיים לפני הפעלת השאילתה.
ישויות
- קיבוץ נתונים שקשורים זה לזה בקבוצות ישויות. קבוצות ישויות מאפשרות שאילתות של ישויות אב, שמחזירות תוצאות עקביות. שאילתות של ישויות אב סורקות במהירות קבוצת ישויות עם מינימום פעולות קלט/פלט, כי הישויות בקבוצת ישויות מאוחסנות במקומות קרובים פיזית בשרתים של Datastore.
- מומלץ להימנע מכתיבה לקבוצת ישויות יותר מפעם אחת בשנייה. כתיבה בקצב קבוע מעל המגבלה הזו גורמת לכך שקריאות עקביות בסופו של דבר יהיו עקביות פחות, מובילה לפסק זמן בקריאות עקביות מאוד וגורמת לירידה בביצועים הכוללים של האפליקציה. כתיבה של קבוצת ישויות או טרנזקציה נחשבת ככתיבה אחת בלבד במסגרת המגבלה הזו.
- אל תכללו את אותה ישות (לפי מפתח) כמה פעמים באותה פעולת commit. הכללה של אותה ישות כמה פעמים באותו קומיט עלולה להשפיע על זמן האחזור של Datastore.
מקשים
- אם לא מספקים שמות של מפתחות בזמן יצירת הישות, המערכת יוצרת אותם באופן אוטומטי. הם מוקצים כך שהם יתחלקו באופן שווה במרחב המפתחות.
- אם משתמשים בשם מותאם אישית למפתח, צריך להשתמש תמיד בתווים בקידוד UTF-8, למעט בקו נטוי (
/). תווים שאינם בקידוד UTF-8 משבשים תהליכים שונים, כמו ייבוא גיבוי של Datastore אל Google BigQuery. A קו נטוי עלול להפריע לפונקציונליות עתידית. - למפתח שמשתמש במזהה מספרי:
- אל תשתמשו במספר שלילי כמזהה. מזהה שלילי עלול להפריע למיון.
- אל תשתמשו בערך
0(אפס) למזהה. אם כן, תקבלו מזהה שהוקצה באופן אוטומטי. - אם אתם רוצים להקצות באופן ידני מזהים מספריים משלכם לישויות שאתם יוצרים, אתם יכולים להשתמש בשיטה
allocateIds()כדי שהאפליקציה שלכם תקבל בלוק של מזהים. הפעולה הזו תמנע מ-Datastore להקצות אחד ממזהי המספרים הידניים שלכם לישות אחרת.
אם אתם מקצים מזהה מספרי ידני או שם מותאם אישית לישויות שאתם יוצרים, אל תשתמשו בערכים שעולים באופן מונוטוני, כמו:
1, 2, 3, …, "Customer1", "Customer2", "Customer3", …. "Product 1", "Product 2", "Product 3", ….אם אפליקציה יוצרת נפח תנועה גדול, מספור רציף כזה עלול ליצור נקודות חמות שמשפיעות על זמן האחזור של Datastore. כדי להימנע מהבעיה של מזהים מספריים עוקבים, צריך לקבל מזהים מספריים מהשיטה
allocateIds(). השיטהallocateIds()יוצרת רצפים מפוזרים היטב של מזהים מספריים.אם מציינים מפתח או שומרים את השם שנוצר, אפשר לבצע בהמשך
lookup()עקבי על הישות הזו בלי לשלוח שאילתה כדי למצוא את הישות.
מדדים
- אם לא תצטרכו להשתמש בנכס מסוים בשאילתות, אל תכללו אותו באינדקסים. אינדוקס של נכס שלא לצורך עלול להגדיל את זמן האחזור כדי להשיג עקביות, ולהגדיל את עלויות האחסון של רשומות האינדקס.
- מומלץ להימנע משימוש ביותר מדי אינדקסים מורכבים. שימוש מוגזם באינדקסים מורכבים עלול להוביל להגדלת זמן האחזור כדי להשיג עקביות, ולהגדלת עלויות האחסון של רשומות האינדקס. אם אתם צריכים להריץ שאילתות אד-הוק על מערכי נתונים גדולים בלי להגדיר מראש אינדקסים, אתם יכולים להשתמש ב-Google BigQuery.
- אל תבצעו אינדוקס של מאפיינים עם ערכים שעולים באופן מונוטוני (כמו
NOW()חותמת זמן). תחזוקה של אינדקס כזה עלולה להוביל לנקודות חמות שמשפיעות על זמן האחזור של Datastore באפליקציות עם שיעורי קריאה וכתיבה גבוהים. הנחיות נוספות לטיפול במאפיינים מונוטוניים מפורטות בקטע שיעורי קריאה/כתיבה גבוהים לטווח מפתחות מצומצם שבהמשך.
מאפיינים
- תמיד צריך להשתמש בתווים בפורמט UTF-8 לנכסים מסוג string. תו שאינו בפורמט UTF-8 במאפיין מסוג מחרוזת עלול להפריע לשאילתות. אם אתם צריכים לשמור נתונים עם תווים שאינם UTF-8, השתמשו במחרוזת בייטים.
- אין להשתמש בנקודות בשמות של נכסים. נקודות בשמות של מאפיינים מפריעות ליצירת אינדקס של מאפייני ישות מוטמעים.
שאילתות
- אם אתם צריכים לגשת רק למפתח מתוצאות השאילתה, אתם יכולים להשתמש בשאילתה של מפתחות בלבד. שאילתה שמחזירה רק מפתחות מחזירה תוצאות עם זמן אחזור נמוך יותר ועלות נמוכה יותר מאשר אחזור של ישויות שלמות.
- אם אתם צריכים לגשת רק למאפיינים ספציפיים של ישות, אתם יכולים להשתמש בשאילתת הקרנה. שאילתת הקרנה מחזירה תוצאות עם זמן אחזור נמוך יותר ועלות נמוכה יותר מאשר אחזור של ישויות שלמות.
- באופן דומה, אם אתם צריכים לגשת רק למאפיינים שנכללים במסנן השאילתות (לדוגמה, אלה שמפורטים בסעיף
order by), אתם יכולים להשתמש בשאילתת הקרנה. - אל תשתמשו בהזחות. במקום זאת, צריך להשתמש בסמנים. שימוש בהיסט רק מונע את החזרת הישויות שדילגתם עליהן לאפליקציה, אבל הישויות האלה עדיין מאוחזרות באופן פנימי. הישויות שדילגתם עליהן משפיעות על זמן האחזור של השאילתה, והחיוב על פעולות הקריאה שנדרשות לאחזור שלהן יחול על האפליקציה שלכם.
- אם אתם צריכים מודל עקביות חזק בשאילתות, אתם צריכים להשתמש בשאילתא לגבי ישות אב.
(כדי להשתמש בשאילתות של צאצאים, קודם צריך לסדר את הנתונים כך שתהיה עקביות חזקה). שאילתא לגבי ישות אב מחזירה תוצאות עקביות. שימו לב: שאילתת keys-only שאינה שאילתת צאצא
שמגיעה אחרי
lookup()לא מחזירה תוצאות חזקות, כי שאילתת keys-only שאינה שאילתת צאצא יכולה לקבל תוצאות מאינדקס שלא עקבי בזמן השאילתה.
עיצוב בהתאם להיקף
עדכונים בקבוצת ישויות אחת
אסור לעדכן קבוצת ישויות יחידה ב-Datastore מהר מדי.
אם אתם משתמשים ב-Datastore, Google ממליצה לתכנן את האפליקציה כך שלא יהיה צורך לעדכן קבוצת ישויות יותר מפעם אחת בשנייה. חשוב לזכור שישות ללא הורה וללא ילדים היא קבוצת ישויות משלה. אם תעדכנו קבוצת ישויות מהר מדי, פעולות הכתיבה ב-Datastore יהיו עם זמן אחזור גבוה יותר, פסק זמן ושגיאות מסוגים אחרים. מצב כזה נקרא התנגשות.
שיעורי הכתיבה ב-Datastore לקבוצת ישויות יחידה יכולים לפעמים לחרוג מהמגבלה של אחת לשנייה, ולכן יכול להיות שבדיקות עומס לא יציגו את הבעיה הזו.
שיעורי קריאה/כתיבה גבוהים לטווח מפתחות מצומצם
כדאי להימנע משיעורי קריאה או כתיבה גבוהים למפתחות Datastore שקרובים זה לזה מבחינה לקסיקוגרפית.
Datastore מבוסס על מסד הנתונים NoSQL של Google, Bigtable, ולכן הוא כפוף למאפייני הביצועים של Bigtable. הגידול ב-Bigtable מתבצע על ידי חלוקת השורות לטבליות נפרדות, והשורות האלה מסודרות לפי מפתח בסדר לקסיקוגרפי.
אם אתם משתמשים ב-Datastore, יכול להיות שתהיה לכם כתיבה איטית בגלל טאבלט פעיל מדי, אם יש עלייה פתאומית בקצב הכתיבה לטווח קטן של מפתחות שחורג מהקיבולת של שרת טאבלט יחיד. בסופו של דבר, Bigtable יפצל את מרחב המפתחות כדי לתמוך בעומס גבוה.
המגבלה על קריאות בדרך כלל גבוהה בהרבה מהמגבלה על כתיבות, אלא אם קוראים ממפתח יחיד בקצב גבוה. Bigtable לא יכול לפצל מפתח יחיד ליותר מטבלט אחד.
אפשר להחיל טבלאות חמות על טווחי מפתחות שמשמשים גם מפתחות של ישויות וגם אינדקסים.
במקרים מסוימים, לנקודה חמה ב-Datastore יכולה להיות השפעה רחבה יותר על אפליקציה מאשר מניעת קריאה או כתיבה לטווח קטן של מפתחות. לדוגמה, יכול להיות שהמקשים לגישה מהירה ייקראו או ייכתבו במהלך הפעלת המכונה, ולכן בקשות הטעינה ייכשלו.
כברירת מחדל, מערכת Datastore מקצה מפתחות באמצעות אלגוריתם מפוזר. לכן, בדרך כלל לא תיתקלו בבעיה של יצירת נקודות חמות בכתיבה ב-Datastore אם תיצרו ישויות חדשות בקצב כתיבה גבוה באמצעות מדיניות הקצאת מזהים שמוגדרת כברירת מחדל. יש כמה מקרים שבהם יכולה להתרחש הבעיה הזו:
אם אתם יוצרים ישויות חדשות בקצב גבוה מאוד באמצעות מדיניות הקצאת מזהים רציפים מדור קודם.
אם אתם יוצרים ישויות חדשות בקצב גבוה מאוד ומקצים מזהים משלכם שגדלים באופן מונוטוני.
אם יוצרים ישויות חדשות בקצב גבוה מאוד לסוג של ישות שבעבר היו לה מעט מאוד ישויות קיימות. כל הישויות יתחילו בשרת טאבלטים זהה, ויעבור זמן עד שטווח המפתחות יפוצל לשרתי טאבלטים נפרדים.
הבעיה הזו תופיע גם אם תיצרו ישויות חדשות בקצב גבוה עם מאפיין אינדקס שגדל באופן מונוטוני כמו חותמת זמן, כי המאפיינים האלה הם המפתחות לשורות בטבלאות האינדקס ב-Bigtable.
מאגר הנתונים מוסיף את מרחב השמות ואת הסוג של קבוצת ישויות הבסיס למפתח השורה ב-Bigtable. אם מתחילים לכתוב למרחב שמות או לסוג חדש בלי להגדיל בהדרגה את נפח התנועה, יכול להיות שתגיעו לנקודה חמה.
אם יש לכם מפתח או מאפיין עם אינדקס שיגדל באופן מונוטוני, תוכלו להוסיף לפניו גיבוב אקראי כדי לוודא שהמפתחות יפוצלו למספר טאבלטים.
באופן דומה, אם אתם צריכים לבצע שאילתה על מאפיין שגדל (או קטן) באופן מונוטוני באמצעות מיון או סינון, אתם יכולים במקום זאת ליצור אינדקס על מאפיין חדש, שבו אתם מוסיפים לפני הערך המונוטוני ערך עם קרדינליות גבוהה בכל מערך הנתונים, אבל משותף לכל הישויות בהיקף השאילתה שאתם רוצים לבצע. לדוגמה, אם רוצים לשלוח שאילתה לגבי רשומות לפי חותמת זמן, אבל צריך להחזיר תוצאות רק עבור משתמש אחד בכל פעם, אפשר להוסיף לפני חותמת הזמן את מזהה המשתמש ולאנדקס את המאפיין החדש הזה. המשתמש עדיין יוכל להריץ שאילתות ולקבל תוצאות מסודרות, אבל נוכחות מזהה המשתמש תבטיח שהאינדקס עצמו יפוצל בצורה טובה.
הסבר מפורט יותר על הבעיה הזו מופיע בפוסט בבלוג של איקאי לאן בנושא שמירת ערכים שעולים באופן מונוטוני ב-Datastore.
הגדלת נפח התנועה
להגדיל בהדרגה את נפח התנועה לסוגים חדשים של Datastore או לחלקים של מרחב המפתחות.
כדאי להגדיל בהדרגה את התנועה לסוגים חדשים של Datastore כדי לתת ל-Bigtable מספיק זמן לפצל את הטבלאות ככל שהתנועה גדלה. מומלץ לבצע לכל היותר 500 פעולות בשנייה בסוג חדש של Datastore, ואז להגדיל את נפח התנועה ב-50% כל 5 דקות. באופן תיאורטי, אפשר להגיע ל-740,000 פעולות בשנייה אחרי 90 דקות באמצעות לוח הזמנים הזה להגדלת נפח התעבורה. חשוב לוודא שפעולות הכתיבה מפוזרות באופן שווה יחסית בכל טווח המפתחות. מהנדסי ה-SRE שלנו קוראים לזה כלל 500/50/5.
הדפוס הזה של הגדלה הדרגתית חשוב במיוחד אם אתם משנים את הקוד כדי להפסיק להשתמש בסוג A ולהתחיל להשתמש בסוג B. דרך פשוטה לטפל בהעברה הזו היא לשנות את הקוד כך שיקרא את סוג ב', ואם הוא לא קיים אז יקרא את סוג א'. עם זאת, הפעולה הזו עלולה לגרום לעלייה פתאומית בתנועה לסוג חדש עם חלק קטן מאוד במרחב המפתחות. יכול להיות שלא תהיה אפשרות לפצל טאבלטים ביעילות ב-Bigtable אם מרחב המפתחות דליל.
אותה בעיה יכולה להתרחש גם אם מעבירים את הישויות לשימוש בטווח אחר של מפתחות באותו סוג.
האסטרטגיה שבה תשתמשו כדי להעביר ישויות לסוג או למפתח חדשים תהיה תלויה במודל הנתונים שלכם. בהמשך מופיעה דוגמה לשיטה שנקראת Parallel Reads (קריאות מקבילות). תצטרכו לקבוע אם האסטרטגיה הזו יעילה לנתונים שלכם. שיקול חשוב יהיה ההשפעה של פעולות מקבילות על העלויות במהלך ההעברה.
קודם קוראים מהישות או מהמפתח הישנים. אם הוא חסר, אפשר לקרוא מהישות או מהמפתח החדשים. קצב גבוה של קריאות של ישויות שלא קיימות עלול להוביל ליצירת נקודות חמות, ולכן חשוב להגדיל את העומס בהדרגה. אסטרטגיה טובה יותר היא להעתיק את הישות הישנה לישות החדשה ואז למחוק את הישות הישנה. כדי לוודא שמרחב המפתחות החדש מפוצל היטב, צריך להגדיל את מספר הקריאות המקבילות בהדרגה.
אחת מהאסטרטגיות האפשריות להגדלה הדרגתית של פעולות קריאה או כתיבה לסוג חדש היא שימוש בגיבוב דטרמיניסטי של מזהה המשתמש כדי לקבל אחוז אקראי של משתמשים שכותבים ישויות חדשות. חשוב לוודא שפונקציית האקראיות או התנהגות המשתמש לא משפיעות על תוצאת הגיבוב של מזהה המשתמש.
במקביל, מריצים משימת Dataflow כדי להעתיק את כל הנתונים מהישויות או מהמפתחות הישנים לישויות או למפתחות החדשים. כדי למנוע נקודות חמות ב-Bigtable, עבודת ה-batch לא צריכה לכתוב למפתחות עוקבים. אחרי שמשימת האצווה מסתיימת, אפשר לקרוא רק מהמיקום החדש.
אפשרות נוספת היא להעביר קבוצות קטנות של משתמשים בכל פעם. מוסיפים שדה לישות המשתמש כדי לעקוב אחרי סטטוס ההעברה של המשתמש. בוחרים קבוצת משתמשים להעברה על סמך גיבוב (hash) של מזהה המשתמש. תהליך Mapreduce או Dataflow יעביר את המפתחות של קבוצת המשתמשים הזו. המשתמשים שההעברה שלהם נמצאת בתהליך ישתמשו בקריאות מקבילות.
שימו לב: לא תוכלו לבטל בקלות את המעבר אלא אם תבצעו כתיבה כפולה של הישויות הישנות והחדשות במהלך שלב ההעברה. הפעולה הזו תגדיל את העלויות של Datastore.
מחיקות
מומלץ להימנע ממחיקה של מספרים גדולים של ישויות Datastore בטווח קטן של מפתחות.
מערכת Bigtable כותבת מחדש את הטבלאות שלה מדי פעם כדי להסיר רשומות שנמחקו, וכדי לארגן מחדש את הנתונים כך שפעולות קריאה וכתיבה יהיו יעילות יותר. התהליך הזה נקרא דחיסה.
אם מוחקים מספר גדול של ישויות Datastore בטווח קטן של מפתחות, השאילתות בחלק הזה של האינדקס יהיו איטיות יותר עד שהדחיסה תסתיים. במקרים קיצוניים, יכול להיות שייגמר הזמן הקצוב לתפוגה של השאילתות לפני שהן יחזירו תוצאות.
שימוש בערך של חותמת זמן בשדה עם אינדקס כדי לייצג את זמן התפוגה של ישות הוא אנטי-תבנית. כדי לאחזר ישויות שתוקפן פג, צריך להריץ שאילתה על השדה הזה שנוסף לאינדקס, שסביר להניח שהוא נמצא בחלק חופף של מרחב המפתחות עם רשומות אינדקס של הישויות שנמחקו לאחרונה.
אפשר לשפר את הביצועים באמצעות 'שאילתות מחולקות', שמוסיפות מחרוזת באורך קבוע לפני חותמת הזמן של התפוגה. האינדקס ממוין לפי המחרוזת המלאה, כך שישויות עם אותה חותמת זמן ימוקמו בטווח המפתחות של האינדקס. מריצים כמה שאילתות במקביל כדי לאחזר תוצאות מכל שארד.
פתרון מלא יותר לבעיה של חותמת הזמן של התפוגה הוא שימוש ב'מספר דור', שהוא מונה גלובלי שמתעדכן מעת לעת. מספר הדור מופיע לפני חותמת הזמן של התפוגה, כדי שהשאילתות ימוינו לפי מספר הדור, אחר כך לפי מחיצת Shard ואז לפי חותמת הזמן. מחיקה של ישויות ישנות מתבצעת בדור קודם. לכל ישות שלא נמחקה צריך להגדיל את מספר הדור. אחרי שהמחיקה מסתיימת, עוברים לדור הבא. שאילתות שמופנות לדור ישן יותר יניבו ביצועים נמוכים עד להשלמת הדחיסה. כדי לצמצם את הסיכון לתוצאות חסרות בגלל מודל עקביות הדרגתי, יכול להיות שתצטרכו לחכות עד להשלמת כמה דורות לפני שתשאלו את האינדקס כדי לקבל את רשימת הישויות למחיקה.
חלוקה למקטעים ושכפול
שימוש ב-sharding או בשכפול למפתחות פעילים ב-Datastore.
אפשר להשתמש בשכפול אם אתם צריכים לקרוא חלק מטווח המפתחות בקצב גבוה יותר ממה שמותר ב-Bigtable. באמצעות האסטרטגיה הזו, אפשר לאחסן N עותקים של אותה ישות, וכך להגדיל את קצב הקריאות פי N לעומת מה שאפשר להשיג עם ישות אחת.
אפשר להשתמש ב-sharding אם אתם צריכים לכתוב לחלק מטווח המפתחות בקצב גבוה יותר ממה שמותר ב-Bigtable. שארדינג מפצל ישות לחלקים קטנים יותר.
דוגמאות לשגיאות נפוצות בפיצול לשברים:
חלוקה באמצעות קידומת זמן. כשהזמן עובר לקידומת הבאה, החלק החדש שלא פוצל הופך לנקודה לשיתוף אינטרנט (Hotspot). במקום זאת, צריך להעביר בהדרגה חלק מהפעולות שלכם לכתיבה עם הקידומת החדשה.
חלוקת הישויות הכי פופולריות בלבד. אם מחלקים רק חלק קטן מסך הישויות, יכול להיות שלא יהיו מספיק שורות בין הישויות הפופולריות כדי להבטיח שהן יישארו בחלוקות שונות.