Оглавление18 разделов
Витрина арбитражной команды должна отвечать на простой вопрос: какой расход привёл к каким наблюдаемым событиям, договорным статусам, начислениям и платежам. Но эти сущности имеют разную кратность и время. Один клик может породить несколько технических событий, одна конверсия — много изменений статуса, одно начисление — несколько корректировок. Если собрать всё одним join, отчёт умножит строки и создаст прибыль из структуры базы. Архитектура начинается с grain каждой таблицы и неизменяемых фактов.
Прямой ответХраните клики, продуктовые события, историю статусов, расходы, начисления и платежи отдельными фактами. Связывайте их через namespace + идентификатор, сохраняйте event/received/effective time и публикуйте витрины с revision, freshness и контрольными суммами.
1. Начните с бизнес-вопросов
Перечислите решения: pacing сегодня, зрелый CPA по когорте, сверка approved, cash forecast и оценка креатива. Каждый вопрос требует собственного времени и уровня детализации.
Одна таблица не обязана обслуживать всё; семантический слой согласует определения.
Полезно создать metric contract для каждого вопроса. В нём grain, numerator, denominator, time axis, maturity, currency, filters и owner. Например, operational CPA по received events не совпадает с finance CPA по approved cohort. Обе метрики могут быть правильными, если названы честно и не используются вместо друг друга.
2. Определите grain клика
Одна строка — один принятый логический клик в namespace трекера. Повтор HTTP-запроса хранится в техническом логе, но не создаёт второй факт.
Поля: click_id, occurred_at, received_at, source, campaign, creative, placement, route version и quality flags.
Запишите зерно как контракт: одна строка представляет один клик, одно изменение статуса, один расходный срез или один агрегат кампании за дату. Если в таблице смешаны клики и дневные суммы, обычный join способен умножить деньги на число событий. Разные зерна хранят отдельно и соединяют только через контролируемую агрегацию.
Для каждой меры укажите аддитивность. Расход обычно суммируется по времени и кампании, уникальные пользователи — нет, а коэффициент конверсии нужно пересчитывать из числителя и знаменателя. Эта пометка предотвращает формально корректные, но неверные отчёты.
3. Отделите расход
Рекламная платформа может отдавать агрегированный spend без click-level связи. Fact spend хранит grain platform-account-campaign-time bucket и исходную валюту.
Нельзя равномерно распределить расход по кликам и затем выдавать полученную точность за факт. Allocation — отдельная модель.
4. Храните продуктовые события
Fact event имеет event_id, type, occurred_at, received_at, entity references и schema version. Повторная доставка меняет delivery log, но не создаёт новое событие.
События не перезаписывают клик и могут оставаться unattributed.
Event fact не должен содержать десятки изменяемых атрибутов кампании в текстовом виде. Он хранит устойчивые foreign keys и минимальный контекст as-of, а исторические dimensions восстанавливают название и свойства. Иначе переименование campaign задним числом перепишет старые события или создаст конфликт между источниками.
5. История статусов — отдельный факт
Pending, approved, rejected, reversal и correction приходят во времени. Таблица status transition хранит from, to, effective_at, received_at, reason code и source revision.
Current status — производная snapshot-витрина, а не единственная история.
6. Начисления и платежи
Accrual отражает договорный расчёт, payment — движение денег. Они связываются, но не совпадают по периоду и сумме из-за удержаний, валюты или частичных платежей.
Финансовый отчёт не строят напрямую из event value, если договор определяет другую базу.
Для сверки создают bridge table между accrual lines и payment lines с типом связи: exact, bundled, partial, adjustment или unknown. Связь не обязана быть один-к-одному. Алгоритмическое сопоставление по сумме и дате даёт candidate, но финансовый статус confirmed появляется только по документированному правилу. Неразнесённый остаток остаётся видимым.
7. Проектируйте измерения
Campaign, creative, offer, GEO и source меняются. Используйте устойчивый внутренний ключ и версии атрибутов, чтобы исторический отчёт не переписывался текущим названием.
Unknown dimension имеет отдельную строку, а не NULL, потерянный в join.
Для изменяемых справочников выберите стратегию: хранить только актуальное значение или интервалы действия версий. Если менеджер, категория или география кампании менялись, исторический отчёт часто должен показывать состояние на момент события, а не сегодняшнее название. Интервалы valid_from и valid_to делают такое соединение явным.
8. Создайте namespace идентификаторов
Одинаковая строка от двух систем не обязана описывать одну сущность. Ключ включает owner/source namespace. Crosswalk хранит подтверждённые преобразования между external и internal IDs.
Слабые временные совпадения не становятся постоянной связью.
9. Нормализуйте время
Все факты хранят UTC и исходную timezone, если она дана. Для отчёта вычисляются локальный день клика, события, обработки и выплаты.
Выбор оси времени указывается в каждой метрике.
При построении partition учитывайте late arrival: received date определяет техническую загрузку, event date — бизнес-когорту. Один event может попасть в сегодняшнюю ingest partition и обновить прошлую business partition. Orchestrator должен знать обе зависимости, а freshness dashboard — показывать до какого event date завершён пересчёт.
10. Нормализуйте валюты версионированно
Исходная сумма и currency неизменяемы. Пересчитанная сумма хранит rate, source, effective date и target currency.
Нельзя заменять исторический курс текущим без новой revision.
11. Обрабатывайте late events
Watermark определяет, до какого event time поток считается достаточно полным. Поздняя строка обновляет затронутую partition и создаёт новую revision.
Дашборд показывает preliminary/final и дату последнего пересчёта.
12. Не делайте destructive update фактов
Исправление приходит как новая версия или компенсирующая запись. Это позволяет воспроизвести отчёт as-of прошлой даты и объяснить изменение.
Snapshot current может перезаписываться, если источник истории остаётся.
Для status history используйте effective_from/effective_to или последовательность событий. Если источник прислал исправление задним числом, сохраняются received_at и source_revision. Это позволяет ответить на два вопроса: что считалось истиной вчера и какой статус применяется сейчас. Финансовая сверка без такой двухвременной логики часто необъяснима.
13. Создайте семантические метрики
Определение FTD, approved, spend, revenue и margin хранится как код и документация с версией. Dashboard вызывает одну реализацию, а не копирует формулу.
Изменение определения не должно молча продолжать тот же временной ряд.
14. Защититесь от fan-out join
Перед join проверяйте grain и ожидаемую кратность. Click → status history является one-to-many; для текущего статуса сначала строят отдельную подвыборку.
Тест сравнивает число уникальных ключей до и после соединения.
Добавьте автоматический тест multiplicity. Перед join код считает rows и distinct business keys по обеим сторонам, после — ожидаемое увеличение. Если click соединён с тремя status rows и двумя spend allocations, результат может умножиться в шесть раз. Правильный запрос сначала агрегирует каждый факт до общей grain.
15. Введите data quality tests
Uniqueness, not-null для обязательных ключей, referential coverage, допустимые переходы статусов, свежесть, контрольные суммы и диапазоны. Ошибка блокирует публикацию затронутой метрики либо помечает degraded.
Тест должен указывать владельца и выборку нарушений.
Контроль баланса сравнивает расход и события с источником в допустимом окне задержки. Проверка уникальности ловит повтор ключа, not-null — потерю обязательного поля, accepted-values — новый неизвестный статус. Важно, чтобы сбой останавливал публикацию затронутого среза, а не только создавал уведомление, которое можно проигнорировать.
16. Управляйте доступом
Сырые идентификаторы и финансовые данные доступны минимальному кругу. Аналитические витрины используют псевдонимные ключи и агрегаты.
Экспорт и изменение схемы журналируются; сроки хранения отличаются по слоям.
Доступ выдаётся по роли и минимально необходимому уровню детализации. Команде оптимизации может быть достаточно агрегатов кампании, финансам — сверочных сумм, а инженерной группе — технических идентификаторов на ограниченный срок. Один общий экспорт для всех увеличивает риск и мешает понять, кто использовал данные.
17. Документируйте lineage
Для показателя видны источники, преобразования, версия кода, дата сборки и owner. Lineage нужен не как диаграмма ради диаграммы, а чтобы оценить impact изменения.
Если поменялся reason code, команда знает, какие отчёты пересчитать.
Impact analysis можно автоматизировать через manifest моделей и tests. Перед изменением поля система показывает downstream datasets, dashboards и exports. Владелец выбирает backfill horizon и предупреждает пользователей о revision. Такой процесс не мешает развитию схемы; напротив, позволяет менять её быстро без скрытого разрушения старых отчётов.
18. Вывод
Сильная витрина не пытается спрятать сложность в одной широкой таблице. Она сохраняет отдельные факты и времена, версионирует определения и допускает неизвестные связи.
Тогда CPA, RevShare и payout можно воспроизвести, а поздняя корректировка становится объяснимой revision, а не загадочным изменением вчерашней цифры.
Практический критерий готовности витрины — способность выбрать одну выплату и пройти назад до начисления, истории статуса, события, клика и агрегата расхода, сохранив все источники времени и версии. Одновременно можно выбрать дневной total и доказать, что join не умножает сущности. Эти два теста ценнее сотни красивых графиков.
Не моделируйте отсутствующие данные как нольNULL, unknown, pending и zero — разные состояния. Их смешивание создаёт ложный доход, ложный отказ или неверное решение об остановке кампании.
Станьте партнёром и начните работать
Перейдите в партнёрскую программу, изучите актуальные условия и выберите подходящий формат сотрудничества.
СТАТЬ ПАРТНЁРОМ
