Elido
7 мин чтенияИнженерия

Запуск миграции с Dub.co: папки превращаются в теги

Как мы реализовали импорт из Dub.co в один клик для Elido — самый чистый API из пяти поддерживаемых, преобразование папок в теги и почему этот переход подходит командам, заботящимся о размещении данных в ЕС.

Marius Voß
DevRel · edge infra
Схема пайплайна: слева REST API Dub.co, поток данных идет через воркер импорта Elido в таблицу ссылок, с боковой панелью, перечисляющей численные гарантии (лимит 50к, бюджет 30 мин, 100 на страницу, папки преобразуются в теги)

Пятый и последний источник миграции в рамках нашего развертывания Tier-3 запущен сегодня. Вставьте API-токен Dub.co, при желании отфильтруйте по слагу проекта и нажмите Start. Через три-пять минут каждая ссылка окажется на вашем домене Elido со слагом, сохраненным в исходном виде, а структура папок Dub будет преобразована в плоский список тегов Elido.

Этот пост — технический отчет: что особенного в Dub, почему их API является самым чистым из пяти поддерживаемых нами вендоров и что мотивирует команды переходить с Dub на Elido.

Почему эта миграция существует#

Dub.co — ближайший open-source аналог Elido по функциональности. Отличный продукт, чистый REST API, современная панель управления. Путь миграции здесь предназначен для команд, которые выбирают Elido по одной из трех причин:

  • Размещение в ЕС. Уровень данных (data plane) Elido привязан к ЕС — Hetzner FRA + POP в ASH, OVH SGP для APAC, все события кликов по умолчанию попадают в ClickHouse в регионе ЕС. Dub Cloud привязан к США; позиция GDPR/Schrems-II является компромиссом.
  • Инфраструктура Edge POP. Три региональных POP с p95 < 15мс cache HIT — это иной целевой показатель задержки, чем путь Dub, использующий только Cloudflare-Workers. Нагрузки, чувствительные к задержкам (mobile-first, ad-attribution), чувствуют разницу.
  • Глубина аналитики. Аналитика кликов на базе ClickHouse с удержанием данных по событиям, пересылкой конверсий в GA4/Meta CAPI и полным историческим воспроизведением. Аналитика Dub чистая, но агрегируется через PostgreSQL; потолок глубины данных здесь другой.

Если ничего из перечисленного не актуально, Dub — хороший продукт. Миграция существует для команд, которым эти аспекты важны.

Почему API Dub самый чистый#

Мы построили воркеры для пяти API различных вендоров. Ранжирование по простоте интеграции:

  1. Dub.co — bearer-токен, ошибки, соответствующие JSON-RFC, пагинация ?page= + ?limit=100, каждое поле задокументировано с примерами полезной нагрузки.
  2. Short.io — чисто, явный boolean HasMore, но партиционирование по доменам требует доработки UX.
  3. Bitly — URL pagination.next соответствует стандартам; API reference достаточно подробный.
  4. TinyURL — только Pro/Bulk, остальное не поддерживается; документация скудная.
  5. Rebrandly — курсор ?last=<id> нормальный, но ограничение 25 ссылок на страницу делает работу медленной.

Преимущество Dub: в их документации есть примеры curl, ответы об ошибках включают как машинный код, так и описание для человека, а пагинация — понятного типа, где ?page=2&limit=100 работает именно так, как вы ожидаете.

const dubPageSize = 100

page := 1
for {
    resp, err := w.fetchPage(ctx, opts.Token, opts.WorkspaceID, page)
    if err != nil { /* mark failed */ return }
    if len(resp) == 0 { break }
    for _, link := range resp { /* import */ }
    if len(resp) < dubPageSize { break }
    page++
}

Dub не возвращает флаг HasMore; мы выводим его из короткой страницы. Это стандартный паттерн REST-пагинации, и он работает отлично — страница короче лимита означает, что мы закончили.

Преобразование папок в теги#

