API שמתנהג מצוין בסביבת ה-staging עלול לקרוס ברגע שתעבורה אמיתית תגיע. הכשל הוא לעיתים רחוקות דרמטי. זהו מוות באלף חתכים. תהליכון חסום כאן, שאילתה מיותרת שם. תחת עומס קל, חוסר היעילות הקטן הזה נסתר. תחת לחץ של סביבת ייצור (production), הם מצטברים לכדי הרעבת מאגר תהליכונים (thread pool starvation), חנק של מסד הנתונים וזמני תגובה שמשמידים את אמון המשתמשים. כוונון ביצועים (Performance tuning) אינו עוסק במציאת פתרון קסם אחד. הוא עוסק בבניית מערכת שבה כל שכבה מכבדת את המחסור בתהליכונים, בזיכרון ובחיבורי מסד נתונים. כשמתייחסים למשאבים הללו כסופיים, מפסיקים להגיב לתקלות ומתחילים למנוע אותן.
תחסלו את ה-Sync-over-Async לפני שהוא יהרוס אתכם
התבנית ההרסנית ביותר באפליקציות ASP.NET Core בעלות תעבורה גבוהה היא sync-over-async. רואים את זה כשמישהו קורא ל-.Result או ל-.Wait() על מתודה אסינכרונית כי הוא צריך את הערך עכשיו ולא רוצה לבצע refactor למחסנית הקריאות (call stack). ההחלטה הזו חוסמת את התהליכון הקורא. התהליכון יושב במצב המתנה, מחכה לעבודה שכבר מתבצעת במקום אחר, אך ה-runtime אינו יכול להשתמש בו עבור בקשה אחרת.
כשמספיק בקשות עושות זאת, מאגר התהליכונים (thread pool) נתקע ב"הרעבה". גרף ה-CPU שלכם נראה תקין כי המעבדים אינם עסוקים, ובכל זאת ה-latency שלכם מזנק. הבקשות נערמות בתור, מחכות לתהליכונים שלעולם לא ישתחררו. התיקון הוא מכני אך דורש משמעת: השתמשו ב-await לאורך כל מחסנית הקריאות. אם מתודה קוראת ל-API אסינכרוני, היא חייבת להיות אסינכרונית בעצמה. אין כאן קיצורי דרך. כל חסימה סינכרונית שאתם מסירים קונה לכם מרווח נשימה (headroom) לתעבורה אמיתית.
הפסיקו לבזבז עבודה על לקוחות מנותקים
לקוחות מתנתקים. דפדפנים סוגרים טאבים. אפליקציות מובייל מאבדות קליטה. אם השרת שלכם לא יודע שהלקוח איננו, הוא ממשיך להריץ שאילתות מסד נתונים, לנתח JSON ולבזבז תהליכונים על תגובה שאף אחד לא יקבל. העבירו CancellationToken לכל פעולה אסינכרונית שתומכת בכך. זה כולל שאילתות Entity Framework, קריאות HTTP עם HttpClient, וכל עבודה רקע ארוכת טווח.
כשהחיבור מתנתק, ה-token מפעיל ביטול, והעבודה נעצרת מיד. זה שומר על מחזורי ה-CPU של מסד הנתונים ומחזיר תהליכונים למאגר מהר יותר. זהו שינוי קטן בחתימות המתודות שמשתלם מאוד תחת עומס.
שמרו ב-Cache את מה שלא משתנה
קטלוג המוצרים שלכם כנראה לא משתנה בין בקשה לבקשה. דגלי הקונפיגורציה שלכם בטח ממש לא. ובכל זאת, הרבה APIs פונים למסד הנתונים שוב ושוב עבור אותם נתונים סטטיים. Output caching ב-ASP.NET Core מאפשר לכם לאחסן תגובות שעובדו (rendered responses) ולהגיש אותן ישירות מהזיכרון מבלי לגעת שוב ב-controllers או במסד הנתונים שלכם.
השתמשו ב-tag-based invalidation בזהירות. כשאתם מעדכנים מוצר, בטלו את התוקף (invalidate) רק עבור התג (tag) הקשור לאותה קטגוריה או פריט. אין צורך לרוקן (flush) את כל ה-cache. זה שומר על יחס hit ratio גבוה ב-cache ועל מספר נמוך של שאילתות למסד הנתונים.
תקנו קודם כל את הגישה לנתונים
קריאות למסד הנתונים צורכות את רוב זמן הבקשה ברוב ה-APIs. לפני שאתם מבצעים אופטימיזציה לכל דבר אחר, תסתכלו כאן.
אם אתם מבצעים שאילתות על נתונים רק כדי להציג אותם ולעולם לא מתכננים לעדכן אותם, הוסיפו AsNoTracking() לשאילתות ה-Entity Framework שלכם. EF Core מדלג על מעקב שינויים (change tracking) ועל יצירת snapshot
