Elido
7 min de lecturaIngeniería

Lanzamiento de la migración de Rebrandly: paginación de 25 por página y un presupuesto de 30 minutos

Cómo construimos las importaciones de Rebrandly con un solo clic para Elido: el tamaño de página lento, la UX del filtro de espacios de trabajo y lo que deliberadamente no migramos.

Marius Voß
DevRel · edge infra
Diagrama del pipeline: la API REST de Rebrandly a la izquierda fluyendo a través del trabajador de importación de Elido hacia la tabla de enlaces, con un panel lateral que enumera las garantías numéricas que mantiene el trabajador (límite de 50k, presupuesto de 30 min, 25 por página, token solo en memoria)

La segunda fuente de migración en nuestro lanzamiento de Nivel 3 se envió hoy. Pegue una API key de Rebrandly, filtre opcionalmente por un espacio de trabajo, haga clic en Inicio. Seis a diez minutos después, cada slashtag está en su dominio de Elido con el slug preservado donde no hubo colisión. La migración de Bitly que llegó hace dos semanas estableció el andamiaje; Rebrandly es el segundo proveedor que lo utiliza.

Esta publicación es el informe técnico: lo que es específico de Rebrandly, lo que mantuvimos idéntico al trabajador de Bitly y dónde la API de Rebrandly forzó una forma diferente.

Lo que se comparte con Bitly#

Toda la funcionalidad iba a ser siempre una tabla y un contrato de trabajador. Ambos se mantuvieron.

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 cambia a rebrandly. source_filter lleva {workspace_id: "..."} cuando el usuario filtra; {} cuando quieren todos los enlaces que la clave puede ver. Todo lo demás — el presupuesto de 30 minutos, el límite de 50k enlaces, la estrategia de conflicto sufijo/saltar/fallar, la etiqueta imported:rebrandly — es idéntico a la ruta de Bitly.

El iniciador del panel (apps/web/src/app/dashboard/integrations/[id]/rebrandly-migration-launcher.tsx) es estructuralmente una copia del de Bitly con el menú desplegable de grupos eliminado — Rebrandly tiene espacios de trabajo, no grupos, y los exponemos como un filtro de texto opcional en lugar de un menú desplegable poblado porque el punto final de Espacios de trabajo está paginado sin autenticación y el usuario típico tiene como máximo dos.

Donde la API de Rebrandly difiere#

Tres cosas:

Tamaño de página. Rebrandly limita una sola página a 25 enlaces. Bitly lo hace a 100. Así que una cuenta de 5,000 enlaces que termina en 4–8 minutos en Bitly toma de 6–10 en Rebrandly. El cuello de botella es el proveedor, no el trabajador.

Paginación. Rebrandly usa un parámetro de cadena de consulta last que toma el ID del último elemento en la página anterior. Bitly devuelve una URL pagination.next. Ambos son de estilo cursor; el de Rebrandly es solo un poco más comunicativo. Todo el bucle es de seis líneas:

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
}

Confiamos en el cursor. Si Rebrandly devuelve el mismo last dos veces, entraríamos en un bucle infinito; el presupuesto de 30 minutos limita el daño.

Alcance del espacio de trabajo. La API key de Rebrandly ve cada enlace en cada espacio de trabajo al que pertenece el usuario. Si tiene una cuenta de agencia con cinco espacios de trabajo de clientes, casi con seguridad querrá importar uno a la vez. El iniciador expone esto como un campo de texto opcional — pegue el ID del espacio de trabajo de la barra de URL de Rebrandly, o déjelo en blanco para "todo lo que la clave ve".

Lo que no migramos#

Historial de clics. Los datos por clic de Rebrandly son solo para el nivel Premium y aparecen como contadores agregados por enlace, no como eventos por clic. Mostramos este límite en cada superficie que ve el usuario — la página de recetas del panel, la página de aterrizaje /migrate-from/rebrandly, la interfaz de usuario de progreso de importación y la sección de preguntas frecuentes. Los nuevos clics aterrizan en los análisis de Elido desde el momento del cambio en adelante.

Plantillas UTM de Rebrandly. Son una característica de tiempo de presentación en Rebrandly que no tiene una superficie de API limpia para la exportación. Reconstrúyalas como reglas de campaña de Elido — la etiqueta imported:rebrandly es el manejador para la reasignación masiva.

Estilo de QR. El QR predeterminado de Elido se genera para cada enlace importado; los diseños personalizados deben volver a aplicarse. La mayoría de los usuarios usan el filtro de etiquetas masivo para asignar una superposición de CTA predeterminada de Elido o una campaña post-hoc.

Manejo del token#

Idéntico a Bitly. El token nunca aterriza en el disco:

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

