Sincronização incremental de episódios
Objetivos operacionais
- Podcasts ativos: último sucesso há no máximo 60 minutos.
- Podcasts favoritos: último sucesso há no máximo 15 minutos.
- Cobertura mínima do catálogo dentro do SLO: 99,9%.
- Histórico: sem TTL; preservar tudo o que RSS/RFC5005 ou Podcast Index expuser.
Fluxo
O cron api-sync-episodes-daily mantém o identificador por compatibilidade, mas
corre a cada minuto. O PodcastSyncDispatcherService consulta no máximo 500
podcasts cujo next_episode_sync_at venceu, ordena pelo vencimento e adquire um
lease atómico de 10 minutos. Só o processo que obteve o lease cria o jobsync-episodes-feed; uma falha no enqueue liberta o lease e torna o feed
imediatamente elegível.
Cada job contém podcastId, leaseToken e o intervalo seguinte. Favoritos usam
prioridade 1 e 15 minutos; os restantes usam prioridade 5 e 60 minutos. O Worker
processa até 10 jobs em paralelo e o limiter BullMQ é global, 120 feeds/minuto.jobId inclui podcast e lease, impedindo duplicação da mesma tentativa.
Abrir, pesquisar ou favoritar um podcast stale tenta adquirir o mesmo lease e
enfileira um refresh em background. A resposta pública continua a usar os dados
existentes. GET /podcasts/:id/episodes mantém o body compatível e acrescenta:
X-Podcast-Sync-StatusX-Podcast-Data-StaleX-Podcast-Last-SuccessX-Podcast-Next-Sync
Fetch e persistência
Os URLs guardados dos feeds e os das páginas seguintes só são buscados em endereços públicos; um URL bloqueado fica na categoria blocked_url, sem nova tentativa na mesma execução; o limite por feed é 30 MiB.
O primeiro pedido RSS envia If-None-Match e If-Modified-Since. Um 304
atualiza tentativa, sucesso, estado e próximo sync, sem parsing nem escritas de
episódios. Em 200, GUIDs string válidos geram dedupe_key=guid:<guid>; sem GUID,
a chave é enclosure:<sha256-url>. O Worker carrega as chaves candidatas numa
única query limitada e usa bulkWrite apenas para episódios novos ou metadados
alterados. A unicidade é {podcast_id,dedupe_key} com filtro parcial.
Timeouts, 408, 429 e 5xx têm três tentativas, backoff exponencial e jitter;Retry-After tem precedência. 404/410 só marcam feed_dead e estadounavailable após três execuções. Tentativas falhadas nunca atualizamlast_episode_sync_success_at.
Histórico e backfill
backfill-podcast-history é uma fila separada, concorrência 1, prioridade 20 e
limite de 10 páginas/minuto. O cursor RFC5005 fica emhistory_backfill_next_url depois de cada página, portanto restart retoma do
checkpoint. Na última página, o Worker consulta até 1000 itens do Podcast Index
quando existe podcast_index_id. Se o total conhecido continuar superior ao
armazenado, o estado final é source_limited; caso contrário é complete.
A origem pode retirar episódios ou não publicar arquivo/paginação. Nesse caso a
Audissey não inventa cobertura: source_limited torna a limitação explícita.
Campos do podcast
last_episode_sync_attempt_at, last_episode_sync_success_at,next_episode_sync_at, episode_sync_lease_token,episode_sync_lease_until, episode_sync_etag,episode_sync_last_modified, episode_sync_status, episode_sync_error,consecutive_episode_sync_failures, latest_episode_publish_date,last_episode_sync_duration_ms,history_backfill_status, history_backfill_next_url,history_backfill_started_at, history_backfill_completed_at ehistory_earliest_publish_date.
Administração e alertas
GET /admin/podcasts/sync-health: cobertura, vencidos, nunca sincronizados,
p95, favoritos atrasados, falhas, backfill e idade/capacidade da fila.POST /admin/podcasts/:id/sync: refresh manual com lease.- O catálogo Admin mostra frescura por podcast e “Sincronizar agora”.
scripts/ensure-podcast-sync-alerts.jsaudita/cria alertas para cobertura
inferior a 99,9%, favorito vencido, fila acima de 7200 e 15 minutos sem
sucesso.
Rollout
- Preencher as variáveis dos três Variable Groups descritas em
docs/setup/ENVIRONMENTS.md. - Fazer deploy da migração
20260804_011e executá-la no Admin. Confirmar que o
TTL desapareceu e o índice GUID é parcial. - Fazer deploy de API/Worker/Admin, mantendo o dispatcher cron desativado.
- Usar
POST /admin/podcasts/:id/syncpara Prova Oral e Bitalk. Confirmar
respetivamente episódios de 31 de julho e 28 de julho, e zero duplicados na
segunda execução. - Ativar o cron em staging durante 24 horas. Exigir cobertura >=99,9%, nenhum
favorito acima de 15 minutos e queue age dentro da capacidade. - Só então ativar dev/prod. O antigo job global deixa de ser agendado; triggers
manuais sempodcastIdpermanecem temporariamente limitados a 500 feeds.
Referência: Podcasting 2.0 — recomendações de arquivo e paginação.