→ buildbench

אזהרה שחוזרת בכל cold start

ארבע issues חדשות ב-Sentry, כולן עם אותה כותרת: Unknown city in resolve_neighborhood. שלוש מהן היו מושבים קטנים — עזריקם, חגור, בית זית — והרביעית הייתה 'Afula', באנגלית, עם השכונה 'רובע יזרעאל' בעברית. ההפרדה הזו בין השלוש לאחת היא כל הסיפור.

הבאג שצדק

Afula הוא באג אמיתי. בקוד יש מילון _CITY_OVERRIDES שממפה איותים לטיניים לעברית — אבל הוא מכיל רק את משפחת Tel Aviv. סקרייפר אחד החליט להחזיר את שם העיר באנגלית, וזה נפל בין הכיסאות: לא תואם לאף canonical, לא מתורגם, נכנס ל-Sentry ויוצר potentially שורת neighborhoods כפולה לעפולה. שורה אחת:

_CITY_OVERRIDES = {
    "תל אביב": "תל אביב יפו",
    "Tel Aviv": "תל אביב יפו",
    "Tel Aviv-Yafo": "תל אביב יפו",
    "Afula": "עפולה",  # ←
}

זה הקל. המעניין היה השלושה האחרים.

הרעש שלא הבנתי

עזריקם, חגור ובית זית הם מושבים קטנים. הקוד מתעד אותם בכוונה — זה האות שצריך להוסיף שכונה קנונית חדשה. הבעיה: מצופה שהאזהרה תיירה פעם אחת לעיר. יש בקוד _warned_cities set שמונע חזרות. אבל ב-Sentry ראיתי את אותה עיר נדלקת שוב ושוב.

לקח לי רגע להבין: ה-set הזה הוא משתנה גלובלי ב-Python process. ב-Lambda זה אומר שכל cold start מאתחל אותו מחדש. סקרייפר רץ פעם ביום, Lambda הצטיא, container חדש, set ריק, אזהרה חוזרת. ה-dedup עובד בדיוק כמו שתוכנן — רק שהתכנון הזה הניח process ארוך-חיים.

התיקון

הפיתוי הראשון היה לשמור seen cities ב-DB. יותר מדי תשתית לבעיה הזו. הפיתוי השני היה להוריד את ה-log level. זה היה משתיק את הרעש אבל גם את האות.

הפתרון בפועל היה לשאול שאלה אחרת: למה אנחנו בכלל מאזהירים על עיר שכבר יש לה שורה ב-Neighborhood? אם יש canonical, היא לא חדשה — לא משנה אם יש לה alias כלשהו או לא. עד עכשיו _known_cities נטענה מ-neighborhood_aliases בלבד. שורה נוספת ב-_load_cache:

city_result = await session.execute(
    select(Neighborhood.city).distinct()
)
_known_cities.update(c for (c,) in city_result.all() if c)

עיר עם canonical אחד שותקת. עיר שלא נוצרה לה עוד שורה — נשמעת. אות הדריפט נשמר, הרעש נעלם.

הלקח

dedup in-memory שמתבסס על משתנה גלובלי הוא הנחה על אורך החיים של ה-process. ב-Lambda זו הנחה שגויה. כשהאזהרה חוזרת לסירוגין על אותו ערך, זה לא תמיד באג ב-dedup — זה לפעמים סימן ש-State שאתה חושב עליו כ-long-lived הוא בעצם ephemeral. הפתרון הוא לעיתים קרובות לא להוסיף persistence, אלא לגזור את ה-state ממקור שכבר persistent — במקרה הזה, ה-DB עצמו.