Redis
 Computer >> コンピューター >  >> プログラミング >> Redis

あなたはおそらくRedisストリームについて間違って考えています

私は個人的にストリームを間違った方法で記述したことで罪を犯しています。私はそれを「単一のキーの下で時間順に並べられた一連のハッシュマップのような要素」と定義しました。 これは正しくありません 。時間とキーに関する最後のビットはOKですが、最初のビットはすべて間違っています。

ストリームが誤解されている理由と、ストリームが実際にどのように動作するかを見てみましょう。この誤解の良い点と悪い点、およびそれがソフトウェアにどのように影響するかを評価します。最後に、RedisStreamsのあまり知られていないプロパティを利用するいくつかの非自明なパターンを調べます。

背景

まず、誤解が始まるXADDコマンドを見てみましょう。公式のredis.ioドキュメントにあるコマンド署名は次のようになります:

XADD key ID field value [field value ...]

<マーク>キー 自明です。 id は新しいエントリのタイムスタンプ/シーケンスの組み合わせですが、実際には、ほとんどの場合*自動生成を示します。この混乱の根源は、フィールドと値から始まります。 HSETのコマンド署名を見ると、非常によく似たパターンが見られます。

HSET key field value [field value ...]

2つのコマンドのシグニチャは1つの引数のみであり、XADDのその引数はほとんどの場合単一の*です。見た目はかなり似ていて、同じ用語を使用していますが、同じである必要がありますよね?

わかった。問題を継続するために、Redisを脇に置いて、プログラミング言語がフィールドと値のペアをどのように処理するかを見てみましょう。ほとんどの場合、言語に関係なく、フィールド値を明確にする最も表現的な方法は、値に相関するフィールドのセット(繰り返しなし)を使用することです。一部の言語はフィールドの順序を保持し、一部は保持しません。言語間の小さな比較で詳しく見ていきましょう:

これは、Redisハッシュにうまくマッピングされます。これらはすべて、順序付けされておらず、繰り返しがないハッシュのプロパティを明確にすることができます。 PHP配列、Python dict、JavaScriptマップでフィールドの順序を定義できますが、Redisでハッシュを使用している場合は問題ありません。アプリケーションレベルでは、この順序に依存できないことを知っておく必要があります。 。

ほとんどの人にとって、自然な結論は、HSETとXADDのコマンドシグニチャには相関関係があるため、見返りにおそらく同様の相関関係があるということです。実際、RESP2のプロトコルレベルでは、これらは両方ともインターリーブされたRESP配列として返されます。これは、HGETALLとXREADの応答が両方ともマップであったRESP3の初期バージョンでも継続されます(詳細は後で説明します)。

バグが気が変わった

通常、私はJavaScriptでコーディングし、場合によってはPythonでコーディングします。 Redisについてコミュニケーションをとる人として、私はできるだけ多くの人に連絡する必要があります。これら2つの言語はかなりよく理解されているため、どちらのデモまたはサンプルコードも、開発者の世界の高い割合で理解されます。最近、PHPカンファレンスで話す機会があり、既存のPythonコードをPHPに変換する必要がありました。私はPHPをオンとオフで20年近く使用していますが、それは私の情熱ではありませんでした。特定のデモはmod_phpスタイルの実行にうまく適合しなかったため、swooleとそのコルーチン実行を使用しました(補足として、JavaScriptの世界で快適であるため、swooleはPHPを非常に馴染みのあるものにします)。ライブラリは少し古く、高レベルの方法でリターンをデコードする際に実際のクライアントライブラリの支援なしで生のRedisコマンドを送信する必要がありました。一般に、生のRedisコマンドを送信して結果をデコードすると、もう少し制御が可能になり、面倒ではありません。

そのため、XADDにコマンドを送信するときに、フィールド値の部分を作成していて、1つずつエラーが発生しました(何年も不在だった後、PHPに戻るまでこれをチョークしてください)。その結果、意図せずに次のようなものを送信してしまいました。

XADD myKey * field0 value1 field0 value2 field2 value3

相関フィールドと値を送信する代わりに( field0 value0へ など)。

コードの後半で、XREADの結果をループ内の既存のPHP配列(連想)に入れ、各フィールドを各値のキーとして割り当て、すでに設定されているものをスキップしていました。それで、私はこのような配列から始めました:

array(1) {
  ["foo"]=>
  string(3) "bar"
}

そして、次のような配列になりました:

array(3) {
  ["foo"]=>
  string(3) "bar"
  ["field0"]=>
  string(6) "value1"
  ["field2"]=>
  string(6) "value3"
}

それがどのように可能であったかを理解することはできませんでした。 value1の理由に関するバグをすばやく突き止めることができました field0に割り当てられました (上記のXADDでのオフバイワンエラー)が、なぜ field0ではなかったのですか value2として設定 ? HSETでは、フィールドを追加するためのこの動作は基本的にアップサートです。フィールドが存在する場合は更新し、存在しない場合はフィールドを追加して値を設定します。

MONITORログを調べて値を再生し、次のようにXREADを実行しました。

