Piąte i ostatnie źródło migracji w ramach naszego wdrożenia Tier-3 zostało uruchomione dzisiaj. Wklej token API Dub.co, opcjonalnie przefiltruj według sluga projektu i kliknij Start. Od trzech do pięciu minut później każdy link znajdzie się w Twojej domenie Elido z zachowanym slugiem, a struktura folderów Dub zostanie spłaszczona do tagów Elido.
Ten post to techniczny opis — co jest specyficzne dla Dub, dlaczego ich API jest najczystsze z pięciu dostawców, których wspieramy, oraz co motywuje do przejścia z Dub na Elido.
Dlaczego ta migracja istnieje#
Dub.co jest najbliższym odpowiednikiem open-source dla Elido pod względem zakresu funkcji. Solidny produkt, czyste REST API, nowoczesny dashboard. Ścieżka migracji jest przeznaczona dla zespołów, które wybierają Elido z jednego z trzech powodów:
- Rezydencja w UE. Płaszczyzna danych Elido jest zakotwiczona w UE — Hetzner FRA + ASH POPs, OVH SGP dla APAC, wszystkie zdarzenia kliknięć domyślnie trafiają do ClickHouse w regionie UE. Dub Cloud jest zakotwiczony w USA; kompromisem jest postawa względem RODO/Schrems-II.
- Zasięg Edge POP. Trzy regionalne POPy z p95 < 15ms cache HIT to inny cel opóźnień niż ścieżka Dub oparta wyłącznie na Cloudflare Workers. Obciążenia wrażliwe na opóźnienia (mobile-first, atrybucja reklam) odczuwają różnicę.
- Głębia analityki. Analityka kliknięć oparta na ClickHouse z retencją dla każdego zdarzenia, przekazywaniem konwersji do GA4/Meta CAPI i pełnym odtwarzaniem historycznym. Analityka Dub jest czysta, ale agregowana w PostgreSQL; pułap głębokości jest inny.
Jeśli żaden z tych punktów nie ma zastosowania, Dub jest świetnym produktem. Migracja jest tutaj dla zespołów, których te punkty dotyczą.
Dlaczego API Dub jest najczystsze#
Zbudowaliśmy już workery dla pięciu API dostawców. Ranking według łatwości integracji:
- Dub.co — token bearer, błędy zgodne z JSON-RFC, stronicowanie
?page=+?limit=100, każde pole udokumentowane przykładowymi payloadami. - Short.io — czyste, jawny boolean
HasMore, ale partycjonowanie per-domena wymaga pracy nad UX. - Bitly — URL
pagination.nextjest zgodny ze standardami; otaczająca dokumentacja API jest wyczerpująca. - TinyURL — tylko Pro/Bulk, reszta nieobsługiwana; dokumentacja jest skąpa.
- Rebrandly — kursor
?last=<id>jest w porządku, ale limit 25 na stronę sprawia, że wszystko wydaje się wolne.
Przewaga Dub: ich dokumentacja zawiera przykłady curl, odpowiedzi na błędy zawierają zarówno kod maszynowy, jak i komunikat dla człowieka, a ich stronicowanie jest tym nudnym rodzajem, w którym ?page=2&limit=100 działa dokładnie tak, jak można przypuszczać.
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 nie zwraca flagi HasMore; wnioskujemy o tym na podstawie krótkiej strony. To standardowy wzorzec stronicowania REST i działa dobrze — strona krótsza niż limit oznacza, że skończyliśmy.
Foldery spłaszczane do tagów#
Dub posiada zarówno foldery, jak i tagi jako prymitywy organizacyjne. Elido ma tylko tagi. Dlatego migracja spłaszcza foldery Dub do zbioru tagów:
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")
Link Dub w folderze campaigns/q3-launch z tagami paid i linkedin importuje się z tagami paid, linkedin, campaigns, q3-launch oraz imported:dub. Semantyka filtrów w Elido obsługuje te same wzorce pobierania co interfejs folderów w Dub — tag-równy, tag-zawiera, multi-tag-AND. Nie wymyślamy na nowo hierarchii folderów po stronie serwera; użytkownik otrzymuje płaską listę tagów i prymitywy filtrów.
Czy mogliśmy dodać foldery do Elido? Tak. Wybraliśmy tylko tagi, gdy model danych Elido został wdrożony w fazie 1; foldery mają sens dla mentalnych modeli systemów plików na komputery, a mniejszy sens dla operacji masowych na krótkich linkach. Migracja użytkowników Dub do tagów Elido jest właściwą stroną tego kompromisu.
Filtrowanie projektów#
Dub używa „przestrzeni roboczych” (w ich nowszym interfejsie), a historycznie nazywał je „projektami”. API akceptuje parametr workspaceId do filtrowania; launcher udostępnia go jako opcjonalne pole tekstowe. Wklej slug przestrzeni roboczej ze swojego adresu URL Dub lub pozostaw puste, aby pobrać każdy link, który token może zobaczyć.
To odzwierciedla filtr przestrzeni roboczej Rebrandly oraz pole domeny w Short.io. Trzech z naszych pięciu dostawców migracji ma koncepcję partycjonowania na konto; udostępniamy to konsekwentnie jako opcjonalne pole tekstowe zamiast wypełnionej listy rozwijanej, ponieważ typowy użytkownik ma co najwyżej dwie przestrzenie robocze, a punkt końcowy listy API dodaje opóźnienie, które nie jest warte tego szlifu.
Czego nie migrujemy#
Reguły geo-targetowania i targetowania urządzeń w Dub. To potężna funkcja Dub, ale kształt reguł nie mapuje się 1:1 na reguły smart-link Elido. Slug się importuje; przebuduj reguły używając składni wyrażeń Elido, która jest bardziej permisywna, ale wymaga innego podejścia mentalnego.
Historia per-kliknięcie. Uniwersalny limit dla wszystkich pięciu źródeł migracji. Dane per-kliknięcie w Dub znajdują się za ich punktem końcowym analityki, który jest powiązany z planem; nowe kliknięcia trafiają do analityki Elido od momentu przełączenia.
Stylizacja QR. Domyślny kod QR Elido jest regenerowany; niestandardowe projekty wymagają ponownego zastosowania. Tag imported:dub jest uchwytem do masowego przypisywania.
ACL przestrzeni roboczej i konfiguracja ról w Dub. Przyznaj ponownie dostęp w Elido używając SCIM/SSO lub zaproszeń członków przestrzeni roboczej; model ról różni się wystarczająco między tymi dwoma produktami, że zautomatyzowane mapowanie mogłoby ujawnić się jako cicha eskalacja uprawnień.
Self-hosted Dub#
Dub jest open-source i instancje self-hosted Dub są powszechne. Migracja korzysta z tego samego REST API, które udostępnia produkt Dub Cloud, więc wskazywanie na self-hosted Dub oznacza nadpisanie DUB_API_BASE. Nie udostępniliśmy tego jako ustawienia self-serve w v1, ponieważ koszt operacyjny jest niebagatelny — różne wersje Dub eksponują subtelnie różne kształty odpowiedzi, a nie chcemy wypuszczać launchera, który po cichu zwraca 500 przy wdrożeniu Dub v0.7, kiedy celem testowym jest v0.9.
W przypadku migracji z self-hosted Dub, napisz na [email protected] podając swoją wersję Dub, a przeprowadzimy jednorazową migrację concierge. Gdy zobaczymy wystarczająco dużo wersji w środowiskach produkcyjnych, nadpisanie stanie się ustawieniem dashboardu self-serve.
Obsługa tokenów#
Ta sama semantyka jednorazowa, co u pozostałych czterech dostawców migracji:
bgCtx := context.WithoutCancel(r.Context())
go h.dub.Run(bgCtx, job.ID, imports.DubJobOptions{
Token: req.Token,
WorkspaceID: req.WorkspaceID,
})
source_token_id pozostaje NULL. Token żyje w pamięci procesu api-core na czas działania workera, a po zakończeniu jest usuwany. Brak trwałości — to jednorazowa migracja, a nie cykliczne wywołanie u dostawcy.
context.WithoutCancel (Go 1.21+) utrzymuje workera przy życiu po zakończeniu żądania HTTP. Ten sam wzorzec co u każdego innego dostawcy migracji w tym wdrożeniu.
Rozwiązywanie konfliktów i kontrakt workera#
Identyczny jak u każdego innego dostawcy. Suffix walks mylink-2, mylink-3, …, do 50 kandydatów; skip pozostawia istniejący link Elido w spokoju; fail przerywa przy pierwszym konflikcie. Kontrakt workera — MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute — jest współdzielony przez wszystkie pięć.
Wyszukiwanie to jeden odczyt indeksowany na wiersz w (domain_id, slug). Deterministyczne suffix walks, przyjaźniejsze błędy niż polowanie na naruszenia unikalności w pgx.
Co mierzyliśmy względem Bitly#
Zarówno Dub, jak i Bitly migrują z mniej więcej taką samą przepustowością — 100 linków na stronę, ~5 wstawień/sek. utrzymywanych stale. Źródło z 5000 linków kończy się w 4–7 minut u obu dostawców. Widoczną dla użytkownika różnicą jest doświadczenie po imporcie: linki zaimportowane z Dub przychodzą ze strukturalnymi okruszkami folder-jako-tag; linki zaimportowane z Bitly przychodzą tylko z tagiem imported:bitly i wszelkimi dowolnymi tagami Bitly.
Możliwość wznowienia i problem z deployem#
Ten sam kompromis co przy pierwszych czterech migracjach. Worker działa w procesie; deploy w trakcie importu zabija goroutine. Cron, który wykrywa utknięcia w 5 minut, zmienia każdy wiersz running bez postępu w 30 minut na failed. Ponowne uruchomienie jest idempotentne w ramach strategii suffix i skip.
Dla kont powyżej 10 000 linków, możliwość wznowienia byłaby opłacalna — zapisywalibyśmy kursor strony Dub w import_jobs.source_filter i wznawialibyśmy od ostatniej ukończonej strony. Wszyscy pięciu dostawcy migracji współdzielą ten sam projekt wewnątrzprocesowy; kiedy wdrożymy możliwość wznowienia, wszyscy skorzystają.
Co dalej w wdrożeniu Tier-3#
Tier-3 zakończony. Pięciu dostawców migracji, jedna współdzielona tabela import_jobs, jeden współdzielony kontrakt workera, jeden współdzielony dashboard z pollingiem UI, pięć landingów SEO, pięć technicznych postów na blogu.
Co jest w kolejce za Tier-3:
- Wznawialność dla kont powyżej 10 000 linków. Checkpointing kursora dla dostawcy.
- Fallout przez eksport CSV dla użytkowników w planach z cofniętymi tokenami. Obecnie tylko concierge.
- Fundament
service_tokensTier-2 — tokeny dostawców do wielokrotnego użytku dla Mailchimp, Brevo, Klaviyo. Ścieżka migracji zweryfikowała wzorzec JSONBsource_filter; Tier-2 potrzebuje trwałych szyfrowanych tokenów, co jest terytorium ADR-0036.
Jeśli zerkałeś na Elido z przestrzeni roboczej Dub, historia migracji jest już udokumentowana. Wypróbuj ją — od tokena do ostatniego zaimportowanego linku w mniej niż pięć minut dla typowych kont.
Powiązane na blogu#
- Shipping the Bitly migration: a worker, a token, a 30-minute budget
- Shipping the Rebrandly migration: 25-per-page pagination
- Shipping the Short.io migration: per-domain pagination at 150/page
- Shipping the TinyURL migration: Pro/Bulk REST, no free-tier path
- Short links as Terraform: the engineering cornerstone