PgSQL · 应用案例 · 海量用户实时定位和圈人-团圆社会公益系统

背景

老人、儿童是最容易走丢的人群,一定要看好老人和小孩,但是万一走丢了怎么办呢?

阿里有一个公益系统,团圆,这个系统是用来帮助发布走丢人群信息的,公安通过发布的走丢人的照片,最后一次的位置信息,向社会发布。

通过公益平台的合作伙伴(例如运营商、购物软件等)可以向最后一次走丢人士出现的位置附近的人推送寻人启事,调动社会力量帮助寻找丢失人。

为了实现这个目的,需要收集社会人士的实时位置,现在有很多技术可以实现,例如手机基站定位、GPS定位等。

假设有10亿手机用户,用户的位置实时变动,实时的位置信息需要更新到数据库中。每天可能有千亿次位置更新。

同时发布走失信息后,需要到数据库中,根据走失位置圈出附近的人。

简单粗暴设计

1、表结构设计:

create table tbl_pos(
  id int primary key,  -- 用户ID
  pos point  -- 用户实时位置
);

2、空间索引

create index idx_tbl_pos on tbl_pos using gist(pos);

性能评测

实时更新10亿用户位置,使用insert on conflict语法。

vi test.sql    

\set id random(1,1000000000)
insert into tbl_pos values (:id, point(random()*180,random()*90)) on conflict (id) do update set pos=excluded.pos;

使用32个并发,实时生成用户随机位置.

nohup pgbench -M prepared -n -r -P 5 -f ./test.sql -c 32 -j 32 -T 120000 > ./pos.log 2>&1 &

1、实时位置更新TPS,约18万/s。

179799

服务器负载,服务器还是非常空闲的,有足够的资源提供给查询

top - 01:52:34 up 76 days, 15:32,  2 users,  load average: 33.74, 33.56, 31.47
Tasks: 1064 total,  34 running, 1030 sleeping,   0 stopped,   0 zombie
%Cpu(s): 47.6 us,  5.4 sy,  0.0 ni, 46.9 id,  0.2 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem : 52807456+total, 32911484+free, 10949652 used, 18801006+buff/cache
KiB Swap:        0 total,        0 free,        0 used. 42997945+avail Mem

2、查询性能。

在位置更新的同时,测试查询性能。

假设走失人口最后位置出现在杭州,那么我们需要查询在某个平面(例如杭州市)内的点。返回500万个点(社会用户),仅需28秒。

使用空间索引,返回速度杠杠的。

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from tbl_pos where box(point(1,1), point(25.5,25.5)) @> pos limit 5000000;
                                                                      QUERY PLAN
-------------------------------------------------------------------------------------------------------------------------------------------------------
 Limit  (cost=0.55..412954.11 rows=407872 width=20) (actual time=1.433..27536.623 rows=5000000 loops=1)
   Output: id, pos
   Buffers: shared hit=6183117 dirtied=31842
   ->  Index Scan using idx_tbl_pos on public.tbl_pos  (cost=0.55..412954.11 rows=407872 width=20) (actual time=1.431..26861.352 rows=5000000 loops=1)
         Output: id, pos
         Index Cond: ('(25.5,25.5),(1,1)'::box @> tbl_pos.pos)
         Buffers: shared hit=6183117 dirtied=31842
 Planning time: 0.353 ms
 Execution time: 27950.171 ms
(9 rows)

实际查询用,可以使用游标,流式返回。例子

postgres=# begin;
BEGIN
postgres=# declare cur cursor for select * from tbl_pos where box(point(1,1), point(25.5,25.5)) @> pos;
DECLARE CURSOR
postgres=# fetch 10 from cur;
    id     |                 pos
-----------+-------------------------------------
 680844515 | (2.08381220698357,1.25674836337566)
 498274514 | (2.23715107887983,1.27883949782699)
  72310072 | (2.1013452205807,1.32945269811898)
 301147261 | (2.12246049195528,1.33455505594611)
 186462127 | (2.13169047608972,1.24054086394608)
 726143191 | (2.27320306934416,1.31862969137728)
 902518425 | (2.27059512399137,1.32658164482564)
 534516939 | (2.18118946999311,1.29441328346729)
 329417311 | (2.27630747482181,1.2547113513574)
 853173913 | (2.28139906190336,1.33868838194758)
