PostgreSQL插件之hll

举报
大象数据库 发表于 2021/02/24 09:59:22 2021/02/24
【摘要】 HyperLogLog(hll)是一个用于计算集合中不重复的元素个数问题的算法,精确的计算需要与基数成比例的内存量,这对于非常大的数据集是不实际的。概率基数估算(如HyperLogLog算法)使用的内存要比这少得多,但代价是仅获得一个基数的近似值。

背景

在现实生活中,我们通常需要统计一个集合中不重复的元素个数。在数据库中通过使用如下的SQL语句进行精确统计:

CREATE TABLE tbl(id INT, a INT);
INSERT INTO tbl VALUES(1, 1), (2, 1), (3, 2), (4, 2), (5, 2), (6, 3);
SELECT COUNT(DISTINCT a) FROM tbl;
 count 
-------
     3
(1 row)

为了计算 count-distinct 通常使用哈希或者排序的方式计算不重复元素个数。但是,如果数据量比较大,哈希或者排序消耗的内存较大。HyperLogLog 是处理这个问题的一个概率算法,但是其消耗资源较少。

postgresql-hll插件引入了一个新的数据类型hll,它是一个HyperLogLog数据结构。下面对其使用进行详细介绍。

支持版本

  1. PostgreSQL 12
  2. PostgreSQL 11
  3. PostgreSQL 10
  4. PostgreSQL 9.6
  5. PostgreSQL 9.5

安装

在华为云使用postgresql-hll,请参考:https://support.huaweicloud.cn/usermanual-rds/rds_09_0043.html 进行安装和卸载。

使用

CREATE TABLE hll_tbl (
    id      integer,
    set     hll
);

--- 插入空的HLL
INSERT INTO hll_tbl(id, set) VALUES (1, hll_empty());

--- 增加一个被散列的整数值到HLL中
UPDATE hll_tbl SET set = hll_add(set, hll_hash_integer(12345)) WHERE id = 1;

--- 增加一个被散列的字符串到HLL中
UPDATE hll_tbl SET set = hll_add(set, hll_hash_text('hello world')) WHERE id = 1;

--- 计算HLL的基数
SELECT hll_cardinality(set) FROM hll_tbl WHERE id = 1;

数据仓库示例

我们假设有一个事实表,记录了用户对我的站点的访问、他们做了什么以及他们来自哪里。表中有上亿行数据,表扫描需要几分钟(或者至少需要很多秒)。

CREATE TABLE facts (
    date            date,
    user_id         integer,
    activity_type   smallint,
    referrer        varchar(255)
);

insert into facts select timestamp '2014-01-10 10:00:00' +
       random() * (timestamp '2021-01-20 20:00:00' - timestamp '2014-01-10 10:00:00'),
       generate_series(1, 10000000), 1, 'no use';

insert into facts select timestamp '2014-01-10 10:00:00' +
       random() * (timestamp '2021-01-20 20:00:00' - timestamp '2014-01-10 10:00:00'),
       generate_series(1, 10000000), 1, 'no use';

如果想快速(毫秒级别)知道每天有多少独立用户访问了站点,可以建立一个聚合表:

CREATE TABLE daily_uniques (
    date            date UNIQUE,
    users           hll
);

INSERT INTO daily_uniques(date, users)
    SELECT date, hll_add_agg(hll_hash_integer(user_id))
    FROM facts
    GROUP BY 1;

我们首先对user_id进行散列,然后按天将这些散列后的值聚合为一个hll。现在我们可以计算每天的hll基数:

SELECT date, hll_cardinality(users) FROM daily_uniques;

如果想要得到这周独立的用户访问数呢?

SELECT hll_cardinality(hll_union_agg(users)) 
	FROM daily_uniques 
	WHERE date >= '2012-01-02'::date AND date <= '2012-01-08'::date;

操作符

插件中已经添加了一些操作符,使用hll变得不那么冗长。它们是最常用函数的简单别名。

散列函数

  • hll_hash_boolean(boolean)
  • hll_hash_smallint(smallint)
  • hll_hash_integer(integer)
  • hll_hash_bigint(bigint)
  • hll_hash_bytea(bytea)
  • hll_hash_text(text)
  • hll_hash_any(any)

示例如下:

SELECT hll_hash_boolean(TRUE);
SELECT hll_hash_boolean(TRUE, 123/*hash seed*/);
SELECT hll_hash_smallint(4::smallint);
SELECT hll_hash_smallint(4::smallint, 123/*hash seed*/);
SELECT hll_hash_integer(21474836);
SELECT hll_hash_integer(21474836, 123/*hash seed*/);
SELECT hll_hash_bigint(223372036854775808);
SELECT hll_hash_bigint(223372036854775808, 123/*hash seed*/);
SELECT hll_hash_bytea(E'\\xDEADBEEF');
SELECT hll_hash_bytea(E'\\xDEADBEEF', 123/*hash seed*/);
SELECT hll_hash_text('foobar');
SELECT hll_hash_text('foobar', 123/*hash seed*/);

SELECT hll_hash_any(123);
SELECT hll_hash_any(123, 123/*hash seed*/);

注意:hll_hash_any会动态地分派给适当的特定类型的函数,这使得它比它包装的特定类型的函数要慢。只有在不知道输入类型时才使用它。

聚集函数

如果要从表或结果集中创建一个hll,请使用hll_add_agg。这里的命名并不是特别有创意:它是一个聚合函数,将值添加到空的hll中。

SELECT date, hll_add_agg(hll_hash_integer(user_id))
    FROM facts
    GROUP BY 1;

上面的示例将为每个包含每天用户的日期提供一个hll。如果您想汇总已经存储到单个hll中的一个hll列表,请使用hll_union_agg。再次说明:它是一个聚合函数,将值合并到一个空的hll中。

SELECT EXTRACT(MONTH FROM date), hll_cardinality(hll_union_agg(users))
    FROM daily_uniques
    GROUP BY 1;

窗口是hll功能的另一个主要例子。执行滑动窗口惟一计数通常涉及一些generate_series技巧,对于已经计算过的滑动窗口则非常简单。

SELECT date, #hll_union_agg(users) OVER seven_days
    FROM daily_uniques
    WINDOW seven_days AS (ORDER BY date ASC ROWS 6 PRECEDING);
【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。