医疗大健康行业案例(老人健康实时监测和预警) - 阿里云RDS PostgreSQL最佳实践

标签

PostgreSQL , pipelineDB , 流式计算 , 独立事件相关性 , 舆情分析 , 实时状态分析 , 递归查询 , 时序数据


背景

人的身体和机器差不多,随着年龄的增长,器官逐渐老化,毛病也会越来越多,注意保养是一方面,另一方面也需要注意实时的监测和发出预警,在问题萌芽状态就解决掉。

以往我们检查身体得去医院或专业的体检机构,很麻烦,随着科技的进步,一些健康指标的监测变得更加方便,例如手环也是一个普及很快的监控检测终端(目前已能够检测心跳、温度、运动等各项指标),未来这种终端能实时检测的项目会越来越多。

如果手环算民用领域的健康检测范畴,那么在专业领域,比如一些医院或养老院,他们有更多的传感器,视频,空间检测等手段,可以对医院或养老院的病人或老人进行更多指标的实时监测。

例如:

1、一个人的行为轨迹,在某个范围不动,持续多久预警。

2、一个人在床的空间,躺了12个小时以上,预警。

3、一个人高度低于40公分,持续5分钟,预警(周围有人,不预警)。(比如高血压、小偷)

4、一个人在马桶的狭小空间,超过30分钟,预警。

以上都有传感器支持这样的数据采集。

架构

数据流分为5个部分:

1、数据(来自传感器)

2、数据转换(格式化、状态化)

可选步骤,数据转换的目的是让数据更容易被识别和处理,可以使用三种方法对数据进行实时转换。

2.1、转换规则定义在数据库的UDF中,通过触发器或规则进行入库时实时转换。

2.2、转换规则定义在数据库的UDF中,通过pipeline Transforms进行转换。

2.3、在应用层进行转换,转换后的数据再入库。

3、定义规则。

定义预警规则,例如前面提到的:

一个人在马桶的狭小空间,超过30分钟,预警。

为了简化访问接口,可以将规则定义到数据库的UDF中,通过视图进行展示。

4、实时规则查询。

查询定义的规则,并预警。

5、预警。

优化思路

1、同一份数据(一条记录可能包含高度、位置、心率等多个维度的属性),可能在多个规则维度上进行计算,例如在心率上有一个预警规则,在位置上又有预警规则。

减少数据扫描量是一个比较通用的优化方法,9.6内核层面也使用了类似的优化方法。

《PostgreSQL 9.6 内核优化之 聚合代码优化OP复用浅析》

但是本例更加复杂,因为不同的规则,涉及的记录范围可能不一样,例如在床上这个空间维度可能12小时才预警(可能有上千条记录),而在高度这个维度,可能5分钟就需要预警。

2、同一个传感器的数据,尽量独立存放,减少数据扫描成本。也就是说每个传感器一张表。类似的优化方法参考

《PostgreSQL 时序最佳实践 - 证券交易系统数据库设计 - 阿里云RDS PostgreSQL最佳实践》

3、由于预警并不需要保留所有记录,可以使用rotate的方式,将历史数据导出到OSS外部表。(偶尔需要查明细时,可以查询,需要海量分析时,可以直接对接HybridDB for PostgreSQL进行分析)

《PostgreSQL 数据rotate用法介绍 - 按时间覆盖历史数据》

4、动态流计算规则,减少计算量。例如当用户进入某个状态后,才触发对应的规则。例如用户进入马桶的空间后,才进行马桶空间内的流计算规则运算。

DEMO

以这个例子为例,讲一下如何预警:

一个人在马桶的狭小空间,超过30分钟,预警。

DEMO就不搞这么复杂,不考虑优化因素。

建表

create table sensor_info(
  sid int,   -- 传感器ID
  pos point, -- 传感器的相对坐标
  crt_time timestamp  -- 上传时间
  -- 其他属性略,为演示方便。
);  

create index idx_sensor_info on sensor_info (sid,crt_time desc);  

create table userinfo (
  uid  -- 用户和传感器的对应关系表,略
  sid
);  

create table statistic_obj_info (
  objid  int,    -- 静态对象空间信息,例如床、马桶
  pos_range box  -- 对象的空间范围,如果使用postgis,请使用geometry来表示一个区间。
);

生成数据

insert into sensor_info select random()*1000, point(trunc((random()*10)::numeric,2), trunc((random()*10)::numeric,2)), now()+(id||' second')::interval from generate_series(1,10000000) t(id);  

postgres=# select * from sensor_info limit 10;
 sid |     pos     |          crt_time