У Dub есть и папки, и теги в качестве примитивов организации. У Elido есть только теги. Поэтому при миграции мы превращаем папки Dub в набор тегов:

tags := make([]string, 0, len(link.Tags)+2)
for _, t := range link.Tags {
    tags = append(tags, t.Name)
}
if link.Folder != nil && link.Folder.Name != "" {
    // Dub folders can nest; flatten the full path.
    for _, segment := range strings.Split(link.Folder.Name, "/") {
        seg := strings.TrimSpace(segment)
        if seg != "" {
            tags = append(tags, seg)
        }
    }
}
tags = append(tags, "imported:dub")

Ссылка Dub в папке campaigns/q3-launch с тегами paid и linkedin импортируется с тегами paid, linkedin, campaigns, q3-launch и imported:dub. Семантика фильтров в Elido обрабатывает те же паттерны поиска, что и UI папок Dub — tag-equals, tag-contains, multi-tag-AND. Мы не переизобретаем иерархию папок на стороне сервера; пользователь получает плоский список тегов и примитивы фильтрации.

Могли ли мы добавить папки в Elido? Да. Мы выбрали подход "только теги", когда модель данных Elido была запущена в Фазе 1; папки имеют смысл для ментальных моделей настольных файловых систем и меньше подходят для массовых операций с короткими ссылками. Миграция пользователей Dub на теги Elido — правильный выбор в этом компромиссе.

Фильтрация проектов#

Dub использует "рабочие пространства" (workspaces) в своем новом UI, а исторически называл их "проектами". API принимает параметр workspaceId для фильтрации; лаунчер открывает его как необязательное текстовое поле. Вставьте слаг рабочего пространства из вашего URL Dub или оставьте поле пустым, чтобы забрать все ссылки, которые видит токен.

Это повторяет фильтр рабочих пространств Rebrandly и поле домена Short.io. У трех из пяти наших вендоров для миграции есть концепция партиционирования по аккаунтам; мы последовательно предоставляем ее как необязательный текстовый ввод, а не выпадающий список, потому что у типичного пользователя максимум два рабочих пространства, а API-эндпоинт получения списка добавляет задержку, которая не стоит затраченных усилий.

Что мы не переносим#

Правила гео-таргетинга и таргетинга по устройствам Dub. Это мощная функция Dub, но структура правил не отображается 1:1 на правила smart-ссылок Elido. Слаги импортируются; правила нужно пересоздать, используя синтаксис выражений Elido, который более гибок, но имеет другую ментальную структуру.

История по каждому клику. Универсальное ограничение для всех пяти источников миграции. Данные по кликам Dub находятся за их эндпоинтом аналитики, который привязан к тарифному плану; новые клики попадают в аналитику Elido с момента перехода.

Стилизация QR. QR-коды Elido по умолчанию перегенерируются; кастомные дизайны нужно применять заново. Тег imported:dub служит маркером для массового переназначения.

ACL рабочих пространств и настройки ролей Dub. Настройте доступ заново в Elido, используя SCIM/SSO или приглашения участников рабочего пространства; модель ролей между продуктами отличается достаточно сильно, чтобы автоматическое сопоставление могло привести к непреднамеренному повышению привилегий.

Self-hosted Dub#

Dub — open-source продукт, и self-hosted инстансы Dub распространены. Миграция использует тот же REST API, который предоставляет Dub Cloud, поэтому указание на self-hosted Dub означает переопределение DUB_API_BASE. Мы не вынесли это в настройки самообслуживания в v1, так как операционная сложность нетривиальна — разные версии Dub предоставляют слегка отличающиеся ответы, и мы не хотим выпускать лаунчер, который будет молча выдавать 500-ю ошибку на деплое Dub v0.7, когда протестированной целью является v0.9.

Для миграций с self-hosted Dub напишите на [email protected] с указанием вашей версии Dub, и мы проведем однократную миграцию в индивидуальном порядке. Как только мы увидим достаточно версий в эксплуатации, переопределение станет настройкой дашборда для самообслуживания.

