6 Min. LesezeitEngineering

Die Rebrandly-Migration bereitstellen: 25-pro-Seite-Pagining und ein 30-Minuten-Budget

Wie wir One-Click-Rebrandly-Importe für Elido gebaut haben – die langsame Seitengröße, die Workspace-Filter-UX und was wir bewusst nicht migrieren.

Marius Voß
DevRel · edge infra
Pipeline-Diagramm: Rebrandly REST API links, die durch den Elido-Import-Worker in die Links-Tabelle fließt, mit einem Seitenpanel, das die numerischen Garantien des Workers auflistet (50k-Limit, 30-Minuten-Budget, 25 pro Seite, Token nur im Speicher)

Die zweite Migrationsquelle in unserem Tier-3-Rollout wurde heute bereitgestellt. Fügen Sie einen Rebrandly API-Key ein, filtern Sie optional nach einem Workspace und klicken Sie auf Start. Sechs bis zehn Minuten später sitzt jeder Slashtag auf Ihrer Elido-Domain, wobei der Slug beibehalten wurde, sofern er nicht kollidierte. Die Bitly-Migration, die vor zwei Wochen live ging, legte das Gerüst; Rebrandly ist der zweite Anbieter, der darauf aufbaut.

Dieser Beitrag ist der technische Bericht – was spezifisch für Rebrandly ist, was wir identisch zum Bitly-Worker gehalten haben und wo die Rebrandly-API eine andere Form erzwang.

Pipeline-Diagramm: Rebrandly REST-API paginiert 25 Links pro Seite in den In-Process-Elido-Import-Worker, der Slugs auflöst und Zeilen in die Links-Tabelle schreibt, während der Cursor bis zur leeren Seite durchläuft

Was wir mit Bitly teilen

Das gesamte Feature sollte schon immer auf einer Tabelle und einem Worker-Vertrag basieren. Beides hat sich bewährt.

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 wechselt zu rebrandly. source_filter trägt {workspace_id: "..."}, wenn der Benutzer filtert; {} wenn er jeden Link sehen will, den der Key sehen kann. Alles andere – das 30-Minuten-Budget, das 50k-Link-Limit, die Suffix/Skip/Fail-Konfliktstrategie, der imported:rebrandly-Tag – ist identisch mit dem Bitly-Pfad.

Der Dashboard-Launcher (apps/web/src/app/dashboard/integrations/[id]/rebrandly-migration-launcher.tsx) ist strukturell eine Kopie des Bitly-Launchers, wobei das Gruppen-Dropdown entfernt wurde – Rebrandly hat Workspaces, keine Gruppen, und wir stellen sie als optionalen Textfilter statt als ausgefülltes Dropdown bereit, da der Workspaces-Endpunkt nicht authentifiziert paginiert ist und der typische Benutzer höchstens zwei hat.

Wo sich die Rebrandly-API unterscheidet

Drei Dinge:

Seitengröße. Rebrandly begrenzt eine einzelne Seite auf 25 Links. Bitly auf 100. Daher benötigt ein 5.000-Link-Account, der bei Bitly in 4–8 Minuten fertig ist, bei Rebrandly 6–10. Der Flaschenhals ist der Anbieter, nicht der Worker.

Pagining. Rebrandly verwendet einen last-Query-String-Parameter, der die ID des letzten Elements der vorherigen Seite übernimmt. Bitly gibt eine pagination.next-URL zurück. Beide sind Cursor-basiert; Rebrandly ist nur etwas gesprächiger. Die gesamte Schleife besteht aus sechs Zeilen:

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
}

Wir vertrauen dem Cursor. Wenn Rebrandly zweimal denselben last zurückgibt, würden wir in einer Endlosschleife hängen; das 30-Minuten-Budget begrenzt den Schaden.

