🚀 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 חיצוניים.