sql >> データベース >  >> NoSQL >> Redis

ソートされたセットとuidを保存するための最良の方法をRedisします

    私の最初のポイントは、4GBは2000万のソートされたセットを格納するのに厳しいことに注意することです。簡単に試してみると、20Mのユーザーがそれぞれ20タグを使用すると、64ビットボックスで約8 GBが必要になります(Redis 2.4で提供されるソートされたセットのziplistメモリの最適化を考慮しています-以前のバージョンではこれを試さないでください) 。

    並べ替えられたセットは、ユースケースをサポートするための理想的なデータ構造です。私はあなたが説明したとおりにそれらを使用します。

    ご指摘のとおり、KEYSを使用してキーを反復処理することはできません。むしろデバッグコマンドとして意図されています。キーの反復をサポートするには、このアクセスパスを提供するデータ構造を追加する必要があります。反復をサポートできるRedisの構造は、リストとソートされたセット(範囲メソッドによる)のみです。ただし、O(n)反復アルゴリズムをO(n ^ 2)(リストの場合)またはO(nlogn)(zsetの場合)に変換する傾向があります。リストは、キーが追加/削除されるときにリストを維持するのが難しいため、キーを保存するのにも適していません。

    より効率的な解決策は、通常のセットで構成されるインデックスを追加することです。ハッシュ関数を使用して特定のユーザーをバケットに関連付け、このバケットに対応するセットにユーザーIDを追加する必要があります。ユーザーIDが数値の場合、単純なモジュロ関数で十分です。そうでない場合は、単純な文字列ハッシュ関数でうまくいきます。

    したがって、user:1000、user:2000、およびuser:1001での反復をサポートするために、モジュロ1000関数を選択しましょう。 user:1000とuser:2000はバケットインデックス:0に配置され、user:1001はバケットインデックス:1に配置されます。

    したがって、zsetに加えて、次のキーがあります。

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

    セットでは、キーのプレフィックスは不要であり、Redisは、セットが十分に小さく保たれている場合、セットをシリアル化することでメモリ消費を最適化できます(Sripathi Krishnanによって提案された整数セットの最適化)。

    グローバル反復は、0から1000(除外)のバケットでの単純なループで構成されます。バケットごとに、SMEMBERSコマンドを適用して対応するセットを取得し、クライアントは個々のアイテムを反復処理できます。

    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()
    

    定数を微調整することで、このプログラムを使用して、このデータ構造のグローバルメモリ消費量を評価することもできます。

    IMOこの戦略は、ユーザーを追加/削除するためのO(1)の複雑さと、すべてのアイテムを反復するための真のO(n)の複雑さを提供するため、シンプルで効率的です。唯一の欠点は、キーの反復順序がランダムであることです。




    1. @を含むMongoDBパスワード

    2. redisでキーを操作する

    3. redis cluster reshard [ERR] MIGRATEの呼び出し:ERR構文エラー

    4. オプションuseFindAndModifyはサポートされていません