Обработка токенов#

Та же семантика однократного использования, что и у четырех других вендоров:

bgCtx := context.WithoutCancel(r.Context())
go h.dub.Run(bgCtx, job.ID, imports.DubJobOptions{
    Token:       req.Token,
    WorkspaceID: req.WorkspaceID,
})

source_token_id остается NULL. Токен живет в памяти процесса api-core на время работы воркера и удаляется по завершении. Никакой персистентности — это разовая миграция, а не постоянный вызов вендора.

context.WithoutCancel (Go 1.21+) позволяет воркеру работать после завершения HTTP-запроса. Тот же паттерн, что и у всех остальных вендоров в этом развертывании.

Разрешение конфликтов и контракт воркера#

Идентично всем остальным вендорам. Перебор суффиксов mylink-2, mylink-3, …, до 50 кандидатов; skip оставляет существующую ссылку Elido без изменений; fail прерывает импорт при первом конфликте. Контракт воркера — MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute — общий для всех пяти.

Поиск выполняется как одно индексированное чтение на строку по (domain_id, slug). Детерминированный перебор суффиксов, более понятные ошибки, чем попытки отловить нарушения уникальности в pgx.

Что мы измерили по сравнению с Bitly#

И Dub, и Bitly мигрируют примерно с одинаковой пропускной способностью — 100 ссылок на страницу, ~5 вставок/сек устойчиво. Источник из 5000 ссылок обрабатывается за 4–7 минут для обоих вендоров. Разница, заметная пользователю, — это опыт после импорта: ссылки, импортированные из Dub, приходят со структурированными "хлебными крошками" (папка как тег); ссылки, импортированные из Bitly, приходят только с тегом imported:bitly и любыми свободными тегами Bitly.

Возобновляемость и проблема деплоя#

Тот же компромисс, что и в первых четырех миграциях. Воркер работает внутри процесса; деплой в процессе импорта убивает горутину. Cron-задача, ищущая "зависшие" импорты каждые 5 минут, переводит любую строку со статусом running без прогресса в течение 30 минут в failed. Повторный запуск идемпотентен благодаря стратегиям suffix и skip.

Для аккаунтов с более чем 10 000 ссылок возобновляемость оправдала бы себя — мы могли бы записывать курсор страницы Dub в import_jobs.source_filter и возобновлять с последней завершенной страницы. Все пять вендоров миграции используют одинаковый in-process дизайн; когда мы реализуем возобновляемость, выиграют все пять.

Что дальше в развертывании Tier-3#

Tier-3 завершен. Пять вендоров миграции, одна общая таблица import_jobs, один общий контракт воркера, один общий UI опроса дашборда, пять SEO-лендингов, пять технических постов в блоге.

Что запланировано после Tier-3:

  • Возобновляемость для аккаунтов более 10 000 ссылок. Контрольные точки курсора для каждого вендора.
  • CSV-экспорт как запасной вариант для пользователей на планах с отозванными токенами. Сейчас — только в индивидуальном порядке.
  • Фундамент service_tokens для Tier-2 — токены вендоров для постоянного использования Mailchimp, Brevo, Klaviyo. Путь миграции подтвердил паттерн JSONB source_filter; Tier-2 требует постоянных зашифрованных токенов, что относится к территории ADR-0036.

Если вы присматривались к Elido из рабочего пространства Dub, история миграции теперь задокументирована. Попробуйте — от токена до последней импортированной ссылки менее чем за пять минут для типичных аккаунтов.

Похожее в блоге#

Попробуйте Elido

URL-сокращатель с хостингом в ЕС: собственные домены, глубокая аналитика, открытый API. Бесплатный тариф — без банковской карты.

Теги
dub.co migration
url shortener
go worker
data migration
engineering
tier 3 integrations

Читать дальше