(10 rows)    

postgres=# \timing
Timing is on.    

postgres=# fetch 10 from cur;
    id     |                 pos
-----------+-------------------------------------
 223759458 | (2.24917919375002,1.31508464924991)
 215111891 | (2.10541740059853,1.26674327999353)
 925178989 | (2.08201663568616,1.2974686967209)
 954808979 | (2.10515496321023,1.32548315450549)
 514021414 | (2.17867707833648,1.27732987515628)
 872436892 | (2.22504794597626,1.31386948283762)
 507169369 | (2.05484946258366,1.30171341821551)
 317349985 | (2.25962312892079,1.30945896729827)
 200956423 | (2.10705514065921,1.30409182514995)
 598969258 | (1.98812280781567,1.30866004619747)
(10 rows)    

Time: 0.306 ms

通过游标,客户端可以边接收,边发短信或者向软件推送寻人启事。

实现流式推送,节省宝贵的寻人时间。

优化设计

单表十亿空间数据,对于查询来说,前面已经看到了,毫无压力。但是随着频繁的更新,可能到GiST索引的膨胀,膨胀后,PostgreSQL提供了并行创建索引的方法(不影响堵塞,可以在一个列创建同样的索引),来维护索引。但是10亿数据创建索引会变得很久。

为了解决这个问题,建议使用分区表。例如将ID哈希,分成64个分区,每个分区1500万左右数据。

在PostgreSQL中,目前性能最好的分区是pg_pathman插件。或者使用schemaless的方式。下面以schemaless为例子。其实在我曾经写过的另外的案例中也非常常见

《行为、审计日志 (实时索引/实时搜索)建模 - 最佳实践 2》

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

定义基表

postgres=# create table tbl_pos(id int primary key, pos point);
CREATE TABLE
postgres=# create index idx_tbl_pos_1 on tbl_pos using gist(pos);
CREATE INDEX

定义自动建表函数

create or replace function create_schemaless(
  target name,   -- 目标表名
  src name       -- 源表名
) returns void as $$
declare
begin
  execute format('create table if not exists %I (like %I including all)', target, src);
  execute format('alter table %I inherit %I', target, src);
exception when others then
  return;
end;
$$ language plpgsql strict;

定义以schemaless的方式写数据的函数

创建一个插入数据的函数,使用动态SQL,如果遇到表不存在的错误,则调用建表函数进行建表。

create or replace function ins_schemaless(
  id int,   -- id
  md int,   -- 取模数
  pos point -- 位置
) returns void as $$
declare
  target name := 'tbl_pos_'||mod(id,md) ;
begin
  execute format('insert into %I values (%L, %L) on conflict (id) do update set pos=point_add(%I.pos, point(random()*10-5, random()*10-5))', target, id, pos, target);
  -- 为了模拟真实情况,因为人的移动速度有限,即使驾车,飞机(少数情况),所以用了pos=point_add(%I.pos, point(random()*10-5, random()*10-5))这种方法模拟更真实的情况
  -- 实际场景,请改成pos=excluded.pos
  exception
    WHEN SQLSTATE '42P01' THEN
    perform create_schemaless(target, 'tbl_pos');
    execute format('insert into %I values (%L, %L) on conflict (id) do update set pos=point_add(%I.pos, point(random()*10-5, random()*10-5))', target, id, pos, target);
    -- 为了模拟真实情况,因为人的移动速度有限,即使驾车,飞机(少数情况),所以用了pos=point_add(%I.pos, point(random()*10-5, random()*10-5))这种方法模拟更真实的情况
    -- 实际场景,请改成pos=excluded.pos
end;
$$ language plpgsql strict;

数据库端的schemaless会牺牲一部分性能,因为无法使用绑定变量。

如果可能的话,建议业务层实现schemaless(自动拼接表名,自动建表,自动写入),以提高性能。

测试功能

