איך לבחור מפתחות Cache לאתר דינמי

26/04/2020

אומרים שיש רק שתי בעיות קשות במדעי המחשב: איך לבחור שמות לדברים ואיך להחליט מתי צריך לנקות את ה Cache.

השבוע העליתי פיצ'ר של Server Side Rendering שדחף אותי לפתור לפחות לאתר טוקוד את הבעיה השניה ובפוסט היום אנסה לסכם את עיקרי הדברים.

1. למה צריך לבחור מפתח Cache

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

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

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

בשביל לממש את הלוגיקה הזאת בקוד העליתי שרת redis ששומר את כל דפי התוצאות שנשלחו לגולשים - והשאלה הקשה מהכותרת היא איך אפשר לדעת שגולש חדש שמגיע מבקש דף שכבר יש לנו?

מה שבטוח לא יעבוד זה להשתמש בכתובת העמוד. הכתובת https://www.tocode.co.il/blog שמובילה לבלוג כאן תישאר זהה גם מחר, למרות שתוכן הדף יהיה שונה.

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

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

דפים מסוימים באתר משתמשים בשורה בודדת מטבלא או בשורות בודדות ממספר טבלאות. לדוגמא דף של פוסט בודד באתר משתמש במידע מטבלת הפוסטים. שני המזהים החשובים לכל שורה בהקשר של Cache הם: id ו updated_at. לכן מפתח Cache טוב לנתיב של פוסט יהיה שם הנתיב, blog_show, ואחריו שני השדות id ו updated_at של הפוסט הספציפי שעכשיו מסתכלים עליו. אם במקרה אשנה בבסיס הנתונים פוסט אחרי שיצרתי אותו (לדוגמא בשביל לתקן שגיאת כתיב), שדה updated_at של השורה ישתנה הגולש הבא יקבל מפתח Cache חדש, הדף ייבנה מחדש והגולש יקבל את הדף הנכון. אבל כל עוד שדות ה id ו updated_at לא משתנים, כל גולש נוסף שמגיע יקבל בדיוק את אותו דף שכבר נוצר ושמור ב redis.

3. מפתח עבור דף שמציג אוסף אוביקטים

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

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

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

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