← Todas as publicações

Projetos e Engenharia de Software

RDX LiveScore: placar em tempo real para contests de radioamador

Como o RDX LiveScore transforma os pacotes UDP dos programas de log em um ranking ao vivo no navegador, e as decisões de arquitetura por trás dele.

Para que serve

O RDX LiveScore mostra, em tempo real, o placar de uma competição de radioamador enquanto ela acontece. Foi feito para o Rio DX Group (RDX), clube brasileiro de radioamadorismo.

Num contest, estações de vários lugares tentam fazer o maior número de contatos de rádio (QSOs) dentro de um período. Cada operador registra os contatos num programa de log, como o N1MM Logger+, e vê a própria pontuação. O que falta é uma visão do grupo: quem está na frente agora, em que banda e com quantos QSOs.

O projeto preenche essa lacuna. Ele recebe os pacotes UDP que os programas de log já sabem enviar, processa cada um e publica um ranking ao vivo no navegador. O operador não digita nada a mais: continua logando como sempre.

O uso real mais recente foi o Sprint Rio DX Group 2026, de 17 a 26 de outubro. Em 27 de outubro, o placar do evento tinha 34 estações, entre 41 inscrições.

O placar na tela

O placar aparece em dois lugares: no site, aberto ao público, e no cliente instalado na estação.

Placar público do CQ World Wide SSB no site, com totais do evento e ranking por categoria
Placar público do CQ WW SSB no site. Ver em tamanho original ↗

No site, qualquer pessoa acompanha o evento sem login. A imagem mostra o placar final real do CQ WW SSB 2026, capturado em 2 de novembro de 2026.

Aba Ranking do cliente Windows, com a posição da estação e as estações próximas
Aba Ranking do cliente Windows. Ver em tamanho original ↗

No cliente, o operador vê a própria posição e as estações mais próximas, sem sair do log. Esta imagem usa dados de exemplo, gerados pela ferramenta de testes do cliente.

Como funciona

Cada pacote percorre o mesmo caminho: chega ao servidor, entra numa fila, é processado e gravado, e só então aparece no placar.

Todo pacote passa pela fila antes de virar placarPrograma de logN1MM+, DXLog, WSJT-X,JTDX, Log4OM ou HRD;envia cada QSO por UDPCliente Windowsgrava em SQLite, envia porHTTPS e espera a confirmaçãoRelay UDP ou envio diretochega na porta UDP 12060,sem confirmação de entregaRecepção (FastAPI)só publica o pacote na fila,sem interpretar o conteúdoFila (RabbitMQ)segura o pacote até oworker poder processarWorker (Python)interpreta, escolhe o eventoe atualiza score e QSOsBanco (MySQL)guarda o XML bruto e tudoo que é derivado deleWebSocket (FastAPI)agrupa em 200 ms e enviasó o que mudouNavegadormostra o placar ao vivo,por evento e categoriaaviso de mudança (RabbitMQ)lê o ranking
Caminho de um pacote, do programa de log ao navegador.

O worker escolhe o evento pelo status, pela janela de tempo e pelo nome do contest informado no pacote. Sem evento compatível, o pacote fica guardado sem vínculo, e o tráfego fora de hora não contamina o ranking.

Como o bruto fica no banco, os dados derivados podem ser reconstruídos depois. Esse reprocessamento é uma ação do administrador e volta a aparecer nas decisões abaixo.

Só chega ao placar o pacote novo, válido e aceito pelo eventoWorker recebe o pacote da filaCópia reenviadapelo cliente?Descarta, porque já foi aplicadaGrava o XML brutortc e rádio: só o mais recenteXML válido?Fica no banco com o erro de parseEvento aceitao pacote?Fica guardado sem vínculosem evento na janela ou estação sem inscrição;ainda pode contar no DX MarathonAplica a regra do tipo de pacoteScorevale o do logger, salvo emevento pontuado por QSOsContatograva o QSO; evento porQSOs recalcula em loteRádioatualiza o status daestação no eventosimnãonãosimnãosim
Decisões do worker para cada pacote.

Se o processamento falhar de forma inesperada, a mensagem vai para uma fila de mensagens mortas, e a fila principal continua andando.

