From 6db9948c54b132aceea5592b798f955183044f77 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 7 Sep 2026 11:42:14 +0000 Subject: [PATCH 1/2] =?UTF-8?q?fix(search):=20=D7=9B=D7=A9=D7=9C=20=D7=91?= =?UTF-8?q?=D7=9E=D7=A1=D7=9C=D7=95=D7=9C=20=D7=94=D7=97=D7=99=D7=A4=D7=95?= =?UTF-8?q?=D7=A9=20=D7=94=D7=9E=D7=94=D7=99=D7=A8=20=D7=A0=D7=A8=D7=A9?= =?UTF-8?q?=D7=9D,=20=D7=95-code=20=D7=A0=D7=95=D7=A1=D7=A3=20=D7=9C=D7=A8?= =?UTF-8?q?=D7=A9=D7=99=D7=9E=D7=AA=20=D7=94=D7=A4=D7=A8=D7=95=D7=A4=D7=99?= =?UTF-8?q?=D7=99=D7=9C=D7=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit שלושה סבבי חקירה על "הפרופיילר לא שומר ערכים" הובילו למקום אחר לגמרי. הסימפטום היה unknown_field:code. השורש הוא שלושה except שקטים במסלול החיפוש. מה שהלוגים הראו --------------- חיפוש תוכן אחד שלא הניב תוצאות: 10:33:27.95 webapp:post:api_search_global — אגרגציה בלי code, 1043ms 10:33:30→37 בניית אינדקס בזיכרון: 67,381 מילים, 392 פונקציות 10:33:46.03 slow_mongo — אגרגציה עם code: {$regex}, 1073ms ה-$project של הרשומה שנדחתה הוא צורת ההחרגה (code: 0, _m: 0, …), כלומר בדיוק המסלול של הפולבאק בוובאפ. לא ניחוש. שני פולבאקים שונים, ואל תבלבלו ביניהם -------------------------------------- להגיע ל-_safe_search בכלל זה מסלול תקין: היא נקראת כשמנוע החיפוש החזיר אפס תוצאות, וההערה בקוד אומרת את זה במפורש. אין שם שום שגיאה. ה-except הפנימי הוא משהו אחר — שאילתת ה-$text עצמה נזרקה. שם חיפשתי במפורש if not results / len(docs)==0 ואין: המעבר ל-$regex הוא רק על חריגה. הכשל היה בלתי נראה, וזה השורש ------------------------------ except Exception: בלי שורת לוג אחת. כשהמסלול המהיר נשבר, המערכת עברה בשקט לשאילתה אחרת — $regex על code, סריקה מלאה בלי אינדקס — שנמדדה כשאילתה האיטית ביותר בכל הלוח (3,730ms). היא רצה רק מפני שהמסלול המהיר נפל, ואיש לא ידע. ההערה שהייתה שם ניחשה "למשל אין אינדקס טקסט". בדקתי את הניחוש ופסלתי אותו: search_text_idx קיים, והרצתי את אותה שאילתת $text בדיוק מול הקלאסטר — היא עובדת ומחזירה תוצאות. הסיבה האמיתית נשארה בלתי ידועה, וזה מה שהשורות החדשות מתקנות. שלושתן נוספו: $text ← $regex, $regex ← הפייפליין הישן, והאחרון שמחזיר "לא נמצאו תוצאות" — כלומר חיפוש שבור שנראה בדיוק כמו חיפוש שלא מצא, וזה ההבדל היחיד שחשוב למשתמש. code ברשימה — משני, ולא במקום התיקון ------------------------------------- בלעדיו השאילתה נדחית, וניתוח שלה רץ על regex של "" שאינו מתאים כמעט לכלום — כלומר דוח שאומר "מהיר, אפס מסמכים נסרקו" על השאילתה האיטית ביותר במערכת. הערך שנשמר הוא דפוס החיפוש שהוקלד, לא תוכן הקובץ. וההערה מעל הרשימה תוקנה. היא טענה שהרשימה נגזרה מ"השדות של code_snippets בפרודקשן" — נמדדו 32 שדות באוסף מול 19 ברשימה. הכלל האמיתי הוא: השדות שמסננים לפיהם בשאילתות המשויכות למשתמש. זה נגזר מסדר הבדיקות — שער הבעלות רץ לפני בדיקת השדות — ולכן שדות ה-worker לעולם אינם מגיעים לשם. מגבלה ידועה שתועדה ולא תוקנה: הרשימה גלובלית אבל מתארת את code_snippets, ובלוג יש 15 צירופי אוסף/פעולה. שאילתה משויכת-משתמש על large_files או markdown_images תיפסל על השדות של עצמה. סעיף נפרד. אימות ----- טסטי הלוג בודקים את ההתנהגות ולא את קיום השורה: הם מאלצים כל אחד משלושת הכשלים ובודקים מה יצא ללוג, כולל exc_info. קריאת קוד הייתה "מאמתת" גם ניסוח שלא רץ לעולם. טסטי משפחות השאילתות מריצים את ההחלטה על הצורות שהריפו באמת בונה, ולא על תוכן הרשימה — assert "code" in ALLOWED_FIELDS היה מאשר את עצמו. מוטציות, כל אחת מפילה בדיוק את שלה: - הסרת שלוש שורות הלוג ← שלושת טסטי הלוג - הסרת code מהרשימה ← שני טסטי החיפוש - הסרת שער הבעלות ← טסט ה-worker ובדרך, טסט קיים תפס את השינוי לבד: הטבלה ב-test_query_profiler_service דורשת כיסוי מלא של הרשימה, ונפלה על code עד שנוספה לו דגימה. בדיוק לשם כך היא נכתבה. 150 טסטים · flake8 זהה לבסיס (webapp/app.py ירד ב-1, השאר 1=1 ו-2=2). מה שלא אימתתי -------------- לא אימתתי מה $text זרק בפועל — עד שהלוג החדש ירוץ בפרודקשן אין דרך לדעת, וזו בדיוק הסיבה שהוא נוסף. כל טענה על הסיבה עד אז היא ניחוש. ממצא לוואי שלא נגעתי בו: בניית האינדקס בזיכרון לוקחת כשבע שניות, בתוך בקשת חיפוש. לא מדדתי כמה פעמים זה קורה. Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01UBugD1DV8LhHBSGnvpAgzK --- docs/whats-new.rst | 2 + services/query_profiler_service.py | 25 +++- tests/test_profiler_raw_query_families.py | 175 ++++++++++++++++++++++ tests/test_query_profiler_service.py | 5 + tests/test_search_fallback_is_logged.py | 100 +++++++++++++ webapp/app.py | 39 ++++- 6 files changed, 341 insertions(+), 5 deletions(-) create mode 100644 tests/test_profiler_raw_query_families.py create mode 100644 tests/test_search_fallback_is_logged.py diff --git a/docs/whats-new.rst b/docs/whats-new.rst index 4bc823ca5..644750057 100644 --- a/docs/whats-new.rst +++ b/docs/whats-new.rst @@ -8,6 +8,8 @@ What's New - **fix: הדגל נכנס למפתח הקאש של** ``/files``\ **, אחרת השינוי לא היה נראה.** העמוד שומר את ה-HTML המרונדר, והמפתח נבנה מפרמטרי החיפוש בלבד. בלי הדגל, משתמש שמדליק את המתג היה מקבל את ה-HTML של המצב הקודם עד שה-TTL פוקע, ומסיק שהפיצ'ר שבור. הדגל נוסף כתחילית למפתח כך שגם ענף ה-fallback מבדיל בין המצבים. ביטול קאש נשקל ונפסל: אין היום שום קוד שמבטל את ``web:files:user:*``, ומפתח שמתאר את התוכן אינו זקוק לפעולה שיכולה להיכשל בשקט. - **fix: ההעדפה נפתרת פעם אחת לכל בקשה.** ``/files`` בונה את מפתח הקאש לפני הרינדור וה-context processor מזין את התבנית — שתי קריאות נפרדות למסד באותה בקשה. שינוי העדפה שנוחת ביניהן היה גורם ל-HTML של מצב אחד להישמר תחת התגית של המצב השני, וכל מי שמבקש אחר כך את התצוגה ההיא היה מקבל את ההפך עד פקיעת ה-TTL. התוצאה נשמרת עכשיו על ``g``, ההיקף היחיד שמובטח שהוא בדיוק בקשה אחת. - **fix: המאזין של המתג מסומן על האלמנט, כי הבלוק** ``extra_js`` **של עמוד ההגדרות מרונדר פעמיים.** ``settings.html`` מצהיר אותו בתוך ``block content``, ולכן Jinja מרנדר אותו גם שם וגם ב-``base.html``. נמדד בדפדפן: לחיצה אחת, אירוע ``change`` אחד, ושתי בקשות ל-``/api/ui_prefs`` משתי שורות שונות באותו עמוד. הכפילות קיימת גם ב-``persistentToggle``, ב-``fontMinus`` וב-``themeSelect``, ולא תוקנה כאן — היא נוגעת בכל העמוד ומצריכה שינוי מבני נפרד. +- **fix (חיפוש): כשל במסלול החיפוש המהיר נרשם ללוג במקום להיבלע.** שלושה ``except`` שקטים ב-``_safe_search`` — הראשון נופל מ-``$text`` ל-``$regex`` על ``code``, השני משם לפייפליין הישן, והשלישי מחזיר "לא נמצאו תוצאות". אף אחד מהם לא כתב שורה אחת, ולכן כשהמסלול המהיר נשבר המערכת עברה בשקט לשאילתה **אחרת**: סריקה מלאה בלי אינדקס, שנמדדה כשאילתה **האיטית ביותר בכל הלוח** (3,730ms). ההערה שהייתה שם ניחשה "למשל אין אינדקס טקסט" — הניחוש נבדק ונפסל: ``search_text_idx`` קיים, ואותה שאילתת ``$text`` בדיוק רצה מול הקלאסטר ומחזירה תוצאות. ⚠️ ולא לבלבל בין שני פולבאקים שונים: להגיע לפונקציה הזו זה מסלול **תקין** שקורה כשמנוע החיפוש החזיר אפס תוצאות; ה-``except`` הוא משהו אחר — השאילתה עצמה נזרקה. כשל שלישי מחזיר "לא נמצאו תוצאות", כלומר חיפוש שבור נראה בדיוק כמו חיפוש שלא מצא — וזה ההבדל היחיד שחשוב למשתמש. +- **fix (פרופיילר): ``code`` נוסף לרשימת השדות שנשמרים עם ערכים אמיתיים.** בלעדיו שאילתת החיפוש נדחתה ב-``unknown_field:code``, וניתוח שלה רץ על ``{"code": {"$regex": ""}}`` — regex שאינו מתאים כמעט לכלום, ולכן ה-explain דיווח "מהיר, אפס מסמכים נסרקו" **על השאילתה האיטית ביותר במערכת**. הערך שנשמר הוא דפוס החיפוש שהוקלד ולא תוכן הקובץ. ובאותו מעבר תוקנה ההערה מעל הרשימה: היא טענה שהרשימה נגזרה מ"השדות של ``code_snippets``" (נמדדו 32 שדות באוסף מול 19 ברשימה), בעוד הכלל האמיתי הוא *השדות שמסננים לפיהם בשאילתות שמשויכות למשתמש* — מה שגם מסביר למה שדות ה-worker אינם שייכים לשם: הם נדחים כ-``owner_missing`` לפני שהרשימה נבדקת בכלל. - **fix (פרופיילר): שינוי מסנן ה-collection באמצע דפדוף כבר אינו מעלים שורות.** ל-``