Dados ao vivo · serviço público

Mapa de Alertas — dados ao vivo que outros sites incorporam

Um mapa de alerta de ataque aéreo onde a página é apenas parte do produto. Alertas, alvos em movimento, a linha de frente e pontos térmicos chegam de cinco fontes independentes, atualizam em seus próprios relógios — 15, 10 e 20 segundos — e saem através de uma API JSON aberta e um widget que outros sites colocam em uma moldura própria.

Quero similarVisitar site
Categoria
Dados ao vivo · serviço público
Custo estimado
a partir de €20.000
Atualizar
alertas 15 s · alvos 10 s · feed 20 s
Fontes
cinco, independentes entre si
Pilha
PHP 8.3, trabalhadores Python, SQLite, Leaflet
map.megainet.art
Mapa de Alertas
Página do projeto — map.megainet.art

01Visão geral do projeto

Mapa de Alertasmostra onde um alerta de ataque aéreo está agora: por oblast e por distrito, com alvos em movimento no mapa, a linha de frente, pontos térmicos de satélites e um feed de canais de monitoramento abaixo. É lido por pessoas que precisam da resposta em segundos, e é lido em um telefone, à noite, em uma internet móvel ruim.

A página, no entanto, é apenas parte do produto. Os mesmos dados saem através de uma API JSON aberta com chaves e limites de taxa, e através de um widget que outros sites incorporam em uma moldura própria. Nós projetamos e construímos tudo isso: os coletores, o armazenamento, a API, o mapa e o widget.

02Contexto e o problema

Os dados que respondem «existe um alerta aqui» não existem em um só lugar. Alertas oficiais vêm por oblast. Alertas locais para comunidades vêm de um serviço diferente. Onde os alvos estão é escrito, em palavras, por canais de monitoramento. A linha de frente é publicada por um projeto de mapeamento. Incêndios são vistos por satélites. Cinco fontes, cinco formatos, cinco ideias diferentes do que «agora» significa.

O segundo problema é o silêncio. Qualquer uma dessas fontes pode parar — um canal fica em silêncio, uma API começa a expirar — e o modo de falha honesto é dizer isso, não continuar desenhando a imagem de ontem. Um mapa que parece calmo porque sua fonte morreu é pior do que nenhum mapa.

03Objetivos do projeto

  • uma imagem montada a partir de cinco fontes independentes, cada uma com seu próprio relógio de atualização;
  • uma resposta em segundos em um telefone, em uma conexão fraca, à noite;
  • dados que podem sair do site: uma API aberta com chaves, limites e tarifas, e um widget incorporável;
  • três idiomas, porque o público não é apenas local;
  • uma fonte que ficou em silêncio deve ser visível como silenciosa, não como calma;
  • o próprio endereço do servidor nunca deve alcançar as fontes que ele lê.

04Cinco fontes, uma imagem

  • Alertas oficiais por oblast — a espinha dorsal do mapa;
  • alertas locais para comunidades e distritos, de um serviço separado;
  • canais de monitoramento no Telegram — leia tanto de prévias públicas quanto, para canais fechados, pelo próprio protocolo de mensagens;
  • a linha de frente, de um projeto de mapeamento que o publica;
  • pontos térmicos de satélites da NASA — incêndios vistos do espaço.

Os coletores funcionam como dois trabalhadores de longa duração sob o supervisor do sistema, cada um reiniciado por conta própria, e escrevem no armazenamento local. O site nunca se comunica diretamente com uma fonte: ele lê o que os coletores já escreveram, razão pela qual uma fonte lenta torna o mapa mais antigo em vez de indisponível.

05Quatro arquivos em vez de um banco de dados

Os dados vivem em quatro arquivos SQLite separados: o log de alertas, assinaturas de push e seu estado por região, eventos do mapa com o feed, e chaves de API com seus contadores de taxa. Não há um banco de dados compartilhado de propósito — a falha de um arquivo não deve derrubar os outros. Se as assinaturas de push estiverem indisponíveis, o mapa ainda é desenhado; se o armazenamento de chaves estiver ocupado, a página ainda responde.

Os mesmos arquivos são escritos pelo painel de controle de todo o grupo — emitindo uma chave, redefinindo um estado de alerta — então cada conexão abre com um tempo de espera. O SQLite desiste instantaneamente de um banco de dados ocupado por padrão, e «instantaneamente» aqui significaria uma página de erro enquanto um trabalhador estava apenas escrevendo uma linha.

06Três relógios em uma página

Dados diferentes envelhecem a velocidades diferentes, então se atualizam a velocidades diferentes: alertas a cada 15 segundos, alvos em movimento a cada 10, o feed a cada 20. Um intervalo comum seria errado em ambas as direções ao mesmo tempo — muito lento para alvos, desperdício para o feed, e um pedido desnecessário a cada poucos segundos para milhares de abas abertas.

Cada bloco também carrega seu próprio «última atualização». Uma página que redesenha silenciosamente ensina você a confiar nela exatamente quando não deveria ser confiável; um carimbo de data/hora que para de se mover é a maneira mais rápida possível de dizer «esta parte está desatualizada».

07Uma API aberta com chaves e tarifas