Tecnologia utilizada

O servidor é Python assíncrono, a interface é React e o cliente da estação é .NET. Tudo no servidor roda em Docker Compose.

Parte Tecnologia O que faz
API e recepção UDP Python, FastAPI, uvicorn, asyncio Escuta a porta UDP 12060 e serve REST e WebSocket
Worker Python, aio-pika Consume a fila e aplica as regras de cada pacote
Fila RabbitMQ 4 Separa a recepção do processamento
Banco MySQL 8.4, asyncmy Guarda pacotes brutos e dados derivados; 26 migrations até aqui
Interface web React, TypeScript, Vite Placar, inscrições, área pessoal e administração
Cliente da estação C#, .NET 9, Avalonia 11, SQLite Fica na bandeja do Windows e entrega os pacotes por HTTPS
Testes pytest, dotnet test Cobrem o servidor e o cliente; o cliente também gera imagens da tela sem abrir janela

O cliente aceita pacotes do N1MM Logger+, DXLog, WSJT-X, JTDX, Log4OM e Ham Radio Deluxe, todos na mesma porta. O MSHV está documentado, mas ainda não foi testado com pacotes reais.

Decisões tomadas

A decisão que sustenta as outras é tratar o pacote bruto como fonte de verdade: o placar sempre pode ser refeito a partir dele.

Guardar o pacote bruto, mesmo o inválido

O XML recebido vai para o banco mesmo quando o parse falha ou nenhum evento o reivindica. A exceção são os pacotes de alta frequência, como rtc e radioinfo, dos quais fica só o mais recente. Assim, corrigir um erro de regra não perde dados: basta reprocessar o evento. Foi o que aconteceu quando descobrimos que a categoria vinha aninhada no pacote rtc do N1MM: 8 estações do CQ WW SSB estavam sem categoria, e o reprocessamento resolveu. O custo é um banco que cresce a cada pacote.

Colocar uma fila entre a recepção e o processamento

O servidor UDP só publica o pacote no RabbitMQ; quem interpreta é um worker separado. Um pacote malformado ou um pico de tráfego não derruba a recepção. O custo são dois serviços a mais para operar: o broker e o worker.

Escrever um parser que não confia no formato

Cada programa de log fala um dialeto, e só os pacotes reais mostraram isso:

  • O Log4OM manda XML no formato do N1MM, mas sem <ID>.
  • O Ham Radio Deluxe pode mandar <mycall> vazio, com a estação em <operator>.
  • A frequência vem em Hz no HRD e em dezenas de Hz no N1MM.
  • O WSJT-X manda ADIF em texto sem cabeçalho, sem dizer de qual programa veio.

Por isso, o QSO sem id do logger recebe uma chave própria, feita da estação, do indicativo trabalhado e do horário. O programa ficou de fora da chave depois que o WSJT-X, enviando em dois formatos, duplicou cada QSO. O custo é que essa regra existe no servidor e no cliente, e as duas precisam mudar juntas.

Deixar o tipo de evento decidir de onde vem o score

Num contest, vale o score calculado pelo logger, que conhece as regras daquela competição. No DX Marathon, o servidor ignora esse score e apura países e zonas a partir dos QSOs. Isso permite que um mesmo QSO do CQ WW conte em três placares: o do contest, o do Sprint e o do Marathon anual. O custo apareceu em números diferentes: uma estação mostrava 1178 QSOs num placar e 1153 no outro. O motivo real foi que o envio começou 23 minutos depois do início, e 31 QSOs nunca chegaram como contato. Isso deu origem à importação de logs Cabrillo e ADIF.

Trocar o relay UDP por um cliente com entrega garantida

Os primeiros relays em Python e C# só repassavam o UDP. O cliente Windows, criado em outubro, grava cada pacote em SQLite antes de enviar e só o dá por entregue quando o servidor confirma. Se a internet cair, os pacotes esperam e seguem na ordem; o servidor ignora os repetidos pelo id. O custo é manter um instalador e um ciclo de versões, hoje na 1.4.0, só para Windows 10 e 11. Para conter esse custo, os formulários ficam no site, e o cliente apenas avisa e abre o link.

