Elido
6 хв читанняІнженерія

Запуск міграції з Short.io: поштучна розбивка по доменах по 150 посилань на сторінку

Як ми створили імпорт з Short.io в один клік для Elido — модель розбивки по доменах, правило деактивації приватних посилань та найшвидше з наших п'яти джерел міграції.

Marius Voß
DevRel · edge infra
Діаграма конвеєра: REST API Short.io зліва, що проходить через імпорт-воркер Elido в таблицю посилань, з бічною панеллю, де перелічені числові гарантії, які забезпечує воркер (ліміт 50 тис., бюджет 30 хв., 150 посилань на сторінку, токен тільки в пам'яті)

Сьогодні ми запустили третє джерело міграції в межах нашого поетапного впровадження (Tier-3). Вставте API-ключ Short.io, виберіть вихідний домен Short.io (наприклад, example.short.gy), виберіть цільовий домен Elido і натисніть «Почати». Через три-шість хвилин кожне посилання опиниться на вашому домені Elido зі збереженим слагом.

Цей пост — інженерний звіт про те, що є специфічного для Short.io, що нас здивувало в їхньому REST API, і чому ми зрештою надали перевагу завданням по доменах, а не пакетній обробці по всьому акаунту.

По доменах, а не по акаунту#

Модель даних Short.io має одну особливість, яка визначила весь UX запуску: посилання організовані за доменами, а /links ендпоінт здійснює пагінацію по кожному домену окремо. Немає запиту «дай мені всі посилання по всіх доменах у цьому акаунті».

Ми розглянули кілька варіантів дизайну:

  • A. Ітерувати кожен домен на стороні сервера, представити користувачеві одне завдання. Швидше з точки зору кількості кліків; складніше відобразити прогрес і вибір стратегії конфліктів для кожного домену.
  • B. Одне завдання Elido на вихідний домен. Повільніше з точки зору кліків (користувач запускає N завдань для N доменів), але кожне завдання має чіткий контракт: один вихідний домен → один цільовий домен → одна стратегія вирішення конфліктів.
  • C. Перерахувати всі домени, дозволити користувачеві вибрати декілька, поставити N завдань у чергу на стороні сервера.

Ми випустили варіант B, а варіант C залишили для наступної ітерації плану впровадження. Інструмент запуску запитує ім'я хоста вихідного домену у вигляді текстового поля (без випадаючого списку — /domains список Short.io дешевий у виклику, але додає зайвий раунд-тріп, до того ж користувач завжди знає ім'я хоста свого домену). Одне завдання на домен, поставлене в чергу з панелі керування по одному.

Перевага розміру сторінки#

Short.io за замовчуванням виконує пагінацію по 150 посилань за виклик — це найбільш щедрий показник серед п'яти наших джерел міграції. Порівняємо:

  • Bitly: 100 per page
  • Rebrandly: 25 per page
  • TinyURL: 100 per page (Pro/Bulk)
  • Dub.co: 100 per page
  • Short.io: 150 per page

Домен Short.io на 5000 посилань потребує 34 раунд-тріпи. Акаунт Rebrandly на 5000 посилань — 200. Воркер витрачає більшу частину свого реального часу на очікування HTTP-відповідей, тому це важливо — Short.io емпірично є найшвидшим джерелом міграції, яке ми підтримуємо.

const shortioPageSize = 150

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

HasMore — це булеве значення, яке Short.io повертає явно — ніякого парсингу курсорів або відстеження останнього ідентифікатора. Їхній API — один із найкраще спроєктованих серед п'яти вендорів, яких ми підтримуємо.

Приватні посилання — що ми робимо#

Short.io має прапорець «private» для кожного посилання. Ми імпортуємо приватні посилання як посилання Elido з is_active=false, щоб слаг не резолвився на периферії (edge). Користувач вибірково активує їх з панелі керування після вибіркової перевірки імпорту.

Обґрунтування: якщо посилання в Short.io було приватним у джерелі, намір користувача полягав у тому, щоб воно не резолвилося публічно. Імпорт як is_active=true зробив би доступними URL, які навмисно були заблоковані. Імпорт як is_active=false зберігає слаг зарезервованим, але недоступним, доки користувач не вирішить інакше — це суворо безпечніше, ніж альтернатива.

isActive := !link.Private
linkID, err := w.links.InsertImported(ctx, sqldb.InsertImportedLinkParams{
    WorkspaceID:    job.WorkspaceID,
    DomainID:       job.TargetDomainID,
    Slug:           slug,
    DestinationURL: link.OriginalURL,
    Title:          truncate(link.Title, 250),
    Tags:           append(link.Tags, "imported:shortio"),
    IsActive:       isActive,
    CreatedByUserID: createdByUserID,
})

Це невелика поверхнева відмінність від Bitly (немає еквівалентного прапорця) та Rebrandly (немає еквівалентного прапорця). Варто висвітлити це в рецепті після імпорту, щоб користувач розумів, чому деякі імпортовані посилання не працюють одразу.

Що ми не мігруємо#

Конфігурації A/B спліт-тестів Short.io не мають чистого експорту — це внутрішній конструктор застосунку, який не надає детерміновану JSON-структуру через REST API. Перебудуйте їх як Elido правила смарт-посилань після імпорту; синтаксис більш виразний, але ментальна модель та сама.

