PgSQL · 实战经验 · 旋转门压缩算法在PostgreSQL中的实现

简介: 背景在物联网、监控、传感器、金融等应用领域,数据在时间维度上流式的产生,而且数据量非常庞大。例如我们经常看到的性能监控视图,就是很多点在时间维度上描绘的曲线。又比如金融行业的走势数据等等。 我们想象一下,如果每个传感器或指标每100毫秒产生1个点,一天就是864000个点。而传感器或指标是非常多的,例如有100万个传感器或指标,一天的量就接近一亿的量。假设我们要描绘一个时间

背景

在物联网、监控、传感器、金融等应用领域,数据在时间维度上流式的产生,而且数据量非常庞大。

例如我们经常看到的性能监控视图,就是很多点在时间维度上描绘的曲线。

又比如金融行业的走势数据等等。
screenshot

我们想象一下,如果每个传感器或指标每100毫秒产生1个点,一天就是864000个点。

而传感器或指标是非常多的,例如有100万个传感器或指标,一天的量就接近一亿的量。

假设我们要描绘一个时间段的图形,这么多的点,渲染估计都要很久。

那么有没有好的压缩算法,即能保证失真度,又能很好的对数据进行压缩呢?

旋转门压缩算法原理

旋转门压缩算法(SDT)是一种直线趋势化压缩算法,其本质是通过一条由起点和终点确定的直线代替一系列连续数据点。

该算法需要记录每段时间间隔长度、起点数据和终点数据, 前一段的终点数据即为下一段的起点数据。

其基本原理较为简单, 参见图。

ec9c8725c8278f69c7dc7583b5b1f1ca2cba0673

8fff584de2bc090714ad56d33b708771601a6633

第一个数据点a上下各有一点,它们与a点之间的距离为E(即门的宽度), 这两个点作为“门”的两个支点。

当只有第一个数据点时,两扇门都是关闭的;随着点数越来越多,门将逐步打开;注意到每扇门的宽度是可以伸缩的,在一段时间间隔里面,门一旦打开就不能闭;

只要两扇门未达到平行,或者说两个内角之和小于180°(本文的算法将利用这一点进行判断),这种“转门”操作即可继续进行。

图中第一个时间段是从a到e, 结果是用a点到e点之间的直线代替数据点(a,b,c,d,e); 起到了可控失真(E)的压缩作用。

第二个时间间隔从e点开始,开始时两扇门关闭,然后逐步打开,后续操作与前一段类似。

在PostgreSQL中实现旋转门压缩算法

通过旋转门算法的原理,可以了解到,有几个必要的输入项。

  • 有x坐标和y坐标的点(如果是时间轴上的点,可以通过epoch转换成这种形式)

  • E,即门的宽度,起到了控制压缩失真度的作用

例子

创建测试表

create table tbl(id int, -- ID,可有可无
val numeric, -- 值(如传感器或金融行业的点值)
t timestamp  -- 取值时间戳
);

插入10万条测试数据

insert into tbl select generate_series(1,100000), round((random()*100)::numeric, 2), clock_timestamp()+(generate_series(1,100000) || ' second')::interval ; 

test=> select * from tbl limit 10;
 id |  val  |             t              
----+-------+----------------------------
  1 | 31.79 | 2016-08-12 23:22:27.530318
  2 | 18.23 | 2016-08-12 23:22:28.530443
  3 |  5.14 | 2016-08-12 23:22:29.530453
  4 | 90.25 | 2016-08-12 23:22:30.530459
  5 |  8.17 | 2016-08-12 23:22:31.530465
  6 | 97.43 | 2016-08-12 23:22:32.53047
  7 | 17.41 | 2016-08-12 23:22:33.530476
  8 |  0.23 | 2016-08-12 23:22:34.530481
  9 | 84.67 | 2016-08-12 23:22:35.530487
 10 | 16.37 | 2016-08-12 23:22:36.530493
(10 rows)

时间如何转换成X轴的数值,假设每1秒为X坐标的1个单位

test=> select (extract(epoch from t)-extract(epoch from first_value(t) over())) / 1 as x,  -- 除以1秒为1个单位
val, t from tbl limit 100;
        x         |  val  |             t              
------------------+-------+----------------------------
                0 | 31.79 | 2016-08-12 23:22:27.530318
 1.00012493133545 | 18.23 | 2016-08-12 23:22:28.530443
 2.00013494491577 |  5.14 | 2016-08-12 23:22:29.530453
 3.00014090538025 | 90.25 | 2016-08-12 23:22:30.530459
 4.00014686584473 |  8.17 | 2016-08-12 23:22:31.530465
 5.00015187263489 | 97.43 | 2016-08-12 23:22:32.53047
 6.00015807151794 | 17.41 | 2016-08-12 23:22:33.530476
 7.00016307830811 |  0.23 | 2016-08-12 23:22:34.530481
 8.00016903877258 | 84.67 | 2016-08-12 23:22:35.530487