O pacote só sai da fila local quando o servidor confirmaPrograma de logmanda o QSOpor UDPDisco localgrava em SQLitecomo pendenteEnviolote de até 50,por HTTPSServidorrespondeu?Espera e tenta de novointervalo dobra, até 60 sMarca pacote a pacoteaceito: enviadorecusado: guarda o motivonão citado: segue pendentenãosimpróximo lote
Entrega de pacotes no cliente Windows.

A fila indisponível no servidor também conta como falha: o lote continua pendente no computador da estação.

Recalcular em lote, não a cada QSO

O score do Marathon depende de todos os contatos da estação: cerca de 300 ms para 9 mil QSOs. Quando alguém reenviava o log, a fila ficava parada por minutos. Agora o worker marca a estação e a recalcula uma vez a cada poucos segundos. O custo é um placar do Marathon alguns segundos atrasado, o que não muda o resultado.

Enviar só o que mudou para o navegador

Ao conectar, o navegador recebe o ranking inteiro pelo WebSocket; depois, só as diferenças. As atualizações de um evento são agrupadas em janelas de 200 ms, para que uma rajada de pacotes não vire uma rajada de consultas. O custo é o servidor guardar o último estado enviado.

Usar WebSocket, mesmo com tráfego de mão única

O placar usa WebSocket, embora os dados só desçam do servidor para o navegador. O único recado do navegador é um __ping__ a cada 20 segundos, que o servidor lê e descarta. Por isso, SSE também atenderia.

Critério WebSocket (em uso) SSE (alternativa)
Direção Duas vias, com só uma usada para dados Uma via, o que o placar precisa
Reconexão Código próprio, com espera de 1 a 10 s Nativa no EventSource
Ao reconectar Recebe um novo ranking completo Igual; o Last-Event-ID não faria falta
Conexão parada Ping do navegador a cada 20 s Exigiria um heartbeat do servidor
nginx Precisa de Upgrade e timeout longo Precisaria de buffering desligado e timeout longo
Autenticação Cookie httpOnly Cookie httpOnly

Escolhi WebSocket porque já conhecia a tecnologia e o FastAPI tem suporte direto. Ele entrou em 25 de maio de 2026, junto com a fila. Hoje, migrar eliminaria o código de reconexão e o ping, mas não mudaria o que o usuário vê. O custo de ter ficado com WebSocket é esse pouco código a mais.

Foi uma escolha por familiaridade, não por necessidade técnica.

Ligar recursos por evento, desligados por padrão

Inscrição, importação de log e “aceitar apenas inscritos” são opções de cada evento. Um recurso novo não muda um evento em andamento. O custo é lembrar de ligar a opção e, em alguns casos, reprocessar o evento depois.

Uso em produção

Os dois eventos de outubro mostram um uso muito concentrado: poucas estações muito ativas e uma longa cauda de participações menores.

