Skip to content

关于时序数据库的一个示例-用户访问统计分析

需求分析

  • 用户访问信息表包括: 访问时间,用户ID ,年龄 ,设备类型 ('手机', '电脑'),访问来源 (如:'搜索引擎', '直接访问'), 用户所在省份, 用户类型 (如: '新用户', '老用户')
  • 超表粒度: 每小时
  • 统计分析: 五分钟, 小时, 天,周
  • 分析类型: 五分钟 ,小时为实时分析 。 天,周,持续聚合
  • 更新策略: 五分钟-跨度 5-30 分钟,频率5分钟。 小时-跨度 1 - 2 小时,频率1小时 。 天 - 每天凌晨一点统计,周每周一凌晨两点统计。
  • 数据保留: 超表 一天, 视图一个月

SQL

表结构

-- 启用TimescaleDB扩展
CREATE EXTENSION IF NOT EXISTS timescaledb;

-- 创建用户访问记录表
CREATE TABLE user_visits (
    time TIMESTAMPTZ NOT NULL,      -- 访问时间
    user_id INT,                    -- 用户ID
    age INT CHECK (age BETWEEN 1 AND 100),  -- 年龄
    device VARCHAR(10) CHECK (device IN ('手机', '电脑')),  -- 设备类型
    source VARCHAR(20),             -- 访问来源 (如: '搜索引擎', '直接访问')
    province VARCHAR(20),           -- 用户所在省份
    user_type VARCHAR(10)           -- 用户类型 (如: '新用户', '老用户')
);
-- 转换为超表,按小时分块
SELECT create_hypertable(
    'user_visits',
    'time',
    chunk_time_interval => INTERVAL '1 hour',
    if_not_exists => TRUE
);

视图

-- 创建视图
-- 五分钟聚合
CREATE MATERIALIZED VIEW visits_5min
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('5 minutes', time) AS bucket,
    province,
    device,
    COUNT(*) AS visit_count
FROM user_visits
GROUP BY bucket, province, device;

-- 开启实时聚合
ALTER MATERIALIZED VIEW visits_5min set (timescaledb.materialized_only = false);

-- 小时聚合
CREATE MATERIALIZED VIEW visits_hourly
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 hour'::interval, bucket) as bucket,
    province,
    device,
    SUM(visit_count) AS visit_count 
FROM visits_5min
GROUP BY 1, 2, 3;


-- 开启实时聚合
ALTER MATERIALIZED VIEW visits_hourly set (timescaledb.materialized_only = false);

-- 天聚合
CREATE MATERIALIZED VIEW visits_daily
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 day', bucket) AS bucket,
    province,
    device,
    SUM(visit_count) AS visit_count
FROM visits_hourly
GROUP BY 1, 2, 3;

-- 周聚合
CREATE MATERIALIZED VIEW visits_weekly
WITH (timescaledb.continuous) AS
SELECT
    time_bucket('1 week', bucket) AS bucket,
    province,
    device,
    SUM(visit_count) AS visit_count
FROM visits_daily
GROUP BY 1, 2, 3;

刷新策略

-- 5分钟聚合:延迟5分钟刷新
SELECT add_continuous_aggregate_policy(
    'visits_5min',
    start_offset => INTERVAL '15 minutes',
    end_offset => INTERVAL '5 minutes',
    schedule_interval => INTERVAL '5 minutes'
);

-- 小时聚合:延迟1小时刷新
SELECT add_continuous_aggregate_policy(
    'visits_hourly',
    start_offset => INTERVAL '3 hours',
    end_offset => INTERVAL '1 hour',
    schedule_interval => INTERVAL '1 hour'
);

-- 天/周聚合:每天凌晨刷新
SELECT add_continuous_aggregate_policy(
    'visits_daily',
    start_offset => INTERVAL '25 days',
    end_offset => INTERVAL '1 hour',
    schedule_interval => INTERVAL '24 hours',
    initial_start => '2023-01-01 01:00:00'
);

SELECT add_continuous_aggregate_policy(
    'visits_weekly',
    start_offset => INTERVAL '8 days',     -- 确保覆盖完整周+1天缓冲
    end_offset => INTERVAL '1 day',        -- 当前时间-1天=周日午夜
    schedule_interval => INTERVAL '1 week', -- 每周执行一次
    initial_start => '2023-01-02 01:00:00+08' -- 指定周日凌晨1点执行(带时区)
);

压缩策略

-- 压缩策略:1天前的数据转为列存
ALTER TABLE user_visits SET (
    timescaledb.compress,
    timescaledb.compress_segmentby = 'province, device',
    timescaledb.compress_orderby = 'time DESC'
);

SELECT add_compression_policy(
    'user_visits',
    compress_after => INTERVAL '1 day'
);

-- 数据保留策略:超表保留7天,物化视图保留30天
SELECT add_retention_policy('user_visits', INTERVAL '7 days');
SELECT add_retention_policy('visits_5min', INTERVAL '30 days');
SELECT add_retention_policy('visits_hourly', INTERVAL '30 days');
SELECT add_retention_policy('visits_daily', INTERVAL '30 days');
SELECT add_retention_policy('visits_weekly', INTERVAL '30 days');

手动刷新策略

call refresh_continuous_aggregate('visits_5min',);

SELECT refresh_continuous_aggregate('visits_hourly');

SELECT refresh_continuous_aggregate('visits_daily','2025-03-30 17:00:00+08','2025-04-30 17:00:00+08');