-----+-------------+----------------------------
 888 | (1.43,5.58) | 2017-07-31 17:23:04.620488
 578 | (5.6,2.01)  | 2017-07-31 17:23:05.620488
 186 | (6.98,9.91) | 2017-07-31 17:23:06.620488
  99 | (4.1,7.46)  | 2017-07-31 17:23:07.620488
  30 | (6.25,6.07) | 2017-07-31 17:23:08.620488
 403 | (5.12,6.26) | 2017-07-31 17:23:09.620488
  60 | (9.8,8)     | 2017-07-31 17:23:10.620488
 654 | (1.83,5.41) | 2017-07-31 17:23:11.620488
 731 | (5.72,4.67) | 2017-07-31 17:23:12.620488
 230 | (4.99,8.3)  | 2017-07-31 17:23:13.620488
(10 rows)
postgres=# select * from sensor_info where sid=1 order by crt_time desc limit 10;
 sid |     pos     |          crt_time
-----+-------------+----------------------------
   1 | (9.83,6.18) | 2017-11-24 10:40:35.620488
   1 | (3.18,9.82) | 2017-11-24 10:39:30.620488
   1 | (1.79,6.24) | 2017-11-24 10:35:15.620488
   1 | (3.13,8.42) | 2017-11-24 10:21:35.620488
   1 | (5.11,4.17) | 2017-11-24 10:09:22.620488
   1 | (9.51,3.41) | 2017-11-24 10:04:00.620488
   1 | (2.24,2.35) | 2017-11-24 09:50:33.620488
   1 | (7.2,8.67)  | 2017-11-24 09:44:18.620488
   1 | (2.32,4.48) | 2017-11-24 08:45:22.620488
   1 | (0.33,9.33) | 2017-11-24 08:44:50.620488
(10 rows)

定义规则

一个人在马桶的狭小空间,超过30分钟,预警。

每个马桶都定义一个相对坐标区间,当感应到用户进入到某个区间后,对老人进行对应规则的监测。

几何操作请参考

https://www.postgresql.org/docs/10/static/functions-geometry.html

或者

http://postgis.net/documentation/

使用UDF定义规则,输出一个JSON。

create or replace function matong_rule(
  v_sid int,   -- 传感器ID
  pos_range box,  -- 空间区间 , 如果使用postgis,请使用geometry来标注一个空间
  ts interval     -- 持续时间,采用interval类型
) returns jsonb as $$
declare
  v sensor_info;  -- 临时类型
  e timestamp;    -- 最后时间
  s timestamp;    -- 最前时间
begin
  for v in select * from sensor_info_1 where sid=v_sid order by crt_time desc
  loop
    if pos_range @> v.pos then
      if e is null then e := v.crt_time; end if;
      s := v.crt_time;
    else
      exit;
    end if;
  end loop;  

  if e-s >= ts then
     return jsonb_build_object('sid', v_sid, 'pos_range', pos_range, 'start_time', s, 'end_time', e, 'interval', e-s);
  else
    return null;
  end if;
end;
$$ language plpgsql strict;

例子,

1为老人身上的传感器ID,中间的BOX为马桶的区间范围,第三个参数为持续时间。

当探测到老人进入了某个需要监测的静态对象空间(例如进入床的空间、进入马桶的空间)时,触发以上规则,进行以上规则的查询。

postgres=# select matong_rule(1,'(10,10),(0,0)','1 sec');
                                                                           matong_rule
-----------------------------------------------------------------------------------------------------------------------------------------------------------------
 {"sid": 1, "end_time": "2017-11-24T10:40:35.620488", "interval": "115 days 17:08:19", "pos_range": "(10,10),(0,0)", "start_time": "2017-07-31T17:32:16.620488"}
(1 row)  

Time: 23.200 ms
postgres=# select matong_rule(1,'(10,10),(1,1)','1 sec');
                                                                      matong_rule
--------------------------------------------------------------------------------------------------------------------------------------------------------
 {"sid": 1, "end_time": "2017-11-24T10:40:35.620488", "interval": "01:55:13", "pos_range": "(10,10),(1,1)", "start_time": "2017-11-24T08:45:22.620488"}
(1 row)  

Time: 11.157 ms
postgres=# select matong_rule(1,'(10,10),(2,2)','1 sec');
                                                                      matong_rule
--------------------------------------------------------------------------------------------------------------------------------------------------------
 {"sid": 1, "end_time": "2017-11-24T10:40:35.620488", "interval": "00:01:05", "pos_range": "(10,10),(2,2)", "start_time": "2017-11-24T10:39:30.620488"}