Cinco estações fizeram 60% dos QSOs nos dois eventosQSOs por estação, em ordem de posição no placar; em destaque, as cinco primeirasCQ WW SSB: 38 estações, 35.223 QSOsCQ WW SSB, 1ª posição: 6018 QSOsCQ WW SSB, 2ª posição: 5549 QSOsCQ WW SSB, 3ª posição: 4450 QSOsCQ WW SSB, 4ª posição: 2740 QSOsCQ WW SSB, 5ª posição: 2370 QSOsCQ WW SSB, 6ª posição: 2125 QSOsCQ WW SSB, 7ª posição: 1984 QSOsCQ WW SSB, 8ª posição: 1400 QSOsCQ WW SSB, 9ª posição: 1200 QSOsCQ WW SSB, 10ª posição: 1178 QSOsCQ WW SSB, 11ª posição: 1070 QSOsCQ WW SSB, 12ª posição: 565 QSOsCQ WW SSB, 13ª posição: 463 QSOsCQ WW SSB, 14ª posição: 444 QSOsCQ WW SSB, 15ª posição: 419 QSOsCQ WW SSB, 16ª posição: 418 QSOsCQ WW SSB, 17ª posição: 388 QSOsCQ WW SSB, 18ª posição: 353 QSOsCQ WW SSB, 19ª posição: 333 QSOsCQ WW SSB, 20ª posição: 299 QSOsCQ WW SSB, 21ª posição: 184 QSOsCQ WW SSB, 22ª posição: 180 QSOsCQ WW SSB, 23ª posição: 159 QSOsCQ WW SSB, 24ª posição: 147 QSOsCQ WW SSB, 25ª posição: 129 QSOsCQ WW SSB, 26ª posição: 127 QSOsCQ WW SSB, 27ª posição: 117 QSOsCQ WW SSB, 28ª posição: 112 QSOsCQ WW SSB, 29ª posição: 85 QSOsCQ WW SSB, 30ª posição: 78 QSOsCQ WW SSB, 31ª posição: 62 QSOsCQ WW SSB, 32ª posição: 56 QSOsCQ WW SSB, 33ª posição: 20 QSOsCQ WW SSB, 34ª posição: 1 QSOsCQ WW SSB, 35ª posição: 0 QSOsCQ WW SSB, 36ª posição: 0 QSOsCQ WW SSB, 37ª posição: 0 QSOsCQ WW SSB, 38ª posição: 0 QSOs6.018As cinco primeiras somam 60% dos QSOsmediana: 316 QSOsSprint Rio DX Group: 34 estações, 20.273 QSOsSprint Rio DX Group, 1ª posição: 4571 QSOsSprint Rio DX Group, 2ª posição: 2759 QSOsSprint Rio DX Group, 3ª posição: 2144 QSOsSprint Rio DX Group, 4ª posição: 1417 QSOsSprint Rio DX Group, 5ª posição: 1215 QSOsSprint Rio DX Group, 6ª posição: 1153 QSOsSprint Rio DX Group, 7ª posição: 1074 QSOsSprint Rio DX Group, 8ª posição: 759 QSOsSprint Rio DX Group, 9ª posição: 571 QSOsSprint Rio DX Group, 10ª posição: 565 QSOsSprint Rio DX Group, 11ª posição: 471 QSOsSprint Rio DX Group, 12ª posição: 445 QSOsSprint Rio DX Group, 13ª posição: 302 QSOsSprint Rio DX Group, 14ª posição: 302 QSOsSprint Rio DX Group, 15ª posição: 232 QSOsSprint Rio DX Group, 16ª posição: 218 QSOsSprint Rio DX Group, 17ª posição: 195 QSOsSprint Rio DX Group, 18ª posição: 191 QSOsSprint Rio DX Group, 19ª posição: 186 QSOsSprint Rio DX Group, 20ª posição: 179 QSOsSprint Rio DX Group, 21ª posição: 165 QSOsSprint Rio DX Group, 22ª posição: 142 QSOsSprint Rio DX Group, 23ª posição: 129 QSOsSprint Rio DX Group, 24ª posição: 111 QSOsSprint Rio DX Group, 25ª posição: 106 QSOsSprint Rio DX Group, 26ª posição: 105 QSOsSprint Rio DX Group, 27ª posição: 105 QSOsSprint Rio DX Group, 28ª posição: 102 QSOsSprint Rio DX Group, 29ª posição: 94 QSOsSprint Rio DX Group, 30ª posição: 78 QSOsSprint Rio DX Group, 31ª posição: 75 QSOsSprint Rio DX Group, 32ª posição: 67 QSOsSprint Rio DX Group, 33ª posição: 44 QSOsSprint Rio DX Group, 34ª posição: 1 QSOs4.571As cinco primeiras somam 59,7% dos QSOsmediana: 193 QSOs
Fonte: API pública de ranking do RDX LiveScore, consultada em 2 de novembro de 2026.

Os totais não podem ser somados. Um QSO do CQ WW também conta no Sprint, e as duas contagens vêm de fontes diferentes: o score do logger no CQ WW e os contatos recebidos no Sprint.

O uso vem em ondas, com os maiores picos nos fins de semana de contest.

