(+351) 21 24 10006  ·  info@bconcepts.pt
Carnaxide, Lisboa

Como manter contadores por utilizador em Real-Time Analytics

João Barros 29 de August de 2026 4 min de leitura

Este tutorial mostra como manter contadores por utilizador em Real-Time Analytics, útil para métricas como eventos por sessão, limites por minuto ou quotas em tempo real. Vai aprender a actualizar contadores incrementais, tratar duplicados simples e expirar contadores antigos para controlar estado.

Pré-requisitos

  • Conta e acesso a um serviço de Real-Time Analytics ou streaming (por exemplo, Azure Stream Analytics, Kusto/ADX ou outro com suporte a stateful processing).
  • Fonte de eventos com campos: userId, eventId, eventTime.
  • Conhecimentos básicos de SQL/KQL ou linguagem de query do serviço usado.

Passo 1: Definir a granularidade e o estado que queres manter

Decide se o contador será por minuto, por hora, por sessão ou acumulado. Isto determina a chave do estado e as regras de expiração. Por exemplo: contador por userId por minuto. A chave será (userId, minuteBucket).

Passo 2: Normalizar eventos e calcular a bucket de tempo

Converte eventTime para um bucket (ex.: minuto) para agrupar eventos no mesmo intervalo. Assim evitam-se problemas de fusos e microsegundos.

// Exemplo KQL para transformar timestamp em bucket por minuto
Events
| extend minuteBucket = startofminute(eventTime)
| project userId, eventId, minuteBucket, eventTime

Passo 3: Deduplicação simples por eventId antes de actualizar o estado

Uma deduplicação básica por eventId evita contagens duplicadas quando eventos são re-entregues. Mantém um pequeno cache de eventId por bucket (ou usa uma função dedupe do serviço).

// Exemplo conceptual: manter apenas o primeiro eventId por user/minute
Events
| extend minuteBucket = startofminute(eventTime)
| summarize firstEventTime = min(eventTime) by userId, minuteBucket, eventId
| summarize eventsCount = count() by userId, minuteBucket

Passo 4: Actualizar o estado com operação atómica (incremental)

Usa uma operação atómica no store de estado (Redis, Cosmos DB, Azure Table, state store do motor) para somar o número de eventos por chave. Isto evita condições de corrida quando múltiplos workers processam o mesmo userId.

// Pseudocódigo conceptual para cada (userId, minuteBucket, delta)
stateKey = userId + ':' + minuteBucket
current = stateStore.get(stateKey) // pode ser 0 se não existir
newValue = current + delta
stateStore.set(stateKey, newValue, ttl=120s) // define TTL para expirar

Passo 5: Gerir expiração e limpeza de estado

Define TTL (time-to-live) adequado para os buckets: por exemplo, manter contadores por 2-3 janelas extras para permitir reprocessamento. A expiração automática liberta memória e permite evitar contadores permanentes para utilizadores inactivos.

// Exemplo de TTL ao escrever em Redis (com comandos Redis simples)
// stateKey = "user:123:2026-08-29T12:34"
INCRBY stateKey 5
EXPIRE stateKey 180  // 180 segundos de TTL

Passo 6: Combinar contadores para relatórios (exemplo por hora)

Para obter métricas por hora combina os minuteBuckets dentro da hora e soma os valores do state store. Podes fazer isto em batch periódica ou numa query que agregue os keys relevantes.

// Pseudocódigo para agregar 60 buckets de um user numa hora
hourBuckets = keysMatching("user:123:2026-08-29T12:*")
sum = 0
for k in hourBuckets:
    sum += get(k)
return sum

Verificar o resultado

Valida o comportamento com estes testes: 1) Envia 10 eventos únicos para o mesmo user no mesmo minuto e confirma que o contador sobe para 10. 2) Reenvia um subset dos mesmos eventId e confirma que o contador não duplica. 3) Aguarda até o TTL expirar e verifica que o estado é removido. Usa logs e consultas ao state store para confirmar os valores por key.

Conclusão

Mantendo contadores por userId com buckets de tempo, deduplicação por eventId, operações atómicas e TTL controlado, consegues métricas em tempo real estáveis e eficientes. Próximos passos: implementar sliding windows, lidar com reordenações de eventos e usar um mecanismo de exactly-once quando necessário. Dica: começa com TTL generoso e reduz à medida que testes comprovem comportamento.