クリーンなデータ構造としてのRedisストリーム

ストリームと呼ばれる新しいRedis 5データ構造は、コミュニティへの強い関心を呼び起こしました。 どういうわけか、制作でストリームを使用する人と話をして、それについて書きます。 しかし、ここで少し異なるトピックを検討します。 多くの人が、ストリームを非常に難しいタスクを解決するための一種の超現実的なツールと考えているように思えます。 実際、このデータ構造は*メッセージングも提供しますが、Redis Streamsの機能がこれによってのみ制限されると仮定するのは信じられないほど単純化されます。



ストリームは素晴らしいテンプレートと「メンタルモデル」であり、システム設計で大きな成功を収めることができますが、実際には、ほとんどのRedisデータ構造と同様に、ストリームはより一般的な構造であり、他のタスクに使用できます。 この記事では、ストリームを純粋なデータ構造として提示し、ブロッキング操作、受信者グループ、およびその他のすべてのメッセージング機能を完全に無視します。



ストリーム-これはステロイドのCSVです



多数の構造化データ要素を記録し、ここでデータベースが過剰になると思う場合は、単にファイルをappend only



モードで開き、各行をCSV(カンマ区切り値)として書き込むことができます。



 (open data.csv in append only) time=1553096724033,cpu_temp=23.4,load=2.3 time=1553096725029,cpu_temp=23.2,load=2.1
      
      





シンプルに見えます。 人々はずっと前にこれをやってきましたが、今でもやっています。何が何であるかを知っていれば、それは信頼できるテンプレートです。 しかし、メモリ内で同等のものは何でしょうか? メモリでは、より高度なデータ処理が可能になり、次のようなCSVファイルの多くの制限が自動的に削除されます。



  1. 範囲要求を満たすことは困難(非効率的)です。

  2. 冗長な情報が多すぎます。各レコードの時間はほぼ同じであり、フィールドが重複しています。 同時に、別のフィールドセットに切り替える場合、データを削除するとフォーマットの柔軟性が低下します。

  3. 要素オフセットは、ファイル内の単なるバイトオフセットです。ファイルの構造を変更すると、オフセットが不正確になるため、プライマリ識別子の概念はありません。 本質的に記録を明確に提示することはできません。

  4. ガベージを収集する機能がなく、ログを書き換えない場合、エントリを削除することはできず、無効としてマークするだけです。 通常、ログの書き換えはいくつかの理由で無駄になります。回避することをお勧めします。


同時に、このようなCSVログはそれ自体が優れています。固定された構造はなく、フィールドは変更でき、生成するのは簡単で、非常にコンパクトです。 Redisストリームのアイデアは、美徳を維持することでしたが、制限を克服することでした。 結果は、Redisソートセットに非常に似たハイブリッドデータ構造です。それらは*基本的なデータ構造のように見えますが、この効果を得るためにいくつかの内部表現を使用します。



スレッドの概要(すでに基本に精通している場合はスキップできます)



Redisストリームは、ベースツリーで接続されたデルタ圧縮マクロノードとして表されます。 その結果、ランダムなレコードの検索、範囲の取得、古い要素の削除などを非常に迅速に行うことができます。同時に、プログラマーのインターフェイスはCSVファイルに非常に似ています。



 > XADD mystream * cpu-temp 23.4 load 2.3 "1553097561402-0" > XADD mystream * cpu-temp 23.2 load 2.1 "1553097568315-0"
      
      





この例からわかるように、XADDコマンドはレコードの識別子を自動的に生成して返します。この識別子は単調に増加し、2つの部分で構成されます:<time>-<counter>。 ミリ秒単位の時間。同じ時間のレコードのカウンターが増分されます。



したがって、 append only



モードでのCSVファイルの概念の最初の新しい抽象化は、XADDのID引数としてアスタリスクを使用することです。これは、サーバーからレコード識別子を無料で取得する方法です。 この識別子は、ストリーム内の特定の要素を示すだけでなく、レコードがストリームに追加された時間にも関連付けられます。 実際、XRANGEを使用すると、範囲クエリを実行したり、個々の要素を取得したりできます。



 > XRANGE mystream 1553097561402-0 1553097561402-0 1) 1) "1553097561402-0" 2) 1) "cpu-temp" 2) "23.4" 3) "load" 4) "2.3"
      
      





この場合、同じIDを使用して範囲を開始および終了し、1つのアイテムを識別しました。 ただし、任意の範囲とCOUNT引数を使用して、結果の数を制限できます。 同様に、範囲の完全な識別子を指定する必要はありません。指定された時間範囲の要素を取得するために、unix時間のみを使用できます。



 > XRANGE mystream 1553097560000 1553097570000 1) 1) "1553097561402-0" 2) 1) "cpu-temp" 2) "23.4" 3) "load" 4) "2.3" 2) 1) "1553097568315-0" 2) 1) "cpu-temp" 2) "23.2" 3) "load" 4) "2.1"
      
      





