postgresql-hll

repository·master·Indexed 22 days ago

https://github.com/citusdata/postgresql-hll

A PostgreSQL extension providing the HyperLogLog (HLL) data structure for efficient, probabilistic distinct value counting. It is designed for high-performance cardinality estimation in large-scale data warehousing, allowing for the estimation of massive datasets with a small memory footprint. The extension includes hashing functions (e.g., hll_hash_integer, hll_hash_text), aggregate functions for pre-aggregating unique counts, and support for lossless unions.

Tokens
3.9K
Snippets
7
Records
24
Agent score
77%

What's inside postgresql-hll

  1. What is the `hll` data type?

    master

    The hll data type is a HyperLogLog implementation used for efficient, fixed-size, set-like distinct value counting. It allows estimating the cardinality of massive datasets (e.g., tens of billions of values) with a small memory footprint (e.g., 1280 bytes) and tunable precision.

    It uses a promotion hierarchy to balance accuracy and performance:

    1. EMPTY: A constant representing an empty set.
    2. EXPLICIT: A sorted list of unique integers for exact counting at low cardinalities.
    3. SPARSE: A map-based probabilistic structure that only stores non-zero registers.
    4. FULL: A fully-materialized, bit-packed list of all registers.

    As the number of distinct values grows, the structure automatically promotes from EMPTY $\rightarrow$ EXPLICIT $\rightarrow$ SPARSE $\rightarrow$ FULL.

  2. How HLL unions and intersections work

    master

    The hll module provides powerful probabilistic set operations:

    Unions

    hlls support "lossless" unions. The union of any number of hlls is mathematically equivalent to a single hll populated by all original inputs. This allows for efficient large-scale analytics, such as calculating unique visitors in a sliding window using Postgres aggregation functions like hll_union_agg and hll_union.

    Intersections

    You can estimate the intersection of sets using the inclusion-exclusion principle combined with the union function.

    Warning on Error Rates: The error in an intersection estimate is proportional to the union of the two hlls. If one hll is significantly larger than the other (e.g., one has 1 billion elements and the other has 10 million), the error margin of the larger hll can easily overwhelm the actual intersection cardinality, leading to disproportionately large relative errors.

  3. Use hll_hashval for hashed data

    master

    The hll_hashval type represents a hashed data value and is backed by a 64-bit integer (int8). It is typically the output of hll_hash_* functions.

    To avoid the overhead of hashing via the standard hll functions, you can cast bigint or integer directly to hll_hashval. Note that casting an integer will result in sign extension to a 64-bit integer.

  4. The importance of hashing for HLL

    master

    It is critical to hash inputs before passing them to the hll module. The error guarantees of the HyperLogLog algorithm rely on the inputs being approximately uniformly random.

    Key Requirements:

    • Uniform Randomness: Use a high-quality hash function like MurmurHash 3 (which is provided in this module) to ensure inputs are distributed uniformly.
    • Consistent Seeding: The seed used for the hash call must remain constant for all inputs to a single hll.
    • Union Compatibility: If you intend to compute the union of two hll objects, the input values for both must have been hashed using the same seed.
  5. Understand the cumulative union and cumulative add data formats

    master

    The project uses two distinct formats for cumulative data processing, distinguished by their filename prefixes:

    cumulative_union format

    Used for union operations where an accumulator multiset is updated by subsequent lines. Format: (cardinality, multiset, union_cardinality, union_multiset) In this format, union_multiset acts as the accumulator for the next operation.

    cumulative_add format

    Used for adding specific values to an existing multiset. Format: (cardinality, raw_value, multiset) In this format, raw_value is the value being added to the accumulator multiset.

  6. Install postgresql-hll via rpmbuild

    master

    To create and install an RPM package, follow these steps:

    1. Set versions (e.g., for version 2.21 and PostgreSQL 11):
      export VER=2.21
      export PGSHRT=11
    2. Configure Makefile: Ensure the Makefile points to the correct pg_config (since rpmbuild does not respect environment variables):
      PG_CONFIG = /usr/pgsql-11/bin/pg_config
    3. Create a tarball:
      tar cvfz postgresql${PGSHRT}-hll-${VER}.tar.gz postgresql-hll \
          --transform="s/postgresql-hll/postgresql${PGSHRT}-hll/g"
    4. Execute rpmbuild:
      rpmbuild -tb postgresql${PGSHRT}-hll-${VER}.tar.gz
    5. Install the RPM:
      • For the standard build:
        rpm -Uv rpmbuild/RPMS/x86_64/postgresql11-hll-2.21.x86_64.rpm
      • For the debug build:
        rpm -Uv rpmbuild/RPMS/x86_64/postgresql11-hll-debuginfo-2.21.x86_64.rpm
    rpmbuild -tb postgresql${PGSHRT}-hll-${VER}.tar.gz
  7. Enable the hll extension in PostgreSQL

    master

    Once the artifacts are built and installed, you must enable the extension within your PostgreSQL database using psql:

    CREATE EXTENSION hll;

    To verify the installation, run \dx in psql. You should see hll listed in the installed extensions:

    postgres=# \dx
                                List of installed extensions
              Name   | Version |   Schema   |            Description
    ---------+---------+------------+-------------------------
             hll   | 2.21    | public     | type for storing hyperloglog data
             plpgsql | 1.0     | pg_catalog | PL/pgSQL procedural language
    (2 rows)
    CREATE EXTENSION hll;
  8. Install postgresql-hll from source

    master

    To build the extension from source, you can use the provided Makefile.

    Basic Build

    If pg_config is in your system PATH, simply run:

    make

    Customizing the Build

    • Specify pg_config path: If pg_config is not in your PATH, provide the absolute path:
      PG_CONFIG=/usr/pgsql-9.11/bin/pg_config make
    • Specify Compiler: To use a specific C/C++ compiler (e.g., gcc instead of clang):
      make CC=gcc CXX=gcc
    • Debug Build: To create a debug build, set DEBUG=1:
      DEBUG=1 make

    Installation

    After building, install the artifacts to your PostgreSQL directory:

    sudo make install

    Troubleshooting

    Ensure you have the PostgreSQL development libraries and headers installed. On Debian-based systems, you can install them using:

    sudo apt-get install postgresql-server-dev-<YOUR_VERSION>
    PG_CONFIG=/usr/pgsql-9.11/bin/pg_config make
  9. Configure `hll` parameters and tuning

    master

    The hll type can be initialized with four parameters to tune accuracy, memory, and behavior. The default signature is: hll(log2m=11, regwidth=5, expthresh=-1, sparseon=1)

    Parameters:

    • log2m: (Integer, 4 to 31) The log-base-2 of the number of registers. Higher values increase accuracy but double the storage per increment. Relative error $\approx \pm 1.04/\sqrt{2^{\text{log2m}}}$.
    • regwidth: (Integer, 1 to 8) Bits per register. In conjunction with log2m, this determines the maximum cardinality that can be estimated.
    • expthresh: (Integer, -1, 0, or 1-18) Controls when the EXPLICIT representation is promoted to SPARSE.
      • -1: 'auto' mode (optimal memory usage).
      • 0: Skip EXPLICIT entirely (promote EMPTY $\rightarrow$ SPARSE).
      • 1-18: Promote at $2^{\text{expthresh}-1}$ cardinality.
    • sparseon: (Integer, 0 or 1) Enables (1) or disables (0) the SPARSE representation. If disabled, EMPTY promotes directly to FULL.
  10. Basic usage of `hll` (Hello World)

    master

    To use hll, you must initialize an empty set, hash your input values, and then add them to the set.

    Important: You cannot add raw integers or strings directly to an hll object. You must use the provided hashing functions (e.g., hll_hash_integer, hll_hash_text) to convert inputs into the hll_hashval type. Alternatively, you can cast an integer to hll_hashval if you want to bypass hashing.

    --- Make a dummy table
    CREATE TABLE helloworld (
            id              integer,
            set     hll
    );
    
    --- Insert an empty HLL
    INSERT INTO helloworld(id, set) VALUES (1, hll_empty());
    
    --- Add a hashed integer to the HLL
    UPDATE helloworld SET set = hll_add(set, hll_hash_integer(12345)) WHERE id = 1;
    
    --- Or add a hashed string to the HLL
    UPDATE helloworld SET set = hll_add(set, hll_hash_text('hello world')) WHERE id = 1;
    
    --- Get the cardinality of the HLL
    SELECT hll_cardinality(set) FROM helloworld WHERE id = 1;
  11. Aggregate unique counts in a Data Warehouse

    master

    In large fact tables, instead of using COUNT DISTINCT (which is slow), you can pre-aggregate unique users into hll columns. This allows for extremely fast queries over various time windows (days, weeks, months) using hll_union_agg and hll_cardinality.

    -- 1. Create the destination table
    CREATE TABLE daily_uniques (
      date            date UNIQUE,
      users           hll
    );
    
    -- 2. Fill it with aggregated unique statistics from a fact table
    INSERT INTO daily_uniques(date, users)
      SELECT date, hll_add_agg(hll_hash_integer(user_id))
      FROM facts
      GROUP BY 1;
    
    -- 3. Query weekly uniques
    SELECT hll_cardinality(hll_union_agg(users)) 
    FROM daily_uniques 
    WHERE date >= '2012-01-02'::date AND date <= '2012-01-08'::date;
    
    -- 4. Query using a sliding window (e.g., 7-day window)
    SELECT date, #hll_union_agg(users) OVER seven_days
    FROM daily_uniques
    WINDOW seven_days AS (ORDER BY date ASC ROWS 6 PRECEDING);