Elido
7 min de leituraEngenharia

Lançando a migração do Short.io: paginação por domínio a 150/página

Como construímos importações do Short.io com um clique para o Elido — o modelo de paginação por domínio, a regra de links privados desativados e a mais rápida de nossas cinco fontes de migração.

Marius Voß
DevRel · edge infra
Diagrama de pipeline: API REST do Short.io à esquerda fluindo através do worker de importação do Elido para a tabela de links, com um painel lateral listando as garantias numéricas que o worker mantém (limite de 50k, orçamento de 30 min, 150 por página, token apenas na memória)

A terceira fonte de migração em nossa implementação de Nível 3 foi lançada hoje. Cole uma Short.io API key, escolha o domínio de origem do Short.io (por exemplo, example.short.gy), escolha o domínio de destino do Elido, clique em Iniciar. Três a seis minutos depois, cada link estará no seu domínio Elido com o slug preservado.

Este post é o artigo de engenharia — o que é específico do Short.io, o que nos surpreendeu na API REST deles e por que acabamos expondo trabalhos por domínio em vez de um lote por conta.

Por domínio, não por conta#

O modelo de dados do Short.io tem um detalhe que moldou toda a UX do iniciador: os links são organizados sob domínios, e o endpoint /links pagina por domínio. Não existe uma chamada "me dê todos os links de todos os domínios nesta conta".

Consideramos alguns designs:

  • A. Iterar cada domínio no servidor, apresentar um trabalho ao usuário. Mais rápido do ponto de vista de contagem de cliques; mais difícil de expor o progresso e a escolha da estratégia de conflito por domínio.
  • B. Um trabalho do Elido por domínio de origem. Mais lento em cliques (o usuário executa N trabalhos para N domínios), mas cada trabalho tem um contrato claro: um domínio de origem → um domínio de destino → uma estratégia de conflito.
  • C. Listar cada domínio, permitir que o usuário selecione vários, enfileirar N trabalhos no servidor.

Lançamos o B e deixamos o C para a iteração do plano de implementação. O iniciador pede o hostname do domínio de origem como um campo de texto (sem dropdown — a lista de /domains do Short.io é barata de chamar, mas adiciona um round-trip e o usuário sempre sabe o hostname do próprio domínio). Um trabalho por domínio, enfileirado no painel, um de cada vez.

A vitória do tamanho de página#

O Short.io pagina a 150 links por chamada por padrão — a mais generosa de nossas cinco fontes de migração. Compare:

  • Bitly: 100 por página
  • Rebrandly: 25 por página
  • TinyURL: 100 por página (Pro/Bulk)
  • Dub.co: 100 por página
  • Short.io: 150 por página

Um domínio do Short.io com 5.000 links leva 34 round-trips. Uma conta do Rebrandly com 5.000 links leva 200. O worker gasta a maior parte do seu tempo de execução esperando por respostas HTTP, então isso importa — o Short.io é empiricamente a fonte de migração mais rápida que suportamos.

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 é um booleano que o Short.io retorna explicitamente — sem parsing de cursor, sem perseguição de last-id. A API deles é uma das mais bem projetadas dos cinco fornecedores que suportamos.

O Short.io tem uma flag "private" por link. Nós importamos links privados como links do Elido com is_active=false para que o slug não resolva na edge. O usuário os ativa seletivamente pelo painel após verificar a importação.

O raciocínio: se um link do Short.io era privado na origem, a intenção do usuário era que ele não fosse resolvido publicamente. Importá-lo como is_active=true exibiria URLs que foram deliberadamente restringidos. Importá-lo como is_active=false mantém o slug reservado, mas inacessível até que o usuário decida — estritamente mais seguro do que a alternativa.

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

Esta é uma pequena diferença de superfície em relação ao Bitly (sem flag equivalente) e ao Rebrandly (sem flag equivalente). Vale a pena destacar na receita pós-importação para que o usuário entenda por que alguns links importados não resolvem imediatamente.

O que não migramos#

As configurações de teste A/B do Short.io não têm uma exportação limpa — eles são um construtor no aplicativo que não expõe uma forma JSON determinística via API REST. Reconstrua como regras de smart-link do Elido pós-importação; a sintaxe é mais expressiva, mas o modelo mental é o mesmo.

O histórico por clique é o limite universal em todas as fontes de migração. Os dados por clique do Short.io residem na exportação de análise deles, que é exclusiva do plano Team (acessado em 2026-05-22) e aparece como contadores agregados em vez de eventos por clique. Novos cliques chegam na análise do Elido a partir da transição.