source_token_id permanece NULL. La tabla service_tokens del ADR-0036 es para las integraciones de token de pegado de Nivel 2 (Mailchimp, Brevo, Klaviyo) donde el uso recurrente justifica la persistencia. Para migraciones de un solo uso, solo en memoria es el compromiso operativo correcto — el usuario pega el token una vez, el trabajador se ejecuta, el token desaparece.

context.WithoutCancel (Go 1.21+) mantiene los valores del contexto — logger, IDs de rastreo, fecha límite — pero elimina su señal de cancelación para que el trabajador sobreviva a la solicitud HTTP que lo inició. Este es el mismo patrón que el trabajador de Bitly y el mismo patrón que usará cualquier futuro proveedor de migración.

Resolución de conflictos#

Tres estrategias, idénticas a Bitly. El usuario elige cuando inicia el trabajo:

  • suffix (predeterminado): recorrer mylink-2, mylink-3, … hasta 50 candidatos. Pasados los 50 lo tratamos como un problema estructural y mostramos un error.
  • skip: dejar el enlace de Elido existente solo, registrar la fila de origen, contar como omitido.
  • fail: abortar todo el trabajo en el primer conflicto. Para una semántica estricta 1:1.

La búsqueda de slug es una lectura indexada por fila:

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
}

Pagamos una lectura extra por fila pero obtenemos un recorrido de sufijo determinista y un mensaje de error más amigable. La alternativa — buscar una violación de unicidad en pgx y analizar el nombre de la restricción fuera de la cadena de error — es el peor compromiso.

Lo que es medible#

Los mismos registros estructurados de zap que Bitly. Espacio de trabajo, dominio de destino, estrategia de conflicto, filtro de espacio de trabajo opcional. Los eventos del ciclo de vida del trabajo — inicio, finalización, cambios de barrido de bloqueos — son preexistentes y el panel golpea el punto final de sondeo cada dos segundos.

Todavía no estamos graficando las métricas de trabajos de migración en producción. La cohorte de Bitly nos dio nuestra primera línea base de tráfico real; los datos de Rebrandly deberían ser directamente comparables porque el trabajador es mecánicamente idéntico y las diferencias son la forma de paginación del proveedor. Primer candidato a alerta: recuento de barrido de bloqueos > 0 en cualquier ventana de una hora — eso significa que un trabajador murió y la interfaz de usuario del usuario está atascada en running.

Capacidad de reanudación y el problema del despliegue#

El mismo compromiso que Bitly. El trabajador está en proceso; un despliegue a mitad de la importación mata a la goroutine. Aceptamos eso para la v1 porque:

  1. La mayoría de los trabajos terminan en menos de diez minutos. Los despliegues son poco frecuentes en los momentos de importación del día.
  2. El campo import_jobs.last_progress_at más un cron de barrido de bloqueos de 5 minutos cambia cualquier fila running sin progreso en los últimos 30 minutos a failed con una razón clara.
  3. La reejecución es idempotente bajo estrategias de sufijo y saltar — los enlaces ya importados se detectan en la segunda pasada y se resuelven según la estrategia.

Para cuentas con más de 10,000 enlaces, la capacidad de reanudación vale la pena — registramos el cursor last de Rebrandly en import_jobs.source_filter y retomamos donde se quedó la última ejecución. Esa es la siguiente iteración; las otras cuatro fuentes de migración se beneficiarán del mismo cambio una vez que lo enviemos.

¿Qué sigue?#

Mismo andamiaje, tres proveedores más para aterrizar en la misma tabla import_jobs.

  • Short.ioGET /links?limit=150&domain_id=…. Paginación por dominio; pedimos al usuario que elija un dominio de origen en lugar de un espacio de trabajo.
  • Dub.coGET /api/links?projectSlug=…&limit=100. Carpetas + etiquetas preservadas; esta es la más limpia de las cuatro.
  • TinyURL — API REST Pro/Bulk. TinyURL gratuito no tiene API y nunca la ha tenido; esa ruta sigue siendo manual.

Cada una aterriza detrás de la misma interfaz de usuario de sondeo del panel y el mismo patrón de etiqueta imported:<vendor>. El trabajador específico del proveedor permanece en services/api-core/internal/imports/<vendor>.go.

Si ha estado posponiendo una comparación con Rebrandly porque la ruta de migración no estaba documentada, ahora lo está. Pruébelo — desde la API key hasta el último enlace importado en menos de diez minutos para cuentas típicas.

Relacionado en el blog#

Prueba Elido

Acortador de URL alojado en la UE: dominios personalizados, análisis profundo y API abierta. Plan gratuito — sin tarjeta de crédito.

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

Seguir leyendo