現時点では、他のAPI機能を示す必要はありません。これに関するドキュメントがあります。 とりあえず、この使用パターンに焦点を当てましょう:追加するXADD、範囲を抽出するXRANGE(およびXREAD)(目的に応じて)、ストリームがデータ構造と呼ばれるほど強力な理由を見てみましょう。



ストリームとAPIについて詳しく知りたい場合は、必ずチュートリアルを読んでください



テニス選手



数日前、Redisの勉強を始めた私の友人が、地元のテニスコート、選手、試合を追跡するアプリケーションをシミュレートしました。 プレーヤーをモデル化する方法は明らかです。プレーヤーは小さなオブジェクトなので、プレーヤーのようなキーを持つハッシュのみが必要player:<id>



。 その後、特定のテニスクラブでゲームを追跡する方法が必要であることがすぐにわかります。 player:1



player:2



対戦し、 player:1



が勝った場合、次のレコードをストリームに送信できます。



 > XADD club:1234.matches * player-a 1 player-b 2 winner 1 "1553254144387-0"
      
      





このような簡単な操作により、次のことができます。



  1. 一意の一致識別子:ストリーム内のID。

  2. 一致を識別するためのオブジェクトを作成する必要はありません。

  3. 特定の日時の試合のページングまたは試合の視聴の無料範囲リクエスト。


ストリームが登場する前に、時間でソートされたセットを作成する必要があります。ソートされたセットの要素は、識別子と一致し、ハッシュ値として別のキーに保存されます。 作業が増えるだけでなく、メモリも増えます。 はるかに多くのメモリ(以下を参照)。



ここでの目標は、Redisストリームが、各要素が小さなハッシュである、時間ごとのキーを持つ、 append only



モードで並べ替えられたセットであることを示すことです。 そして、そのシンプルさにおいて、これはモデリングの文脈における真の革命です。



記憶



上記のユースケースは、よりまとまりのあるプログラミングパターンではありません。 スレッドのメモリ消費量は、各オブジェクトのソートされたセット+ハッシュを使用する従来のアプローチとは非常に異なるため、以前はまったく実装できなかったものが機能し始めています。



前に示した構成で100万件の一致を保存するためのメモリ量の統計は次のとおりです。



   +  = 220  (242 RSS)  = 16,8  (18.11 RSS)
      
      





差は1桁以上(つまり、13倍)です。 これは、以前はメモリ内で実行するには高すぎたタスクを操作できることを意味します。 今、彼らは非常に実行可能です。 魔法は、Redisストリームを導入することです。マクロノードには、listpackと呼ばれるデータ構造に非常にコンパクトにエンコードされたいくつかの要素を含めることができます。 この構造は、たとえば、整数が意味論的な文字列であっても、バイナリ形式でのエンコードを処理します。 さらに、デルタ圧縮を適用し、同じフィールドを圧縮します。 ただし、このようなマクロノードはメモリ最適化を使用して設計されたベースツリーにリンクされているため、IDまたは時間で検索することは可能です。 一緒に、これはメモリの経済的な使用を説明しますが、興味深い部分は、意味的にユーザーがスレッドを非常に効率的にする実装の詳細を見ないということです。



カウントしましょう。 約18 MBのメモリに100万件のレコードを保存できる場合、180 MBに1,000万件、1.8 GBに1億件を保存できます。 わずか18 GBのメモリで、10億個のアイテムを保持できます。



時系列



上記のテニスの試合の例は、時系列にRedisストリームを使用することと意味的に*非常に異なることに注意することが重要です。 はい、論理的にはまだ何らかのイベントを登録していますが、根本的な違いがあります。 最初のケースでは、オブジェクトをレンダリングするためのレコードを記録して作成します。 時系列では、オブジェクトを実際に表していない外部で発生する何かを単純に測定します。 この区別は些細なことですが、そうではありません。 Redisストリームを使用して共通の順序で小さなオブジェクトを作成し、そのようなオブジェクトに識別子を割り当てることができるという考え方を理解することが重要です。



しかし、時系列を使用する最も簡単な方法でさえ、明らかに大きなブレークスルーです。なぜなら、ストリームが出現する前は、Redisはここで何もすることが実質的に無力だったからです。 ストリームのメモリ特性と柔軟性、および上限のあるストリームを制限する機能(XADDパラメーターを参照)は、開発者にとって非常に重要なツールです。



結論



ストリームは柔軟で多くのユースケースを提供しますが、例とメモリ消費量を明確に示すために非常に短い記事を書きたいと思いました。 おそらく多くの読者にとって、このストリームの使用は明らかでした。 しかし、ここ数か月の開発者との会話から、多くの人がストリームとストリーミングデータの間に強い関連性があるという印象を受けました。まるでそこにデータ構造があるだけのようです。 そうではありません。 :-)



All Articles