这看起来很像前面的代码块。最重要的是:它还在 site_id 上进行分片,并对分片计数和复制因子使用相同的默认配置。因为这三个都匹配,所以 http_request 分片和 http_request_1min 分片之间存在一对一的对应关系,Citus 会将匹配的分片放在同一个 worker 上。这称为; 它使诸如联接(join)之类的查询更快,并使我们的汇总成为可能。
协同定位(co-location)
为了填充 http_request_1min,我们将定期运行 INSERT INTO SELECT。 这是可能的,因为这些表位于同一位置。为方便起见,以下函数将汇总查询包装起来。
-- single-row table to store when we rolled up last CREATE TABLE latest_rollup ( minute timestamptz PRIMARY KEY, -- "minute" should be no more precise than a minute CHECK (minute = date_trunc('minute', minute)) ); -- initialize to a time long ago INSERT INTO latest_rollup VALUES ('10-10-1901'); -- function to do the rollup CREATE OR REPLACE FUNCTION rollup_http_request() RETURNS void AS $$ DECLARE curr_rollup_time timestamptz := date_trunc('minute', now()); last_rollup_time timestamptz := minute from latest_rollup; BEGIN INSERT INTO http_request_1min ( site_id, ingest_time, request_count, success_count, error_count, average_response_time_msec ) SELECT site_id, date_trunc('minute', ingest_time), COUNT(1) as request_count, SUM(CASE WHEN (status_code between 200 and 299) THEN 1 ELSE 0 END) as success_count, SUM(CASE WHEN (status_code between 200 and 299) THEN 0 ELSE 1 END) as error_count, SUM(response_time_msec) / COUNT(1) AS average_response_time_msec FROM http_request -- roll up only data new since last_rollup_time WHERE date_trunc('minute', ingest_time) <@ tstzrange(last_rollup_time, curr_rollup_time, '(]') GROUP BY 1, 2; -- update the value in latest_rollup so that next time we run the -- rollup it will operate on data newer than curr_rollup_time UPDATE latest_rollup SET minute = curr_rollup_time; END; $$ LANGUAGE plpgsql;上述函数应该每分钟调用一次。您可以通过在 coordinator 节点上添加一个 crontab 条目来做到这一点:
* * * * * psql -c 'SELECT rollup_http_request();'或者,诸如 pg_cron 之类的扩展允许您直接从数据库安排周期性查询。
pg_cron
https://github.com/citusdata/pg_cron
之前的仪表板查询现在好多了:
SELECT site_id, ingest_time as minute, request_count, success_count, error_count, average_response_time_msec FROM http_request_1min WHERE ingest_time > date_trunc('minute', now()) - '5 minutes'::interval; 过期的旧数据汇总使查询更快,但我们仍然需要使旧数据过期以避免无限的存储成本。 只需决定您希望为每个粒度保留数据多长时间,然后使用标准查询删除过期数据。 在以下示例中,我们决定将原始数据保留一天,将每分钟的聚合保留一个月:
DELETE FROM http_request WHERE ingest_time < now() - interval '1 day'; DELETE FROM http_request_1min WHERE ingest_time < now() - interval '1 month';在生产中,您可以将这些查询包装在一个函数中,并在 cron job 中每分钟调用一次。
通过在 Citrus 哈希分布之上使用表范围分区,数据过期可以更快。 有关详细示例,请参阅部分。
时间序列数据
这些是基础!我们提供了一种架构,可以摄取 HTTP 事件,然后将这些事件汇总到它们的预聚合形式中。 这样,您既可以存储原始事件,也可以通过亚秒级查询为您的分析仪表板提供动力。
接下来的部分将扩展基本架构,并向您展示如何解决经常出现的问题。
近似不同计数HTTP 分析中的一个常见问题涉及:上个月有多少独立访问者访问了您的网站?准确地回答这个问题需要将所有以前见过的访问者的列表存储在汇总表中,这是一个令人望而却步的数据量。然而,一个近似的答案更易于管理。
近似的不同计数
一种称为 hyperloglog 或 HLL 的数据类型可以近似地回答查询; 要告诉您一个集合中大约有多少个独特元素,需要的空间非常小。其精度可以调整。 我们将使用仅使用 1280 字节的那些,将能够以最多 2.2% 的错误计算多达数百亿的唯一访问者。