(1 row)  

Time: 11.325 ms
postgres=# select matong_rule(1,'(10,10),(3,3)','1 sec');
                                                                      matong_rule
--------------------------------------------------------------------------------------------------------------------------------------------------------
 {"sid": 1, "end_time": "2017-11-24T10:40:35.620488", "interval": "00:01:05", "pos_range": "(10,10),(3,3)", "start_time": "2017-11-24T10:39:30.620488"}
(1 row)  

Time: 11.019 ms

实际上传感器上传的可能并不是相对坐标,而是直接上传静态对象(马桶、床、。。。)的唯一标识,(例如对静态对象全部编号,并且有对应的传感器可以感应到有老人存在这个空间内,并实时上报监控数据。)

这样就更加简单了,连区间判断都不需要,也是更加优雅的做法。但是坏处就是,老人离开监控区间的话,就无法被监控了,还是得靠老人身上的传感器。

这两种形态的总结:

1、轻终端,重服务端。

终端的能力较弱,只是基本的数据采集,全部送到服务端进行计算。

2、重终端,轻服务端。

终端能力较强,可以进行数据采集,并且有部分数据计算能力,同时在大楼内增设一层静态终端(如床、马桶),对进入这个空间的老人(传感器)进行联动,上报更加精准的判断数据。

查询规则

输出所有用户的状态。

例如,查询传感器ID 1-100的老人,在厕所空间内活动的状态。

postgres=# select matong_rule(id ,'(10,10),(3,3)','1 sec') from generate_series(1,10) t(id);
                                                                      matong_rule
--------------------------------------------------------------------------------------------------------------------------------------------------------
 {"sid": 1, "end_time": "2017-11-24T10:40:35.620488", "interval": "00:01:05", "pos_range": "(10,10),(3,3)", "start_time": "2017-11-24T10:39:30.620488"}
 {"sid": 2, "end_time": "2017-11-24T10:43:06.620488", "interval": "00:10:36", "pos_range": "(10,10),(3,3)", "start_time": "2017-11-24T10:32:30.620488"}  

(10 rows)  

Time: 115.501 ms

预警

有记录返回则预警。

对于PostgreSQL,还有一个很有意思的功能,异步消息,可以用于异步的预警。

《从电波表到数据库小程序之 - 数据库异步广播(notify/listen)》

《从微信小程序 到 数据库"小程序" , 鬼知道我经历了什么》

《[转载]postgres+socket.io+nodejs实时地图应用实践》

而如果你使用的是pipelinedb,那么也可以选择transform的实时预警功能。

http://docs.pipelinedb.com/continuous-transforms.html#built-in-transform-triggers

CREATE TABLE t (user text, value int);

CREATE OR REPLACE FUNCTION insert_into_t()
  RETURNS trigger AS
  $$
  BEGIN
    INSERT INTO t (user, value) VALUES (NEW.user, NEW.value);
    RETURN NEW;
  END;
  $$
  LANGUAGE plpgsql;

CREATE CONTINUOUS TRANSFORM ct AS
  SELECT user::text, value::int FROM stream WHERE value > 100
  THEN EXECUTE PROCEDURE insert_into_t();

活动大盘

轨迹查询,老人在每个场所停留的时间,同一个地方多条记录,在绘制大盘时,只保留两条(到达和离开的时间)。

方法如下。

《车联网案例,轨迹清洗 - 阿里云RDS PostgreSQL最佳实践 - 窗口函数》

优化点1介绍

前面提到了一个关于IO放大的优化,将所有的传感器的数据分开存放,可以将IO放大的问题完全消除。我们看看经过IO消除后的性能如何:

postgres=#       create table sensor_info_1 (like sensor_info including all);
CREATE TABLE
Time: 1.765 ms
postgres=# insert into sensor_info_1 select * from sensor_info where sid=1;
INSERT 0 9835
Time: 39.805 ms
postgres=# \d sensor_info_1
                     Table "postgres.sensor_info_1"
  Column  |            Type             | Collation | Nullable | Default
----------+-----------------------------+-----------+----------+---------
 sid      | integer                     |           |          |
 pos      | point                       |           |          |
 crt_time | timestamp without time zone |           |          |
Indexes:
    "sensor_info_1_sid_crt_time_idx" btree (sid, crt_time DESC)  

