6 min czytaniaInżynieria

Wdrażamy migrację z Short.io: paginacja per domena po 150 elementów na stronę

Jak stworzyliśmy importowanie z Short.io jednym kliknięciem dla Elido — model paginacji per domena, zasada dezaktywacji prywatnych linków oraz najszybsze z naszych pięciu źródeł migracji.

Marius Voß
DevRel · edge infra
Diagram potoku: interfejs API REST Short.io po lewej stronie, przepływający przez worker importu Elido do tabeli linków, z panelem bocznym zawierającym numeryczne gwarancje workera (limit 50 tys., budżet 30 min, 150 na stronę, token tylko w pamięci)

Trzecie źródło migracji w ramach naszego wdrożenia Tier-3 zostało dziś udostępnione. Wklej klucz API Short.io, wybierz źródłową domenę Short.io (np. example.short.gy), wybierz docelową domenę Elido, kliknij Start. Od trzech do sześciu minut później każdy link znajdzie się w Twojej domenie Elido z zachowanym slugiem.

Ten wpis to opracowanie techniczne — co jest specyficzne dla Short.io, co nas zaskoczyło w ich interfejsie API REST i dlaczego zdecydowaliśmy się na udostępnienie zadań per domena, a nie wsadowe przetwarzanie per konto.

Per domena, nie per konto#

Model danych Short.io ma jeden haczyk, który ukształtował cały UX launchera: linki są zorganizowane w ramach domen, a endpoint /links paginuje per domena. Nie ma wywołania „daj mi wszystkie linki ze wszystkich domen na tym koncie”.

Rozważyliśmy kilka projektów:

  • A. Iteracja po każdej domenie po stronie serwera, prezentacja jednego zadania użytkownikowi. Szybsze pod kątem liczby kliknięć; trudniejsze do pokazania postępu i wyboru strategii konfliktów per domena.
  • B. Jedno zadanie Elido per domena źródłowa. Wolniejsze pod kątem kliknięć (użytkownik uruchamia N zadań dla N domen), ale każde zadanie ma jasny kontrakt: jedna domena źródłowa → jedna domena docelowa → jedna strategia konfliktów.
  • C. Wylistowanie wszystkich domen, pozwolenie użytkownikowi na zaznaczenie wielu, zakolejkowanie N zadań po stronie serwera.

Wdrożyliśmy B, a C zostawiliśmy na iterację planu wdrażania. Launcher prosi o nazwę hosta domeny źródłowej w polu tekstowym (brak listy rozwijanej — lista /domains Short.io jest tania w wywołaniu, ale dodaje jeden round-trip, a użytkownik zawsze zna nazwę hosta własnej domeny). Jedno zadanie na domenę, kolejkowane z pulpitu nawigacyjnego jedno po drugim.

Zysk z rozmiaru strony#

Short.io domyślnie paginuje po 150 linków na wywołanie — to najbardziej hojna wartość spośród naszych pięciu źródeł migracji. Porównaj:

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

Domena Short.io z 5000 linków wymaga 34 round-tripów. Konto Rebrandly z 5000 linków wymaga 200. Worker spędza większość czasu oczekując na odpowiedzi HTTP, więc to ma znaczenie — Short.io jest empirycznie najszybszym źródłem migracji, jakie obsługujemy.

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 to wartość logiczna, którą Short.io zwraca bezpośrednio — brak parsowania kursora, brak śledzenia ostatniego ID. Ich API jest jednym z lepiej zaprojektowanych spośród pięciu dostawców, których obsługujemy.

Prywatne linki — co robimy#

Short.io ma flagę „prywatny” dla każdego linku. Importujemy prywatne linki jako linki Elido z is_active=false, więc slug nie rozwiązuje się na brzegu (edge). Użytkownik włącza je selektywnie z pulpitu nawigacyjnego po wyrywkowym sprawdzeniu importu.

Uzasadnienie: jeśli link w Short.io był prywatny u źródła, intencją użytkownika było, aby nie rozwiązywał się publicznie. Importowanie go jako is_active=true ujawniłoby adresy URL, które były celowo zablokowane. Importowanie go jako is_active=false zachowuje slug jako zarezerwowany, ale nieosiągalny, dopóki użytkownik nie zdecyduje inaczej — jest to zdecydowanie bezpieczniejsze niż alternatywa.

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,
})

To niewielka różnica w porównaniu z Bitly (brak równoważnej flagi) i Rebrandly (brak równoważnej flagi). Warto o tym wspomnieć w przepisie po imporcie, aby użytkownik zrozumiał, dlaczego niektóre zaimportowane linki nie rozwiązują się natychmiast.

Czego nie migrujemy#

Konfiguracje testów A/B Short.io nie mają przejrzystego eksportu — to wbudowany w aplikację kreator, który nie udostępnia deterministycznej struktury JSON przez API REST. Odbuduj jako reguły smart-link w Elido po imporcie; składnia jest bardziej wyrazista, ale model mentalny pozostaje ten sam.

Historia kliknięć jest uniwersalnym ograniczeniem dla każdego źródła migracji. Dane per kliknięcie w Short.io znajdują się w ich eksporcie analityki, który jest dostępny tylko w planie Team (dostęp 2026-05-22) i prezentowany jako zagregowane liczniki, a nie zdarzenia per kliknięcie. Nowe kliknięcia trafiają do analityki Elido od momentu przełączenia.

