A single-value
metrics aggregation that calculates an approximate count of
distinct values. Values can be extracted either from specific fields in the
document or generated by a script.
Assume you are indexing store sales and would like to count the unique number of sold products that match a query:
POST /sales/_search?size=0 { "aggs" : { "type_count" : { "cardinality" : { "field" : "type" } } } }
Response:
{ ... "aggregations" : { "type_count" : { "value" : 3 } } }
This aggregation also supports the precision_threshold
option:
POST /sales/_search?size=0 { "aggs" : { "type_count" : { "cardinality" : { "field" : "type", "precision_threshold": 100 } } } }
The |
Computing exact counts requires loading values into a hash set and returning its size. This doesn’t scale when working on high-cardinality sets and/or large values as the required memory usage and the need to communicate those per-shard sets between nodes would utilize too many resources of the cluster.
This cardinality
aggregation is based on the
HyperLogLog++
algorithm, which counts based on the hashes of the values with some interesting
properties:
For a precision threshold of c
, the implementation that we are using requires
about c * 8
bytes.
The following chart shows how the error varies before and after the threshold:
For all 3 thresholds, counts have been accurate up to the configured threshold. Although not guaranteed, this is likely to be the case. Accuracy in practice depends on the dataset in question. In general, most datasets show consistently good accuracy. Also note that even with a threshold as low as 100, the error remains very low (1-6% as seen in the above graph) even when counting millions of items.
The HyperLogLog++ algorithm depends on the leading zeros of hashed values, the exact distributions of hashes in a dataset can affect the accuracy of the cardinality.
Please also note that even with a threshold as low as 100, the error remains very low, even when counting millions of items.
On string fields that have a high cardinality, it might be faster to store the
hash of your field values in your index and then run the cardinality aggregation
on this field. This can either be done by providing hash values from client-side
or by letting Elasticsearch compute hash values for you by using the
mapper-murmur3
plugin.
Pre-computing hashes is usually only useful on very large and/or high-cardinality fields as it saves CPU and memory. However, on numeric fields, hashing is very fast and storing the original values requires as much or less memory than storing the hashes. This is also true on low-cardinality string fields, especially given that those have an optimization in order to make sure that hashes are computed at most once per unique value per segment.
The cardinality
metric supports scripting, with a noticeable performance hit
however since hashes need to be computed on the fly.
POST /sales/_search?size=0 { "aggs" : { "type_promoted_count" : { "cardinality" : { "script": { "lang": "painless", "source": "doc['type'].value + ' ' + doc['promoted'].value" } } } } }
This will interpret the script
parameter as an inline
script with the painless
script language and no script parameters. To use a stored script use the following syntax:
POST /sales/_search?size=0 { "aggs" : { "type_promoted_count" : { "cardinality" : { "script" : { "id": "my_script", "params": { "type_field": "type", "promoted_field": "promoted" } } } } } }
The missing
parameter defines how documents that are missing a value should be treated.
By default they will be ignored but it is also possible to treat them as if they
had a value.