Dois dias do CQ WW concentram 43% dos pacotes1.484.219 pacotes gravados por dia, de 15/05 a 02/11 de 202615/05: 28.271 pacotes16/05: 13.601 pacotes17/05: 21.976 pacotes18/05: 20 pacotes19/05: 31 pacotes20/05: 335 pacotes21/05: 4 pacotes22/05: 45 pacotes23/05: 6.072 pacotes24/05: 9.839 pacotes25/05: 67 pacotes26/05: 5.553 pacotes27/05: 4.909 pacotes28/05: 8.728 pacotes29/05: 10.700 pacotes30/05: 37.349 pacotes31/05: 40.087 pacotes01/06: 3.528 pacotes02/06: 1.070 pacotes04/06: 49 pacotes05/06: 2.623 pacotes06/06: 19.421 pacotes07/06: 47.254 pacotes08/06: 12.375 pacotes09/06: 8.717 pacotes10/06: 8.835 pacotes11/06: 9.346 pacotes12/06: 8.652 pacotes13/06: 16.162 pacotes14/06: 16.176 pacotes15/06: 8.957 pacotes16/06: 2.733 pacotes22/06: 2.450 pacotes23/06: 8.645 pacotes24/06: 8.534 pacotes25/06: 8.596 pacotes26/06: 10.411 pacotes27/06: 28.022 pacotes28/06: 26.175 pacotes29/06: 1.967 pacotes30/06: 1.536 pacotes01/07: 4.214 pacotes02/07: 9.421 pacotes03/07: 8.619 pacotes04/07: 10.959 pacotes05/07: 7.377 pacotes06/07: 157 pacotes08/07: 5 pacotes09/07: 91 pacotes10/07: 197 pacotes11/07: 18.871 pacotes12/07: 12.885 pacotes14/07: 64 pacotes16/07: 112 pacotes17/07: 266 pacotes18/07: 11.152 pacotes19/07: 6.369 pacotes20/07: 5 pacotes21/07: 75 pacotes22/07: 615 pacotes23/07: 378 pacotes24/07: 216 pacotes25/07: 3.511 pacotes26/07: 1.888 pacotes29/07: 9 pacotes30/07: 67 pacotes01/08: 2.651 pacotes02/08: 706 pacotes03/08: 300 pacotes04/08: 66 pacotes05/08: 1 pacotes06/08: 79 pacotes07/08: 1 pacotes08/08: 4.169 pacotes09/08: 370 pacotes10/08: 6 pacotes11/08: 36 pacotes13/08: 25 pacotes14/08: 171 pacotes15/08: 3.482 pacotes16/08: 5.440 pacotes19/08: 5 pacotes20/08: 55 pacotes21/08: 101 pacotes22/08: 8.233 pacotes23/08: 16.557 pacotes24/08: 191 pacotes25/08: 128 pacotes27/08: 91 pacotes28/08: 465 pacotes29/08: 4.032 pacotes30/08: 15 pacotes31/08: 38 pacotes01/09: 62 pacotes03/09: 1.085 pacotes04/09: 1.067 pacotes05/09: 4.541 pacotes06/09: 3.873 pacotes08/09: 13 pacotes12/09: 2.098 pacotes13/09: 1.354 pacotes24/09: 238 pacotes25/09: 530 pacotes26/09: 10.109 pacotes27/09: 4.196 pacotes28/09: 41 pacotes29/09: 984 pacotes30/09: 2.068 pacotes01/10: 2.027 pacotes02/10: 687 pacotes03/10: 160 pacotes04/10: 3.588 pacotes10/10: 252 pacotes11/10: 303 pacotes12/10: 38 pacotes13/10: 87 pacotes14/10: 50 pacotes15/10: 23.293 pacotes16/10: 1.126 pacotes17/10: 13.368 pacotes18/10: 41.847 pacotes19/10: 19.498 pacotes20/10: 23.751 pacotes21/10: 16.726 pacotes22/10: 9.235 pacotes23/10: 29.904 pacotes24/10: 353.199 pacotes25/10: 290.445 pacotes26/10: 10.856 pacotes27/10: 8.874 pacotes28/10: 1.469 pacotes29/10: 328 pacotes30/10: 4.674 pacotes31/10: 142 pacotes01/11: 1.091 pacotes02/11: 184 pacotes24/10: 353.19925/10: 290.44507/06: 47.254junjulagosetoutnov
Fonte: tabela n1mm_packets da produção, consultada em 2 de novembro de 2026.

