Архитектура системСистемный архитектор

Спроектируйте рекламный обмен на планетарном уровне в реальном времени, который организует аукционные решения менее чем за 100 мс среди различных платформ со стороны спроса, контролирует расход бюджета по кампаниям без распределенной блокировки, обнаруживает клик-фрод через поведенческие отпечатки в пути запроса и реконсилирует расхождения в биллинге через неизменяемые записи в реестре, при этом соблюдая региональные требования к хранению данных для соответствия GDPR.

Проходите собеседования с ИИ помощником Hintsage

Ответ на вопрос

История вопроса

Ранее размещение рекламы полагалось на статические конфигурации водопадов, где издатели последовательно приоритизировали партнеров по спросу, создавая задержки и утечки доходов. Переход к Header Bidding и протоколам OpenRTB обеспечил демократизацию доступа к инвентарю, но ввел серьезные инженерные ограничения: аукционы должны завершаться в пределах 100 мс, чтобы предотвратить уход пользователей со страниц, а контроль бюджета должен предотвращать перерасход по тысячам узлов. Этот вопрос возник из необходимости заменить централизованные пайплайны Apache Kafka на архитектуры с краевыми вычислениями, способные принимать автономные финансовые решения, при этом соблюдая строгие требования к аудируемости и размещению данных.

Проблема

Традиционные архитектуры полагаются на централизованные кластеры PostgreSQL или Redis для счетчиков бюджета и хранилищ функций, создавая задержки между регионами, что нарушает 100 мс SLA во время пиковых нагрузок, таких как Черная пятница. Наивная оптимистичная блокировка на уменьшении бюджета вызывает «громкое стадное поведение» и отброшенные ставки, в то время как асинхронное обнаружение мошенничества позволяет ботам исчерпать бюджет кампании до срабатывания детектора. Более того, реконсиляция биллинга между DSP (платформами со стороны спроса) страдает от сетевых разделений, когда пиксели показов срабатывают, но сообщения об одобрении теряются, что приводит к утечке доходов или дублированию зарядов, подрывающим доверие рекламодателей.

Решение

Развертывайте сайдкары Envoy Proxy с фильтрами WebAssembly на краевых PoP (точках присутствия), чтобы выполнять логику аукциона в пределах 10 мс от конечных пользователей. Реализуйте счетчики CRDT (конфликтно-независимый реплицируемый тип данных) с использованием Redis и Gossip протокола для контроля бюджета, позволяя узлам на краях принимать ставки локально, гарантируя при этом глобальную согласованность бюджета через конечную сходимость. Встраивайте легковесные модели TensorFlow Lite в краевой слой для обнаружения ботов в реальном времени с использованием поведенческих отпечатков, таких как паттерны скорости мыши и энтропия навигации. Используйте Apache Pulsar с гео-репликацией и BookKeeper для неизменяемых аудиторских логов, обеспечивая семантику «ровно один раз» с помощью идемпотентных продюсеров и окон дедубликации. Для соответствия GDPR реализуйте проверки K-Anonymity и маршрутизацию по месту хранения данных через Anycast DNS с учетом EDNS Client Subnet.

Ситуация из жизни

Во время Черной пятницы 2023 года наша платформа столкнулась с 40-кратным увеличением трафика, что привело к насыщению нашего централизованного Redis хранилища бюджета в us-east-1, что вызвало 12% таймаутов аукционов и угрожало потерей потенциального дохода в размере 2 миллионов долларов. Инженерной команде пришлось принять критически важное архитектурное решение: поддерживать сильную согласованность и принимать нарушения задержки или приоритизировать скорость и рисковать катастрофическим перерасходом бюджета.

Решение A: Redis Cluster с Redlock

Мы рассмотрели возможность реализации алгоритмов Redlock на пяти независимых узлах мастер Redis, чтобы обеспечить строгую согласованность бюджета. Этот подход теоретически гарантировал, что ни одна кампания не превысила своего ежедневного лимита, требуя согласия большинства для каждого уменьшения. Однако время обратной связи между узлами на краях и кластером Redis в среднем составляло 35 мс, а под нагрузкой конкуренция за блокировки вызвала повторные попытки 8% запросов, превышая наш 100 мс SLA. Хотя это обеспечивало идеальную точность бюджета, неприемлемая задержка и операционная сложность сделали его неподходящим для реального времени.

Решение B: Локальное кэширование в памяти с асинхронной синхронизацией

В качестве альтернативы мы оценили возможность разрешить каждому узлу на краю поддерживать исключительно локальные счетчики бюджета, которые синхронизировались асинхронно в общий реестр каждые 30 секунд. Это обеспечивало задержку аукциона менее 5 мс и справлялось с пиковыми нагрузками без внешних зависимостей. К сожалению, во время всплеска несколько узлов на краях перерасходовали высокозначимые кампании на 800 тысяч долларов в совокупности до того, как произошло синхронизация, что вызвало проблемы с доверием рекламодателей и контрактные штрафы. Скорость была оптимальной, но финансовый риск оказался катастрофическим для смежной с системой платежей.