编写实现螺旋门算法的函数

create or replace function f (
  i_radius numeric,       --  压缩半径
  i_time timestamp,       --  开始时间
  i_interval_s numeric,   --  时间转换间隔 (秒,例如每5秒在坐标上表示1个单位间隔,则这里使用5) 
  OUT o_val numeric,      --  值,纵坐标 y  (跳跃点y)
  OUT o_time timestamp,   --  时间,横坐标 x (跳跃点x)
  OUT o_x numeric         -- 跳跃点x, 通过 o_time 转换
)
returns setof record as $$
declare
  v_time timestamp;       -- 时间变量
  v_x numeric;            -- v_time 转换为v_x
  v_val numeric;          -- y坐标
  v1_time timestamp;      -- 前一点 时间变量
  v1_x numeric;           -- 前一点 v_time 转换为v_x
  v1_val numeric;         -- 前一点 y坐标
  v_start_time numeric;   -- 记录第一条的时间坐标, 用于计算x偏移量
  v_rownum int8;          -- 用于标记是否第一行
  v_max_angle1 numeric;   -- 最大上门夹角角度
  v_max_angle2 numeric;   -- 最大下门夹角角度
  v_angle1 numeric;       -- 上门夹角角度
  v_angle2 numeric;       -- 下门夹角角度
begin
  for v_rownum, v_time , v_val in select row_number() over(), t, val from tbl where t>i_time order by t limit 100 -- 这条QUERY可以做成execute的动态QUERY,本文略
  LOOP
    -- 第一行,第一个点,是实际要记录的点位
    if v_rownum=1 then 
      v_start_time := extract(epoch from v_time);  
      v_x := 0;  
      o_val := v_val;  
      o_time := v_time;  
      o_x := v_x;  
      -- raise notice 'rownum=1 %, %', o_val,o_time;
      return next;  -- 返回第一个点  
    else
      v_x := (extract(epoch from v_time) - v_start_time) / i_interval_s;  -- 生成X坐标
      SELECT 180-ST_Azimuth(
                              ST_MakePoint(o_x, o_val+i_radius),    -- 门上点
                              ST_MakePoint(v_x, v_val)              -- next point
                           )/(2*pi())*360 as degAz,                 -- 上夹角
                 ST_Azimuth(
                              ST_MakePoint(o_x, o_val-i_radius),    -- 门下点
                              ST_MakePoint(v_x, v_val)              -- next point
                           )/(2*pi())*360 As degAzrev               -- 下夹角
      INTO v_angle1, v_angle2; 
          
      select GREATEST(v_angle1, v_max_angle1), GREATEST(v_angle2, v_max_angle2) into v_max_angle1, v_max_angle2;

      if (v_max_angle1 + v_max_angle2) >= 180 then  -- 找到四边形外的点位,输出上一个点,并从上一个点开始重新计算四边形
        -- raise notice 'max1 %, max2 %', v_max_angle1 , v_max_angle2;
        -- 复原
	v_angle1 := 0;
        v_max_angle1 := 0;
        v_angle2 := 0;
        v_max_angle2 := 0;

        -- 门已完全打开,输出前一个点的值
	o_val := v1_val; 
        o_time := v1_time; 
        v1_x := (extract(epoch from v1_time) - v_start_time) / i_interval_s;  -- 生成前一个点的X坐标 
        o_x := v1_x; 

        -- 用新的门,与当前点计算新的夹角 
        SELECT 180-ST_Azimuth(
                                ST_MakePoint(o_x, o_val+i_radius),    -- 门上点
                                ST_MakePoint(v_x, v_val)              -- next point
                             )/(2*pi())*360 as degAz,                 -- 上夹角
                   ST_Azimuth(
                                ST_MakePoint(o_x, o_val-i_radius),    -- 门下点
                                ST_MakePoint(v_x, v_val)              -- next point
                             )/(2*pi())*360 As degAzrev               -- 下夹角
        INTO v_angle1, v_angle2;

        select GREATEST(v_angle1, v_max_angle1), GREATEST(v_angle2, v_max_angle2) into v_max_angle1, v_max_angle2; 

        -- raise notice 'new max %, new max %', v_max_angle1 , v_max_angle2;

        -- raise notice 'rownum<>1 %, %', o_val, o_time;

	return next;
      end if; 

      -- 记录当前值,保存作为下一个点的前点
      v1_val := v_val; 
      v1_time := v_time; 
    end if; 
  END LOOP; 
end; 
$$ language plpgsql strict;

压缩测试

