שמירה במטמון של 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 כותבת למאגר הנתונים, היא כותבת גם למטמון בהקשר. כשפונקציית NDB קוראת ישות, היא בודקת קודם את המטמון בהקשר. אם הישות נמצאת שם, לא מתבצעת אינטראקציה עם Datastore.

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

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

Memcache

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

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

‫Memcache לא תומך בעסקאות. לכן, יכול להיות שעדכון שאמור לחול על Datastore ועל memcache יחול רק על אחד מהם. כדי לשמור על עקביות במקרים כאלה (יכול להיות שזה יפגע בביצועים), הישות המעודכנת נמחקת מ-Memcache ואז נכתבת ב-מאגר נתונים. פעולת קריאה עוקבת תגלה שהישות חסרה ב-Memcache, תאחזר אותה ממאגר הנתונים ואז תעדכן אותה ב-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)