No mesmo período, passaram pelo sistema 96 indicativos e 10 eventos, com 71 usuários cadastrados. O dia mais movimentado teve 39 indicativos enviando dados. Só 0,04% dos pacotes deram erro de parse, e 2% ficaram sem evento.

No CQ WW, o pico foi de 22.816 pacotes em uma horaPacotes gravados por hora (UTC), com a janela do contest em destaqueJanela do contest: 48 horas23/10 12h: 614 pacotes23/10 13h: 502 pacotes23/10 14h: 1.330 pacotes23/10 15h: 1.359 pacotes23/10 16h: 1.089 pacotes23/10 17h: 823 pacotes23/10 18h: 999 pacotes23/10 19h: 1.929 pacotes23/10 20h: 2.263 pacotes23/10 21h: 2.081 pacotes23/10 22h: 2.997 pacotes23/10 23h: 5.002 pacotes24/10 00h: 14.816 pacotes24/10 01h: 18.812 pacotes24/10 02h: 16.339 pacotes24/10 03h: 12.929 pacotes24/10 04h: 11.427 pacotes24/10 05h: 11.547 pacotes24/10 06h: 11.610 pacotes24/10 07h: 9.252 pacotes24/10 08h: 8.028 pacotes24/10 09h: 7.657 pacotes24/10 10h: 10.664 pacotes24/10 11h: 12.020 pacotes24/10 12h: 13.911 pacotes24/10 13h: 15.797 pacotes24/10 14h: 18.564 pacotes24/10 15h: 22.816 pacotes24/10 16h: 20.851 pacotes24/10 17h: 19.509 pacotes24/10 18h: 17.132 pacotes24/10 19h: 18.453 pacotes24/10 20h: 17.994 pacotes24/10 21h: 16.968 pacotes24/10 22h: 15.918 pacotes24/10 23h: 10.185 pacotes25/10 00h: 7.738 pacotes25/10 01h: 7.085 pacotes25/10 02h: 6.217 pacotes25/10 03h: 6.001 pacotes25/10 04h: 6.241 pacotes25/10 05h: 6.058 pacotes25/10 06h: 6.110 pacotes25/10 07h: 6.869 pacotes25/10 08h: 6.259 pacotes25/10 09h: 6.713 pacotes25/10 10h: 8.276 pacotes25/10 11h: 12.081 pacotes25/10 12h: 14.789 pacotes25/10 13h: 16.699 pacotes25/10 14h: 20.595 pacotes25/10 15h: 22.193 pacotes25/10 16h: 18.803 pacotes25/10 17h: 18.411 pacotes25/10 18h: 14.805 pacotes25/10 19h: 15.733 pacotes25/10 20h: 14.248 pacotes25/10 21h: 15.162 pacotes25/10 22h: 18.330 pacotes25/10 23h: 15.029 pacotes26/10 00h: 3.664 pacotes26/10 01h: 675 pacotes26/10 02h: 539 pacotes26/10 03h: 89 pacotes26/10 04h: 40 pacotes26/10 05h: 33 pacotes26/10 06h: 43 pacotes26/10 07h: 61 pacotes26/10 08h: 85 pacotes26/10 09h: 81 pacotes26/10 10h: 78 pacotes26/10 11h: 75 pacotes26/10 12h: 210 pacotes26/10 13h: 1.465 pacotes26/10 14h: 825 pacotes26/10 15h: 476 pacotes26/10 16h: 543 pacotes26/10 17h: 498 pacotes26/10 18h: 347 pacotes26/10 19h: 44 pacotes26/10 20h: 28 pacotes26/10 21h: 82 pacotes26/10 22h: 415 pacotes26/10 23h: 460 pacotes22.81624/10 00h25/10 00h26/10 00h
Fonte: tabela n1mm_packets da produção, 23 a 26 de outubro de 2026.

Na média, essa hora de pico equivale a cerca de 6 pacotes por segundo. O maior minuto teve 1.157 pacotes, e o maior segundo, 88.

A maior parte do tráfego é score, não contato:

Tipo de pacote Pacotes Parcela
Score (dynamicresults, rtc) 965.635 65,1%
Spots de DX (spot) 388.082 26,1%
Contatos (contactinfo, contactreplace, contactdelete) 104.816 7,1%
Rádio (radioinfo) 17.409 1,2%
Outros 8.277 0,6%