Історія кліків — це універсальне обмеження для кожного джерела міграції. Дані по кожному кліку в Short.io знаходяться в їхньому експорті аналітики, який доступний лише в плані Team (станом на 2026-05-22) і відображається як агреговані лічильники, а не події по кожному кліку. Нові кліки потрапляють в аналітику Elido з моменту перемикання.

Дизайни QR-кодів і пресети UTM для посилань — та ж історія, що й з Bitly та Rebrandly. Позначені тегом imported:shortio, готові до масової обробки через кампанії Elido.

Передача домену#

Цікавий випадок використання Short.io — «Я використовую брендований домен на Short.io і хочу перенести його на Elido, не змінюючи URL». Міграція чисто обробляє частину з посиланнями; зі сторони DNS — це одна зміна CNAME.

Ми задокументували послідовність передачі на сторінці /migrate-from/shortio — тримайте обидві поверхні (сервіси) у робочому стані паралельно, доки не закінчиться ваша підписка на Short.io, а потім спрямуйте DNS на Elido. Немає потреби терміново вимикати Short.io в день завершення імпорту.

Користувацькі домени в Elido використовують on-demand TLS від Caddy з domain-manager як джерелом списку дозволених доменів, тому перемикання — це зміна CNAME плюс API-виклик для верифікації домену. Жодних танців з сертифікатами зі сторони користувача.

Вирішення конфліктів та контракт воркера#

Ідентично до Bitly та Rebrandly — суфікси mylink-2, mylink-3, ... у разі зіткнення; skip залишає існуюче посилання Elido в спокої і логує вихідний рядок; fail зупиняє роботу при першому ж конфлікті. Пошук — це одне індексоване читання на рядок.

Контракт воркера — MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute, progressEvery=50, errorLogCap=1_000 — є спільним для всіх п'яти вендорів. Ці константи виконують більшу частину роботи, і це не налаштування конфігурації. Це контракт, на який розраховує UI опитування панелі керування.

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

bgCtx := context.WithoutCancel(r.Context())
go h.shortio.Run(bgCtx, job.ID, imports.ShortioJobOptions{
    Token:    req.Token,
    DomainID: req.DomainID,
})

source_token_id залишається NULL. Та ж семантика одноразового використання, що й у Bitly та Rebrandly — користувач вставляє токен один раз, воркер виконує роботу, токен видаляється з пам'яті після завершення. Ми не зберігаємо його, оскільки цінність збереження (повторного використання) не стосується міграцій.

context.WithoutCancel підтримує життя воркера після того, як HTTP-запит, що його запустив, завершиться. Такий самий шаблон, як і для кожного іншого вендора міграції в цьому впровадженні.

Порівняння зі шляхом CSV-експорту#

Short.io надає CSV-експорт у планах Team. Ми обрали REST замість CSV, тому що:

  • REST зберігає теги Short.io структурно. CSV перетворює їх на рядок, розділений комами, який потребує розбиття після парсингу.
  • REST надає прапорець private. CSV не включає його послідовно.
  • REST надає нам детермінований прогрес (переглянуто посилань / залишилося посилань). CSV — це одноразове завантаження файлу без сигналу про прогрес під час виконання.
  • REST є агностичним до плану — кожен план Short.io надає /links. CSV-експорт доступний лише в Team.

Шлях через CSV залишається в нашому арсеналі для користувачів застарілих акаунтів Short.io, чий API-токен було відкликано, але у яких залишився CSV з останнього експорту.

Можливість відновлення та проблема деплою#

Такий самий компроміс, як і в перших двох міграціях. Воркер працює в процесі; деплой під час імпорту вбиває горутину. Поле import_jobs.last_progress_at плюс 5-хвилинний крон для очищення «завислих» завдань переводить будь-який рядок зі статусом running без прогресу за останні 30 хвилин у статус failed. Повторний запуск є ідемпотентним при використанні suffix та skip.

Для акаунтів з більш ніж 10 000 посилань у кількох доменах Short.io, дизайн завдань по доменах тут допомагає — кожен домен незалежно обмежений 30-хвилинним бюджетом, тому деплой під час роботи над третім доменом не призведе до втрати роботи, виконаної для перших двох.

Що далі#

Ще два вендори на черзі:

  • Dub.coGET /api/links?projectSlug=…&limit=100. Папки перетворюються на теги. Найчистіший API з усіх п'яти.
  • TinyURL — Pro/Bulk REST API по 100 посилань на сторінку. Безкоштовний TinyURL не має API і ніколи не мав; це залишається шляхом для ручної міграції.

Після Dub і TinyURL, впровадження Tier-3 буде завершено. П'ять сторінок міграції (/migrate-from/bitly, /migrate-from/rebrandly, /migrate-from/shortio, /migrate-from/dub, /migrate-from/tinyurl) і п'ять технічних блог-постів охоплюють кожен запит «міграції від» (migration-from), на який може потрапити людина, що шукає альтернативу Bitly.

Якщо ви відкладали порівняння Short.io, тому що історія міграції була недокументованою, тепер вона задокументована. Спробуйте — від API-ключа + домену до останнього імпортованого посилання менш ніж за шість хвилин для типових акаунтів.

Схоже в блозі#

Спробуйте Elido

URL-скорочувач із хостингом у ЄС: власні домени, глибока аналітика, відкритий API. Безкоштовний тариф — без кредитної картки.

Теги
short.io migration
url shortener
go worker
data migration
engineering
tier 3 integrations

Читати далі