Upgrade to Pro — share decks privately, control downloads, hide ads and more …

HyperLogLog is interesting

HyperLogLog is interesting

builderscon tokyo 2019の発表資料
https://builderscon.io/tokyo/2019

Okada Haruki

August 31, 2019
Tweet

More Decks by Okada Haruki

Other Decks in Technology

Transcript

  1. Difficulty Difficulty PV 数: アクセスがあるたびにinteger をインクリメン トすればよい URL あたり4 byte

    とか8 byte ユニークユーザー数: どうやって出す? Count-distinct problem とよばれる すべての要素を覚えておく必要があるため、ナイ ーブな⽅法だとどうしても⾮効率になる
  2. Simple set approach Simple set approach class UniqueUserStats { private

    Map<PageURL, Set<CookieId>> map; public void record(PageURL url, CookieId user) { Set<CookieId> set = map.get(url); set.add(user); } public int countUU(PageURL url) { Set<CookieId> set = map.get(url); return set.size(); } }
  3. Simple set approach Simple set approach O(N) space 必要 (N

    = ユニークユーザー数) UUID String のcookieId を考える(36 byte) 1 億UU の保存に3.6GB 100URL で360GB abuse 耐性が無い でたらめなcookieId を送り続けるといずれメモリ に保持できる数を超えてサーバーが死ぬ
  4. Batch approach Batch approach 以下のようなSQL で事前にログを集計して、たとえ ばMySQL などのレポートDB に⼊れておく "

    リアルタイム" がみたせない SELECT url, COUNT(DISTINCT cookie_id) uu FROM access_log GROUP BY url
  5. Solution: Probabilistic approach Solution: Probabilistic approach 確率的アルゴリズムによる近似値を使う HyperLogLog Philippe Flajolet

    et al., 2007. HyperLogLog: the analysis of a near-optimal cardinality estimation algorithm 集合のcardinality( 要素数) をO(1) space で⾼精度に 推定できる このスライドでは以下HLL と省略
  6. Intuitive explanation Intuitive explanation ⾔いかえると、 2^k 回試⾏しないと上位k bit が連続 して0

    であるような数が出ない 「64bit int を⼀様ランダムに選ぶ」ことを繰り返す とき「上位何bit 0 が連続したか」だけ記録しておけ ば「試⾏した回数」を推定できる
  7. Use hash function for randomization Use hash function for randomization

    よい64bit hash 関数を使うと、hash 値は64bit int 空 間に⼀様に分布する つまり、cardinality が N であるデータセットの要素 をhash 関数にかける ⇔ 「64bit int を⼀様ランダム に選ぶ」試⾏をN 回繰り返す
  8. What does "LogLog" means What does "LogLog" means いま、cardinality N

    を「上位の連続した0-bit 数」で 近似した つまり log2(N) までの数だけで N を近似したことに なる log2(log2(N)) bit
  9. Improve accuracy Improve accuracy データセットの各要素に対し複数のhash 関数をか けるのは時間がかかる かわりに、ハッシュ値の先頭 p bit

    を使って、 m = 2^p 個のbucket に振り分ける bucket ごとに、残りの 64 - p bit を使って、上位の 連続する0-bit を数える
  10. Entire HLL process Entire HLL process したがってHLL は2 step にわけられる

    (1) Sketch construction データセットを⾛査して各要素をbucket に振り分 け、上位の連続する0-bit を数えて保存 データセットの要素数を N とすると O(N) time (2) Estimation sketch を⾛査して、前述の式でcardinality を計算 する bucket 数は固定なので O(1) time
  11. Pseudo code Pseudo code Sketch construction byte[] sketch = new

    byte[m]; for (String element : dataset) { long hash = calcHash(element); int bucket = calcBucket(hash); byte leadingZeros = calcLeadingZeros(hash); sketch[bucket] = Math.max(sketch[bucket], leadingZeros); }
  12. Pseudo code Pseudo code Estimation double z = 0; for

    (byte leadingZeros : sketch) { z += 1.0 / Math.pow(2, leadingZeros); } return alpha * m * m / z;
  13. HLL is a random variable HLL is a random variable

    HLL は任意のデータセットに対して前述のアルゴリ ズムで値を定める確率変数である この確率変数の期待値がcardinality n に等しいとい うこと
  14. Accuracy Accuracy bucket 数を m としたとき、標準誤差 = 1.04/√m by Flajolet

    et al., 2007. ここでいう標準誤差 := 標準偏差を真のcardinality で割って得た相対誤差 Redis はデフォルトだと16384 bucket なので 1.04/√16384 = 0.008125 => 誤差0.81%
  15. Accuracy Accuracy 「どんな⼊⼒に対しても誤差が0.81% 以内」という 意味ではない 例: Redis 4.0.9 で以下の⼊⼒は相対誤差-90% となる

    $ redis-cli PFADD foo 98567648 19857710 293736832 \ 275337325 304058906 154945851 \ 227134849 290132289 168593923 \ 279957693 $ redis-cli PFCOUNT foo (integer) 1
  16. What causes high error ? What causes high error ?

    ハッシュ値の衝突 だが⼀般的にはハッシュのpre image を求めるの は困難 同じbucket へ振り分けられて、かつ上位0 bit が同じ 数連続している場合 前述の98567648, 19857710,... はすべて、Redis HLL において同じbucket かつ同じ数0-bit 数が連続 する
  17. HLL feature: Streaming update HLL feature: Streaming update HLL sketch

    全体を再構築することなく要素を追加で きる public void add(String element) { long hash = calcHash(element); int bucket = calcBucket(hash); byte leadingZeros = calcLeadingZeros(hash); sketch[bucket] = Math.max(sketch[bucket], leadingZeros); }
  18. HLL feature: Merge two sketches HLL feature: Merge two sketches

    2つのHLL sketch はloss less でmerge できる (bucket 数やhash 関数は同じ前提)
  19. HLL feature: Merge two sketches HLL feature: Merge two sketches

    public byte[] merge(byte[] sketch1, byte[] sketch2) { int m = sketch1.length; byte[] merged = new byte[m]; for (int i = 0; i < m; i++) { merged[i] = Math.max(sketch1[i], sketch2[i]); } return merged; }
  20. HLL feature: Easy to parallelize HLL feature: Easy to parallelize

    merge がloss less なので、⼤量のデータセットの HLL sketch 構築は容易に並列化できる
  21. Sketch & Estimation Sketch & Estimation HLL sketch はFlajolet et

    al., 2007 が初出ではない Durand & Flajolet., 2003. Loglog Counting of Large Cardinalities sketch 構築はHLL と同じだが、cardinality の計算⽅ 法が違う
  22. LogLog-Beta LogLog-Beta Jason Qin et al., 2016. LogLog-Beta and More:

    A New Algorithm for Cardinality Estimation Based on LogLog Counting オリジナルのHLL はcardinality が⼩さいときに誤差 が⼤きくなる LogLog-Beta はすべてのcardinality range でよい精度 を⽰す
  23. Otmar Ertl method Otmar Ertl method Otmar Ertl, 2017. New

    cardinality estimation algorithms for HyperLogLog sketches Redis 5.0.5 (現時点のlatest )で採⽤されている estimation これもsketch はオリジナルのHLL と同じで、計算⽅ 法が違う
  24. Using HLL on Redis Using HLL on Redis Redis のHLL

    関連command は PFxxx Philippe Flajolet に由来 $ for i in `seq 1000`; do redis-cli PFADD foo $i > /dev/null done $ redis-cli PFCOUNT foo (integer) 1001
  25. Merge Merge $ for i in `seq 2000 2500`; do

    redis-cli PFADD bar $i > /dev/null done $ redis-cli PFMERGE merged foo bar $ redis-cli PFCOUNT merged (integer) 1506
  26. Get sketch Get sketch $ reids-cli PFADD baz 1 $

    redis-cli GET baz "HYLL\x01\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x80]f\x80b\x97"
  27. Performance Performance PFADD: < 1 microsec PFMERGE: ~ 200 microsec

    (okada 調べ) High traf c な場合は注意
  28. History History Redis 2.8.9 で導⼊ Linear Counting とHLL の併⽤ HLL

    は⼩さいcardinality に対して誤差が⼤きくな るため、閾値を超えるまではHLL でなくLinear Counting を使う Flajolet et al., 2007 でも提案されている⼿法 http://antirez.com/news/75
  29. Redis HLL Representation Redis HLL Representation Redis はHLL sketch を2

    通りでencode する sparse representation dense representation Redis HLL は以下の構造を持つ hyperloglog.c struct hllhdr { char magic[4]; /* "HYLL" */ uint8_t encoding; /* HLL_DENSE or HLL_SPARSE. */ uint8_t notused[3]; /* Reserved for future use, must be zero. uint8_t card[8]; /* Cached cardinality, little endian. */ uint8_t registers[]; /* Data bytes. */ };
  30. Sparse representation Sparse representation Redis デフォルトではregister 数 m = 16384

    以下Redis の⽤語と揃えてbucket = register と表記 cardinality が⼩さいうちは、ほとんどのregister は 値が0 のまま sketch をrun length encoding で圧縮して保持するこ とで空間効率を⾼める
  31. Dense representation Dense representation sparse representation の使⽤領域がしきい値を超え ると、dense にpromote される

    hll_sparse_max_bytes con g で指定 1 要素あたり6 bit の m 要素の配列として表現 register の値は⾼々64bit なので、 log2(64) = 6 bit で⼗分
  32. Dense representation Dense representation ただし6 bit のprimitive は無いのでuint8_t array とし

    て保持 16384 * (6/8) = 12288 bytes bit shifting でうまいことregister の値を取り出す
  33. Difficulty Difficulty 以下のディメンションを任意に組み合わせたい 地域 (100 種類) URL (100 ページ) OS

    (10 種類) 流⼊キーワード (10000 種類) 流⼊元サイト (1000 サイト) 1 兆通り Redis の場合、sketch は12KB => 計12PB
  34. Probabilistic approach again Probabilistic approach again MinHash Andrei Z. Broder,

    1997. On the resemblance and containment of documents Jaccard Index を近似する確率的アルゴリズム
  35. HyperLogLog and MinHash HyperLogLog and MinHash AdRoll tech blog で紹介されている⼿法

    http://tech.adroll.com/blog/data/2013/07/10/hll- minhash.html
  36. HyperMinHash HyperMinHash Yu & Weber, 2017. HyperMinHash: MinHash in LogLog

    space hash 値のbit パターンをうまいことencode して、 O(log(log(N))) space で、MinHash とHLL 両⽅の 性質を実現 Jaccard Index の推定とHLL の推定が、 HyperMinHash sketch のみで可能