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