Решение C: Гибридная архитектура CRDT с иерархическим контролем

Мы реализовали гибридный подход с использованием Delta-State CRDTs в Redis на трех уровнях: краевом, региональном и глобальном. Узлы на краях принимают ставки, используя локальные PN-Counters (положительные-негативные счетчики) с консервативными локальными порогами, установленными на 95% от глобального бюджета. Когда местные бюджеты исчерпаются, узлы запрашивают региональные кэши с согласованностью Read-Your-Writes. Оставшиеся 5% резерва управляются глобальным реестром с использованием слияний CRDT в ходе синхронизации поддержки общности. Для борьбы с мошенничеством мы развернули модели TinyML на узлах на краях, обученные для обнаружения паттернов ботов без сетевых вызовов. Мы выбрали это решение, потому что оно обеспечивало 99.9% точности бюджета, при этом поддерживая 45 мс p99 задержки.

Результат

Платформа обработала 12 миллионов запросов в секунду во время пикового времени без перерасхода бюджета по лимитированным кампаниям. Задержка обнаружения мошенничества снизилась с 150 мс до 8 мс, блокируя 3.4% злоумышленного трафика до подачи ставок. Реконсиляция CRDT достигла конечной согласованности в пределах 200 мс между регионами, что значительно ниже окна реконсиляции биллинга, а соответствие GDPR было обеспечено за счет локальной обработки данных на крае.

Что кандидаты часто упускают

Как вы предотвращаете перерасход бюджета, когда несколько узлов на краях одновременно уменьшают один и тот же бюджет кампании без получения глобальных блокировок?

Большинство кандидатов предлагают распределенные блокировки или атомарные операции уменьшения в Redis, которые не работают при сетевых разделениях или высокой задержке. Правильный подход использует PN-Counters (положительные-негативные счетчики), реализованные как CRDTs. Каждый узел на краю поддерживает локальный счетчик увеличения для трат и счетчик уменьшения для возмещений. Когда узел принимает ставку, он увеличивает свой локальный счетчик трат. Во время синхронизации поддержки узлы обмениваются состояниями своих счетчиков и объединяют их с использованием операции Join (взятие максимума каждого компонента счетчика). Чтобы предотвратить временный перерасход, реализуйте локально алгоритмы Token Bucket с консервативными лимитами. Если сумма всех локальных трат приближается к глобальному лимиту, узлы переходят в режим "экономии", где запрашивают у регионального координирующего органа оставшиеся 5% бюджета. Это обеспечивает, что хотя временный перерасход на 1-2% теоретически возможен во время разделения, система никогда не превышает 105% бюджета, что приемлемо для SLA цифровой рекламы, в отличие от традиционных банковских систем.

Как вы обеспечиваете однократный биллинг, когда пиксели отслеживания показов срабатывают из браузеров пользователей, но сетевые сбои предотвращают доставку сообщений об одобрении серверу аукциона?

Кандидаты часто предлагают идемпотентность Kafka или обновления баз данных, упуская проблему от конца до конца. Решение требует идемпотентных ключей, генерируемых на краю с использованием UUIDv7 (временной порядок), встроенных в разметку рекламных материалов. Когда браузер вызывает пиксель показа, он включает этот ключ. Уровень Nginx на крае записывает в Apache Pulsar с включенной дедубликацией, используя 24-часовое окно. Хранение BookKeeper в Pulsar гарантирует, что дублирующие записи с одинаковым ключом игнорируются на уровне брокера, а не на уровне потребителя. Кроме того, реализуйте At-Least-Once доставку в BigQuery таблицу стадии, разделенную по идемпотентному ключу, с MERGE операциями, которые дедублицируют в процессе ETL. Эта защита с двумя уровнями обеспечивает, что даже если браузер несколько раз попытается вызвать пиксель из-за ошибок 500, рекламодатель будет Charged exactly once.

Как вы обрабатываете рассинхронизацию часов между географически распределенными участниками торгов при определении победителей аукциона на основе времени отклика?

Это тонкий вопрос. Кандидаты часто предлагают NTP или TrueTime (из Spanner), но это добавляет задержку. Правильная архитектура исключает зависимости от часов реального времени из логики аукциона. Вместо сравнения временных меток из ответов DSP используйте Логические часы (метки времени Ламорта) или просто FIFO очереди на краю. Когда аукцион начинается, узел на крае запускает таймер высокой разрешающей способности (Performance.now() в V8 или C++ chrono). Ответы DSP ранжируются по порядку прибытия на сетевой интерфейс, а не по временным меткам заголовков. Чтобы справиться с задержками, реализуйте настраиваемый тайм-аут с использованием адаптивных алгоритмов тайм-аута, которые регулируются на основе исторической p99 задержки для каждого DSP. Для анализа после событий и споров по биллингу запишите показания монотонных часов и UTC временную метку с неопределенностями, сохраняя их в CockroachDB, который автоматически обрабатывает окна неопределенности. Это обеспечивает справедливость даже в тех случаях, когда часы серверов одного DSP опережают другие на 200 мс.