postgres=# select ins_schemaless(2,32,point(1,2));
 ins_schemaless
----------------  

(1 row)  

postgres=# select ins_schemaless(1,32,point(1,2));
 ins_schemaless
----------------  

(1 row)  

postgres=# select tableoid::regclass,* from tbl_pos;
 tableoid  | id |  pos
-----------+----+-------
 tbl_pos_2 |  2 | (1,2)
 tbl_pos_1 |  1 | (1,2)
(2 rows)

schemaless设计压测

vi ~/test.sql
\set id random(1,1000000000)
select ins_schemaless(:id, 32, point(random()*360-180, random()*180-90));  

nohup pgbench -M prepared -n -r -P 5 -f ./test.sql -c 32 -j 32 -T 120000 > ./pos.log 2>&1 &

性能依旧杠杠的。

125977 tps

小结

1、通过PostgreSQL的空间数据类型、空间索引。加上insert on conflict的特性。实现了单机约18万行/s的10亿用户的实时位置更新,同时输出500万个点的量级,仅需20几秒。真正实现了团圆公益系统的时效性。

2、采用游标,流式返回,实现了边获取数据,边向社会各界发送寻人启事的目的。

3、另一方面,用户位置的变更,实际上是有一定过滤性的,比如用户从办公室去上个洗手间,虽然位置可能发生了变化,但是非常细微,这种变化在这套系统中可以过滤(不更新),从而减少数据的更新量。

按照现有的测试数据,可以做到每天155亿次的更新。假设每10条更新仅有1条是有效更新,那么实际上可以支持1550亿次的MOVE采集。

4、PostgreSQL是一个很有爱心的数据库系统哦。

5、将来流计算引擎pipelinedb插件化后,PostgreSQL内部将整合这个流计算引擎,通过流计算引擎,理论上可以轻松实现40万行/s级别的更新速度,每天支撑300多亿次的实时位置更新。

6、采用流计算的方法除了提高性能,同时也降低了XID的消耗,在目前32BIT XID的情形下,可以有效的环节FREEZE带来的负担。如果不使用流计算,也建议合并更新,例如一个事务中更新若干条,比如100条,那么一天的事务数就将到了1.5亿。

7、参考

https://www.postgresql.org/docs/9.6/static/gist-implementation.html#GIST-BUFFERING-BUILD

《行为、审计日志 (实时索引/实时搜索)建模 - 最佳实践 2》

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

时间: 2025-01-19 05:34:41

PgSQL · 应用案例 · 海量用户实时定位和圈人-团圆社会公益系统的相关文章

海量用户实时定位和圈人 - 团圆社会公益系统(位置寻人\圈人)

标签 PostgreSQL , PostGIS , 空间数据 , 空间索引 , 寻人启事 , 位置寻人 , 公益系统 , 实时定位 , 海量圈人 背景 老人.儿童是最容易走丢的人群,一定要看好老人和小孩,但是万一走丢了怎么办呢? 阿里有一个公益系统,团圆,这个系统是用来帮助发布走丢人群信息的,公安通过发布的走丢人的照片,最后一次的位置信息,向社会发布. 通过公益平台的合作伙伴(例如运营商.购物软件等)可以向最后一次走丢人士出现的位置附近的人推送寻人启事,调动社会力量帮助寻找丢失人. 为了实现这个

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

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

PgSQL · 应用案例 · 手机行业分析、决策系统设计-实时圈选、透视、估算

