Redis の高度なデータ構造 HyperLogLog

HyperLogLog (Hyper[ˈhaɪpə(r)]) は、新しいデータ構造 (実際の型は文字列型) ではなく、カーディナリティー アルゴリズムです。HyperLogLog は、独立した合計の統計を完了するために非常に小さなメモリ領域を使用できます。 IP、電子メール、ID などです。

あなたが大規模な Web サイトの開発と保守を担当している場合、ある日、プロダクト マネージャーが Web サイトの各ページの毎日の UV データを必要としていて、この統計モジュールを開発するように頼まれました。どうすればそれを達成できますか?

PV をカウントするのは非常に簡単で、各 Web ページに独立した Redis カウンタを与え、このカウンタのキー サフィックスにその日の日付を追加するだけで十分です。このようにして、1リクエスト、1カウント、最終的には全てのPVデータをカウントすることができます。

しかし、UV は異なります。アクセスを繰り返す必要があり、同じユーザーによる 1 日内の複数のアクセス要求は 1 回しかカウントされません。これには、すべての Web ページ リクエストにユーザー ID が必要であり、ログイン ユーザーか非ログイン ユーザーかに関係なく、ユーザーを識別するために一意の ID が必要です。

簡単な解決策は、ページごとに独立したセット コレクションを用意し、その日にこのページにアクセスしたすべてのユーザー ID を保存することです。リクエストが来たときは、sadd を使用してユーザー ID を詰め込みます。このコレクションのサイズはshardを通じて取り出すことができ、この数値がこのページのUVデータとなります。

ただし、人気のあるページに数千万の UV があるなど、ページの訪問数が非常に大きい場合は、カウントするために大規模なセット コレクションが必要となり、スペースの無駄になります。このようなページが多数ある場合、必要なストレージ容量は膨大になります。このような重複排除機能にそれほど多くのストレージ容量を費やす価値があるでしょうか? 実際、必要なデータはそれほど正確である必要はありません。1050w と 1060w という 2 つの数値は、ボスにとってはそれほど違いがありません。では、より良い解決策はあるでしょうか?

ここで HyperLogLog が登場します。Redis が提供する HyperLogLog データ構造は、この統計的問題を解決するために使用されます。HyperLogLog は、不正確な重複除外カウント ソリューションを提供します。不正確ではありますが、非常に不正確というわけではありません。Redis は公式に標準誤差 0.81% を与えており、上記の UV 統計要件を満たすことができます。

操作コマンド

HyperLogLog は、pfadd、pfcount、pfmerge の 3 つのコマンドを提供します。

たとえば、08-15 の訪問ユーザーは u1、u2、u3、u4 で、08-16 の訪問ユーザーは u-4、u-5、u-6、u-7 です。

パスされた

pfadd キー要素 [要素 …]

pfadd は HyperLogLog に要素を追加するために使用され、追加が成功すると 1 を返します。

pfadd 08-15:u:id "u1" "u2" "u3" "u4"

pfcount

pfcount キー [キー …]

pfcount は、1 つ以上の HyperLogLog の独立した合計を計算するために使用されます。たとえば、08-15:u:id の独立した合計は 4 です。

pfcount 08-15:u:id

この時点で u1、u2、u3、u90 を挿入すると、結果は 5 になります。

pfadd 08-15:u:id "u1" "u2" "u3" "u90"

pfcount 08-15:u:id

100 万件のユーザー レコードを挿入するなど、データを挿入し続ける場合。メモリの増加は非常にわずかですが、pfcount の統計結果は間違ったものになります。

コレクション タイプと HperLogLog を使用して、何百万ものユーザーの訪問によって占有されるスペースをカウントします。

データ型 1日1ヶ月1年

収集タイプ 80M 2.4G 28G

ハイパーログログ 15k 450k 5M

HyperLogLog のメモリ使用量が驚くほど少ないことがわかりますが、このような小さなスペースでこれほど大量のデータを見積もることは 100% 正しいわけではなく、エラー率が存在するはずです。前述したように、Redis が提供する公式の数値はエラー率 0.81% です。

pfmerge

pfmerge destkey ソースキー [ソースキー ... ]

pfmerge は複数の HyperLogLog の結合を見つけて destkey に割り当てることができます。ご自身でテストしてください。

原理の概要

基本的

HyperLogLog は、確率論のベルヌーイ実験に基づいており、最尤推定法を組み合わせてバケットの最適化を実行します。

実際、ビッグ データ シナリオでカーディナリティを正確に計算するためのこれより効率的なアルゴリズムはこれまでのところ見つかっていないため、絶対的な精度が追求されない場合は、確率的アルゴリズムを使用することが良い解決策となります。確率アルゴリズムは、データセット自体を直接保存するのではなく、特定の確率と統計的手法を通じて値を推定するため、メモリを大幅に節約し、誤差を一定の範囲内に確実に制御できます。現在基数カウントに使用されている確率的アルゴリズムには次のものがあります。