O mapa publica o que sabe: alertas atuais, eventos, um ponto de análise com um mapa de calor de «oblast × bucket de tempo», o ritmo do dia, tipos de alvos e direções. Chamadas anônimas têm um limite modesto por minuto; uma chave aumenta esse limite, e as tarifas sobem em etapas para aqueles que constroem sobre isso.

As chaves são emitidas a partir do painel de controle compartilhado, nunca digitadas à mão, e o uso é contado por chave por dia. Um ponto de extremidade é deliberadamente fechado para todos, exceto para o proprietário da chave — aquele que informa a uma chave sua própria tarifa, limite e consumo. Tudo o mais é aberto, porque um mapa de serviço público que esconde seus dados é apenas metade de um serviço público.

08Um widget que vive nos sites de outras pessoas

O widget é a parte do produto que sai de casa. Dois tamanhos fixos — uma faixa de status e um painel quadrado — três idiomas, dois temas e uma política de moldura escrita especificamente para que a incorporação seja permitida aqui e em nenhum outro lugar do site: o mapa e o widget podem ser emoldurados, a conta e as páginas de suporte não podem.

Como vive em uma moldura, o widget não tem cookies próprios e não escolhe uma geração de interface: ele parece o mesmo para todos. Essa é uma decisão, não uma limitação — um widget que renderiza de forma diferente dependendo das escolhas anteriores de um visitante seria impossível de suportar na página de outra pessoa.

09Push que conhece sua região

Uma assinatura está ligada a uma região, e o estado de cada região é mantido ao lado das assinaturas. É isso que faz uma notificação chegar na borda — quando o alerta em seu oblast começa ou termina — em vez de a cada atualização da fonte. A diferença é o produto inteiro: um é um sinal, o outro é ruído que as pessoas desligam em um dia.

Há um aplicativo nativo em cima dos mesmos dados, e ele se comunica com uma API de aplicativo em vez de com as páginas da web. Um endpoint de saúde próprio responde ao registro da frota, porque uma verificação de atividade não deve ser mais cara do que o trabalho que verifica.

10Três idiomas, incluindo o widget

O mapa fala ucraniano, inglês e russo, assim como a referência da API, as páginas da conta e o widget. Uma nova string de interface é adicionada a todos os três de uma vez e uma verificação conta os que estão faltando: a contagem deve ser zero. Interfaces meio traduzidas não se anunciam — elas mostram silenciosamente uma língua dentro da outra e parecem boas para quem as escreveu.

Os nomes das regiões são uma história à parte: eles vêm das fontes na sua própria grafia, então o mapa mantém um dicionário próprio em vez de confiar no que chegou no feed.

11Lendo fontes sem mostrar seu endereço

Tudo que os coletores buscam sai através de um proxy SOCKS5, incluindo o protocolo de mensagens usado para canais fechados — o proxy lá é feito em código, sem uma dependência extra. O endereço do servidor nunca chega a uma fonte, e uma fonte que decide bloquear alguém bloqueia o proxy, não a máquina que serve o mapa.

O limitador de taxa fica na frente da API pela mesma razão ao contrário: um endpoint público com dados ao vivo atrai raspadores, e um limite por chave por minuto é o que mantém um serviço público utilizável para as pessoas para quem foi escrito.

12Pilha de tecnologia

  • PHP 8.3 sem um framework: um pequeno roteador, endpoints e renderizadores;
  • Trabalhadores Python sob o supervisor do sistema — um apenas na biblioteca padrão, um falando o protocolo de mensagens;
  • SQLite, quatro arquivos separados, cada um com sua própria razão de existir;
  • Leafletpara o mapa, ativos hash, um widget com tamanhos fixos;
  • push web com estado por região, um aplicativo nativo com os mesmos dados;
  • egress SOCKS5 para cada solicitação de saída, limites de taxa por chave na frente da API.

13O resultado

Um mapa que responde em segundos e continua respondendo quando uma de suas cinco fontes para. Dados que outros sites podem usar: uma API aberta com chaves e tarifas, e um widget em três idiomas e dois temas. Notificações que disparam na borda de um evento em vez de em cada consulta. Um aplicativo nativo com os mesmos dados, e um painel de controle onde chaves são emitidas e estados são redefinidos.

14Principais aprendizados

Três coisas que levaríamos para qualquer projeto com dados ao vivo.Separe o coletor da página: um site que busca uma fonte enquanto um visitante espera é um site que cai sempre que a fonte cai.Dê a cada bloco seu próprio relógio e seu próprio timestamp: um único intervalo está errado em ambas as direções, e um redesenho silencioso oculta a obsolescência.Diga quando você não souber: um bloco vazio que admite isso vale mais do que um mapa com aparência calma com os dados de ontem.

E uma lição de produto: no momento em que os dados podem ser incorporados em outro lugar, o widget se torna o produto. Ele precisa de tamanhos fixos, sua própria política de quadro e traduções — o mesmo cuidado que a página principal, não um resquício dela.

Se você tem dados ao vivo e precisa de um mapa, uma API e um widget que outros sites possam usar, este é o tipo de trabalho que fazemos de ponta a ponta.