Save 37% off PRO during our Black Friday Sale! »

HyperLogLog is interesting

HyperLogLog is interesting

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

8d80c422d5385850366791e27d9ebcf4?s=128

Okada Haruki

August 31, 2019
Tweet

Transcript

  1. HyperLogLog sketch は⾯⽩い HyperLogLog sketch は⾯⽩い builderscon tokyo 2019 builderscon

    tokyo 2019 Haruki Okada (@ocadaruma) Haruki Okada (@ocadaruma)
  2. Introduction Introduction リアルタイムアクセス解析システム HogeAnalytics を作りたい Web サイトのアクセス統計をリアルタイムに提供 ページごとのPV 数 ページごとのユニークユーザー(cookie)

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

    とか8 byte ユニークユーザー数: どうやって出す? Count-distinct problem とよばれる すべての要素を覚えておく必要があるため、ナイ ーブな⽅法だとどうしても⾮効率になる
  6. 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(); } }
  7. Simple set approach Simple set approach O(N) space 必要 (N

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

    リアルタイム" がみたせない SELECT url, COUNT(DISTINCT cookie_id) uu FROM access_log GROUP BY url
  9. 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 と省略
  10. HyperLogLog HyperLogLog 様々なミドルウェアにHLL を⽤いたapprox distinct が搭載されている Redis, Presto, Redshift, BigQuery,...

    16KB で、数⼗億を超えるcardinality を0.81% の誤差 で近似
  11. Agenda Agenda 1. How HLL works 2. HLL on Redis

    3. HyperMinHash
  12. How HLL works How HLL works

  13. Intuitive explanation Intuitive explanation 64bit int を⼀様ランダムに選ぶとき、上位k bit が連 続して0

    となる確率は 1/2^k
  14. Intuitive explanation Intuitive explanation ⾔いかえると、 2^k 回試⾏しないと上位k bit が連続 して0

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

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

    を「上位の連続した0-bit 数」で 近似した つまり log2(N) までの数だけで N を近似したことに なる log2(log2(N)) bit
  18. Improve accuracy Improve accuracy これだけだと精度が悪いし、2^k 単位でしか近似で きない 複数のhash 関数を使ってその平均を取ることで精 度が向上する

  19. Improve accuracy Improve accuracy データセットの各要素に対し複数のhash 関数をか けるのは時間がかかる かわりに、ハッシュ値の先頭 p bit

    を使って、 m = 2^p 個のbucket に振り分ける bucket ごとに、残りの 64 - p bit を使って、上位の 連続する0-bit を数える
  20. None
  21. HLL Sketch HLL Sketch この、上位の連続する0-bit 数を保持した m 個の bucket 列をsketch

    とよぶ byte[] sketch = new byte[m];
  22. Stochastic averaging Stochastic averaging sketch を⾛査し、bucket ごとの値を平均して最終的 な値を計算する Flajolet &

    Martin., 1985. Probabilistic Counting Algorithms for Data Base Applications
  23. Estimation Estimation sketch をM としてM[i] でi 番⽬のbucket を表すと、 HLL では以下の式で最終的なcardinality

    を計算する (αm は、bucket 数 m に依存したbias correction factor)
  24. 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
  25. 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); }
  26. Pseudo code Pseudo code Estimation double z = 0; for

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

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

    et al., 2007. ここでいう標準誤差 := 標準偏差を真のcardinality で割って得た相対誤差 Redis はデフォルトだと16384 bucket なので 1.04/√16384 = 0.008125 => 誤差0.81%
  29. 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
  30. What causes high error ? What causes high error ?

    ハッシュ値の衝突 だが⼀般的にはハッシュのpre image を求めるの は困難 同じbucket へ振り分けられて、かつ上位0 bit が同じ 数連続している場合 前述の98567648, 19857710,... はすべて、Redis HLL において同じbucket かつ同じ数0-bit 数が連続 する
  31. 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); }
  32. HLL feature: Merge two sketches HLL feature: Merge two sketches

    2つのHLL sketch はloss less でmerge できる (bucket 数やhash 関数は同じ前提)
  33. 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; }
  34. HLL feature: Easy to parallelize HLL feature: Easy to parallelize

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

    al., 2007 が初出ではない Durand & Flajolet., 2003. Loglog Counting of Large Cardinalities sketch 構築はHLL と同じだが、cardinality の計算⽅ 法が違う
  36. 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 でよい精度 を⽰す
  37. Otmar Ertl method Otmar Ertl method Otmar Ertl, 2017. New

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

  39. 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
  40. 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
  41. 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"
  42. Performance Performance PFADD: < 1 microsec PFMERGE: ~ 200 microsec

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

    は⼩さいcardinality に対して誤差が⼤きくな るため、閾値を超えるまではHLL でなくLinear Counting を使う Flajolet et al., 2007 でも提案されている⼿法 http://antirez.com/news/75
  44. Redis v4.0.0 Redis v4.0.0 アルゴリズムがLogLog-Beta に切り替わった Linear Counting を併⽤しなくなった https://github.com/antirez/redis/pull/3677

  45. Redis v5.0.0 Redis v5.0.0 LogLog-Beta から、Otmar Ertl, 2017. によるアルゴ リズムに切り替わった

    https://github.com/antirez/redis/pull/4749
  46. 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. */ };
  47. Sparse representation Sparse representation Redis デフォルトではregister 数 m = 16384

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

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

    て保持 16384 * (6/8) = 12288 bytes bit shifting でうまいことregister の値を取り出す
  50. HyperMinHash HyperMinHash

  51. Intersection cardinality Intersection cardinality HLL を使うことで省メモリにcardinality を保持・計 算でき、union も取れることがわかった union

    が取れるならintersection も欲しくなる
  52. Hoge Analytics 2.0 Hoge Analytics 2.0

  53. Difficulty Difficulty 以下のディメンションを任意に組み合わせたい 地域 (100 種類) URL (100 ページ) OS

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

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

    http://tech.adroll.com/blog/data/2013/07/10/hll- minhash.html
  56. MinHash MinHash N をデータセットのcardinality とすると、MinHash sketch は O(log(N)) space 必要

    HLL のように空間効率のよい表現がほしい
  57. 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 のみで可能
  58. Conclusion Conclusion HyperLogLog sketch は⾯⽩い 確率的アルゴリズムは⾯⽩い