Мы напечатали 18 000 флаеров для запуска продукта в регионе DACH в марте. Одна короткая ссылка на обороте, три региональных лендинга, на которые мы хотели направить людей: /de для посетителей из Германии, /fr для небольшой французской части аудитории и /en для всех остальных. Руководитель отдела маркетинга задал очевидный вопрос: печатаем три флаера или один?
Печатаете один. Ссылка берет на себя маршрутизацию.
«Смарт-ссылка» - это единый короткий URL, целевой адрес которого вычисляется в момент перенаправления, а не в момент создания ссылки. Есть один слаг. Есть несколько возможных пунктов назначения. Решение принимается в том же обработчике, который иначе просто выдал бы 302 - не нужно вызывать отдельный сервис, использовать JS-заглушку на лендинге или делать лишний переход. Этот пост о том, как это устроено внутри, о шести измерениях, по которым маршрутизирует Elido, и о случаях, когда стоит выбрать другой инструмент.
Три вещи, которыми не является смарт-ссылка#
Люди приходят к смарт-ссылкам, имея три разных предыдущих опыта, и в каждом случае компромиссы различаются.
Простой редирект. Один слаг, один пункт назначения, ноль логики. Обработчик редиректа ищет данные в кэше и выдает 302. По задержке это не превзойти; но и условие добавить нельзя. Это база - всё, что сложнее, чего-то стоит.
Смарт-ссылка на границе (edge). Один слаг, несколько возможных пунктов назначения, крошечный шаг оценки правил, вставленный между поиском в кэше и ответом. Поскольку правило живет в том же процессе, что и поиск в кэше, стоимость составляет доли миллисекунды (0.3ms p50 / 1ms p95 в случае Elido). Посетитель видит один HTTP-цикл. Кэш браузера не «отравляется», потому что ответы 302 не кэшируются по умолчанию согласно RFC 7234 §4.2.2 - факт, который здесь важен, так как маршрутизация для каждого запроса имеет смысл только в том случае, если каждому запросу позволено выбирать свой собственный пункт назначения.
Маршрутизатор на JavaScript на лендинге. Рендерится нейтральная HTML-страница, JS проверяет navigator.userAgent или сервис geo-IP, затем выполняет window.location = '/foo'. Это худший вариант из трех. Посетитель видит рендер HTML, затем редирект, затем актуальную страницу - как минимум один лишний цикл, часто два, если проверка геопозиции выполняется сторонним сервисом. SEO-индексация путается, потому что поисковые роботы видят нейтральную страницу. Браузеры, блокирующие куки, и расширения для обеспечения приватности ломают работу JS. В заметках к релизу Apple Intelligent Tracking Prevention 2.3 прямо упоминается этот паттерн: ссылки для отслеживания на стороне клиента через document referrer ограничиваются, и для смягчения последствий требуется участие сервера. Если вы используете маршрутизацию на JS сегодня, вы уже переплачиваете.
Правильное место для принятия решения о маршрутизации - тот же переход (hop), который уже выдает редирект. Это то, что делают смарт-ссылки на границе (edge).
Почему это живет на границе - бюджет задержки#
У уровня редиректов Elido есть жесткий бюджет задержки: p50 5 мс, p95 15 мс при попадании в кэш, без учета TLS-handshake. Это число не является абстрактным идеалом - всё, что выбивается за эти рамки, безжалостно удаляется. Синхронное чтение SQL в горячем пути, компиляция регулярных выражений при каждом запросе, блокирующий ввод-вывод для события клика: всё это убрано и вынесено в воркеры холодного пути.
Две причины существования этого бюджета:
- Мобильные сети добавляют свою «наценку». Руководство Apple по снижению сетевых задержек подробно объясняет, как задержки в сотовых сетях накапливаются в цепочках редиректов. Каждый дополнительный переход добавляет RTT, который сеть посетителя и так уже увеличила. Чем меньше переходов мы добавляем, тем меньше их наказывает сеть.
- Близость к границе (edge) - это реальный рычаг управления. Вводная статья Cloudflare по маршрутизации на стороне edge формулирует это так же: самое дешевое решение - это то, которое принимается в том же процессе, что и запись ответа, в точке присутствия (POP), ближайшей к посетителю. Мы не уникальны в использовании edge-маршрутизации; уникальность в том, что она встроена в сокращатель URL, а не требует от вас развертывания отдельной функции Cloudflare Workers / Vercel Edge Functions.
Если бы мы перенесли оценку правил на нижестоящий сервис - скажем, гипотетический «rules-api», доступный по HTTP - мы бы добавляли цикл в пределах того же региона при каждом запросе. В регионе это минимум около 5 мс (переход в пределах региона через частную сеть), а при межрегиональном трафике хвост задержек очень быстро становится неприятным. 15 мс p95 не переживут такой цикл. Поэтому правила смарт-ссылок встроены прямо в бинарный файл edge, оцениваясь с помощью скомпилированных матчеров, созданных при загрузке ссылки в кэш. Весь движок правил занимает около 400 строк на Go.
Эта тесная связь также позволяет нам редактировать правила в реальном времени: изменения правил распространяются через pub/sub канал in-memory кеша (link:invalidate), на который подписан каждый edge POP. L1 LRU очищается в течение секунды после публикации, следующий запрос заполняет данные из L2, и новое правило вступает в силу. Подробнее об этом ниже.
Шесть измерений маршрутизации#
Смарт-ссылки Elido сопоставляются по шести параметрам. Каждый из них соответствует определенным входным данным, к которым edge имеет доступ при каждом запросе.
Страна. Двухбуквенный код ISO 3166-1 alpha-2, полученный из IP посетителя через geoip. Полезно, когда у вас есть региональные витрины магазинов и рост конверсии в каждой стране оправдывает сложность маршрутизации. Классическая ловушка здесь - путешественники: немец в отпуске в Испании попадет на испанскую страницу, если вы маршрутизируете только по стране. Если языковые предпочтения важнее географического положения, используйте маршрутизацию по languages. Мы обсуждаем полный процесс geoip в посте о приватности аналитики - IP-адрес усекается перед сохранением, чтобы соответствовать требованиям GDPR.
Устройство. mobile, tablet, desktop, извлекается из строки User-Agent во время запроса. Сценарий, который используют маркетологи: баннеры для установки приложения, которые ведут в App Store на iOS, Play Store на Android и на маркетинговую страницу на десктопе. Нюанс, за которым стоит следить: строки User-Agent на iPad стали движущейся мишенью с тех пор, как iPadOS начала представлять десктопный Safari UA по умолчанию, и наше определение планшетов учитывает это, но оно не дает 100% точности в каждой версии браузера. Если разница между трафиком с планшетов и десктопов имеет для вас денежное значение, настройте инструменты отслеживания на целевой странице и проверяйте данные.
ОС. ios, android, macos, windows, linux. Тот же источник User-Agent, что и для устройства, но более узкое разделение. Кейс с диплинками: направляйте посетителей с iOS на Universal Link, которую перехватывает приложение, а если его нет - на App Store; направляйте Android на Play Store с сохранением данных реферера. Именно для этого мы создали интеграцию с Apple App Site Association.
Язык. Основной тег языка из заголовка Accept-Language посетителя. Коды ISO 639-1, такие как de, fr, pt. Ловушка: Accept-Language - это предпочтение браузера, которое часто не совпадает с IP-геопозицией. Французский экспат в Берлине получит country: DE, languages: ["fr", "en"] - если вы хотите отправить его на /fr, маршрутизируйте по языку; если вы хотите отправить его на немецкую витрину, потому что тестируете локализованные цены через A/B тест, маршрутизируйте по стране. Выстраивайте последовательность правил соответствующим образом.
Время суток и день недели. Окно HH:MM в любом часовом поясе IANA, плюс битовая карта days_of_week. Естественное применение - акции, ограниченные по времени: лендинг «счастливые часы», который становится активным в 17:00 Europe/Berlin с Пн по Пт и переключается на обычную страницу вне этого окна. Окно time_start / time_end поддерживает переход через полночь (22:00 → 02:00), что кажется очевидным, но доставило нам хлопот при переносе движка правил из прототипа, который этого не умел. Полная схема приведена в руководстве по смарт-ссылкам.
Хост реферера. Нормализованная часть имени хоста из заголовка Referer. Полезно для целевых страниц, учитывающих партнеров: посетители, пришедшие с partner.example, попадают на кобрендинговый лендинг; все остальные - на страницу по умолчанию. Это стало менее полезным, чем раньше - современные браузеры агрессивно обрезают Referer, когда ссылающаяся страница устанавливает Referrer-Policy: no-referrer или когда навигация пересекает контексты HTTPS способом, который политика не разрешает. Относитесь к правилам по рефереру как к мягкому сигналу, а не как к способу аутентификации.
Вот и всё. Шесть измерений покрывают маркетинговые решения по маршрутизации, которые мы видели за три года общения с клиентами. Сознательно опущены идентификация пользователя (мы не знаем её на этапе редиректа), произвольные HTTP-заголовки (стоимость реализации не оправдывает выгоду для тех немногих команд, которые об этом просили) и рандомизированные сплиты (вместо этого используйте ротацию вариантов, которая является отдельной функцией).
Семантика первого совпадения; обязательный запасной вариант (fallback)#
Правила представляют собой массив. Edge обходит их по порядку. Выигрывает первое правило, блок match которого полностью удовлетворен, и его destination_url становится целью редиректа. destination_url верхнего уровня ссылки является безусловным запасным вариантом. Мы принципиально не создаем смарт-ссылку без него - смарт-ссылка никогда не выдает 404 по дизайну.
Минимально жизнеспособный вид:
{
"destination_url": "https://acme.example/en",
"targeting_rules": [
{
"match": { "countries": ["DE", "AT", "CH"] },
"destination_url": "https://acme.example/de"
},
{
"match": { "languages": ["fr"] },
"destination_url": "https://acme.example/fr"
}
]
}
Посетители из региона DACH попадают на /de, потому что правило 1 срабатывает первым. У французского экспата в Берлине country=DE, поэтому он также попадает на /de - правило 1 срабатывает раньше, чем у правила 2 появляется шанс. Если вы хотите направить французского экспата на /fr, поменяйте правила местами, чтобы правило по языку проверялось первым. Порядок в дашборде - это порядок, в котором мы проводим оценку.
Две вещи, которые из этого следуют и которые стоит проговорить вслух:
- Более широкие правила должны быть последними. Правило без условий
matchсовпадает со всем; если оно будет первым, ни одно правило ниже него никогда не сработает. Дашборд проверяет это и предупреждает вас, но API - нет, поэтому правила, созданные скриптами, нуждаются в проверке на здравомыслие. - Взаимное исключение на вашей совести. Если два правила подходят одному посетителю, молча выигрывает первое. Ошибки не будет, флаг не выставится, метрика не запишется. Мы рассматривали возможность выдачи предупреждения в момент загрузки ссылки, если обнаруживается пересечение двух правил, и это в планах на следующий минорный релиз. А пока: читайте свои правила сверху вниз и доверяйте порядку.
Цена вопроса: распространение инвалидации кэша#
У каждого решения по маршрутизации есть окно распространения. Правила смарт-ссылок, отредактированные в дашборде, распространяются через кэши L1 во всех трех POP Elido примерно за 1 секунду в штатном режиме. Примерно, потому что:
- L1 LRU в каждом POP хранит ссылки с правилами с TTL 60 секунд (архитектура кэша описана здесь). TTL - это верхняя граница: даже без публикации инвалидации устаревшая запись исчезнет в течение минуты.
- Публикация инвалидации происходит через pub/sub in-memory кеша. POP в ЕС и на востоке США используют один кеш-кластер; у Азиатско-Тихоокеанского региона свой собственный. Межрегиональное распространение - это, по сути, задержка репликации кеша плюс обработка pub/sub нашим подписчиком, которая в наших метриках за последний квартал составляла менее 1 секунды для p99.
- POP, потерявший подписку на кеш, откатывается к TTL 60 секунд. Мы алертим при потере подписки; у дежурного инженера есть 5 минут буферизованных кликов до того, как вступит в действие WAL.
Перевод: для маркетинговых потоков, где 60 секунд устаревшей маршрутизации не критичны, об этом можно не думать. Для потоков, где актуальность важна - ротация юридического дисклеймера, разделение биллинговых когорт, где неверный пункт назначения списывает неверную валюту - правильная стратегия: сначала status=disabled, затем через минуту включение и публикация нового правила. Мы добавили эндпоинт GET /v1/links/{id}/status, чтобы CI-пайплайн мог опрашивать статус распространения перед переключением чего-либо на следующем этапе.
Когда не стоит использовать смарт-ссылку#
Три случая, когда правильным инструментом будет не смарт-ссылка.
Серверный рендеринг (SSR) целевой страницы лучше. Если вариант должен быть внедрен в HTML-ответ - скажем, локализация цен, зависящая от состояния аутентификации посетителя, или лендинг, который подтягивает специфический для когорты баннер из вашей CMS - это работа для собственного сервера целевой страницы, а не для редиректа. Редирект выбирает, куда отправить посетителя; пункт назначения выбирает, что рендерить. Логика маршрутизации, живущая на edge, не видит вашу сессию аутентификации, и попытка втиснуть её туда потребует либо утечки сессии в путь редиректа (чего мы делать не будем), либо проксирования через edge (чего мы не делаем из-за бюджета задержки). Рендерите варианты на источнике (origin).
Статистически строгое A/B тестирование. Смарт-ссылки маршрутизируют каждый запрос, а не каждого посетителя. Если посетитель перейдет по ссылке дважды за пять минут с одного и того же устройства, он может увидеть два разных пункта назначения при рандомизированном правиле. Это правильное поведение для задачи «отправить 50% мобильного трафика на A и 50% на B», но неправильное для задачи «измерить, конвертирует ли вариант A лучше варианта B на 4-недельном окне». Для последнего вам нужны стабильные куки вариантов и инструмент для экспериментов, который правильно обрабатывает статистику. PostHog, GrowthBook и LaunchDarkly отлично с этим справляются. Мы - нет, и не планируем - это другая работа. Используйте ротацию вариантов с round_robin для простых выборок и обращайтесь к платформам для экспериментов, когда нужно защитить результат данными.
Маршрутизация с учетом личности (Identity-aware). Смарт-ссылки намеренно лишены состояния (stateless). Они оценивают только country | device | OS | language | time | referrer и ничего больше. Если вам нужно маршрутизировать на основе уровня подписки залогиненного пользователя, его фича-флагов или чего-либо, требующего поиска «кто этот человек», путь редиректа - неподходящий уровень. Разрешайте личность на источнике и отдавайте вариант оттуда. Или, если вам действительно нужно решение в момент редиректа, создавайте персональные короткие ссылки для пользователей через API - каждый аутентифицированный пользователь получает свой слаг, пункт назначения которого верен для этого пользователя в момент создания, и вам никогда не придется заниматься разрешением личности на горячем пути.
Что дальше#
Если вы хотите опробовать структуру правил на своих данных, в руководстве в документации описаны схема JSON и редактор в дашборде. Конструктор правил находится на странице редактирования любой ссылки в дашборде - Links → ⋯ → Targeting.
Два улучшения, которые появятся в следующем минорном релизе: иерархия запасных вариантов для languages (чтобы pt-BR чисто деградировал до pt, а затем до en, без написания трех правил) и этап статического анализа при сохранении ссылки, который помечает пересекающиеся правила, чтобы дашборд мог предупредить об этом до того, как правило вступит в силу. Оба улучшения касаются реализации, без ломающих изменений в схеме. Если у вас есть структура правил, которую мы не поддерживаем, и вы считаете, что должны, канал обратной связи находится внизу страницы фичи смарт-ссылок.
Похожее в блоге#
Попробуйте Elido
Вставьте URL - получите короткую ссылку
Без регистрации. Ссылка живёт 30 дней. Зарегистрируйтесь, чтобы оставить её навсегда.
Бесплатно, без регистрации · 2 в день