Workspace-Scoping. Der API-Key von Rebrandly sieht jeden Link in jedem Workspace, dem der Benutzer angehört. Wenn Sie einen Agentur-Account mit fünf Kunden-Workspaces haben, möchten Sie fast sicher einen nach dem anderen importieren. Der Launcher macht dies als optionales Textfeld verfügbar – fügen Sie die Workspace-ID aus der URL-Leiste von Rebrandly ein oder lassen Sie es leer für „alles, was der Key sieht“.

Was wir nicht migrieren

Klick-Historie. Rebrandlys Klickdaten sind nur im Premium-Tarif verfügbar und erscheinen als aggregierte Zähler pro Link, nicht als Klick-Ereignisse. Wir weisen auf jeder Oberfläche, die der Benutzer sieht, auf diese Grenze hin – die Dashboard-Rezeptseite, die /migrate-from/rebrandly-Landingpage, die Import-Fortschritts-UI und den FAQ-Bereich. Neue Klicks landen ab dem Zeitpunkt der Umstellung in der Elido-Analytik.

Rebrandly UTM-Vorlagen. Sie sind ein Feature zur Präsentationszeit in Rebrandly, das keine saubere API-Schnittstelle für den Export hat. Bauen Sie sie als Elido-Kampagnenregeln nach – der imported:rebrandly-Tag ist das Handle für die Massenzuweisung.

QR-Styling. Der Standard-Elido-QR wird für jeden importierten Link generiert; benutzerdefinierte Designs müssen neu angewendet werden. Die meisten Benutzer verwenden den Bulk-Tag-Filter, um nachträglich ein standardmäßiges Elido-CTA-Overlay oder eine Kampagne zuzuweisen.

Zweispaltige Übersicht: Was ein Rebrandly-Import mitbringt (Links, beibehaltene Slugs, Ziel-URLs, der imported:rebrandly-Tag, Standard-QR, neue Klicks) versus was zurückbleibt (Klick-Historie, UTM-Vorlagen, benutzerdefiniertes QR-Styling)

Token-Handhabung

Identisch zu Bitly. Das Token landet nie auf der Festplatte:

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

source_token_id bleibt NULL. Die service_tokens-Tabelle ist für die Tier-2-Paste-Token-Integrationen (Mailchimp, Brevo, Klaviyo), bei denen die wiederkehrende Nutzung Persistenz rechtfertigt. Für Einmal-Migrationen ist „nur im Speicher“ der richtige operative Kompromiss – der Benutzer fügt das Token einmal ein, der Worker läuft, das Token ist weg.

context.WithoutCancel (Go 1.21+) behält die Werte des Kontextes bei – Logger, Trace-IDs, Deadline –, entfernt aber sein Abbruchsignal, sodass der Worker den HTTP-Request, der ihn gestartet hat, überlebt. Dies ist dasselbe Muster wie beim Bitly-Worker und dasselbe Muster, das jeder zukünftige Migrationsanbieter verwenden wird.

Konfliktlösung

Drei Strategien, identisch zu Bitly. Der Benutzer wählt beim Starten des Jobs:

  • suffix (Standard): durchlaufe mylink-2, mylink-3, … bis zu 50 Kandidaten. Über 50 behandeln wir als strukturelles Problem und zeigen einen Fehler an.
  • skip: lass den bestehenden Elido-Link in Ruhe, protokolliere die Quellzeile, zähle als übersprungen.
  • fail: brich den gesamten Job beim ersten Konflikt ab. Für strikte 1:1-Semantik.
Entscheidungsdiagramm ausgehend von einer Slug-Existenzprüfung in drei Strategien: suffix durchläuft mylink-2 bis mylink-50, skip lässt den bestehenden Link in Ruhe und protokolliert die Zeile, und fail bricht den gesamten Job beim ersten Konflikt ab

Die Slug-Suche ist ein indizierter Lesezugriff pro Zeile:

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
}

Wir bezahlen einen zusätzlichen Lesezugriff pro Zeile, erhalten aber einen deterministischen Suffix-Durchlauf und eine freundlichere Fehlermeldung. Die Alternative – nach einer Verletzung der Eindeutigkeit in pgx zu fischen und den Constraint-Namen aus der Fehlermeldung zu parsen – ist der schlechtere Kompromiss.

