פונקציה משותפת ב-root layout של האתר הפכה מונה חשיפות של מבחן A/B למונה צפיות בדפים (page-view counter), מה שניפח את גודל המדגם והפך את שיעורי ההמרה לחסרי משמעות. המבחן, שכלל חלוקה של 50/50 בדף הבית, דיווח על 178 hits עבור וריאנט אחד ו-57 עבור השני – רחוק מאוד מהחלוקה השווה המצופה.

איך השגיאה חמקה מהרנדומייזר

המפתח בדק תחילה את הרנדומייזר שמקצה מבקרים לוריאנט. הוא קרא את ה-middleware, בדק את לוגיקת ה-cookie והריץ סקריפט שקרא לפונקציית ההקצאה 10,000 פעמים; הוא הניב חלוקה מושלמת של 50/50. הרנדומייזר עצמו עבד; הבעיה הייתה באופן שבו נרשמה החשיפה.

פונקציה אחת ביצעה שתי משימות:

  1. הגדרת הוריאנט – רצה בכל טעינת דף כדי לשמור על חוויית משתמש עקבית.
  2. רישום אירוע החשיפה – אמור להיורה רק פעם אחת לכל מבקר, ברגע שהוריאנט מופיע לראשונה.

הפונקציה הייתה חלק מה-root layout, קומפוננטה שמוצגת (rendered) בכל ניווט. מכיוון שקוד רישום החשיפה התבצע בכל פעם שה-layout רונדר, כל צפייה בדף נספרה כחשיפה חדשה. שני הוריאנטים של דף הבית השתמשו בעצי layout מעט שונים, ולכן שיעורי הצפיות בדפים שלהם היו שונים, מה שיצר אשליה של רנדומייזר תקול.

למה לטעות בספירה הייתה חשיבות

נתוני ההמרה – קליקים, הרשמות, רכישות – נרשמו בצורה נכונה. אך המכנה (מספר החשיפות) היה שגוי. שיעורי ההמרה שחושבו נראו נמוכים בהרבה מהמציאות, וכל החלטה שהתבססה על שיעורים אלו לא הייתה אמינה.

המבחן רץ במשך שבועיים לפני שהאי-התאמה עלתה על פני השטח, מה שאילץ את הצוות לזרוק את כל סט הנתונים.

התיקון

הפתרון היה פשוט: פיצול האחריות לפונקציות נפרדות. קוד רישום החשיפה בודק כעת האם המבקר כבר נספר, ונורה רק פעם אחת לכל משתמש. קוד הגדרת הוריאנט נשאר במקומו וממשיך לרוץ בכל ניווט.

שלושה תובנות לכל מי שמריץ ניסויים

  • פריד בין הגדרת state לבין אירועים חד-פעמיים. פונקציה שמקצה וריאנט וגם רושמת חשיפה תיצור התנגשות, כיוון שהראשונה חוזרת על עצמה בעוד שהשנייה לא אמורה לעשות זאת.
  • הימנע מלוגיקה חד-פעמית ב-root layout. כל דבר שמוצב בקומפוננטה שמוצגת בכל טעינת דף יתבצע שוב ושוב, מה שיהפוך "פעם אחת למבקר" ל"פעם אחת לצפייה בדף".
  • כאשר החלוקה שנצפתה סותרת את הרנדומייזר, בדקו קודם כל את המונה. מפתחים בודקים לעיתים קרובות את ההוגנות של הרנדומייזר, אך לעיתים רחוקות מוודאים שמנגנון הספירה מדויק.

מה כדאי לשים לב אליו בהמשך

כל ניסוי המסתמך על מונה יחיד עבור חשיפה צריך להיבדק כדי לראות היכן המונה הזה נמצא בעץ הקומפוננטות. אם המונה נמצא ב-global layout, הוסיפו בדיקה שמקשרת את האירוע למזהה קבוע – כמו cookie או דגל ב-local-storage. על צוותים גם לבנות sanity check בלוחות הבקרה שלהם: אם התפלגות הוריאנטים שנצפתה חורגת מעבר לשולי סטטיסטיים קטנים, סמנו את המבחן לבדיקת מונה לפני שמניחים שהרנדומייזר תקול.

בקיצור, רנדומייזר שמתפקד היטב הוא חסר תועלת ללא ספירת חשיפות מהימנה. פיצול האחריות והצבת אירועים חד-פעמיים מחוץ לקומפוננטות שמוצגות תמיד שומרים על אמינות מבחני A/B וחוסכים שבועות של ניתוח מיותר.