As contagens de rtc e radioinfo ficam abaixo do tráfego real, porque hoje só o pacote mais recente desses tipos fica no banco.

Testes de stress

A fila absorveu rajadas muito acima da capacidade do worker, e o WebSocket entregou todas as atualizações até 8.000 conexões. O teste rodou numa cópia local da stack, não na produção, então os números valem como ordem de grandeza.

A máquina tinha 16 threads e 31 GB, com um worker de concorrência 4, o padrão de produção. O gerador de carga rodou no mesmo computador, sem passar pela internet e sem nginx.

O worker sustenta cerca de 390 pacotes/s; a perda surge acima de 1.000/sProcessados por segundoPacotes perdidos (%)Segundos até terminar901003525003881.0003542.0003925.0000100050001.0003,812.0004,345.0001110014500261.000542.0001225.000Eixo horizontal: pacotes enviados por segundo, durante 10 s
Teste de carga local, 2 de novembro de 2026, com pacotes de contato por UDP.

Nenhum pacote recebido se perdeu depois da fila: sem erros no worker e com a fila de mensagens mortas vazia. As perdas aconteceram antes de o pacote entrar na fila, e a causa exata não foi investigada. Acima de 390 pacotes por segundo, o placar apenas atrasa enquanto a fila esvazia. O maior segundo real, com 88 pacotes, fica bem abaixo disso, mas a produção é menor que a máquina do teste: 6 núcleos e 11 GB. O cliente Windows não entrou neste teste; pelo desenho, ele reenviaria o que não fosse confirmado.

Com 8.000 conexões, a atualização chega em 371 ms na medianaTempo do envio do pacote até o navegador simulado, em milissegundos0200400600200 ms: espera proposital do agrupamentoLimite padrão de 1.024 arquivos abertos:sem ajuste, o teste parou em 1.002 conexões100 conexões: p95 de 252,1 ms500 conexões: p95 de 268,2 ms1.000 conexões: p95 de 298,4 ms2.000 conexões: p95 de 344,7 ms4.000 conexões: p95 de 343 ms8.000 conexões: p95 de 517,7 ms100 conexões: mediana de 241,2 ms500 conexões: mediana de 248,1 ms1.000 conexões: mediana de 251,2 ms2.000 conexões: mediana de 265,5 ms4.000 conexões: mediana de 292,9 ms8.000 conexões: mediana de 371,3 msp95: 518 msmediana: 371 ms1.0002.0004.0008.000Conexões WebSocket simultâneas
Teste de carga local, 2 de novembro de 2026, com 15 atualizações por rodada.

O teste revelou um limite: com o padrão do container, a API aceitou só 1.002 conexões, e as demais falharam. Com o nofile em 65.535, as 8.000 conectaram e receberam todas as atualizações, usando cerca de 2,1 GB de memória na API. O cenário tinha 50 estações e uma mudança de score a cada 2 segundos, então não mede muitas mudanças simultâneas. A produção também usava o limite de 1.024 e passou para 65.535 depois do teste. Agora, o limite vem do nginx do servidor, bem acima dos 71 usuários cadastrados.

Onde o projeto está

O sistema está em produção em cloud.riodxgroup.com. São mais de 200 commits desde 9 de maio de 2026, cerca de 140 deles em outubro, o mês do Sprint.

Três aprendizados ficaram desse período:

  • Pacote real vale mais que especificação. As diferenças de formato apareceram em testes com estações reais, durante os eventos.
  • Regra de competição muda rápido. Em 15 de outubro, o Marathon passou a contar 2 m e 70 cm. No dia seguinte, a contagem voltou a 160–6 m, para seguir o regulamento do Sprint. Com o histórico guardado, voltar atrás custa pouco.
  • Teste de carga acha o que o uso real esconde. O limite de 1.024 arquivos estava em produção e só foi percebido quando um teste o atingiu.

Continuam fora do escopo o Challenge por banda e a exportação ADIF. O suporte ao Windows 7 e 8.1 também ficou para depois, por falta de máquina de teste.