Sistemas Descentralizados/03 - Nostr4 min
Relays e Fan-out
- Que problema isso resolve?
- Quando usar / quando NÃO usar?
- Qual o principal trade-off?
- Como isso falha em produção?
Conceito
Um relay é um servidor WebSocket que faz três coisas: aceita evento, guarda evento, entrega evento por filtro. É deliberadamente burro.
O que ele explicitamente não faz:
- não conversa com outros relays
- não tem consenso, nem visão global
- não decide o que é verdade
- não conhece seus seguidores, seu feed, nem sua timeline
Quem monta a timeline é o cliente. Essa inversão é a decisão arquitetural central do Nostr.
O cliente é quem faz o fan-out
O cliente mantém conexões com N relays simultaneamente e:
- publica o mesmo evento assinado em todos
- consulta todos com o mesmo filtro
- deduplica localmente por
id— trivial, porque oidé hash do conteúdo
┌── relay.damus.io ──┐
cliente ───────┼── nos.lol ─────────┼──── dedup por id ──> timeline
├── relay.snort.social┤
└── meu-relay.com ───┘
O relay virou cache replicado e descartável. Se um te bane, some ou vira lixo, você continua existindo nos outros — mesma chave, mesmos seguidores, mesmo histórico. Não há migração porque nunca houve nada preso ao servidor.
O problema difícil: descoberta
Se o fan-out fosse a história toda, o Nostr teria um problema fatal de escala. Se cada cliente precisasse perguntar a todos os relays do mundo por todos os autores que segue, o custo cresceria sem limite.
Pior: o problema aparece antes da escala. Se você e outra pessoa não têm nenhum relay em comum, vocês são invisíveis um para o outro apesar de ambos estarem "no Nostr".
A resposta: outbox model (NIP-65)
A solução, que é conceitualmente bonita: cada usuário publica um evento kind: 10002 dizendo em quais relays ele escreve e em quais ele lê.
| Marcador | Significa |
|---|---|
write |
"eu publico aqui — venha me buscar aqui" |
read |
"eu leio aqui — me mande menções aqui" |
Com isso o cliente para de perguntar a esmo:
- Busca o
kind: 10002de cada pessoa que você segue - Descobre os relays
writede cada uma - Consulta cada autor no relay dele, não todos em todos
O nome vem de "caixa de saída": em vez de todo mundo despejar tudo numa caixa comum, cada autor tem a sua, e o leitor vai buscar onde a pessoa realmente escreve.
Repare no que isso é, tecnicamente: o índice de roteamento virou dado do próprio protocolo, publicado pelo usuário e assinado por ele. Não é DNS, não é registro central, não é tabela de nenhum servidor — é só mais um evento. Descoberta resolvida com o mecanismo que já existia.
Complementando, o NIP-66 permite que monitores publiquem quais relays existem e estão vivos — mesma ideia, aplicada aos relays em vez de aos autores.
Tipos de relay que apareceram na prática
Como relay é barato de rodar, o ecossistema se diferenciou sozinho:
| Tipo | Modelo | Por que existe |
|---|---|---|
| Público grátis | aberto a todos | porta de entrada; sofre com spam |
| Pago | assinatura mensal | o preço é o anti-spam |
| Pessoal | você hospeda o seu | garante que seus dados existem |
| Web of trust | só quem é seguido por quem o dono segue | filtro social |
| Especializado | só um kind (git, podcast, mídia) |
otimizado para um caso |
| Arquivo | guarda histórico longo | contrapõe o descarte dos outros |
Trade-offs
O trade-off é limpo e vale como padrão de arquitetura geral:
Servidor burro + cliente esperto = servidor descartável.
O que se ganha:
- Relay trivial de implementar (centenas de linhas) → relays abundantes → censura precisa de coordenação global
- Nenhum relay precisa ser confiável, porque nenhum é necessário
- Trocar de relay não é migração, é mudar uma lista
O que se paga, e o cliente paga tudo:
- Banda e bateria: N conexões WebSocket abertas, N cópias de cada evento chegando
- Complexidade: dedup, reconciliação, timeouts por relay, relay lento travando a UI
- Consultas ruins: "todas as respostas a este post" exige perguntar a todo mundo e torcer
- Sem completude: nenhuma resposta é "a resposta certa" — só "o que esses relays tinham". Daí existir o NIP-67 (EOSE Completeness Hint), para o relay ao menos avisar se terminou de verdade ou desistiu.
E há a assimetria estrutural: escrever é barato e confiável, ler é caro e incompleto. Isso é o oposto de um banco de dados normal, e reorganiza o design de qualquer app Nostr.
Exemplo prático
"Quantos seguidores eu tenho?" — pergunta simples, sem resposta.
Num sistema centralizado é um COUNT com índice. No Nostr:
- Seguidores estão em eventos
kind: 3de outras pessoas, espalhados - Você teria que varrer todos os relays existentes procurando
kind: 3que citem sua pubkey - Não existe "todos os relays existentes" — não há registro completo
- Relays limitam consultas amplas assim, e com razão
O melhor que dá é uma estimativa por amostragem: "3.400 seguidores segundo os 8 relays que consultei". Números diferentes em clientes diferentes, todos corretos dentro do seu escopo.
Isso incomoda no começo e é a lição mais transferível da nota: num sistema sem estado global, agregações deixam de ser fatos e viram observações. Todo produto construído nesse tipo de sistema precisa desenhar a UI para amostras — e não prometer contadores exatos que não pode entregar.