门宽为15,起始时间为’2016-08-12 23:22:27.530318’,每1秒表示1个X坐标单位。

test=> select * from f(15,'2016-08-12 23:22:27.530318',1);
 o_val |           o_time           |       o_x        
-------+----------------------------+------------------
 18.23 | 2016-08-12 23:22:28.530443 |                0
  5.14 | 2016-08-12 23:22:29.530453 | 1.00001287460327
 90.25 | 2016-08-12 23:22:30.530459 | 2.00001883506775
......
 87.90 | 2016-08-12 23:24:01.53098  | 93.0005400180817
 29.94 | 2016-08-12 23:24:02.530985 | 94.0005450248718
 63.53 | 2016-08-12 23:24:03.53099  | 95.0005497932434
 12.25 | 2016-08-12 23:24:04.530996 | 96.0005559921265
 83.21 | 2016-08-12 23:24:05.531001 | 97.0005609989166
(71 rows)

可以看到100个点,压缩成了71个点。

对比一下原来的100个点的值

test=> select val, t, (extract(epoch from t)-extract(epoch from first_value(t) over()))/1 as x from tbl where t>'2016-08-12 23:22:27.530318' order by t limit 100;
  val  |             t              |        x         
-------+----------------------------+------------------
 18.23 | 2016-08-12 23:22:28.530443 |                0
  5.14 | 2016-08-12 23:22:29.530453 | 1.00001001358032
 90.25 | 2016-08-12 23:22:30.530459 |  2.0000159740448
......
 83.21 | 2016-08-12 23:24:05.531001 | 97.0005581378937
 87.97 | 2016-08-12 23:24:06.531006 | 98.0005631446838
 58.97 | 2016-08-12 23:24:07.531012 | 99.0005691051483
(100 rows)

使用excel绘图,进行压缩前后的对比

上面是压缩后的数据绘图,下面是压缩前的数据绘图

红色标记的位置,就是通过旋转门算法压缩掉的数据。

失真度是可控的。

screenshot

流式压缩的实现

本文略,其实也很简单,这个函数改一下,创建一个以数组为输入参数的函数。

以lambda的方式,实时的从流式输入的管道取数,并执行即可。

也可以写成聚合函数,在基于PostgreSQL 的流式数据库pipelineDB中调用,实现流式计算。

小结

通过旋转门算法,对IT监控、金融、电力、水利等监控、物联网、等流式数据进行实时的压缩。

数据不需要从数据库LOAD出来即可在库内完成运算和压缩。

用户也可以根据实际的需求,进行流式的数据压缩,同样数据也不需要从数据库LOAD出来,在数据库端即可完成。

PostgreSQL的功能一如既往的强大,好用,快用起来吧。

参考

  1. http://baike.baidu.com/view/3478397.htm
  2. http://postgis.net/docs/manual-2.2/ST_Azimuth.html
  3. https://www.postgresql.org/docs/devel/static/functions-conditional.html
  4. http://gis.stackexchange.com/questions/25126/how-to-calculate-the-angle-at-which-two-lines-intersect-in-postgis
  5. http://gis.stackexchange.com/questions/668/how-can-i-calculate-the-bearing-between-two-points-in-postgis
  6. http://www.pipelinedb.com/
