cardinality-aggregation.asciidoc 8.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. [[search-aggregations-metrics-cardinality-aggregation]]
  2. === Cardinality aggregation
  3. ++++
  4. <titleabbrev>Cardinality</titleabbrev>
  5. ++++
  6. A `single-value` metrics aggregation that calculates an approximate count of
  7. distinct values.
  8. Assume you are indexing store sales and would like to count the unique number of sold products that match a query:
  9. [source,console]
  10. --------------------------------------------------
  11. POST /sales/_search?size=0
  12. {
  13. "aggs": {
  14. "type_count": {
  15. "cardinality": {
  16. "field": "type"
  17. }
  18. }
  19. }
  20. }
  21. --------------------------------------------------
  22. // TEST[setup:sales]
  23. Response:
  24. [source,console-result]
  25. --------------------------------------------------
  26. {
  27. ...
  28. "aggregations": {
  29. "type_count": {
  30. "value": 3
  31. }
  32. }
  33. }
  34. --------------------------------------------------
  35. // TESTRESPONSE[s/\.\.\./"took": $body.took,"timed_out": false,"_shards": $body._shards,"hits": $body.hits,/]
  36. ==== Precision control
  37. This aggregation also supports the `precision_threshold` option:
  38. [source,console]
  39. --------------------------------------------------
  40. POST /sales/_search?size=0
  41. {
  42. "aggs": {
  43. "type_count": {
  44. "cardinality": {
  45. "field": "type",
  46. "precision_threshold": 100 <1>
  47. }
  48. }
  49. }
  50. }
  51. --------------------------------------------------
  52. // TEST[setup:sales]
  53. <1> The `precision_threshold` options allows to trade memory for accuracy, and
  54. defines a unique count below which counts are expected to be close to
  55. accurate. Above this value, counts might become a bit more fuzzy. The maximum
  56. supported value is 40000, thresholds above this number will have the same
  57. effect as a threshold of 40000. The default value is +3000+.
  58. ==== Counts are approximate
  59. Computing exact counts requires loading values into a hash set and returning its
  60. size. This doesn't scale when working on high-cardinality sets and/or large
  61. values as the required memory usage and the need to communicate those
  62. per-shard sets between nodes would utilize too many resources of the cluster.
  63. This `cardinality` aggregation is based on the
  64. https://static.googleusercontent.com/media/research.google.com/fr//pubs/archive/40671.pdf[HyperLogLog++]
  65. algorithm, which counts based on the hashes of the values with some interesting
  66. properties:
  67. * configurable precision, which decides on how to trade memory for accuracy,
  68. * excellent accuracy on low-cardinality sets,
  69. * fixed memory usage: no matter if there are tens or billions of unique values,
  70. memory usage only depends on the configured precision.
  71. For a precision threshold of `c`, the implementation that we are using requires
  72. about `c * 8` bytes.
  73. The following chart shows how the error varies before and after the threshold:
  74. ////
  75. To generate this chart use this gnuplot script:
  76. [source,gnuplot]
  77. -------
  78. #!/usr/bin/gnuplot
  79. reset
  80. set terminal png size 1000,400
  81. set xlabel "Actual cardinality"
  82. set logscale x
  83. set ylabel "Relative error (%)"
  84. set yrange [0:8]
  85. set title "Cardinality error"
  86. set grid
  87. set style data lines
  88. plot "test.dat" using 1:2 title "threshold=100", \
  89. "" using 1:3 title "threshold=1000", \
  90. "" using 1:4 title "threshold=10000"
  91. #
  92. -------
  93. and generate data in a 'test.dat' file using the below Java code:
  94. [source,java]
  95. -------
  96. private static double error(HyperLogLogPlusPlus h, long expected) {
  97. double actual = h.cardinality(0);
  98. return Math.abs(expected - actual) / expected;
  99. }
  100. public static void main(String[] args) {
  101. HyperLogLogPlusPlus h100 = new HyperLogLogPlusPlus(precisionFromThreshold(100), BigArrays.NON_RECYCLING_INSTANCE, 1);
  102. HyperLogLogPlusPlus h1000 = new HyperLogLogPlusPlus(precisionFromThreshold(1000), BigArrays.NON_RECYCLING_INSTANCE, 1);
  103. HyperLogLogPlusPlus h10000 = new HyperLogLogPlusPlus(precisionFromThreshold(10000), BigArrays.NON_RECYCLING_INSTANCE, 1);
  104. int next = 100;
  105. int step = 10;
  106. for (int i = 1; i <= 10000000; ++i) {
  107. long h = BitMixer.mix64(i);
  108. h100.collect(0, h);
  109. h1000.collect(0, h);
  110. h10000.collect(0, h);
  111. if (i == next) {
  112. System.out.println(i + " " + error(h100, i)*100 + " " + error(h1000, i)*100 + " " + error(h10000, i)*100);
  113. next += step;
  114. if (next >= 100 * step) {
  115. step *= 10;
  116. }
  117. }
  118. }
  119. }
  120. -------
  121. ////
  122. image:images/cardinality_error.png[]
  123. For all 3 thresholds, counts have been accurate up to the configured threshold.
  124. Although not guaranteed, this is likely to be the case. Accuracy in practice depends
  125. on the dataset in question. In general, most datasets show consistently good
  126. accuracy. Also note that even with a threshold as low as 100, the error
  127. remains very low (1-6% as seen in the above graph) even when counting millions of items.
  128. The HyperLogLog++ algorithm depends on the leading zeros of hashed
  129. values, the exact distributions of hashes in a dataset can affect the
  130. accuracy of the cardinality.
  131. ==== Pre-computed hashes
  132. On string fields that have a high cardinality, it might be faster to store the
  133. hash of your field values in your index and then run the cardinality aggregation
  134. on this field. This can either be done by providing hash values from client-side
  135. or by letting Elasticsearch compute hash values for you by using the
  136. {plugins}/mapper-murmur3.html[`mapper-murmur3`] plugin.
  137. NOTE: Pre-computing hashes is usually only useful on very large and/or
  138. high-cardinality fields as it saves CPU and memory. However, on numeric
  139. fields, hashing is very fast and storing the original values requires as much
  140. or less memory than storing the hashes. This is also true on low-cardinality
  141. string fields, especially given that those have an optimization in order to
  142. make sure that hashes are computed at most once per unique value per segment.
  143. ==== Script
  144. If you need the cardinality of the combination of two fields,
  145. create a <<runtime,runtime field>> combining them and aggregate it.
  146. [source,console]
  147. ----
  148. POST /sales/_search?size=0
  149. {
  150. "runtime_mappings": {
  151. "type_and_promoted": {
  152. "type": "keyword",
  153. "script": "emit(doc['type'].value + ' ' + doc['promoted'].value)"
  154. }
  155. },
  156. "aggs": {
  157. "type_promoted_count": {
  158. "cardinality": {
  159. "field": "type_and_promoted"
  160. }
  161. }
  162. }
  163. }
  164. ----
  165. // TEST[setup:sales]
  166. // TEST[s/size=0/size=0&filter_path=aggregations/]
  167. ////
  168. [source,console-result]
  169. --------------------------------------------------
  170. {
  171. "aggregations": {
  172. "type_promoted_count": {
  173. "value": 5
  174. }
  175. }
  176. }
  177. --------------------------------------------------
  178. ////
  179. ==== Missing value
  180. The `missing` parameter defines how documents that are missing a value should be treated.
  181. By default they will be ignored but it is also possible to treat them as if they
  182. had a value.
  183. [source,console]
  184. --------------------------------------------------
  185. POST /sales/_search?size=0
  186. {
  187. "aggs": {
  188. "tag_cardinality": {
  189. "cardinality": {
  190. "field": "tag",
  191. "missing": "N/A" <1>
  192. }
  193. }
  194. }
  195. }
  196. --------------------------------------------------
  197. // TEST[setup:sales]
  198. <1> Documents without a value in the `tag` field will fall into the same bucket as documents that have the value `N/A`.
  199. ==== Execution Hint
  200. There are different mechanisms by which cardinality aggregations can be executed:
  201. - by using field values directly (`direct`)
  202. - by using global ordinals of the field and resolving those values after
  203. finishing a shard (`global_ordinals`)
  204. - by using segment ordinal values and resolving those values after each
  205. segment (`segment_ordinals`)
  206. Additionally, there are two "heuristic based" modes. These modes will cause
  207. Elasticsearch to use some data about the state of the index to choose an
  208. appropriate execution method. The two heuristics are:
  209. - `save_time_heuristic` - this is the default in Elasticsearch 8.4 and later.
  210. - `save_memory_heuristic` - this was the default in Elasticsearch 8.3 and
  211. earlier
  212. When not specified, Elasticsearch will apply a heuristic to chose the
  213. appropriate mode. Also note that some data (i.e. non-ordinal fields), `direct`
  214. is the only option, and the hint will be ignored in these cases. Generally
  215. speaking, it should not be necessary to set this value.