Представьте: вы запустили A/B-тест через Optimizely, но через неделю заметили, что LCP вырос на 300 мс из-за стороннего скрипта. Или вы не можете получить сырые данные — только агрегированные графики. А стоимость лицензии для 10 000 посетителей — существенные затраты в месяц. Кастомная платформа A/B-тестирования решает все эти проблемы: вы контролируете код, данные и бюджет. Экономия на лицензионных отчислениях может достигать 70%, а окупаемость наступает в течение 3–6 месяцев. К тому же кастомная платформа работает в 2–3 раза быстрее за счёт отсутствия внешних скриптов.
Мы накопили более 5 лет опыта в разработке таких систем для интернет-магазинов с трафиком 1 млн+ посетителей в месяц — более 50 проектов. Наша платформа строится по модульному принципу и легко адаптируется под любую архитектуру.
Преимущества кастомной платформы A/B-тестирования
Готовые инструменты ускоряют старт, но при масштабировании становятся дорогими и негибкими. Кастомная платформа окупается уже на нескольких тестах — в одном из проектов конверсия выросла на 15% после первого эксперимента. При годовом использовании экономия на лицензионных отчислениях может покрыть стоимость разработки в течение нескольких месяцев.
Как мы решаем проблему детерминированного распределения?
Ключевое требование A/B-теста: пользователь всегда должен попадать в один и тот же вариант эксперимента. Для этого мы используем hash-based assignment. Хеш от 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 — колоночной СУБД, оптимизированной для аналитических запросов. Это позволяет быстро считать конверсии и строить отчёты даже при миллионах событий.
Вычисление результатов: Z-тест
После сбора данных мы используем двусторонний Z-тест для пропорций (Wikipedia). Он показывает, является ли разница между конверсиями контрольной и тестовой групп статистически значимой. Минимальный обнаруживаемый эффект (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} Чтобы результаты были достоверны, мы также проверяем Sample Ratio Mismatch (SRM) — не отклонилось ли реальное распределение пользователей от ожидаемого. Если p-value хи-квадрат теста ниже 0.01, данные помечаются как ненадёжные.
| Параметр | Расчет |
|---|---|
| Конверсия | conversions / users |
| Lift | (p2-p1)/p1 * 100% |
| Доверительный интервал | p ± 1.96 * SE |
| Этап | Длительность | Результат |
|---|---|---|
| Аналитика | 1–2 дня | Цели, метрики, архитектура |
| Проектирование | 2–3 дня | Схема БД, API, контракты |
| Реализация | 7–10 дней | Код, тесты |
| Тестирование | 2 дня | Юнит, интеграционное, нагрузка |
| Деплой | 1–2 дня | Развёртывание, мониторинг |
Как работают Feature Flags в A/B-тестах?
A/B-тестирование и feature flags — смежные концепции. Мы интегрируем их так: вариант эксперимента содержит JSON-конфигурацию, которая влияет на работу фич. Например, вариант treatment_a включает {"checkout_steps": 1, "show_trust_badges": true}. Код на фронте или бэкенде просто читает этот конфиг и меняет поведение.
$variant = $experimentService->getVariant($userId, 'checkout-redesign'); $config = $experimentService->getVariantConfig('checkout-redesign', $variant); $checkoutSteps = $config['checkout_steps'] ?? 3; Что входит в работу
- Разработка assignment service с hash-based распределением и unit-тестами
- Трекинг событий с асинхронной записью в ClickHouse
- Вычисление статистической значимости (Z-тест, доверительные интервалы)
- Админ-панель для запуска и мониторинга экспериментов
- Документация по интеграции и обучение вашей команды
- Поддержка первого месяца пилотного запуска
Процесс работы
- Аналитика — разбираем ваши цели, метрики, текущую архитектуру (1–2 дня)
- Проектирование — готовим схему БД, API, контракты (2–3 дня)
- Реализация — пишем код, пишем тесты (7–10 дней)
- Тестирование — юнит-тесты, интеграционное тестирование, нагрузка (2 дня)
- Деплой — разворачиваем в вашем окружении, настраиваем мониторинг (1–2 дня)
По всем вопросам обращайтесь к нам — мы поможем оценить объём работ и рассчитать экономию.
Сроки и бюджет
Ориентировочное время разработки — от 14 до 21 дня. Стоимость рассчитывается индивидуально в зависимости от сложности интеграции и дополнительных требований. Закажите разработку кастомной платформы уже сегодня — мы подготовим предложение в течение 2 рабочих дней.
Мы гарантируем качество кода: используем код-ревью, покрытие тестами не менее 80%, и предоставляем гарантию на устранение ошибок в течение 30 дней после ввода в эксплуатацию. Получите консультацию и узнайте, как кастомная платформа A/B-тестирования может улучшить ваши конверсии без компромиссов по производительности.







