大數據計算:如何僅用1.5KB內存為十億對象計數
Big Data Counting: How To Count A Billion Distinct Objects Using Only 1.5K
This is a guest post by Matt Abrams (@abramsm), from Clearspring, discussing how they are able to accurately estimate the cardinality of sets with billions of distinct elements using surprisingly small data structures. Their servers receive well over 100 billion events per month.
在Clearspring,我們從事統計數據。統計一組不同元素且數量很大的數據集時,是一個挑戰。
為了更好地理解已經明確基數的大數據集的挑戰,我們假設你的日志文件包含16個字符的ID,并且你想統計不同ID的數量.例如:
4f67bfc603106cb2
這16個字符需要用128位來表示。6萬5千個ID將需要1MB的空間。我們每天收到30多億條事件記錄,每條記錄都有一個ID。這些ID需要3840億位或45GB的存儲。而這僅僅是ID字段需要的空間。我們采取一種簡單的方法獲取日常事件記錄中以ID為基數的數據。最簡單的辦法就是使用哈希集合且存放到內存中,其中哈希集包含唯一ID的列表(即輸入文件中可能會有多條記錄的id是相同,但在哈希集中只存放一條)。即使我們假設只有1/3的條記錄ID是唯一的(即2/3的記錄ID是重復的),哈希集仍需要119GB的RAM,這其中不包括Java需要在內存中存儲對象的開銷。你需要一臺配備幾百GB內存的機器來計算不同的元素,并且這只是計算一天內日志事件記錄的唯一ID的內存消耗。如果我們想要統計數周或數月的數據,這問題只會變得更加困難。我們身邊當然不會有一臺配備幾百GB內存的空閑機器,所以我們需要一個更好的解決方案。
解決這一問題的常見辦法是使用位圖(博客:海量數據處理算法—Bit-Map)。位圖可以快速、準確地獲取一個給定輸入的基數。位圖的基本思想是使用哈希函數把數據集映射到一個bit位,每個輸入元素與bit位是一一對應。這樣Hash將沒有產生碰撞沖突,并減少需要計算每個元素映射到1個bit的空間。雖然Bit-map大大節省了存儲空間,但當統計很高的基數或非常大的不同的數據集,它們仍然有問題。例如,如果我們想要使用Bit-map計數十億,你將需要Bit-map位,或需要每個約120 MB的計數器。稀疏的位圖可以被壓縮,以獲得更多的空間效率,但也并不總是有幫助的。
幸運的是,基數估計是一個熱門的研究領域。我們已經利用這項研究提供了一個開源實現的基數估計、集合元素檢測和top-k算法。
基數估計算法就是使用準確性換取空間。為了說明這一點,我們用三種不同的計算方法統計所有莎士比亞作品中不同單詞的數量。請注意,我們的輸入數據集增加了額外的數據以致比問題的參考基數更高。這三種技術是:Java HashSet、Linear Probabilistic Counter以及一個Hyper LogLog Counter。結果如下:
該表顯示,我們統計這些單詞只用了512 bytes,而誤差在3%以內。相比之下,HashMap的計數準確度最高,但需要近10MB的空間,你可以很容易地看到為什么基數估計是有用的。在實際應用中準確性并不是很重要的,這是事實,在大多數網絡規模和網絡計算的情況下,用概率計數器會節省巨大的空間。
線性概率計數器
線性概率計數器是高效的使用空間,并且允許實現者指定所需的精度水平。該算法在注重空間效率時是很有用的,但你需要能夠控制結果的誤差。該算法分兩步運行:第一步,首先在內存中分配一個初始化為都為0的Bit-map,然后使用哈希函數對輸入數據中的每個條目進行hash計算,哈希函數運算的結果是將每條記錄(或者是元素)映射到Bit-map的一個Bit位上,該Bit位被置為1;第二步,算法計算空的bit位數量,并使用這個數輸入到下面的公式來進行估算:
n=-m ln Vn
注意:ln Vn=Loge(Vn) 自然對數
在公式中,m是 Bit-map的大小,Vn是空bit位和map的大小的比率。需要重點注意的是原始Bit-map的大小,可以遠小于預期的最大基數。到底小多少取決于你可以承受誤差的大小。因為Bit-map的大小m小于不同元素的總數將會產生碰撞。雖然碰撞可以節省空間,但同時也造成了估算結果出現誤差。所以通過控制原始map的大小,我們可以估算碰撞的次數,以致我們將在最終結果中看到誤差有多大。
Hyper LogLog
顧名思義,Hyper LogLog計數器就是估算Nmax為基數的數據集僅需使用loglog(Nmax)+O(1) bits就可以。如線性計數器的Hyper LogLog計數器允許設計人員指定所需的精度值,在Hyper LogLog的情況下,這是通過定義所需的相對標準差和預期要計數的最大基數。大部分計數器通過一個輸入數據流M,并應用一個哈希函數設置h(M)來工作。這將產生一個S = h(M) of {0,1}^∞字符串的可觀測結果。通過分割哈希輸入流成m個子字符串,并對每個子輸入流保持m的值可觀測 ,這就是相當一個新Hyper LogLog(一個子m就是一個新的Hyper LogLog)。利用額外的觀測值的平均值,產生一個計數器,其精度隨著m的增長而提高,這只需要對輸入集合中的每個元素執行幾步操作就可以完成。其結果是,這個計數器可以僅使用1.5 kb的空間計算精度為2%的十億個不同的數據元素。與執行 HashSet所需的120 兆字節進行比較,這種算法的效率很明顯。
合并分布式計數器
我們已經證明了使用上面描述的計數器我們可以估算大集合的基數。但是,如果你的原始輸入數據集不適合于單臺機器,將怎么做呢?這正是我們在Clearspring所面臨的問題。我們的數據分散在數百臺服務器上,并且每個服務器只包含整個數據集子集的一部分。這事實上我們能合并一組分布式計數器的內容是至關重要的。這個想法有點令人費解,但如果你花費一些時間去思考這個問題,就會發現其與基本的基數估計值相比并沒有太大的不同。因為這個計數器表示映射中的位作為基數,我們可以采取兩個兼容計數器并將他們bit位合并到單一的map上。這個算法已經處理碰撞,所以我們可以得到一個基數估計所需的精密,即使我們從來沒有把所有的輸入數據到一臺機器。這是非常有用的,節省了我們在網絡中移動數據的大量時間和精力。
Next Steps
希望這篇文章能幫助你更好地理解這個概念和概率計數器的應用。如果估算大集合的基數是一個問題,而你又碰巧使用一個基于JVM的語言,那么你應該使用stream-lib項目——它提供了其他幾個流處理工具以及上文所述的算法的實現。
本文來自:High Scalability
本人結束語:自認英語不好,翻譯可能會有出入。但我看了csdn的翻譯,我懷疑那是技術員人翻譯的嗎? 一些翻譯直接是工具翻譯過來。
若深入了解Hyper LogLog:請看http://algo.inria.fr/flajolet/Publications/FlFuGaMe07.pdf和http://www.ic.unicamp.br/~celio/peer2peer/math/bitmap-algorithms/durand03loglog.pdf
At Clearspring we like to count things. Counting the number of distinct elements (the cardinality) of a set is challenge when the cardinality of the set is large.
To better understand the challenge of determining the cardinality of large sets let's imagine that you have a 16 character ID and you'd like to count the number of distinct IDs that you've seen in your logs. Here is an example:
4f67bfc603106cb2
These 16 characters represent 128 bits. 65K IDs would require 1 megabyte of space. We receive over 3 billion events per day, and each event has an ID. Those IDs require 384,000,000,000 bits or 45 gigabytes of storage. And that is just the space that the ID field requires! To get the cardinality of IDs in our daily events we could take a simplistic approach. The most straightforward idea is to use an in memory hash set that contains the unique list of IDs seen in the input files. Even if we assume that only 1 in 3 records are unique the hash set would still take 119 gigs of RAM, not including the overhead Java requires to store objects in memory. You would need a machine with several hundred gigs of memory to count distinct elements this way and that is only to count a single day's worth of unique IDs. The problem only gets more difficult if we want to count weeks or months of data. We certainly don't have a single machine with several hundred gigs of free memory sitting around so we needed a better solution.
One common approach to this problem is the use of bitmaps. Bitmaps can be used to quickly and accurately get the cardinality of a given input. The basic idea with a bitmap is mapping the input dataset to a bit field using a hash function where each input element uniquely maps to one of the bits in the field. This produces zero collisions, and reduces the space required to count each unique element to 1 bit. While bitmaps drastically reduce the space requirements from the naive set implementation described above they are still problematic when the cardinality is very high and/or you have a very large number of different sets to count. For example, if we want to count to one billion using a bitmap you will need one billion bits, or roughly 120 megabytes for each counter. Sparse bitmaps can be compressed in order to gain space efficiency, but that is not always helpful.
Luckily, cardinality estimation is a popular area of research. We've leveraged this research to provide a open source implementation of cardinality estimators, set membership detection, and top-k algorithms.
Cardinality estimation algorithms trade space for accuracy. To illustrate this point we counted the number of distinct words in all of Shakespeare's works using three different counting techniques. Note that our input dataset has extra data in it so the cardinality is higher than the standard reference answer to this question. The three techniques we used were Java HashSet, Linear Probabilistic Counter, and a Hyper LogLog Counter. Here are the results:
Counter |
Bytes Used |
Count |
Error |
HashSet |
10447016 |
67801 |
0% |
Linear |
3384 |
67080 |
1% |
HyperLogLog |
512 |
70002 |
3% |
The table shows that we can count the words with a 3% error rate using only 512 bytes of space. Compare that to a perfect count using a HashMap that requires nearly 10 megabytes of space and you can easily see why cardinality estimators are useful. In applications where accuracy is not paramount, which is true for most web scale and network counting scenarios, using a probabilistic counter can result in tremendous space savings.
Linear Probabilistic Counter
The Linear Probabilistic Counter is space efficient and allows the implementer to specify the desired level of accuracy. This algorithm is useful when space efficiency is important but you need to be able to control the error in your results. This algorithm works in a two-step process. The first step assigns a bitmap in memory initialized to all zeros. A hash function is then applied to the each entry in the input data. The result of the hash function maps the entry to a bit in the bitmap, and that bit is set to 1. The second step the algorithm counts the number of empty bits and uses that number as input to the following equation to get the estimate.
n=-m ln Vn
In the equation m is the size of the bitmap and Vn is the ratio of empty bits over the size of the map. The important thing to note is that the size of the original bitmap can be much smaller than the expected max cardinality. How much smaller depends on how much error you can tolerate in the result. Because the size of the bitmap, m, is smaller than the total number of distinct elements, there will be collisions. These collisions are required to be space-efficient but also result in the error found in the estimation. So by controlling the size of the original map we can estimate the number of collisions and therefore the amount of error we will see in the end result.
Hyper LogLog
The Hyper LogLog Counter's name is self-descriptive. The name comes from the fact that you can estimate the cardinality of a set with cardinality Nmax using just loglog(Nmax) + O(1) bits. Like the Linear Counter the Hyper LogLog counter allows the designer to specify the desired accuracy tolerances. In Hyper LogLog's case this is done by defining the desired relative standard deviation and the max cardinality you expect to count. Most counters work by taking an input data stream, M, and applying a hash function to that set, h(M). This yields an observable result of S = h(M) of {0,1}^∞ strings. Hyper LogLog extends this concept by splitting the hashed input stream into m substrings and then maintains m observables for each of the substreams. Taking the average of the additional observables yields a counter whose accuracy improves as m grows in size but only requires a constant number of operations to be performed on each element of the input set. The result is that, according to the authors of thispaper, this counter can count one billion distinct items with an accuracy of 2% using only 1.5 kilobytes of space. Compare that to the 120 megabytes required by the HashSet implementation and the efficiency of this algorithm becomes obvious.
Merging Distributed Counters
We've shown that using the counters described above we can estimate the cardinality of large sets. However, what can you do if your raw input dataset does not fit on single machine? This is exactly the problem we face at Clearspring. Our data is spread out over hundreds of servers and each server contains only a partial subset of the the total dataset. This is where the fact that we can merge the contents of a set of distributed counters is crucial. The idea is a little mind-bending but if you take a moment to think about it the concept is not that much different than basic cardinality estimation. Because the counters represent the cardinality as set of bits in a map we can take two compatible counters and merge their bits into a single map. The algorithms already handle collisions so we can still get a cardinality estimation with the desired precision even though we never brought all of the input data to a single machine. This is terribly useful and saves us a lot of time and effort moving data around our network.
Next Steps
Hopefully this post has helped you better understand the concept and application of probabilistic counters. If estimating the cardinality of large sets is a problem and you happen to use a JVM based language then you should check out the stream-lib project — it provides implementations of the algorithms described above as well as several other stream-processing utilities.