背景 经营分析.决策支持是现代企业的一个让数据发挥有效价值的分析型系统. 在各个行业中随处可见,例如共享充电宝中,协助销售了解实时的设备租赁情况,销售业绩.在电商中,协助小二和商户发掘目标用户群体.金融行业中,协助输出国民的存款.消费.贷款的画像. PostgreSQL, Greenplum都是非常适合于经营分析.决策支持的数据库.因为它们具备了一些特性,适合实时的分析透视.(流式计算.合并写入.阅后即焚.GIN倒排索引.varbit类型.列存储.BITMAP合并扫描.HLL估值类型.采样算法等

海量用户通信业务平台的设计和数据处理实践【大数据100分】

以下为分享实景全文: 我将我的时间分为三个Session: 1. 神州泰岳积极参与大数据时代的业务拓展 2. 海量用户通信业务平台的设计实践 3. 对于数据运营的思考 一.神州泰岳近几年在大数据领域做了不少投资和业务布局.归纳起来主要集中在四个层面: 1.入口:"智慧线" 2.基础设施:"IaaS""DBaaS""Hadoop""MPP" "智能推荐引擎" 3.数据源建设:"用

PgSQL · 应用案例 · 经营、销售分析系统DB设计之共享充电宝

背景 共享充电宝.共享单车.共享雨伞,共享女朋友^|^,共享汽车,... 共享经济最近几年发展确实非常迅猛. 共享必定涉及被共享对象的管理.会员的管理等,实际上也属于一种物联网系统. 本文以共享充电宝的场景为例,分享一下共享充电宝的经营分析.销售管理系统的后台数据库的设计.(老板关心的是整体销售的业绩,以及各个渠道的透视等.销售经理关心的是他管辖片区的销售业绩,运维人员关心的是设备的状态.) 一.数据结构和数据量 业务模式是什么样的? 在饭店.商场.火车站.足浴店等各种场所,都能看到充电宝的身影

面试经之海量用户积分排名算法探讨

某海量用户网站,用户拥有积分,积分可能会在使用过程中随时更新.现在要为该网站设计一种算法,在每次用户登录时显示其当前积分排名.用户最大规模为2亿:积分为非负整数,且小于100万. PS: 据说这是迅雷的一道面试题,不过问题本身具有很强的真实性,所以本文打算按照真实场景来考虑,而不局限于面试题的理想环境. 存储结构 首先,我们用一张用户积分表user_score来保存用户的积分信息. 表结构: 示例数据: 下面的算法会基于这个基本的表结构来进行. 算法1:简单SQL查询 首先,我们很容易想到用一条

揭秘个人信息泄露网上贩卖“黑市” 可实现精准实时定位

刚刚买了房,装修公司的电话就打过来了,刚刚买了车,保险公司的电话就跟过来了,属于隐私的个人信息被泄露,让人烦心更让人忧心,记者发现贩卖个人信息的黑市在网络上十分活跃,一些信息贩子甚至公然叫卖,只要提供一个人的手机号码,就能查到他最为私密的个人信息,而且范围覆盖全国,果真如此吗? 网上"黑市":个人信息随意买卖     贩卖个人信息的QQ群 记者登录了一个专门贩卖个人信息的QQ群,里面的群员多达1946名,而且非常活跃,浏览这个QQ群里的留言可以发现,各类公民个人信息被公开叫卖.种类之多

[转载]浅析海量用户的分布式系统设计

我们常常会听说,某个互联网应用的服务器端系统多么牛逼,比如QQ拉.微信拉.淘宝拉.那么,一个互联网应用的服务器端系统,到底牛逼在什么地方?为什么海量的用户访问,会让一个服务器端系统变得更复杂?本文就是想从最基本的地方开始,探寻服务器端系统技术的基础概念.   承载量是分布式系统存在的原因 当一个互联网业务获得大众欢迎的时候,最显著碰到的技术问题,就是服务器非常繁忙.当每天有1000万个用户访问你的网站时,无论你使用什么样的服务器硬件,都不可能只用一台机器就承载的了.因此,在互联网程序员解决服务器

营销2.0案例与用户行为的思考

前段时间看了格雷兄的一篇文章<黄球--一个貌似简单却极难复制的营销2.0案子>,刚好晚上与丁欣等几个朋友讨论这方面事情,也有自己的一些感触. 黄球的案例大家请参见陈格雷的那篇文章,我这里就不多介绍了,最后有张图显示这个案例所用的方式:体验营销.网络营销.游戏式营销.消费者创造内容. 从这个案例中,然我想起来从前做的一些传媒策划案例,其中有一些并不是很成功,我觉得很重要的一个原因就是在于互动性太少,游戏性太少.而经历过各种各样广告营销洗礼之后的消费者,变得越来越理智,对于显浅的营销行为已经不买账