Projekty QR i presety UTM dla linków — ta sama historia co w przypadku Bitly i Rebrandly. Otagowane imported:shortio, gotowe do masowej obsługi przez kampanie Elido.

Przejęcie domeny#

Interesującym przypadkiem użycia Short.io jest „Prowadzę markową domenę w Short.io i chcę ją przenieść do Elido bez zmiany adresu URL”. Migracja sprawnie obsługuje stronę linków; strona DNS to jedna zmiana CNAME.

Proces przejęcia dokumentujemy na stronie /migrate-from/shortio — utrzymuj obie powierzchnie rozwiązujące się równolegle, dopóki subskrypcja Short.io się nie skończy, a następnie skieruj DNS na Elido. Nie ma pośpiechu, aby wyłączać Short.io w dniu zakończenia importu.

Własne domeny w Elido wykorzystują TLS na żądanie Caddy'ego z domain-manager jako źródłem listy dozwolonych, więc przełączenie to zmiana CNAME oraz wywołanie API weryfikacji domeny. Brak tańca z certyfikatami po stronie użytkownika.

Rozwiązywanie konfliktów i kontrakt workera#

Identycznie jak w Bitly i Rebrandly — dodawanie przyrostka mylink-2, mylink-3, … w przypadku kolizji; pominiecie (skip) pozostawia istniejący link Elido w spokoju i loguje wiersz źródłowy; niepowodzenie (fail) przerywa przy pierwszym konflikcie. Wyszukiwanie to jeden odczyt indeksowany na wiersz.

Kontrakt workera — MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute, progressEvery=50, errorLogCap=1_000 — jest wspólny dla wszystkich pięciu dostawców. Te stałe wykonują większość pracy i nie są pokrętłami konfiguracji. To kontrakt, który zakłada interfejs sondowania pulpitu nawigacyjnego.

Obsługa tokenów#

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

source_token_id pozostaje NULL. Ta sama semantyka jednorazowego użytku co w Bitly i Rebrandly — użytkownik wkleja token raz, worker działa, token jest usuwany z pamięci po zakończeniu. Nie utrwalamy go, ponieważ wartość utrwalania (cykliczne użycie) nie ma zastosowania do migracji.

context.WithoutCancel utrzymuje workera przy życiu po zakończeniu żądania HTTP, które go zainicjowało. Ten sam wzorzec co u każdego innego dostawcy migracji w tym wdrożeniu.

Porównanie ze ścieżką eksportu CSV#

Short.io udostępnia eksport CSV w planach Team. Wybraliśmy REST zamiast CSV, ponieważ:

  • REST zachowuje strukturalnie tagi Short.io. CSV spłaszcza je do ciągu oddzielonego przecinkami, co wymaga rozdzielania po parsowaniu.
  • REST ujawnia flagę private. CSV nie uwzględnia jej konsekwentnie.
  • REST daje nam deterministyczny postęp (zobaczone linki / pozostałe linki). CSV to jednorazowe przesłanie pliku bez sygnału postępu w trakcie działania.
  • REST jest niezależny od planu — każdy plan Short.io udostępnia /links. Eksport CSV jest dostępny tylko w planie Team.

Ścieżka CSV pozostaje w naszym zanadrzu dla użytkowników starych kont Short.io, których token API został cofnięty, ale wciąż mają plik CSV z ostatniego eksportu.

Możliwość wznowienia i problem wdrożeń#

Ten sam kompromis co przy dwóch pierwszych migracjach. Worker działa w procesie; wdrożenie w trakcie importu zabija gorutynę. Pole import_jobs.last_progress_at plus 5-minutowy cron sprzątający zablokowane zadania zmienia każdy wiersz running bez postępu w ciągu ostatnich 30 minut na failed. Ponowne uruchomienie jest idempotentne w ramach suffix i skip.

Dla kont z ponad 10 000 linków w wielu domenach Short.io, projekt zadań per domena pomaga — każda domena jest niezależnie ograniczona 30-minutowym budżetem, więc wdrożenie w trakcie trzeciej domeny nie powoduje utraty pracy z dwóch pierwszych.

Co dalej#

Do wdrożenia zostały jeszcze dwa źródła:

  • Dub.coGET /api/links?projectSlug=…&limit=100. Foldery są spłaszczane do tagów. Najczystsze API z całej piątki.
  • TinyURL — API REST Pro/Bulk po 100 na stronę. Darmowy TinyURL nie ma API i nigdy nie miał; to pozostaje ścieżką ręczną.

Po Dub i TinyURL wdrożenie Tier-3 zostanie zakończone. Pięć stron lądowania migracji (/migrate-from/bitly, /migrate-from/rebrandly, /migrate-from/shortio, /migrate-from/dub, /migrate-from/tinyurl) i pięć technicznych wpisów na blogu pokrywa każde zapytanie typu „migracja z”, na które może trafić osoba szukająca alternatywy dla Bitly.

Jeśli zwlekałeś z porównaniem Short.io ponieważ proces migracji był nieudokumentowany, teraz jest udokumentowany. Wypróbuj — klucz API + domena do ostatniego zaimportowanego linku w mniej niż sześć minut dla typowych kont.

Powiązane na blogu#

Wypróbuj Elido

Skracarka URL hostowana w UE: własne domeny, głęboka analityka i otwarte API. Darmowy plan — bez karty kredytowej.

Tagi
short.io migration
url shortener
go worker
data migration
engineering
tier 3 integrations

Czytaj dalej