> XREAD STREAMS myKey 0
1) 1) "myKey"
   2) 1) 1) "1592409586907-0"
         2) 1) "field0"
            2) "value1"
            3) "field0"
            4) "value2"
            5) "field2"
            6) "value3"

繰り返しが存在し、記録されます。アップサートされません。 さらに、順序は保持されます これはハッシュのようなものではありません!

JSONを概算として使用してこれを考えると、ストリームエントリは次のようになっていると思いました:

{
  "field1"  : "value",
  "field2"  : "value",
  "field3"  : "value"
}

しかし、実際には次のようになります:

[
  ["field1", "value"],
  ["field2", "value"],
  ["field3", "value"]
]

これはどういう意味ですか?

朗報

現在Streamsで機能するコードがあり、エントリがハッシュマップのようなものであると想定している場合は、おそらく問題ありません。特定のアプリケーションで期待どおりに動作しない可能性があるため、重複フィールドの入力に関する潜在的なバグに注意する必要があります。これはすべての状況やクライアントライブラリに当てはまるわけではありませんが、繰り返しを許可しないデータ構造を提供し(上記を参照)、XADDに提供するときにこれを引数にシリアル化することをお勧めします。一意のフィールドが入力され、一意のフィールドが出力されます。

悪いニュース

すべてのクライアントライブラリとツールが正しく機能するわけではありません。比較的低レベルのクライアントライブラリ(非網羅的:node_redis、hiredis)は、Redisからの出力を言語構造に変更する限りはあまり機能しません。他の高レベルのライブラリは Redisの実際の戻り値を言語構造に抽象化します。選択したライブラリがこれを実行しているかどうかを確認し、実行している場合は問題を挿入する必要があります。いくつかの高レベルのライブラリは最初からそれを正しく理解していたので(stackexchange.redis)、賞賛が必要です。

少し悪い他の部分:RESP3を非常に早く採用した場合は、XREAD/XREADGROUPがRESP3マップタイプを返すことを経験した可能性があります。 4月上旬まで、開発中のバージョンのRedis 6は、Streamsを読み取るときに、混乱して繰り返しのあるマップを返していました。ありがたいことに、これは解決され、Redis 6のGAバージョン(本番環境でRESP3を実際に使用するのは初めて)がXREAD/XREADGROUPの適切な返品とともに出荷されました。

楽しい部分

Streamsについておそらく間違っていることを説明したので、これまで誤解されていたこの構造をどのように活用できるかについて少し考えてみましょう。

ストリームエントリの順序に意味的な意味を適用する

したがって、実際には、このパターンで遊ぶ3つのベクトルがあります。ベクターグラフィックスのパスを保存することを想像してみてください。各ストリームエントリは一意のポリゴンまたはパスになり、フィールドと値は座標になります。たとえば、SVGのこのフラグメントを見てください:

<polyline points="50,150 50,200 200,200 200,100">

これは次のように明確に表現できます:

> XADD mySVG * 50 150 50 200 200 200 200 100

追加の各形状は、同じキーの別のエントリになります。 Redisハッシュを使用してこのようなことを行おうとすると、座標は2つだけになり、順序は保証されません。確かに、ビットフィールドなどでこれを行うことはできますが、長さや座標サイズに関してはかなりの柔軟性が失われます。 Streamsを使用すると、タイムスタンプを使用して、時間の経過とともに表示される一連の図形を表すために、きちんとしたことを行うこともできます。

順序付けられたアイテムの時系列セットを作成する

これには小さなハックが必要ですが、多くの機能を提供できます。データのような配列のシーケンスを保持していると想像してください。事実上、配列の配列— JSONでは、次のように考えることができます:

[
  ["A New Hope", "The Empire Strikes Back", "Return of the Jedi"],
  ["The Phantom Menace", "Attack of the Clones", "Revenge of the Sith"],
  ["The Force Awakens", "The Last Jedi", "The Rise of Skywalker"]
]

これを1つの小さなニュアンスを持つ一連のストリームエントリとして明確にすることができます。内部リストの(疑似)要素の数が奇数でないことを確認する必要があります。上記のように奇妙な場合は、何らかの方法でそれを記録する必要があります。空の文字列を使用してこれを行う方法は次のとおりです。

> XADD starwars * "A New Hope" "The Empire Strikes Back" "Return of the Jedi" ""
"1592427370458-0"
> XADD starwars * "The Phantom Menace" "Attack of the Clones" "Revenge of the Sith" ""
"1592427393492-0"
> XADD starwars * "The Force Awakens" "The Last Jedi" "The Rise of Skywalker" ""
"1592427414475-0"
> XREAD streams starwars 0
1# "starwars" => 
   1) 1) "1592427370458-0"
      2) 1) "A New Hope"
         2) "The Empire Strikes Back"
         3) "Return of the Jedi"
         4) ""
   2) 1) "1592427393492-0"
      2) 1) "The Phantom Menace"
         2) "Attack of the Clones"
         3) "Revenge of the Sith"
         4) ""
   3) 1) "1592427414475-0"
      2) 1) "The Force Awakens"
         2) "The Last Jedi"
         3) "The Rise of Skywalker"
         4) ""