Designs de QR e predefinições de UTM por link — a mesma história do Bitly e Rebrandly. Marcados com imported:shortio, prontos para acompanhamento em massa via campanhas do Elido.

Entrega de domínio#

O caso de uso interessante do Short.io é "estou executando um domínio com marca no Short.io e quero movê-lo para o Elido sem alterar a URL". A migração lida com o lado do link de forma limpa; o lado do DNS é uma alteração de CNAME.

Documentamos a sequência de entrega na página de destino /migrate-from/shortio — mantenha ambas as superfícies resolvendo em paralelo até que sua assinatura do Short.io termine, então aponte o DNS para o Elido. Não há urgência em derrubar o Short.io no dia em que a importação termina.

Domínios personalizados no Elido usam TLS sob demanda do Caddy com domain-manager como fonte da lista de permissões, então a transição é uma alteração de CNAME mais uma chamada de API de verificação de domínio. Sem dança de certificados do lado do usuário.

Resolução de conflitos e o contrato do worker#

Idêntico ao Bitly e Rebrandly — sufixos incrementais mylink-2, mylink-3, … em caso de colisão; ignorar deixa o link existente do Elido sozinho e registra a linha de origem; falhar aborta no primeiro conflito. A busca é uma leitura indexada por linha.

O contrato do worker — MaxLinksPerImport=50_000, ImportRunBudget=30*time.Minute, progressEvery=50, errorLogCap=1_000 — é compartilhado entre todos os cinco fornecedores. Essas constantes fazem a maior parte do trabalho e não são botões de configuração. Elas são o contrato que a UI de polling do painel assume.

Manipulação de token#

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

source_token_id permanece NULL. A mesma semântica de execução única do Bitly e Rebrandly — o usuário cola o token uma vez, o worker é executado, o token é descartado da memória após a conclusão. Não persistimos porque o valor da persistência (uso recorrente) não se aplica a migrações.

context.WithoutCancel mantém o worker vivo após a requisição HTTP que o iniciou. O mesmo padrão de todos os outros fornecedores de migração nesta implementação.

Comparação com o caminho de exportação CSV#

O Short.io expõe uma exportação CSV nos planos Team. Escolhemos REST em vez de CSV porque:

  • REST preserva as tags do Short.io estruturalmente. CSV as achata em uma string separada por vírgulas que requer divisão pós-parse.
  • REST expõe a flag private. O CSV não a inclui consistentemente.
  • REST nos dá progresso determinístico (links vistos / links restantes). O CSV é um upload de arquivo de uma única vez, sem sinal de progresso durante o processo.
  • REST é agnóstico ao plano — cada plano do Short.io expõe /links. A exportação CSV é apenas para o plano Team.

O caminho CSV permanece em nossa manga para usuários em contas legadas do Short.io cujo token de API foi revogado, mas que ainda possuem um CSV da última exportação.

Resumibilidade e o problema do deploy#

Mesmo compromisso das duas primeiras migrações. O worker é in-process; um deploy durante a importação mata a goroutine. O campo import_jobs.last_progress_at mais o cron de varredura de travamento de 5 minutos altera qualquer linha running sem progresso nos últimos 30 minutos para failed. Re-executar é idempotente sob sufixo e ignorar.

Para contas com mais de 10.000 links em vários domínios do Short.io, o design de trabalho por domínio ajuda aqui — cada domínio é limitado pelo orçamento de 30 minutos independentemente, então um deploy no meio do terceiro domínio não perde o trabalho dos dois primeiros.

O que vem a seguir#

Mais dois fornecedores para chegar:

  • Dub.coGET /api/links?projectSlug=…&limit=100. Pastas são achatadas em tags. A API mais limpa das cinco.
  • TinyURL — API REST Pro/Bulk a 100 por página. O TinyURL gratuito não tem API e nunca teve; isso permanece um caminho manual.

Após Dub e TinyURL, a implementação de Nível 3 está concluída. As cinco páginas de destino de migração (/migrate-from/bitly, /migrate-from/rebrandly, /migrate-from/shortio, /migrate-from/dub, /migrate-from/tinyurl) e os cinco posts de engenharia cobrem todas as consultas de migração de fornecedor que um pesquisador de alternativas ao Bitly possa encontrar.

Se você estava esperando por uma comparação com o Short.io porque a história da migração não estava documentada, agora está. Experimente — chave de API + domínio até o último link importado em menos de seis minutos para contas típicas.

Relacionado no blog#

Experimente o Elido

Encurtador de URL hospedado na UE: domínios personalizados, análises profundas e API aberta. Plano gratuito — sem cartão de crédito.

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

Continuar lendo