Друге джерело міграції в нашому розгортанні Tier-3 було випущено сьогодні. Вставте API-ключ Rebrandly, за бажанням відфільтруйте за робочим простором і натисніть «Start». Через шість-десять хвилин кожен слештег опиняється на вашому домені Elido зі збереженим слагом, де не виникло конфліктів. Міграція з Bitly, яка з'явилася два тижні тому, заклала каркас; Rebrandly - другий вендор, що скористався ним.
Цей пост - інженерний звіт про те, що є специфічним для Rebrandly, що ми залишили ідентичним воркеру Bitly і де API Rebrandly змусило обрати іншу структуру.
Що спільного з Bitly
Ця функція з самого початку планувалася як одна таблиця та один контракт воркера. Обидва витримали перевірку.
CREATE TABLE import_jobs (
id BIGSERIAL PRIMARY KEY,
workspace_id BIGINT NOT NULL,
source_vendor TEXT NOT NULL,
target_domain_id BIGINT NOT NULL,
status TEXT NOT NULL DEFAULT 'queued',
conflict_strategy TEXT NOT NULL DEFAULT 'suffix',
source_filter JSONB NOT NULL DEFAULT '{}'::jsonb,
-- counters + error_log + timestamps elided
);
source_vendor змінюється на rebrandly. source_filter містить {workspace_id: "..."}, коли користувач застосовує фільтр, або {}, коли потрібно імпортувати всі посилання, доступні за цим ключем. Все інше - 30-хвилинний бюджет, обмеження в 50 тис. посилань, стратегія вирішення конфліктів (суфікс/пропуск/помилка), тег imported:rebrandly - ідентичне до шляху Bitly.
Лаунчер на дашборді (apps/web/src/app/dashboard/integrations/[id]/rebrandly-migration-launcher.tsx) структурно є копією лаунчера для Bitly, але без випадаючого списку груп - у Rebrandly є робочі простори (workspaces), а не групи, і ми виставляємо їх як опціональний текстовий фільтр, а не як випадаючий список, оскільки ендпоінт Workspaces використовує пагінацію без автентифікації, а типовий користувач має щонайбільше два робочих простори.
У чому різниця API Rebrandly
Три моменти:
Розмір сторінки. Rebrandly обмежує одну сторінку 25 посиланнями. Bitly - 100. Тому акаунт на 5000 посилань, який обробляється за 4–8 хвилин у Bitly, забере 6–10 хвилин у Rebrandly. Вузьким місцем є вендор, а не воркер.
Пагінація. Rebrandly використовує параметр запиту last, який приймає ID останнього елемента на попередній сторінці. Bitly повертає URL pagination.next. Обидва варіанти мають курсорний тип; варіант Rebrandly просто здійснює більше запитів. Весь цикл складається з шести рядків:
last := ""
for {
page, err := w.fetchPage(ctx, opts.Token, opts.WorkspaceID, last)
if err != nil { /* mark failed */ return }
if len(page) == 0 { break }
for _, link := range page {
// ... resolve slug, insert, update counters ...
}
last = page[len(page)-1].ID
}
Ми довіряємо курсору. Якби Rebrandly двічі повернув однаковий last, ми б зациклилися; 30-хвилинний бюджет обмежує шкоду.
Обмеження робочим простором. API-ключ Rebrandly бачить кожне посилання в кожному робочому просторі, до якого належить користувач. Якщо у вас агенційський акаунт із п'ятьма клієнтськими робочими просторами, ви майже напевно захочете імпортувати їх по одному. Лаунчер виставляє це як опціональне текстове поле - вставте ID робочого простору з адресного рядка Rebrandly або залиште порожнім, щоб імпортувати «все, що бачить ключ».
Що ми не мігруємо
Історію кліків. Дані Rebrandly про кліки доступні лише для тарифів Premium і відображаються як сумарні лічильники на посилання, а не як події кліків. Ми акцентуємо на цьому обмеженні на кожній сторінці, яку бачить користувач - на сторінці рецепта на дашборді, на лендингу /migrate-from/rebrandly, в UI прогресу імпорту та в розділі FAQ. Нові кліки потрапляють в аналітику Elido з моменту перемикання.
UTM-шаблони Rebrandly. Це функція відображення в Rebrandly, яка не має чіткого API для експорту. Перестворіть їх як правила кампаній в Elido - тег imported:rebrandly є інструментом для масового перепризначення.
Стилізація QR. Стандартний QR-код Elido генерується для кожного імпортованого посилання; кастомний дизайн потрібно застосовувати повторно. Більшість користувачів використовують масовий фільтр за тегами, щоб застосувати накладання CTA або кампанії Elido постфактум.
Обробка токенів
Ідентична до Bitly. Токен ніколи не записується на диск:
bgCtx := context.WithoutCancel(r.Context())
go h.rebrandly.Run(bgCtx, job.ID, imports.RebrandlyJobOptions{
Token: req.Token,
WorkspaceID: req.WorkspaceID,
})
source_token_id залишається NULL. Таблиця service_tokens призначена для інтеграцій Tier-2 із токенами, що вставляються (Mailchimp, Brevo, Klaviyo), де повторне використання виправдовує зберігання. Для одноразових міграцій використання лише в пам'яті є правильним операційним рішенням - користувач один раз вставляє токен, воркер виконує роботу, токен видаляється.
context.WithoutCancel (Go 1.21+) зберігає значення контексту - логер, trace ID, дедлайни - але видаляє сигнал скасування, щоб воркер пережив HTTP-запит, який його ініціював. Це той самий патерн, що й у воркера Bitly, і той самий патерн, який використовуватимуть усі майбутні вендори міграції.
Вирішення конфліктів
Три стратегії, ідентичні до Bitly. Користувач обирає їх під час запуску завдання:
- suffix (за замовчуванням): додавати
mylink-2,mylink-3… до 50 варіантів. Після 50 ми вважаємо це структурною проблемою і видаємо помилку. - skip: залишити існуюче посилання Elido, залогувати джерело, зарахувати як пропущене.
- fail: перервати все завдання при першому конфлікті. Для суворої семантики 1:1.
Пошук слага - це одне індексоване читання на рядок:
func (w *RebrandlyWorker) resolveSlug(ctx context.Context, domainID int64, desired, strategy string) (string, error) {
if _, err := w.links.GetByDomainSlug(ctx, domainID, desired); err != nil {
if errors.Is(err, pgx.ErrNoRows) { return desired, nil }
return "", fmt.Errorf("slug lookup: %w", err)
}
// suffix/skip/fail branching identical to bitly.go
}
Ми платимо додатковим читанням на рядок, але отримуємо детермінований перебір суфіксів і більш зрозуміле повідомлення про помилку. Альтернатива - відловлювання порушення унікальності в pgx і розбір назви обмеження з рядка помилки - гірше рішення.
Що піддається вимірюванню
Ті самі структуровані zap-логи, що й у Bitly. Робочий простір, цільовий домен, стратегія конфліктів, опціональний фільтр робочого простору. Події життєвого циклу завдання - старт, завершення, очищення завислих завдань - існують попередньо, і дашборд опитує ендпоінт кожні дві секунди.
Ми поки що не графікуємо метрики завдань міграції в продакшені. Когорта Bitly дала нам першу реальну базову лінію трафіку; дані Rebrandly мають бути прямо порівнянними, оскільки воркер механічно ідентичний, а відмінності полягають лише у формі пагінації вендора. Перший кандидат на алерт: кількість очищень завислих завдань > 0 за будь-яку годину - це означає, що воркер «впав», а UI користувача завис у стані running.
Відновлюваність і проблема деплою
Та сама логіка, що й у Bitly. Воркер працює в процесі; деплой під час імпорту вбиває горутину. Ми приймаємо це для v1, тому що:
- Більшість завдань завершуються менш ніж за десять хвилин. Деплої рідкісні в час активного імпорту.
- Поле
import_jobs.last_progress_atплюс cron-завдання для очищення завислих завдань, що працює кожні 5 хвилин, переводить будь-який рядокrunningбез прогресу за останні 30 хвилин у станfailedіз чіткою причиною. - Повторний запуск є ідемпотентним при стратегіях suffix і skip - вже імпортовані посилання виявляються на другому проході та вирішуються згідно зі стратегією.
Для акаунтів, де посилань понад 10 000, відновлюваність виправдовує себе - ми записуємо курсор last від Rebrandly в import_jobs.source_filter і продовжуємо з того місця, де зупинилися. Це наступна ітерація; чотири інших джерела міграції отримають користь від тієї самої зміни, як тільки ми її випустимо.
Що далі
Той самий каркас, ще три вендори, які будуть інтегровані в ту саму таблицю import_jobs.
- Short.io -
GET /links?limit=150&domain_id=…. Пагінація по доменах; ми попросимо користувача обрати джерельний домен, а не робочий простір. - Dub.co -
GET /api/links?projectSlug=…&limit=100. Збереження папок + тегів; це найчистіша реалізація з усіх чотирьох. - TinyURL - Pro/Bulk REST API. Безкоштовний TinyURL не має API і ніколи не мав; цей шлях залишається ручним.
Кожен із них буде працювати через той самий UI опитування на дашборді та патерн тегів imported:<vendor>. Специфічний для вендора воркер залишається в services/api-core/internal/imports/<vendor>.go.
Якщо ви відкладали порівняння з Rebrandly через те, що шлях міграції не був задокументований, тепер він задокументований. Спробуйте - від API-ключа до останнього імпортованого посилання менше ніж за десять хвилин для типових акаунтів.
Пов'язане в блозі
Спробуйте Elido
Вставте URL - отримайте коротке посилання
Без реєстрації. Посилання живе 30 днів. Зареєструйтесь, щоб зберегти назавжди.
Безкоштовно, без реєстрації · 2 на день