このパターンでは、長さ0の文字列を除外する必要があるという(わずかな)費用で多くの利益を得ることができます。

ページ付けキャッシュとしてのストリーム

よく目にするトリッキーなのは、サイト上のアイテム(eコマース、メッセージボードなど)のリストです。これは一般的にキャッシュされますが、このタイプのデータをキャッシュするための最良の方法を見つけようとする人々がいるのを見てきました。結果セット全体を並べ替えられたセットのようなものにキャッシュしてZRANGEで外側にページ分割するのか、それともページ全体を保存するのか文字列キーで?どちらの方法にも長所と短所があります。

結局のところ、Streamsは実際にこれに対応しています。たとえば、eコマースのリストを見てください。それぞれにIDが付いた一連のアイテムがあります。これらのアイテムは、通常は反転する有限の一連の種類でリストされます(A-Z、Z-A、低から高、高から低、最高から最低の評価、最低から最高の評価など)。

ストリームでこのタイプのデータをモデル化するには、特定の「チャンク」サイズを決定し、それをエントリにします。エントリで結果ページ全体ではなくチャンクするのはなぜですか?これにより、ページネーションにさまざまなサイズのページを含めることができます(たとえば、1ページあたり10アイテムは、それぞれ5つの2つのチャンクで構成できますが、1ページあたり25は、それぞれ5つの5つのチャンクになります)。各エントリには、製品IDにマップするフィールドが含まれ、値は製品データになります。チャンクサイズが人為的に小さいこの単純化された例を見てください:

あなたはおそらくRedisストリームについて間違って考えています

キャッシュされた値を取得する場合は、COUNT引数をインターフェイスの結果ページを構成するチャンクの数に設定してXRANGEを実行します。したがって、4つのアイテムの最初のページを取得する場合は、次のようにします。

> XRANGE listcache - + COUNT 2
1) 1) "0-1"
   2) 1) "123"
      2) "{ \"Red Belt\", ... }"
      3) "456"
      4) "{ \"Yellow Belt\", ... }"
2) 1) "0-2"
   2) 1) "789"
      2) "{ \"Blue Belt\", ... }"
      3) "012"
      4) "{ \"Purple Belt\", ... }"

4つのアイテムの2ページ目を取得するには、1ずつ増加する下限ストリームIDを指定する必要があります。この場合、下限は 0- 2になります。

> XRANGE listcache 0-3 + COUNT 2
1) 1) "0-3"
   2) 1) "345"
      2) "{ \"Black Belt\", ... }"
      3) "678"
      4) "{ \"Red Boa\", ... }"
2) 1) "0-4"
   2) 1) "901"
      2) "{ \"Yellow Boa\", ... }"
      3) "234"
      4) "{ \"Green Belt\", ... }"

XRANGEはこの使用法では事実上O(1)であるため、これは並べ替えられたセットまたはリストよりも計算が複雑になるという利点がありますが、覚えておくべきことがいくつかあります。

  • XREVRANGEは逆に使用できますが、これは「チャンク」の順序を逆にするだけです。各チャンク内では、アプリケーションロジックの順序を逆にする必要がありますが、これは比較的簡単なはずです。
  • ストリームIDを手動で線形に設定した場合、リストのさまざまな部分を探すのは「無料」です。したがって、チャンク1はストリームID 0-1です。 、チャンク2はストリームID 0-2 等々。 0-0 でストリームエントリを追加することはできないため、これは0ではなく1から開始する必要があることに注意してください。

他のキーと同様に、有効期限を使用して、ストリームが存続する期間を管理できます。これを行う方法の例は、stream-row-cacheにあります。

この投稿が、Streamsが実際にどのように機能するか、およびアプリケーションでStreamsのこれらのほとんど未知のプロパティをどのように活用できるかについての追加のコンテキストを提供することを願っています。


  1. おそらく知られていない PUBG に関する 11 の事実

    PlayerUnknown の Battlegrounds または PUBG は、なんらかの紹介が必要な名前ではありません。そして、ゲームのコントロールと、プロ プレイヤーがまだ精査していない優れたトリックはほとんどありません。過去 2 年半にわたる PUBG の素晴らしい走りの中で、プレイヤーは、高品質の武器、狙撃ポイント、略奪エリアから、ゲームプレイの戦術、ヘッドショット メカニズム、車両操作まで、あらゆるものにアクセスしてマスターしてきました。ただし、ゲームを最高の状態でプレイする方法についてすべての知識を持っていても、世界中の PUBG の驚異的なファン フォローを定義するいくつかの事

  2. おそらく知らなかった Facebook のトリック

    Facebookは、最近の私たちの日常生活の一部です。 Facebook アカウントの使い方は人それぞれです。ビジネス目的で使用する人もいれば、友人とつながるために使用する人もいます。 Facebook アカウントを使用しているうちに、多くの追加機能の必要性を認識し、Facebook はユーザー エクスペリエンスを強化するために新しい機能を追加し続けています。しかし、ほとんどのユーザーにとってそれほど明白ではないものもあります。この記事では、あまり知られていないヒントやコツについて説明します。 送信したがまだ承認されていないすべての友達リクエストを表示: 長い間Facebookを使用し