תארו לעצמכם שהשקתם מבחן A/B דרך Optimizely, אבל אחרי שבוע גיליתם ש-LCP עלה ב-300 אלפיות השנייה בגלל סקריפט חיצוני. או שאינכם יכולים לקבל נתונים גולמיים — רק גרפים מצטברים. ועלות הרישיון עבור 10,000 מבקרים היא הוצאה חודשית משמעותית. פלטפורמת A/B מותאמת אישית פותרת את כל הבעיות הללו: אתם שולטים בקוד, בנתונים ובתקציב. החיסכון בעלויות הרישוי יכול להגיע עד 70%, עם תקופת החזר של 3–6 חודשים. יתרה מכך, פלטפורמה מותאמת אישית פועלת פי 2–3 מהר יותר כי אין סקריפטים חיצוניים.
צברנו ניסיון של למעלה מ-5 שנים בפיתוח מערכות כאלה עבור חנויות מקוונות עם 1M+ מבקרים חודשיים — יותר מ-50 פרויקטים. הפלטפורמה שלנו בנויה על עיקרון מודולרי ומתאימה עצמה בקלות לכל ארכיטקטורה.
יתרונות של פלטפורמת A/B מותאמת אישית
כלים מוכנים מאיצים את ההתחלה אך הופכים ליקרים ולא גמישים בקנה מידה. פלטפורמה מותאמת אישית מחזירה את ההשקעה לאחר מספר מבחנים בלבד — בפרויקט אחד, ההמרה עלתה ב-15% לאחר הניסוי הראשון. בשימוש שנתי, החיסכון ברישוי יכול לכסות את עלות הפיתוח תוך מספר חודשים.
כיצד אנו פותרים הקצאה דטרמיניסטית
הדרישה המרכזית של מבחן A/B: משתמש חייב תמיד ליפול לאותו וריאנט ניסוי. כדי להשיג זאת, אנו משתמשים ב-הקצאה מבוססת hash. hash של user_id ושם הניסוי מודולו 100% קובע את מספר הווריאנט. התוצאה נשמרת במסד הנתונים, מה שמבטיח עקביות על פני ביקורים מרובים.
הנה סכימת הטבלה לאחסון ניסויים והקצאות:
CREATE TABLE experiments (
id SERIAL PRIMARY KEY,
slug VARCHAR(100) UNIQUE NOT NULL,
name VARCHAR(255) NOT NULL,
description TEXT,
status VARCHAR(20) DEFAULT 'draft', -- draft, running, paused, completed
traffic SMALLINT DEFAULT 100, -- % трафика, участвующего в эксперименте
start_at TIMESTAMPTZ,
end_at TIMESTAMPTZ,
created_at TIMESTAMPTZ DEFAULT NOW(),
updated_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE experiment_variants (
id SERIAL PRIMARY KEY,
experiment_id INTEGER REFERENCES experiments(id),
slug VARCHAR(100) NOT NULL,
name VARCHAR(255),
weight SMALLINT DEFAULT 50,
config JSONB DEFAULT '{}',
UNIQUE(experiment_id, slug)
);
CREATE TABLE user_assignments (
user_id BIGINT NOT NULL,
experiment_id INTEGER REFERENCES experiments(id),
variant_id INTEGER REFERENCES experiment_variants(id),
assigned_at TIMESTAMPTZ DEFAULT NOW(),
PRIMARY KEY (user_id, experiment_id)
);
שירות ה-PHP מיישם התפלגות דטרמיניסטית באמצעות crc32 ושמירה במסד נתונים:
class ExperimentAssignmentService {
public function getVariant(int $userId, string $experimentSlug): ?string {
$experiment = $this->getActiveExperiment($experimentSlug);
if (!$experiment) return null;
$existing = $this->assignmentRepo->find($userId, $experiment['id']);
if ($existing) return $existing['variant_slug'];
$trafficBucket = $this->hashToBucket($userId, $experimentSlug . '_traffic');
if ($trafficBucket >= $experiment['traffic']) return null;
$variantBucket = $this->hashToBucket($userId, $experimentSlug);
$variant = $this->selectVariant($experiment['variants'], $variantBucket);
$this->assignmentRepo->assign($userId, $experiment['id'], $variant['id']);
$this->eventTracker->track($userId, 'experiment.assigned', [
'experiment' => $experimentSlug,
'variant' => $variant['slug'],
]);
return $variant['slug'];
}
private function hashToBucket(int $userId, string $salt): int {
$hash = crc32($userId . '_' . $salt);
return abs($hash) % 100;
}
private function selectVariant(array $variants, int $bucket): array {
$cumulative = 0;
foreach ($variants as $variant) {
$cumulative += $variant['weight'];
if ($bucket < $cumulative) return $variant;
}
return end($variants);
}
} מעקב אירועים ב-ClickHouse
אנו שולחים את כל פעולות המשתמש המשמעותיות עם הקשר הניסוי. כדי לא להאט את חוויית המשתמש, האירועים נכתבים באופן אסינכרוני דרך תור.
class ExperimentEventTracker {
public function track(int $userId, string $event, array $properties = []): void {
$activeVariants = $this->assignmentRepo->getUserVariants($userId);
$payload = [
'event' => $event,
'user_id' => $userId,
'session_id' => session_id(),
'occurred_at' => now()->toIso8601String(),
'experiments' => $activeVariants,
'properties' => $properties,
];
$this->queue->push(new TrackExperimentEvent($payload));
}
}הנתונים מאוחסנים ב-ClickHouse — DBMS עמודי המותאם לשאילתות אנליטיות. זה מאפשר חישובי המרה מהירים ויצירת דוחות גם עם מיליוני אירועים.
חישוב תוצאות: מבחן Z
לאחר איסוף הנתונים, אנו משתמשים ב-מבחן Z דו-צדדי לפרופורציות (ויקיפדיה). הוא מצביע האם ההבדל בין ההמרות של קבוצת הביקורת וקבוצת הניסוי הוא מובהק סטטיסטית. האפקט המינימלי שניתן לזיהוי (MDE) מוגדר מראש — לדוגמה, 5% בעוצמה של 80%.
import numpy as np
from scipy import stats
def calculate_significance(control, treatment):
p1 = control['conversions'] / control['users']
p2 = treatment['conversions'] / treatment['users']
n1, n2 = control['users'], treatment['users']
p_pool = (control['conversions'] + treatment['conversions']) / (n1 + n2)
se = np.sqrt(p_pool * (1 - p_pool) * (1/n1 + 1/n2))
if se == 0:
return {'error': 'Insufficient data'}
z = (p2 - p1) / se
p_value = 2 * (1 - stats.norm.cdf(abs(z)))
diff = p2 - p1
se_diff = np.sqrt(p1*(1-p1)/n1 + p2*(1-p2)/n2)
ci = [diff - 1.96*se_diff, diff + 1.96*se_diff]
return {'significant': p_value < 0.05, 'p_value': round(p_value, 6), 'lift': round((p2-p1)/p1*100,2) if p1>0 else None}כדי להבטיח תוצאות אמינות, אנו בודקים גם אי-התאמה ביחס המדגם (SRM) — האם התפלגות המשתמשים בפועל חורגת מהצפוי. אם ערך ה-p של מבחן chi-square נמוך מ-0.01, הנתונים מסומנים כלא אמינים.
| פרמטר | חישוב |
|---|---|
| המרה | המרות / משתמשים |
| Lift | (p2-p1)/p1 * 100% |
| רווח בר-סמך | p ± 1.96 * SE |
| שלב | משך | תוצאה |
|---|---|---|
| אנליטיקה | 1–2 ימים | יעדים, מדדים, ארכיטקטורה |
| עיצוב | 2–3 ימים | סכימת DB, API, חוזים |
| יישום | 7–10 ימים | קוד, בדיקות |
| בדיקות | 2 ימים | יחידה, אינטגרציה, עומס |
| פריסה | 1–2 ימים | שחרור, ניטור |
כיצד דגלי תכונה עובדים במבחני A/B?
מבחני A/B ו-דגלי תכונה הם מושגים קשורים. אנו משלבים אותם כך: וריאנט ניסוי מכיל תצורת JSON המשפיעה על התנהגות התכונה. לדוגמה, וריאנט CREATE TABLE experiments ( id SERIAL PRIMARY KEY, slug VARCHAR(100) UNIQUE NOT NULL, name VARCHAR(255) NOT NULL, description TEXT, status VARCHAR(20) DEFAULT 'draft', -- draft, running, paused, completed traffic SMALLINT DEFAULT 100, -- % трафика, участвующего в эксперименте start_at TIMESTAMPTZ, end_at TIMESTAMPTZ, created_at TIMESTAMPTZ DEFAULT NOW(), updated_at TIMESTAMPTZ DEFAULT NOW() ); CREATE TABLE experiment_variants ( id SERIAL PRIMARY KEY, experiment_id INTEGER REFERENCES experiments(id), slug VARCHAR(100) NOT NULL, name VARCHAR(255), weight SMALLINT DEFAULT 50, config JSONB DEFAULT '{}', UNIQUE(experiment_id, slug) ); CREATE TABLE user_assignments ( user_id BIGINT NOT NULL, experiment_id INTEGER REFERENCES experiments(id), variant_id INTEGER REFERENCES experiment_variants(id), assigned_at TIMESTAMPTZ DEFAULT NOW(), PRIMARY KEY (user_id, experiment_id) ); מאפשר class ExperimentAssignmentService { public function getVariant(int $userId, string $experimentSlug): ?string { $experiment = $this->getActiveExperiment($experimentSlug); if (!$experiment) return null; $existing = $this->assignmentRepo->find($userId, $experiment['id']); if ($existing) return $existing['variant_slug']; $trafficBucket = $this->hashToBucket($userId, $experimentSlug . '_traffic'); if ($trafficBucket >= $experiment['traffic']) return null; $variantBucket = $this->hashToBucket($userId, $experimentSlug); $variant = $this->selectVariant($experiment['variants'], $variantBucket); $this->assignmentRepo->assign($userId, $experiment['id'], $variant['id']); $this->eventTracker->track($userId, 'experiment.assigned', [ 'experiment' => $experimentSlug, 'variant' => $variant['slug'], ]); return $variant['slug']; } private function hashToBucket(int $userId, string $salt): int { $hash = crc32($userId . '_' . $salt); return abs($hash) % 100; } private function selectVariant(array $variants, int $bucket): array { $cumulative = 0; foreach ($variants as $variant) { $cumulative += $variant['weight']; if ($bucket < $cumulative) return $variant; } return end($variants); } } . קוד הפרונטאנד או הבקאנד פשוט קורא את התצורה הזו ומשנה התנהגות.
$variant = $experimentService->getVariant($userId, 'checkout-redesign');
$config = $experimentService->getVariantConfig('checkout-redesign', $variant);
$checkoutSteps = $config['checkout_steps'] ?? 3; מה כלול
- פיתוח שירות הקצאה עם התפלגות מבוססת hash ובדיקות יחידה
- מעקב אירועים עם כתיבה אסינכרונית ל-ClickHouse
- חישוב מובהקות סטטיסטית (מבחן Z, רווחי בר-סמך)
- פאנל ניהול להפעלה וניטור ניסויים
- תיעוד אינטגרציה והדרכת צוות
- תמיכה בחודש הראשון של ההשקה הפיילוטית
תהליך העבודה שלנו
- אנליטיקה — אנו מנתחים את היעדים, המדדים והארכיטקטורה הנוכחית שלכם (1–2 ימים)
- עיצוב — מכינים סכימת DB, API, חוזים (2–3 ימים)
- יישום — כותבים קוד, כותבים בדיקות (7–10 ימים)
- בדיקות — בדיקות יחידה, בדיקות אינטגרציה, עומס (2 ימים)
- פריסה — פריסה בסביבה שלכם, הגדרת ניטור (1–2 ימים)
לכל שאלה, צרו קשר — נעזור להעריך את היקף העבודה ולחשב חיסכון.
לוח זמנים ותקציב
זמן פיתוח משוער: 14 עד 21 ימים. העלות מחושבת באופן פרטני בהתאם למורכבות האינטגרציה ודרישות נוספות. הזמינו פלטפורמה מותאמת אישית היום — נספק הצעה תוך 2 ימי עסקים.
אנו מבטיחים איכות קוד: אנו משתמשים בסקירות קוד, כיסוי בדיקות של לפחות 80%, ומספקים אחריות לתיקון באגים ל-30 יום לאחר ההשקה. קבלו ייעוץ וגלו כיצד פלטפורמת A/B מותאמת אישית יכולה לשפר את ההמרות שלכם ללא פשרות בביצועים.







