🚀 The Performance Bible: CodeKeeper Optimization Guide
המדריך הזה נכתב לאחר ה-Refactor הגדול של דצמבר 2025, שבו הורדנו את ה-p95 של המערכת מ-1.8 שניות ל-200ms. אלו העקרונות שחייבים להישמר:
0. חוק הברזל: Cache First (מנע את הגישה ל-DB)
לפני אופטימיזציה של שאילתה – בדוק אם היא הכרחית. המערכת משתמשת ב-Redis Cache כשכבת הגנה ראשונה.
Heavy Lists: TTL ארוך (3-5 דקות).
Metadata: TTL קצר (15-60 שניות).
Validation: השתמש ב-
/admin/cache-inspectorכדי לוודא שמפתחות נוצרים עם Hash קבוע ולא כתובות זיכרון.
הכלל: כל קריאת קריאה (Read) כבדה חייבת לעבור דרך CacheManager.
1. חוק ה-Projection (אל תמשוך מה שאתה לא מציג)
לעולם אל תמשוך את השדות code או content בשאילתות רשימה.
הבעיה: שליפת 50 קבצים עם קוד מלא יוצרת Payload של מגה-בייטים, חונקת את ה-RAM של השרת ומאיטה את ה-Serialization.
הפתרון: שימוש קבוע ב-LIST_EXCLUDE_HEAVY_PROJECTION.
איך זה עובד: ה-Repository מחזיר רק מטא-דאטה. התוכן המלא נשלף רק ב-Explicit Fetch (כשמשתמש פותח קובץ ספציפי).
ובצינור אגרגציה — ההחרגה צמודה ל-$match. רק $match רשאי לבוא לפניה. שלב אחר באמצע, למשל $addFields, משאיר אותה כשלב בצינור, ואז $sort ו-$group שאחריה עדיין נספרים על המסמכים המלאים — כך ”שאר קבצים“ נפל על שגיאה 292 ב-23.9.2026. הנימוק והמדידות: ה-docstring של _latest_version_per_file_stages ב-webapp/app.py. האכיפה: tests/test_files_pipelines_drop_heavy_fields_early.py.
2. חישובים ב-DB (Smart Metadata)
במקום למשוך קוד ולספור שורות בפייתון, אנחנו מחשבים ושומרים:
file_size: גודל הקובץ בבייטים.lines_count: מספר השורות.
הכלל: אם אפשר לחשב את זה ב-MongoDB בזמן השמירה – עשה זאת שם. זה הופך את הרשימות ל“נוצה“.
3. אינדוקס מורכב (Compound Indexes)
אל תסמוך על אינדקסים פשוטים. השאילתות שלנו מורכבות ולכן האינדקסים חייבים להיות תואמים:
{ "user_id": 1, "is_deleted": 1, "created_at": -1 }– לשליפה מהירה וממוינת של קבצי משתמש.{ "user_id": 1, "is_favorite": 1 }– לדף המועדפים.
זכור: שאילתה בלי אינדקס (COLLSCAN) היא פצצת זמן מתקתקת.
4. אופטימיזציית API וחיפוש (The Snippet Pattern)
בחיפוש גלובלי, במקום להחזיר את כל הקובץ שבו נמצאה המילה:
משתמשים ב-
$regexFindבתוך מונגו כדי לחתוך Snippet קטן סביב ההתאמה.מחזירים ל-UI רק את ה-Preview.
זה חוסך 99% מהתעבורה בחיפושים כבדים.
5. הזרקת נתונים אסינכרונית (Lazy Loading)
בדפים כבדים (כמו Observability):
Backend: ה-Route מחזיר HTML ריק תוך פחות מ-200ms.
Frontend: שימוש ב-Skeleton Loaders בזמן שנתונים נמשכים ב-API נפרד ברקע.
Concurrency: תלוי בשרת, ולא אותו כלל לשני הצדדים:
ב-WebApp (Flask על WSGI עם worker של gevent) — אין להשתמש ב-
asyncioבכלל. ה-monkey patching של gevent כבר הופך את ה-I/O לקואופרטיבי, ולכןasyncio.to_threadלא קונה כאן דבר.to_threadעצמו אינו פותח לולאה — הוא קורוטינה שדורשת לולאה שכבר רצה, ומעבירה את הפונקציה החוסמת ל-thread נפרד. הבעיה היא שכדי להמתין לו מקוד סינכרוני מישהו חייב לפתוח לולאה (asyncio.runאוloop.run_until_complete) — והלולאה הזו נראית ”רצה“ לכל גרינלט אחר באותו worker ומפילה אותו. שירות שנצרך מה-WebApp נכתב סינכרוני. ראוdocs/observability/asyncio-loop-safety.rst.בקוד שרץ בתוך event loop אמיתי (הבוט, שרתי aiohttp/ASGI) —
asyncio.to_threadהוא הכלי הנכון לעטוף קריאה סינכרונית כבדה.
6. הגדרות סביבה (The Production Sweet Spot)
LOG_LEVEL=INFO: צמצום כתיבת לוגים מיותרת.MONGODB_MIN_POOL_SIZE=10: שמירת חיבורים ”חמים“ למניעת SSL Handshake Timeouts.AIOHTTP_LIMIT_PER_HOST=25: פתיחת צוואר הבקבוק מול GitHub/APIs חיצוניים.