相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍如何基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
2月前
|
算法 数据可视化 测试技术
HNSW算法实战:用分层图索引替换k-NN暴力搜索
HNSW是一种高效向量检索算法,通过分层图结构实现近似最近邻的对数时间搜索,显著降低查询延迟。相比暴力搜索,它在保持高召回率的同时,将性能提升数十倍,广泛应用于大规模RAG系统。
222 10
HNSW算法实战:用分层图索引替换k-NN暴力搜索
|
7月前
|
负载均衡 算法 关系型数据库
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
本文聚焦 MySQL 集群架构中的负载均衡算法,阐述其重要性。详细介绍轮询、加权轮询、最少连接、加权最少连接、随机、源地址哈希等常用算法,分析各自优缺点及适用场景。并提供 Java 语言代码实现示例,助力直观理解。文章结构清晰,语言通俗易懂,对理解和应用负载均衡算法具有实用价值和参考价值。
大数据大厂之MySQL数据库课程设计:揭秘MySQL集群架构负载均衡核心算法:从理论到Java代码实战,让你的数据库性能飙升!
|
2月前
|
机器学习/深度学习 缓存 算法
微店关键词搜索接口核心突破:动态权重算法与语义引擎的实战落地
本文详解微店搜索接口从基础匹配到智能推荐的技术进阶路径,涵盖动态权重、语义理解与行为闭环三大创新,助力商家提升搜索转化率、商品曝光与用户留存,实现技术驱动的业绩增长。
|
4月前
|
机器学习/深度学习 算法 文件存储
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
神经架构搜索(NAS)正被广泛应用于大模型及语言/视觉模型设计,如LangVision-LoRA-NAS、Jet-Nemotron等。本文回顾NAS核心技术,解析其自动化设计原理,探讨强化学习、进化算法与梯度方法的应用与差异,揭示NAS在大模型时代的潜力与挑战。
1135 6
神经架构搜索NAS详解:三种核心算法原理与Python实战代码
|
2月前
|
存储 人工智能 算法
从零掌握贪心算法Java版:LeetCode 10题实战解析(上)
在算法世界里,有一种思想如同生活中的"见好就收"——每次做出当前看来最优的选择,寄希望于通过局部最优达成全局最优。这种思想就是贪心算法,它以其简洁高效的特点,成为解决最优问题的利器。今天我们就来系统学习贪心算法的核心思想,并通过10道LeetCode经典题目实战演练,带你掌握这种"步步为营"的解题思维。
|
3月前
|
机器学习/深度学习 资源调度 算法
遗传算法模型深度解析与实战应用
摘要 遗传算法(GA)作为一种受生物进化启发的优化算法,在复杂问题求解中展现出独特优势。本文系统介绍了GA的核心理论、实现细节和应用经验。算法通过模拟自然选择机制,利用选择、交叉、变异三大操作在解空间中进行全局搜索。与梯度下降等传统方法相比,GA不依赖目标函数的连续性或可微性,特别适合处理离散优化、多目标优化等复杂问题。文中详细阐述了染色体编码、适应度函数设计、遗传操作实现等关键技术,并提供了Python代码实现示例。实践表明,GA的成功应用关键在于平衡探索与开发,通过精心调参维持种群多样性同时确保收敛效率
|
3月前
|
机器学习/深度学习 边缘计算 人工智能
粒子群算法模型深度解析与实战应用
蒋星熠Jaxonic是一位深耕智能优化算法领域多年的技术探索者,专注于粒子群优化(PSO)算法的研究与应用。他深入剖析了PSO的数学模型、核心公式及实现方法,并通过大量实践验证了其在神经网络优化、工程设计等复杂问题上的卓越性能。本文全面展示了PSO的理论基础、改进策略与前沿发展方向,为读者提供了一份详尽的技术指南。
粒子群算法模型深度解析与实战应用
|
10月前
|
人工智能 编解码 算法
DeepSeek加持的通义灵码2.0 AI程序员实战案例:助力嵌入式开发中的算法生成革新
本文介绍了通义灵码2.0 AI程序员在嵌入式开发中的实战应用。通过安装VS Code插件并登录阿里云账号,用户可切换至DeepSeek V3模型,利用其强大的代码生成能力。实战案例中,AI程序员根据自然语言描述快速生成了C语言的base64编解码算法,包括源代码、头文件、测试代码和CMake编译脚本。即使在编译错误和需求迭代的情况下,AI程序员也能迅速分析问题并修复代码,最终成功实现功能。作者认为,通义灵码2.0显著提升了开发效率,打破了编程语言限制,是AI编程从辅助工具向工程级协同开发转变的重要标志,值得开发者广泛使用。
8849 71
DeepSeek加持的通义灵码2.0 AI程序员实战案例:助力嵌入式开发中的算法生成革新
|
6月前
|
存储 关系型数据库 测试技术
拯救海量数据:PostgreSQL分区表性能优化实战手册(附压测对比)
本文深入解析PostgreSQL分区表的核心原理与优化策略,涵盖性能痛点、实战案例及压测对比。首先阐述分区表作为继承表+路由规则的逻辑封装,分析分区裁剪失效、全局索引膨胀和VACUUM堆积三大性能杀手,并通过电商订单表崩溃事件说明旧分区维护的重要性。接着提出四维设计法优化分区策略,包括时间范围分区黄金法则与自动化维护体系。同时对比局部索引与全局索引性能,展示后者在特定场景下的优势。进一步探讨并行查询优化、冷热数据分层存储及故障复盘,解决分区锁竞争问题。
825 2
|
存储 人工智能 自然语言处理
Delta-CoMe:清华联合OpenBMB等高校开源的新型增量压缩算法
Delta-CoMe是由清华大学NLP实验室联合OpenBMB开源社区、北京大学和上海财经大学提出的新型增量压缩算法。该算法通过结合低秩分解和低比特量化技术,显著减少了大型语言模型的存储和内存需求,同时保持了模型性能几乎无损。Delta-CoMe特别适用于处理数学、代码和多模态等复杂任务,并在推理速度上有所提升。
345 6
Delta-CoMe:清华联合OpenBMB等高校开源的新型增量压缩算法

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版
  • 推荐镜像

    更多