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

redisでパイプラインを使用すると、なぜ100,000レコードと非常に遅いのですか?

    このようなベンチマーク(特にJVMを使用したベンチマーク)を作成する前に考慮する必要のあるポイントがいくつかあります。

    • ほとんどの(物理)マシンでは、パイプラインを使用すると、Redisは100K ops/s以上を処理できます。ベンチマークは100Kアイテムのみを扱っているため、意味のある結果を生成するのに十分な長さではありません。さらに、JITの次の段階が始まる時間はありません。

    • 絶対時間はあまり関連性のない指標です。ベンチマークを少なくとも10秒間実行し続けながら、スループット(つまり、1秒あたりの操作数)を表示する方が、より適切で安定した指標になります。

    • 内側のループは大量のゴミを生成します。 Jedis + Redisのベンチマークを計画している場合は、独自のプログラムのオーバーヘッドを低く抑える必要があります。

    • すべてをmain関数に定義したため、ループはJITによってコンパイルされません(使用するJVMによって異なります)。内部メソッド呼び出しのみが可能です。 JITを効率的にしたい場合は、JITでコンパイルできるメソッドにコードをカプセル化してください。

    • オプションで、実際の測定を実行する前にウォームアップフェーズを追加して、ベアボーンインタープリターで最初の反復を実行するオーバーヘッドとJIT自体のコストを考慮しないようにすることができます。

    さて、Redisパイプラインに関しては、パイプラインが長すぎます。パイプラインの100Kコマンドは、JedisがRedisに何かを送信する前に6MBのバッファーを構築する必要があることを意味します。これは、ソケットバッファー(クライアント側、場合によってはサーバー側)が飽和状態になり、Redisが6MBの通信バッファーも処理する必要があることを意味します。

    さらに、ベンチマークはまだ同期しています(パイプラインを使用しても、魔法のように非同期になることはありません)。つまり、パイプラインの最後のクエリがRedisに送信されるまで、Jedisは返信の読み取りを開始しません。パイプラインが長すぎると、物事をブロックする可能性があります。

    パイプラインのサイズを100〜1000回の操作に制限することを検討してください。もちろん、より多くのラウンドトリップが生成されますが、通信スタックへの圧力は許容レベルまで減少します。たとえば、次のプログラムについて考えてみます。

    import redis.clients.jedis.*;
    import java.util.*;
    
    public class TestPipeline {
    
        /**
         * @param args
         */
    
        int i = 0; 
        Map<String, String> map = new HashMap<String, String>();
        ShardedJedis jedis;  
    
        // Number of iterations
        // Use 1000 to test with the pipeline, 100 otherwise
        static final int N = 1000;
    
        public TestPipeline() {
          JedisShardInfo si = new JedisShardInfo("127.0.0.1", 6379);
          List<JedisShardInfo> list = new ArrayList<JedisShardInfo>();
          list.add(si);
          jedis = new ShardedJedis(list);
        } 
    
        public void push( int n ) {
         ShardedJedisPipeline pipeline = jedis.pipelined();
         for ( int k = 0; k < n; k++) {
          map.put("id", "" + i);
          map.put("name", "lyj" + i);
          pipeline.hmset("m" + i, map);
          ++i;
         }
         pipeline.sync(); 
        }
    
        public void push2( int n ) {
         for ( int k = 0; k < n; k++) {
          map.put("id", "" + i);
          map.put("name", "lyj" + i);
          jedis.hmset("m" + i, map);
          ++i;
         }
        }
    
        public static void main(String[] args) {
          TestPipeline obj = new TestPipeline();
          long startTime = System.currentTimeMillis();
          for ( int j=0; j<N; j++ ) {
           // Use push2 instead to test without pipeline
           obj.push(1000); 
           // Uncomment to see the acceleration
           //System.out.println(obj.i);
         }
         long endTime = System.currentTimeMillis();
         double d = 1000.0 * obj.i;
         d /= (double)(endTime - startTime);
         System.out.println("Throughput: "+d);
       }
     }
    

    このプログラムを使用すると、パイプラインの有無にかかわらずテストできます。パイプラインを使用する場合は、少なくとも10秒間実行されるように、反復回数(Nパラメーター)を必ず増やしてください。ループ内のprintlnのコメントを外すと、プログラムが最初は遅く、JITが最適化を開始するにつれて速くなることがわかります(そのため、プログラムは少なくとも数秒実行して意味のある結果を得る必要があります)。

    私のハードウェア(古いAthlonボックス)では、パイプラインを使用すると8〜9倍のスループットを得ることができます。内側のループでキー/値のフォーマットを最適化し、ウォームアップフェーズを追加することで、プログラムをさらに改善できます。




    1. MongoDBスキーマ設計:常にスキーマがあります

    2. MongoDBを使用して配列から特定のアイテムを削除する

    3. コマンドラインからMongoDBデータベースを削除するにはどうすればよいですか?

    4. Mongo C#JSONリーダーは値を期待していましたが、「replSetGetStatus」が見つかりました