sql >> Base de Datos >  >> NoSQL >> Redis

Redis conjuntos ordenados y la mejor manera de almacenar uids

Mi primer punto sería tener en cuenta que 4 GB son escasos para almacenar 20 millones de conjuntos ordenados. Un intento rápido muestra que 20 millones de usuarios, cada uno de ellos con 20 etiquetas, ocuparía alrededor de 8 GB en una caja de 64 bits (y representa las optimizaciones de memoria ziplist del conjunto ordenado proporcionadas con Redis 2.4; ni siquiera intente esto con versiones anteriores) .

Los conjuntos ordenados son la estructura de datos ideal para respaldar su caso de uso. Los usaría exactamente como los describiste.

Como señaló, KEYS no se puede usar para iterar en las claves. Es más bien un comando de depuración. Para admitir la iteración de claves, debe agregar una estructura de datos para proporcionar esta ruta de acceso. Las únicas estructuras en Redis que pueden soportar la iteración son la lista y el conjunto ordenado (a través de los métodos de rango). Sin embargo, tienden a transformar los algoritmos de iteración O(n) en O(n^2) (para lista) u O(nlogn) (para zset). Una lista también es una mala opción para almacenar claves, ya que será difícil mantenerla a medida que se agregan o eliminan claves.

Una solución más eficiente es agregar un índice compuesto por conjuntos regulares. Debe usar una función hash para asociar un usuario específico a un depósito y agregar la identificación del usuario al conjunto correspondiente a este depósito. Si la identificación del usuario son valores numéricos, una función de módulo simple será suficiente. Si no lo son, una simple función de hashing de cadenas hará el truco.

Entonces, para admitir la iteración en usuario:1000, usuario:2000 y usuario:1001, elijamos una función de módulo 1000. usuario:1000 y usuario:2000 se colocarán en el índice de depósito:0, mientras que el usuario:1001 se colocará en el índice de depósito:1.

Entonces, además de los zsets, ahora tenemos las siguientes claves:

index:0 => set[ 1000, 2000 ]
index:1 => set[ 1001 ]

En los conjuntos, el prefijo de las claves no es necesario y permite a Redis optimizar el consumo de memoria serializando los conjuntos siempre que se mantengan lo suficientemente pequeños (optimización de conjuntos enteros propuesta por Sripathi Krishnan).

La iteración global consiste en un bucle simple en los cubos de 0 a 1000 (excluidos). Para cada depósito, se aplica el comando SMEMBERS para recuperar el conjunto correspondiente y, a continuación, el cliente puede iterar sobre los elementos individuales.

Aquí hay un ejemplo en Python:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
# ----------------------------------------------------

import redis, random

POOL = redis.ConnectionPool(host='localhost', port=6379, db=0)

NUSERS = 10000
NTAGS = 500
NBUCKETS = 1000

# ----------------------------------------------------
# Fill redis with some random data

def fill(r):
  p = r.pipeline()
  # Create only 10000 users for this example
  for id in range(0,NUSERS):
    user = "user:%d" % id
    # Add the user in the index: a simple modulo is used to hash the user id
    # and put it in the correct bucket
    p.sadd( "index:%d" % (id%NBUCKETS), id )
    # Add random tags to the user
    for x in range(0,20):
      tag = "tag:%d" % (random.randint(0,NTAGS))
      p.zincrby( user, tag, 1 )
    # Flush the pipeline every 1000 users
    if id % 1000 == 0:
      p.execute()
      print id
  # Flush one last time
  p.execute()

# ----------------------------------------------------
# Iterate on all the users and display their 5 highest ranked tags

def iterate(r):
  # Iterate on the buckets of the key index
  # The range depends on the function used to hash the user id
  for x in range(0,NBUCKETS):
    # Iterate on the users in this bucket
    for id in r.smembers( "index:%d"%(x) ):
      user = "user:%d" % int(id)
      print user,r.zrevrangebyscore(user,"+inf","-inf", 0, 5, True )

# ----------------------------------------------------
# Main function

def main():
  r = redis.Redis(connection_pool=POOL)
  r.flushall()
  m = r.info()["used_memory"]
  fill(r)
  info = r.info()
  print "Keys: ",info["db0"]["keys"]
  print "Memory: ",info["used_memory"]-m
  iterate(r)

# ----------------------------------------------------

main()

Al ajustar las constantes, también puede usar este programa para evaluar el consumo de memoria global de esta estructura de datos.

En mi opinión, esta estrategia es simple y eficiente, porque ofrece complejidad O(1) para agregar/eliminar usuarios, y verdadera complejidad O(n) para iterar en todos los elementos. El único inconveniente es que el orden de iteración de la clave es aleatorio.