postgres=# create or replace function matong_rule(
postgres(#   v_sid int,   -- 传感器ID
postgres(#   pos_range box,  -- 空间区间
postgres(#   ts interval     -- 持续时间,采用interval类型
postgres(# ) returns jsonb as $$
postgres$# declare
postgres$#   v sensor_info;  -- 临时类型
postgres$#   e timestamp;    -- 最后时间
postgres$#   s timestamp;    -- 最前时间
postgres$# begin
postgres$#   for v in select * from sensor_info_1 where sid=v_sid order by crt_time desc
postgres$#   loop
postgres$#     if pos_range @> v.pos then
postgres$#       if e is null then e := v.crt_time; end if;
postgres$#       s := v.crt_time;
postgres$#     else
postgres$#       exit;
postgres$#     end if;
postgres$#   end loop;
postgres$#
postgres$#   if e-s >= ts then
postgres$#      return jsonb_build_object('sid', v_sid, 'pos_range', pos_range, 'start_time', s, 'end_time', e, 'interval', e-s);
postgres$#   else
postgres$#     return null;
postgres$#   end if;
postgres$# end;
postgres$# $$ language plpgsql strict;
CREATE FUNCTION
Time: 0.469 ms
postgres=# select matong_rule(1,'(10,10),(3,3)','1 sec');
                                                                      matong_rule
--------------------------------------------------------------------------------------------------------------------------------------------------------
 {"sid": 1, "end_time": "2017-11-24T10:40:35.620488", "interval": "00:01:05", "pos_range": "(10,10),(3,3)", "start_time": "2017-11-24T10:39:30.620488"}
(1 row)  

Time: 0.620 ms

相比一次查询11毫秒,提升到了0.6毫秒。

小结

养老院是医疗大健康的典型案例之一,里面涉及到大量的空间数据、时间数据、大量的规则。需要一个功能强大的数据库来支撑,否则所有数据都要搬运到APP层面处理,效率低下。

PostgreSQL很好的支撑了养老院老人健康实时检测,健康报告、轨迹查询等场景的应用,同时历史的传感器数据通过OSS可以和HybridDB for PostgreSQL打通,实现一站时预警、分析需求。

参考

《PostgreSQL 9.6 内核优化之 聚合代码优化OP复用浅析》

《车联网案例,轨迹清洗 - 阿里云RDS PostgreSQL最佳实践 - 窗口函数》

《潘金莲改变了历史之 - PostgreSQL舆情事件分析应用》

《数据入库实时转换 - trigger , rule》

《PostgreSQL 时序最佳实践 - 证券交易系统数据库设计 - 阿里云RDS PostgreSQL最佳实践》

《PostgreSQL 数据rotate用法介绍 - 按时间覆盖历史数据》

《数据保留时间窗口的使用》

时间: 2024-10-02 10:53:47

医疗大健康行业案例(老人健康实时监测和预警) - 阿里云RDS PostgreSQL最佳实践的相关文章

车联网案例,轨迹清洗 - 阿里云RDS PostgreSQL最佳实践 - 窗口查询

标签 PostgreSQL , 窗口函数 , 车联网 , 轨迹 , 轨迹清洗 , lag , lead 背景 车联网中一个非常典型的场景是采集车辆的行驶轨迹,通常来说车辆的轨迹并不会实时上报,可能会堆积若干条轨迹记录,或者间隔多少时间上报一次. 一个典型的数据结构如下 (car_id, pos geometry, crt_time timestamp) 车辆在行驶,行驶过程中会遇到堵车,红绿灯,那么上报的轨迹记录可能是这样的 1, 位置1, '2017-01-01 12:00:00' 1, 位置

阿里云RDS PostgreSQL OSS 外部表 - 并行写提速案例

标签 PostgreSQL , oss对象存储 , 阿里云RDS PG , 并行写 , dblink , 异步调用 , 异步任务监控 , OSS外部表 , 数据传输 背景 阿里云RDS PostgreSQL.HybridDB for PostgreSQL提供了一个非常强大的功能,OSS对象存储外部表. 阿里云的RDS PostgreSQL用户可以利用OSS存储冷数据(OSS外部表的形态呈现),实现冷热分离:也可以利用OSS作为数据的中转桥梁,打通其他云端业务,例如HDB FOR PostgreS

基于 阿里云 RDS PostgreSQL 打造实时用户画像推荐系统

标签 PostgreSQL , 实时推荐系统 , 用户画像 , 标签 , TAG , 比特位 背景 用户画像在市场营销的应用重建中非常常见,已经不是什么新鲜的东西,比较流行的解决方案是给用户贴标签,根据标签的组合,圈出需要的用户. 通常画像系统会用到宽表,以及分布式的系统. 宽表的作用是存储标签,例如每列代表一个标签. 但实际上这种设计不一定是最优或唯一的设计,本文将以PostgreSQL数据库为基础,给大家讲解一下更加另类的设计思路,并且看看效率如何. 业务场景模拟 假设有一个2B的实时用户推

云端海量任务调度系统数据库设计 - 阿里云RDS PostgreSQL案例

标签 PostgreSQL , 任务调度系统 , 数据库设计 , schemaless 背景 任务调度系统中的任务状态管理,通常会用到数据库来存储任务调度的过程状态,控制任务的锁等. <advisory lock 实现高并发非堵塞式 业务锁> 如果是小量任务,是挺好实现的,但是每小时处理几十亿或者几亿的任务,如何设计这样的任务状态管理数据库呢? 挑战 对于一个面向多个用户的任务调度平台(例如云端的任务调度平台,将面向所有租户使用). 较大的挑战是任务数据的写入(海量),另一个是任务状态的更新(

阿里云RDS PG实践 - 流式标签 - 万亿级,实时任意标签圈人

标签 PostgreSQL , 阅后即焚 , 流计算 , 标签 背景 varbitx是阿里云RDS PG提供的一个BIT操作插件,使用这个插件已经成功的帮助用户提供了万亿级的毫秒级实时圈人功能. <阿里云RDS for PostgreSQL varbitx插件与实时画像应用场景介绍> <基于 阿里云 RDS PostgreSQL 打造实时用户画像推荐系统(varbitx)> 结合阅后即焚的流式批量处理,schemaless UDF,可以实现高效的增.删标签,以及毫秒级别的按标签圈人

海量实时计算+OLTP+OLAP DB设计 - 阿里云(RDS、HybridDB) for PostgreSQL最佳实践 - 泛电网系统应用

标签 PostgreSQL , 国家电网 , 电表 , 余额 , 流式计算 , 状态监测 , 上下文相关 背景 电网系统是一个关系民生,又非常典型的传统系统,虽然传统,量可不小.在互联网化(物联网化)的今天,有很多值得借鉴和思考的点供给其他相关系统参考. 每个省份大概有亿级户电表,最大的地市可能有千万户级别. 以往我们电费是怎么交的呢?我们小区是两个月交一次,也就是说先消费,再付款的方式.这么说起来电网真的是很仁义啊,现在哪有这么多先消费再付款的呀.移动话费.家庭宽带.天然气等等,都是充值后使用

PgSQL · 应用案例 · 阿里云RDS金融数据库(三节点版) - 背景篇

背景 提到金融级数据库,大家可能不约而同的会想到Oracle,DB2等商业数据库.但是随着开源数据库的发展,开源数据库正在逐渐成为数据库产业的核心,比如MySQL.PostgreSQL数据库 ,已经深入阿里.平安科技.苏宁.高德.国家电网(还有很多)的核心.可以看到,不管是MySQL还是PostgreSQL,有越来越多成功的核心应用案例. 目前还有一些金融企业核心数据库依旧是老牌的商业数据库,个人认为并不是这些商业数据库比开源数据库有多优秀,而是牵一发而动全身,非单纯技术层面的问题.特别是关系民

阿里云RDS金融数据库(三节点版) - 案例篇

标签 PostgreSQL , MySQL , 三节点版 , 金融数据库 , Raft , 分布式共享存储版 背景 土豆哪里去挖? 土豆郊区去挖. 一挖一麻袋? 一挖一麻袋. 挖掘机不仅是城市建设的必备法宝,居然也是挖土豆利器. 但是科技行业一定不喜欢挖掘机,经常会听说xx互联网软件故障提到"光纤被挖断",导致XX机房故障几小时. 这和数据库有什么关系呢?当然有,一些企业在业务层面可能做到了多机房,多活的架构,但数据库并没有做到0丢失的多机房或多活部署,如果数据库所在机房网络故障,那么

阿里云RDS for PostgreSQL varbitx插件与实时画像应用场景介绍

标签 PostgreSQL , varbitx , 阿里云 , 实时画像 背景 PostgreSQL 内置的varbit, bit类型的操作函数比较简单,阿里云RDS for PostgreSQL对其进行了扩展. 支持更多的bit操作,可以覆盖更广的应用场景,例如实时用户画像推荐系统.门禁广告系统.购票系统等. 阿里云 varbitx 插件介绍 增加的函数接口如下 1. bit_count bit_count ( varbit, int, -- (0|1) int, -- (n) int --