בונים API מהיר עם קאשינג ב-Redis - הפרויקט שמלמד אתכם למה זה קשה יותר ממה שנשמע¶
יש משפט ידוע בתכנות - יש רק שני דברים קשים באמת במדעי המחשב, מתן שמות (naming things) וביטול תוקף של קאש (cache invalidation). הראשון נשמע כמו בדיחה. השני הוא בדיחה שנהיית אמיתית ברגע שתנסו לבנות API עם קאשינג. הנה איך בונים אחד, ולמה החלק הקשה הוא לא לשמור נתונים במהירות אלא לדעת מתי לזרוק אותם.
מה קאשינג בעצם פותר¶
תארו לעצמכם endpoint שמחשב דוח - שאילתה כבדה שמצטרפת כמה טבלאות, מסננת, ומחשבת סכומים. אם מאה משתמשים מבקשים את אותו דוח בדקה הקרובה, אין שום סיבה להריץ את השאילתה הכבדה הזאת מאה פעמים. קאשינג אומר - מריצים את החישוב היקר פעם אחת, שומרים את התוצאה במקום מהיר לגישה, ולכל בקשה הבאה עונים מהקאש במקום לחזור למסד הנתונים. התוצאה היא גם פחות עומס על מסד הנתונים וגם תגובה מהירה משמעותית למשתמש.
Redis הוא הבחירה הנפוצה למקום הזה - מאגר מידע שרץ כולו בזיכרון, ולכן קריאה ממנו מהירה בסדרי גודל מקריאה ממסד נתונים רגיל שיושב על דיסק.
דגם העבודה - Cache-Aside¶
הדגם הנפוץ ביותר לקאשינג נקרא cache-aside, וההיגיון שלו פשוט. כשמגיעה בקשה, קודם בודקים אם התשובה כבר קיימת בקאש. אם כן - מחזירים אותה מיד, בלי לגעת במסד הנתונים. אם לא - פונים למסד הנתונים, מחשבים את התשובה, שומרים אותה בקאש לפעם הבאה, ורק אז מחזירים אותה למשתמש:
async function getReport(userId) {
const cacheKey = `report:${userId}`;
const cached = await redis.get(cacheKey);
if (cached) {
return JSON.parse(cached);
}
const report = await db.computeExpensiveReport(userId);
await redis.set(cacheKey, JSON.stringify(report), "EX", 300);
return report;
}
השורה עם ה-EX בסוף היא בדיוק השלב הבא שצריך להבין.
TTL - למה קאש לא אמור לחיות לנצח¶
כל ערך שנשמר בקאש צריך זמן תפוגה, TTL - זמן חיים. בלי זה, קאש רק יגדל ויגדל, ובעיקר - הוא יחזיק נתונים ישנים לנצח גם אחרי שהם השתנו במקור. TTL קצר מדי מבטל את התועלת של הקאש, כי הנתונים מתעדכנים שוב ושוב. TTL ארוך מדי מגדיל את הסיכוי שמשתמשים יראו מידע לא עדכני. אין מספר קסם - זה תלוי כמה קריטי שהמידע יהיה טרי, וכמה יקר לחשב אותו מחדש. דוח סטטיסטי שמשתנה לאט יכול לשבת בקאש לדקות ארוכות. מלאי מוצר בזמן אמת צריך TTL הרבה יותר קצר, אם בכלל.
ביטול קאש - הבעיה הקשה באמת¶
TTL פותר חלק מהבעיה - הנתונים לא נשארים ישנים לנצח. אבל מה קורה כשמשהו משתנה במסד הנתונים ממש עכשיו, ואתם לא רוצים לחכות שה-TTL יפוג? זו בדיוק הבעיה של ביטול קאש. אם עדכנתם רשומה במסד הנתונים אבל הקאש עדיין מחזיק את הגרסה הישנה, המשתמשים רואים מידע שגוי עד שהקאש פג. הפתרון הנפוץ הוא לבטל את הערך בקאש באופן יזום בדיוק ברגע שהנתון המקורי משתנה - כלומר, כל פעולת כתיבה למסד הנתונים צריכה "לדעת" גם למחוק את המפתח המתאים בקאש. זה נשמע פשוט, אבל בפועל קל לשכוח נקודת עדכון אחת, ואז יש לכם באג שקשה לשחזר כי הוא תלוי בתזמון.
מוקש נפוץ - לקשש נתונים שמשתנים כל הזמן¶
לא כל דבר כדאי לקשש. נתון שמשתנה כמעט בכל בקשה לא מרוויח הרבה מקאשינג, ורק מוסיף מורכבות. המועמדים הכי טובים לקאשינג הם נתונים שיקר לחשב אותם ושלא משתנים בכל שנייה - דוחות, רשימות פופולריות, תוצאות חיפוש נפוצות. לפני שמוסיפים קאש לאיזשהו endpoint, שווה לשאול קודם אם באמת יש שם בעיית ביצועים, כי קאשינג לא נכון מוסיף מורכבות בלי תועלת אמיתית.
איפה בונים את זה בפועל¶
להבין את התאוריה זה שלב אחד, אבל להרגיש על בשרכם מתי TTL קצר מדי הורג ביצועים ומתי ביטול קאש שנשכח יוצר באג מוזר - זה קורה רק כשבונים API אמיתי עם Redis. בקורס צד-שרת בונים בדיוק פרויקט כזה, כולל קאשינג של שאילתות יקרות בתוך API אמיתי.
בניתם קאשינג וה-API שלכם עדיין מחזיר נתונים ישנים? זה בדיוק סוג השאלה שכדאי לשאול בקהילה שלנו.