שמירה במטמון ב-NDB

‫NDB מנהל את המטמונים בשבילכם. יש שתי רמות של שמירת נתונים במטמון: מטמון בהקשר ומטמון של שער לשירות המטמון הרגיל של App Engine, ‏ memcache. שני סוגי המטמון מופעלים כברירת מחדל לכל סוגי הישויות, אבל אפשר להגדיר אותם בהתאם לצרכים מתקדמים. בנוסף, NDB מטמיע תכונה שנקראת auto-batching, שמנסה לקבץ פעולות יחד כדי לצמצם את מספר הפעמים שהנתונים עוברים בין הלקוח לשרת.

מבוא

השימוש במטמון עוזר לרוב סוגי האפליקציות. ‫NDB שומר באופן אוטומטי במטמון נתונים שהוא כותב או קורא (אלא אם האפליקציה מוגדרת אחרת). קריאה מהמטמון מהירה יותר מקריאה מ-Datastore.

אפשר לשנות את התנהגות הקאשינג של הרבה פונקציות NDB על ידי העברת ארגומנטים של אפשרויות הקשר. לדוגמה, אפשר להתקשר אל key.get(use_cache=False, use_memcache=False) כדי לעקוף את השמירה במטמון. אפשר גם לשנות את מדיניות ברירת המחדל של שמירת נתונים במטמון בהקשר של NDB, כמו שמתואר בהמשך.

זהירות: אם משתמשים בכלי Datastore Viewer במסוף הניהול כדי לשנות את התוכן של Datastore, הערכים שנשמרו במטמון לא יעודכנו. לכן יכול להיות שיהיו אי-התאמות במטמון. בדרך כלל אין בעיה עם מטמון בהקשר. ב-Memcache, מומלץ להשתמש במסוף הניהול כדי לנקות את המטמון.

אובייקטים של הקשר

ניהול המטמון משתמש במחלקה בשם Context: כל שרשור וכל טרנזקציה מבוצעים בהקשר חדש. מכיוון שכל בקשת HTTP נכנסת מתחילה שרשור חדש, כל בקשה מופעלת גם עם הקשר חדש. כדי לגשת להקשר הנוכחי, משתמשים בפונקציה ndb.get_context().

זהירות: אין טעם לשתף אובייקטים של Context בין כמה תהליכים או בקשות. אל תשמרו את ההקשר כמשתנה גלובלי! אפשר לאחסן אותו במשתנה מקומי או במשתנה מקומי לשרשור.

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

המטמון בהקשר

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

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

כשפונקציית NDB שולחת שאילתה ל-Datastore, רשימת התוצאות מאוחזרת מ-Datastore. עם זאת, אם תוצאה כלשהי נמצאת במטמון בהקשר, נעשה בה שימוש במקום בערך שאוחזר משאילתת Datastore. תוצאות השאילתה נכתבות בחזרה למטמון בהקשר אם מדיניות המטמון מאפשרת זאת (אבל אף פעם לא ל-Memcache).

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

Memcache

Memcache הוא שירות המטמון הרגיל של App Engine, והוא מהיר הרבה יותר מ-Datastore אבל איטי יותר מהמטמון בהקשר (אלפיות השנייה לעומת מיליוניות השנייה).

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

‫Memcache לא תומך בעסקאות. לכן, יכול להיות שעדכון שאמור לחול על Datastore ועל memcache יחול רק על אחד מהם. כדי לשמור על עקביות במקרים כאלה (יכול להיות שזה יפגע בביצועים), הישות המעודכנת נמחקת מ-memcache ואז נכתבת ב-Datastore. פעולת קריאה שתתבצע לאחר מכן תגלה שהרשומה חסרה ב-memcache, תשלוף אותה מ-Datastore ואז תעדכן אותה ב-memcache כתוצאה לוואי של הקריאה. בנוסף, קריאות של NDB בתוך עסקאות מתעלמות מ-Memcache.

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

פונקציות של מדיניות

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

context = ndb.get_context()
context.set_cache_policy(func)

ועוד אחד ל-memcache, שמוגדר באמצעות

context = ndb.get_context()
context.set_memcache_policy(func)

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

context = ndb.get_context()
context.set_cache_policy(lambda key: key.kind() != 'Account')

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

יש גם פונקציית מדיניות של Datastore שקובעת אילו ישויות נכתבות ב-Datastore עצמו:

context = ndb.get_context()
context.set_datastore_policy(func)

הפונקציה הזו פועלת כמו פונקציות המדיניות של מטמון בהקשר ו-memcache: אם פונקציית המדיניות של Datastore מחזירה False עבור מפתח נתון, הישות התואמת לא תיכתב ב-Datastore. (יכול להיות שהנתונים ייכתבו למטמון בתהליך או ל-memcache אם פונקציות המדיניות מאפשרות זאת). האפשרות הזו שימושית במקרים שבהם יש לכם נתונים דמויי ישויות שאתם רוצים לשמור במטמון, אבל לא צריך לאחסן אותם ב-Datastore. בדומה למדיניות בנושא מטמון, אפשר להעביר True או False במקום פונקציה שתמיד מחזירה את אותו ערך.

‫Memcache automatically expires items when under memory pressure. אפשר להגדיר פונקציית מדיניות של זמן קצוב לתפוגה של memcache כדי לקבוע את משך החיים המקסימלי של ישות במטמון:

context = ndb.get_context()
context.set_memcache_timeout_policy(func)

הפונקציה הזו נקראת עם ארגומנט של מפתח, והיא צריכה להחזיר מספר שלם שמציין את משך החיים המקסימלי בשניות. הערך 0 או None מציין משך חיים בלתי מוגבל (כל עוד יש לשרת memcache מספיק זיכרון). לנוחותכם, אתם יכולים פשוט להעביר קבוע מספרי במקום פונקציה שתמיד מחזירה את אותו ערך. מידע נוסף על פסק זמן זמין במסמכי התיעוד של memcache.

הערה: אין מדיניות נפרדת לגבי משך החיים של מטמון בהקשר: משך החיים של המטמון זהה לזה של ההקשר שלו, בקשת HTTP נכנסת אחת. אבל אפשר לנקות את המטמון בתהליך על ידי קריאה ל-
context = ndb.get_context()
context.clear_cache()

ההקשר החדש מתחיל עם מטמון ריק של נתונים בתהליך.

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

  • לא לשמור במטמון ישויות ששייכות למחלקה ספציפית של מודל.
  • הגדרת פסק זמן של memcache לישויות במחלקת המודל הזו ל-30 שניות.
  • אין צורך לכתוב ישויות מסוג המודל הזה ב-Datastore.

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

משתנה מחלקה סוג תיאור
_use_cache bool המדיניות הזו קובעת אם לאחסן ישויות במטמון בתוך התהליך. היא מבטלת את מדיניות ברירת המחדל של המטמון בתוך התהליך.
_use_memcache bool ההגדרה קובעת אם יש לאחסן ישויות ב-memcache. ההגדרה הזו מבטלת את מדיניות ברירת המחדל של memcache.
_use_datastore bool מציין אם לאחסן ישויות במאגר הנתונים. ההגדרה הזו מבטלת את מדיניות ברירת המחדל של מאגר הנתונים.
_memcache_timeout int משך החיים המקסימלי של ישויות ב-memcache. הערך הזה מבטל את מדיניות ברירת המחדל של הזמן הקצוב לתפוגה ב-memcache.

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

  • default_cache_policy(key)
  • default_memcache_policy(key)
  • default_datastore_policy(key)
  • default_memcache_timeout_policy(key)