線形カウンティング (LC): 初期のカーディナリティ推定アルゴリズム。LC は空間の複雑さの点で優れていません。

LogLog Counting (LLC): LC と比較して、LogLog Counting はより多くのメモリを節約し、スペースの複雑さが低くなります。

HyperLogLog Counting (HLL): HyperLogLog Counting は LLC の最適化と改善に基づいており、同じ空間複雑さの場合、LLC のカーディナリティ推定誤差よりも小さくなる可能性があります。

HyperLogLog アルゴリズムを理解するために例を見てみましょう。ある日、フォックス先生とマーク先生はコイン投げゲームをしました。ルールは、マーク先生がコインを投げる責任があるということです。何ラウンド行うかは教師が自分で決めることができます。最後に、フォックス氏に最長のラウンドが何回投げられて表になったかを伝える必要があります。そうすれば、フォックス氏はマーク氏が合計何ラウンドプレーしたかを推測します。

 

上の図のように n 回実行されます。

1 回目: 表が出るまでに 3 回のトスがかかりました。この時点では k=3、n=1

2 番目のテスト: 表を得るまでに 2 回のトスが必要でした。この時点では k=2、n=2

3 番目の実験: 表が現れるまでに 4 回のトスが必要でした。この時点では k=4、n=3

…………

n 回目の実験: 表が出るまでに 7 回投げた このとき、k=7、n=n と推定します

k は各ラウンドで 1 (コインの表側) を投げるのにかかる回数です。私たちが知っているのは最大の k 値であり、これはマーク氏がフォックス氏に伝えた数であり、k_max で表すことができます。 。各コイントスの結果は 0 と 1 のみなので、どのラウンドでも k_max が出現する確率が推測でき、その回数 n = 2^ (k_max は kmax と最尤推定法を組み合わせることにより推測できます) 。確率では、この種の問題をベルヌーイ実験と呼びます。

マーク氏は n ラウンドを完了し、最長のトスは 4 回であるとフォックス氏に伝えました。フォックス氏もこの時点では自信を持っており、すぐに答えは 16 回でした。最終結果は次のとおりです: マーク氏は 3 回しかトスしませんでしたラウンド、

この 3 ラウンドで、k_max=4 を計算式に入れると、フォックス氏は n=2^4 と計算したため、マーク氏は 16 ラウンドを投げたと推測されますが、フォックス氏は負けてミルクティーを買った責任がありました。一週間。

したがって、この推定方法には大きな誤差が存在しますが、この誤差を改善するために、HLL ではバケット平均の概念が導入されています。

同じコイン投げの例を考えてみます。コイン投げ実験のグループが 1 つしかない場合、式から導出される実験回数の推定誤差は明らかに比較的大きく、非常に低くなります。各グループは複数のコイン投げ実験を実行し、レポートします。各実験中に表に投げられるトスの最大数実験全体の数は、100 グループの平均に基づいて推定できます。

バケット平均化の基本原理は、統計データを m 個のバケットに分割し、各バケットが独自の k_max をカウントし、それぞれのカーディナリティ推定値を取得し、最終的にこれらのカーディナリティ推定値を平均して全体のカーディナリティ推定値を取得することです。LLC では全体の基準値を推定するために幾何平均が使用されますが、統計データの量が少ない場合には誤差が大きくなります。HLL は LLC に基づいて改良され、調和平均は不健全な統計を除外するために使用されます。価値観。

調和平均とは何ですか? 例えば

平均給与を求めます。A さんは 1,000/月、B さんは 30,000/月です。平均を取る方法は次のとおりです: (1000 + 30000) / 2 = 15500

調和平均の使用方法は次のとおりです: 2/(1/1000 + 1/30000) ≈ 1935.484

調和平均の平均に対する利点は、大きな値の影響を受けにくく、平均よりも効果が優れていることがわかります。

例を挙げて原則を理解する

ここで、前のビジネス シナリオに接続します。Web ページの UV データを毎日カウントします。

1. ビット列に変換

ハッシュ関数によりデータはビット列に変換され、例えば「5」と入力すると「101」に変換され、文字列の場合も同様です。なぜこのように変換したいのですか?

コインを投げることに相当するからで、ビット列では0が裏、1が表で、最終的に10010000にデータを変換すると、右から左、低いほうから高いほうへ、と考えることができます。最初の出現 1 の場合、それは正です。

次に、上記の推定結論に基づいて、複数のコイントス実験における最大トス数によって実験の総数を推定することができ、また、保存されたデータによれば、変換後に最大数 1 が表示されます。保存されているデータ量を推定します。

2.バケツ

バケット化とは、ラウンドを何回に分割するかです。コンピュータの記憶域に抽象化すると、長さ L の大きなビット (ビット) 配列 S が格納され、S は平均して m 個のグループに分割され、この m 個のグループはラウンド数に相当し、各グループが占めるビット数に相当します。は平均して P を設定します。次の関係を導き出すのは簡単です。

