Como manter contadores por utilizador em Real-Time Analytics
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.