Qualquer solução real precisa se encaixar nos requisitos, que estão meio ausentes na pergunta original. Minha primeira resposta assumiu um conjunto de dados pequeno, mas essa abordagem não é dimensionada à medida que a classificação densa é feita (por exemplo, via Lua) em O (N) pelo menos.
Portanto, supondo que haja muitos usuários com pontuações, a direção sugerida pelo for_stack é melhor, na qual várias estruturas de dados são combinadas. Creio que esta é a essência de sua última observação.
Para armazenar as pontuações dos usuários, você pode usar um Hash. Embora conceitualmente você possa usar uma única chave para armazenar um hash de todas as pontuações de usuários, na prática você deseja fazer um hash do hash para que ele seja dimensionado. Para manter este exemplo simples, vou ignorar o dimensionamento de hash.
É assim que você adicionaria (atualizaria) a pontuação de um usuário em Lua:
local hscores_key = KEYS[1]
local user = ARGV[1]
local increment = ARGV[2]
local new_score = redis.call('HINCRBY', hscores_key, user, increment)
Em seguida, queremos rastrear a contagem atual de usuários por valor de pontuação discreta, portanto, mantemos outro hash para isso:
local old_score = new_score - increment
local hcounts_key = KEYS[2]
local old_count = redis.call('HINCRBY', hcounts_key, old_score, -1)
local new_count = redis.call('HINCRBY', hcounts_key, new_score, 1)
Agora, a última coisa que precisamos manter é a classificação por pontuação, com um conjunto ordenado. Cada nova pontuação é adicionada como membro no zset e as pontuações que não têm mais usuários são removidas:
local zdranks_key = KEYS[3]
if new_count == 1 then
redis.call('ZADD', zdranks_key, new_score, new_score)
end
if old_count == 0 then
redis.call('ZREM', zdranks_key, old_score)
end
A complexidade desse script de 3 partes é O(logN) devido ao uso do Sorted Set, mas observe que N é o número de valores de pontuação discretos, não os usuários no sistema. Obter a classificação densa de um usuário é feito por meio de outro script mais curto e simples:
local hscores_key = KEYS[1]
local zdranks_key = KEYS[2]
local user = ARGV[1]
local score = redis.call('HGET', hscores_key, user)
return redis.call('ZRANK', zdranks_key, score)