L = Sの長さ

L = m * p

K 単位で、S = L / 8 / 1024 が占めるメモリ

3. 対応

アクセス ユーザー ID が idn, n->0,1,2,3.... であるとします。

この統計的問題では、さまざまなユーザー ID がユーザーを識別するため、ユーザーの ID をハッシュする入力として使用できます。たった今:

ハッシュ(ID) = ビット文字列

ユーザー ID が異なれば、ビット列も異なります。各ビット列に対して、1 の位置が少なくとも 1 回出現する必要があります。各ビット文字列をベルヌーイ試行と比較します。

次に、ラウンドを分割する、つまりバケットを分割する必要があります。したがって、各ビット文字列の最初の数桁が 10 進数に変換された後、その値がそのビット文字列が含まれるバケットのラベルに対応するように設定できます。バケットの下のフラグを計算するビット列の下位 2 ビットを使用すると仮定すると、バケットは合計 4 つあり、このときのユーザー ID のビット列は 1001011000011 となります。そのバケットの添え字は 1*2^1 + 1*2^0 = 3 で、これは 3 番目のバケット、つまり 3 ラウンド目にあります。

上記の例では、バケット番号を計算した後、残りのビット文字列は 10010110000 となり、低位から高位まで、最初に 1 が出現するのは 5 です。つまり、このときの第3バケットでは、k_max=5となる。5に対応するバイナリ値は101であり、第3バケットには101が格納される。

上記のプロセスを模倣して、複数の異なるユーザー ID が異なるバケットに分散され、各バケットには k_max が設定されます。次に、特定のページに何人のユーザーがアクセスしたかをカウントする必要がある場合、それは推定値になります。最後に、すべてのバケットの k_max を結合し、推定式に代入して推定値を取得します。

Redis での HyperLogLog の実装

Redis の実装では、HyperLogLog は 12KB を占有し (占有メモリ = 16834 * 6 / 8 / 1024 = 12K)、合計 16384 個のバケットがあります。つまり、2^14 = 16384、各バケットには 6 ビットがあり、各バケットは最大です。バケットで表現できる数値は 25+24+...+1 = 63 で、2 進数では 111 111 になります。

コマンドの場合: pfadd キー値

保存するとき、値は 64 ビット、つまり 64 ビットのビット列にハッシュされます。最初の 14 ビットはバケット化に使用され、残りの 50 ビットは最初の 1 が出現する位置の記録に使用されます。

バケット番号を表現するビットを 14 ビットとしたのは、バケットを 16384 個に分割し、2^14 = 16384 とちょうど良く、無駄なく最大の時間でバケットを使い切ることができるからです。文字列の最初の 14 桁が 00 0000 0000 0010 (右から左に読む) で、その 10 進数値が 2 であるとします。次に、値は変換された値に対応し、それを番号 2 のバケットに入れます。

インデックスの変換ルール:

まず、完全な値のビット列は64ビットなので、14を引いた残りは50ビットとなり、極端な話、1が現れる位置は50ビット目、つまり50の位置になります。この時点でインデックス = 50 になります。この時点で、まずインデックスをバイナリ (110010) に変換します。

16384 個のバケットのうち、各バケットは 6 ビットで構成されているためです。したがって、110010 が No.2 バケットに設定されます。50 はすでに最悪のケースであり、すべて対応していることに注意してください。そうすれば、他のことは何も考えずに確実に対応できます。

fpadd のキーには複数の値を設定できるためです。たとえば次の例です。

pfadd lgh golang

pfadd lgh Python

パス指定された lgh Java

上記の方法では、異なるバケットに異なる値が設定されることになりますが、同じバケットに出現する場合、つまり最初の14ビットの値は同じですが、その後に1が出現する位置が異なります。次に、元のインデックスが新しいインデックスより大きいかどうかを比較します。「はい」の場合は交換します。いいえ、変化はありません。

最終的に、キーに対応する 16384 個のバケットには多くの値が設定され、各バケットには k_max が設定されます。このとき、pfcountを呼び出すと調和平均により推定され、偏差が補正されると同時にキーの値が何倍に設定されているか、つまり統計値を計算することができます。具体的な推定式は以下の通りです。

 

値は64ビットのビット列に変換され、最終的に上記の方法に従って各バケットに記録されます。64 ビットを 10 進数に変換すると、2^64 になります。HyperLogLog は、2^64 の数値までカウントするために、16384 * 6 /8 / 1024 =12K の記憶域のみを使用します。

同時に、特定のアルゴリズムの実装に関して、HLL には段階的偏差補正アルゴリズムもあります。これ以上深い理解はしません。

おすすめ

転載: blog.csdn.net/yaya_jn/article/details/130646727