Was messbar ist

Dieselbe strukturierten Zap-Logs wie bei Bitly. Workspace, Zieldomain, Konfliktstrategie, optionaler Workspace-Filter. Job-Lebenszyklus-Ereignisse – Start, Abschluss, Stuck-Sweep-Flips – sind bereits vorhanden und das Dashboard ruft alle zwei Sekunden den Polling-Endpunkt auf.

Wir grafen die Metriken der Migrationsjobs in Produktion noch nicht. Die Bitly-Kohorte gab uns unsere erste Baseline für echten Traffic; die Rebrandly-Daten sollten direkt vergleichbar sein, da der Worker mechanisch identisch ist und die Unterschiede in der Pagining-Form des Anbieters liegen. Erster Alert-Kandidat: Stuck-Sweep-Count > 0 in einem beliebigen einstündigen Fenster – das bedeutet, ein Worker ist gestorben und die UI des Benutzers hängt auf running.

Resumability (Wiederaufnahme) und das Deployment-Problem

Derselbe Kompromiss wie bei Bitly. Der Worker läuft in-process; ein Deployment während des Imports beendet die Goroutine. Wir akzeptieren das für v1, weil:

  1. Die meisten Jobs sind in unter zehn Minuten fertig. Deployments sind zu Import-Zeiten eher selten.
  2. Das import_jobs.last_progress_at-Feld plus ein 5-Minuten-Stuck-Sweep-Cron setzt jede running-Zeile ohne Fortschritt in den letzten 30 Minuten auf failed mit einem klaren Grund.
  3. Das erneute Ausführen ist bei Suffix- und Skip-Strategien idempotent – bereits importierte Links werden beim zweiten Durchlauf erkannt und gemäß der Strategie aufgelöst.

Für Accounts mit mehr als 10.000 Links lohnt sich Resumability – wir zeichnen den Rebrandly last-Cursor in import_jobs.source_filter auf und machen da weiter, wo der letzte Lauf aufgehört hat. Das ist die nächste Iteration; die vier anderen Migrationsquellen werden von derselben Änderung profitieren, sobald wir sie bereitstellen.

Was kommt als Nächstes

Dasselbe Gerüst, drei weitere Anbieter landen in derselben import_jobs-Tabelle.

  • Short.io - GET /links?limit=150&domain_id=…. Pro-Domain-Pagining; wir bitten den Benutzer, eine Quelldomain statt eines Workspaces zu wählen.
  • Dub.co - GET /api/links?projectSlug=…&limit=100. Ordner + Tags beibehalten; dies ist das sauberste der vier.
  • TinyURL - Pro/Bulk REST API. Kostenloses TinyURL hat keine API und hatte sie noch nie; dieser Pfad bleibt manuell.

Jeder landet hinter derselben Dashboard-Polling-UI und demselben imported:<vendor>-Tag-Muster. Der anbieterspezifische Worker bleibt in services/api-core/internal/imports/<vendor>.go.

Wenn Sie mit einem Rebrandly-Vergleich gezögert haben, weil der Migrationspfad nicht dokumentiert war, ist er jetzt dokumentiert. Probieren Sie es aus - vom API-Key bis zum letzten importierten Link in unter zehn Minuten für typische Accounts.

Verwandtes im Blog

Elido testen

URL einfügen, kurzer Link in Sekunden

Kein Konto nötig. Link bleibt 30 Tage aktiv. Konto erstellen, um ihn dauerhaft zu behalten.

Kostenlos, keine Anmeldung erforderlich · 2 pro Tag

Elido testen

URL-Shortener mit EU-Hosting: eigene Domains, tiefe Analytik und eine offene API. Kostenloser Tarif - keine Kreditkarte nötig.

Tags
rebrandly migration
url shortener
go worker
data migration
engineering
tier 3 integrations

Weiterlesen