PostgreSQL 滴滴派单 高峰区域集中打车冲突优化1 - 宇宙大爆炸理论与PostgreSQL实践

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
云数据库 RDS MySQL,集群系列 2核4GB
推荐场景:
搭建个人博客
云数据库 RDS SQL Server,基础系列 2核4GB
简介:

标签

PostgreSQL , 大爆炸 , 冲突 , 打车集中 , 区域集中 , 锁冲突 , adlock


背景

在打车高峰时期,通常会出现某个区域打车的人特别多的情况,比如在下班时,写字楼。比如在演唱会结束时,演唱会现场。

这个场景有一个特点,同一个点发出请求,按距离搜索最近的车,然后锁定车辆。

在数据库中,如果大家都这么操作,会带来锁冲突的问题。我在上一篇文档中,介绍了adlock,可以大幅提高吞吐,但是还有一个隐藏的可以提升的点(而且可以提升非常多),如果所有人都是从同一个位置发起的锁定附近车辆请求,那么会出现冗余扫描+FILTER的情况。(因为A锁定了第一辆车后,B必须跳过第一辆车去锁定第二辆附近的车,然后是C必须跳过最近的前面2辆车,以此类推)并发越高,跳过的车辆越多。

《滴滴打车派单系统思考 数据库设计与实现 - 每月投入6140元, 1天最多可盈利117亿 -_-!》

怎么解决这个问题呢?

未来优化3:对于同一个时刻,同一个地点,有多人打车时,如果都按同样的就近选择CAR的规则,会导致同一辆CAR被多次挑选中,本文使用了ADVISORY LOCK来避免行锁冲突。但是依旧有更好的优化,因为这种方法虽然没有了锁冲突,但是扫描依旧是从近到远的,所以
可能并发时,一些会话存在多行扫描才找到没有被锁定的行。 有一种方法,比如,类似组提交,对同一个地点同时打车的多人,一次取多辆CAR,在程序中分配给不同的人。 还有一种方法,需要数据库内来实现,给一个离散因子,每次获取到的可能不是最近的CAR
,满足多少米内的周边的CARs,随机挑选,但是这个随机挑选必须在INDEX中完成,必须保证在库中的index scan, heap scan都只扫一条。(类似索引的位点随机扫)

从宇宙大爆炸开始

pic

宇宙大爆炸理论是说最开始宇宙是一个点,大爆炸开始后,一个点逐渐扩散,形成了现在的宇宙。

滴滴打车的场景与之类似,打车高峰时,一个写字楼的人很多,集中在一个点,车辆在外围或附近,前面讲了如果大家都从这个点去计算与车辆的距离,对所有人来说,最近的车辆都是同一辆车,所以存在冲突的问题。

pic

思路是什么?

把集中在写字楼一个位置的点打散,在计算离人最近的车辆时,就能够尽可能的避免冲突,从而减少filter,提高性能,提高吞吐。

例子

本文先举一个例子,空间数据的例子与此类似(无非就是X和Y轴都给定一个离散范围,进行离散),下一篇文档再介绍。

例如我们有1000万条记录,ID为PK,如果需求是找出与某个输入值最近的ID,并锁定它,如果它已被锁定,则锁定下一个与输入值最近的ID。

例如输入值为50,

优先锁定50,如果50已经被其他会话锁定了,则锁定49和51,以此类推。

1、创建测试表

postgres=# create table a(id int primary key, info text, crt_time timestamp);    
CREATE TABLE    

2、写入1000万测试数据

postgres=# insert into a select generate_series(1,10000000), 'test', now();    

3、创建GIST索引,支持距离搜索操作符

postgres=# create extension btree_gist;    
CREATE EXTENSION    
postgres=# create index idx_a_1 on a using gist(id);    

创建一个测试脚本,模拟高峰期打车的情况,(输入同一个点,去锁定离他最近的点,如果离它最近的点已经被锁定,则跳过)。我们这里同样使用pg_try_advisory_xact_lock()来实现跳过已经被锁定的记录。

vi test.sql    
    
select * from a where pg_try_advisory_xact_lock(id) order by id <-> 5000000 for update limit 1;    

开启压测,可以达到约4.9万TPS

pgbench -M prepared -n -r -P 1 -f ./test.sql -c 56 -j 56 -T 120    
    
progress: 3.0 s, 45775.8 tps, lat 1.224 ms stddev 0.828    
progress: 4.0 s, 45571.5 tps, lat 1.229 ms stddev 0.826    
progress: 5.0 s, 49345.6 tps, lat 1.135 ms stddev 0.747    
progress: 6.0 s, 48948.0 tps, lat 1.144 ms stddev 0.856    
progress: 7.0 s, 49578.2 tps, lat 1.129 ms stddev 0.758    

分析可优化的空间,filter

前面介绍了,并发的从同一点去搜索附近的点,并锁定它,存在一个问题,(因为A锁定了第一辆车后,B必须跳过第一辆车去锁定第二辆附近的车,然后是C必须跳过最近的前面2辆车,以此类推)并发越高,跳过的车辆越多。

通过下面的SQL可以观察到这个现象。

1、会话A

postgres=# begin;    
BEGIN    
    
    
postgres=# explain (analyze,verbose,timing,costs,buffers) select * from a where pg_try_advisory_xact_lock(id) order by id <-> (5000000-500000+100) for update limit 1;    
                                                                QUERY PLAN                                                                    
------------------------------------------------------------------------------------------------------------------------------------------    
 Limit  (cost=0.42..0.54 rows=1 width=27) (actual time=0.096..0.097 rows=1 loops=1)    
   Output: id, info, crt_time, ((id <-> 4500100)), ctid    
   Buffers: shared hit=5    
   ->  LockRows  (cost=0.42..397168.88 rows=3333333 width=27) (actual time=0.095..0.095 rows=1 loops=1)    
         Output: id, info, crt_time, ((id <-> 4500100)), ctid    
         Buffers: shared hit=5    
         ->  Index Scan using idx_a_1 on public.a  (cost=0.42..363835.55 rows=3333333 width=27) (actual time=0.092..0.092 rows=1 loops=1)    
               Output: id, info, crt_time, (id <-> 4500100), ctid    
               Order By: (a.id <-> 4500100)    
               Filter: pg_try_advisory_xact_lock((a.id)::bigint)    
               Buffers: shared hit=4    
 Planning time: 0.111 ms    
 Execution time: 0.135 ms    
(13 rows)    

2、会话B,在会话A没有释放锁之前,从同一个点发起请求,锁定附近的点。

Rows Removed by Filter: 1

postgres=# explain (analyze,verbose,timing,costs,buffers) select * from a where pg_try_advisory_xact_lock(id) order by id <-> (5000000-500000+100) for update limit 1;    
                                                                QUERY PLAN                                                                    
------------------------------------------------------------------------------------------------------------------------------------------    
 Limit  (cost=0.42..0.54 rows=1 width=27) (actual time=0.128..0.128 rows=1 loops=1)    
   Output: id, info, crt_time, ((id <-> 4500100)), ctid    
   Buffers: shared hit=5    
   ->  LockRows  (cost=0.42..397168.88 rows=3333333 width=27) (actual time=0.127..0.127 rows=1 loops=1)    
         Output: id, info, crt_time, ((id <-> 4500100)), ctid    
         Buffers: shared hit=5    
         ->  Index Scan using idx_a_1 on public.a  (cost=0.42..363835.55 rows=3333333 width=27) (actual time=0.114..0.114 rows=1 loops=1)    
               Output: id, info, crt_time, (id <-> 4500100), ctid    
               Order By: (a.id <-> 4500100)    
               Filter: pg_try_advisory_xact_lock((a.id)::bigint)    
               Rows Removed by Filter: 1    
               Buffers: shared hit=4    
 Planning time: 0.112 ms    
 Execution time: 0.168 ms    
(14 rows)    

可以看到,发生了FILTER,因为按距离来说,4500100才是需要被返回的,然而它已经被锁了,所以会跳过它,找到下一个可以锁的点。发生了一条FILTER。

并发越多,rows removed by filter会越多,因为都是被锁定过的。造成性能影响。

优化

把一个点分散,例如我们把集中的点,打散到方圆1公里的平面上。(也就是说在搜索最近的车辆时,并不是拿你当前的定位来计算与车辆的距离。而是拿一个以你当前位置为中心,方圆一公里内的“随机点”来寻找这个点最近的车辆。)

对于本例,我们设置一个范围值,比如把点打散到上下5千以内的一个随机点,再求离他最近的点,并锁定。

vi test.sql    
    
\set seed random(1,5000)    
select * from a where pg_try_advisory_xact_lock(id) order by id <-> (5000000+2500-:seed) for update limit 1;    

压测结果

pgbench -M prepared -n -r -P 1 -f ./test.sql -c 56 -j 56 -T 120    
    
progress: 5.0 s, 150380.9 tps, lat 0.372 ms stddev 0.165    
progress: 6.0 s, 151711.9 tps, lat 0.369 ms stddev 0.168    
progress: 7.0 s, 152098.8 tps, lat 0.368 ms stddev 0.154    
progress: 8.0 s, 152003.3 tps, lat 0.368 ms stddev 0.156    
progress: 9.0 s, 152421.4 tps, lat 0.367 ms stddev 0.154    
progress: 10.0 s, 153108.7 tps, lat 0.366 ms stddev 0.148    
progress: 11.0 s, 151427.8 tps, lat 0.370 ms stddev 0.156    

小结

使用把集中点,按一个范围,随机打散的方法,锁定附近点的处理吞吐提升了3倍多,从4.9万提升到了15.1万。

找到优化的方向,朝这个方向去优化,就会有性能的提升。这个思路对所有热点消除的应用场景都有帮助。

《滴滴打车派单系统思考 数据库设计与实现 - 每月投入6140元, 1天最多可盈利117亿 -_-!》

相关实践学习
使用PolarDB和ECS搭建门户网站
本场景主要介绍基于PolarDB和ECS实现搭建门户网站。
阿里云数据库产品家族及特性
阿里云智能数据库产品团队一直致力于不断健全产品体系,提升产品性能,打磨产品功能,从而帮助客户实现更加极致的弹性能力、具备更强的扩展能力、并利用云设施进一步降低企业成本。以云原生+分布式为核心技术抓手,打造以自研的在线事务型(OLTP)数据库Polar DB和在线分析型(OLAP)数据库Analytic DB为代表的新一代企业级云原生数据库产品体系, 结合NoSQL数据库、数据库生态工具、云原生智能化数据库管控平台,为阿里巴巴经济体以及各个行业的企业客户和开发者提供从公共云到混合云再到私有云的完整解决方案,提供基于云基础设施进行数据从处理、到存储、再到计算与分析的一体化解决方案。本节课带你了解阿里云数据库产品家族及特性。
目录
相关文章
|
11月前
|
SQL 关系型数据库 测试技术
沉浸式学习PostgreSQL|PolarDB 20: 学习成为数据库大师级别的优化技能
在上一个实验《沉浸式学习PostgreSQL|PolarDB 19: 体验最流行的开源企业ERP软件 odoo》 中, 学习了如何部署odoo和polardb|pg. 由于ODOO是非常复杂的ERP软件, 对于关系数据库的挑战也非常大, 所以通过odoo业务可以更快速提升同学的数据库优化能力, 发现业务对数据库的使用问题(如索引、事务对锁的运用逻辑问题), 数据库的代码缺陷, 参数或环境配置问题, 系统瓶颈等.
911 1
|
5天前
|
监控 关系型数据库 数据库
PostgreSQL的索引优化策略?
【8月更文挑战第26天】PostgreSQL的索引优化策略?
25 1
|
27天前
|
监控 关系型数据库 数据库
如何优化PostgreSQL的性能?
【8月更文挑战第4天】如何优化PostgreSQL的性能?
31 7
|
3月前
|
自然语言处理 关系型数据库 数据库
技术经验解读:【转】PostgreSQL的FTI(TSearch)与中文全文索引的实践
技术经验解读:【转】PostgreSQL的FTI(TSearch)与中文全文索引的实践
25 0
|
4月前
|
存储 JSON 关系型数据库
PostgreSQL Json应用场景介绍和Shared Detoast优化
PostgreSQL Json应用场景介绍和Shared Detoast优化
|
4月前
|
弹性计算 关系型数据库 数据库
开源PostgreSQL在倚天ECS上的最佳优化实践
本文基于倚天ECS硬件平台,以自顶向下的方式从上层应用、到基础软件,再到底层芯片硬件,通过应用与芯片的硬件特性的亲和性分析,实现PostgreSQL与倚天芯片软硬协同的深度优化,充分使能倚天硬件性能,帮助开源PostgreSQL应用实现性能提升。
|
4月前
|
SQL 运维 关系型数据库
基于AnalyticDB PostgreSQL的实时物化视图研发实践
AnalyticDB PostgreSQL版提供了实时物化视图功能,相较于普通(非实时)物化视图,实时物化视图无需手动调用刷新命令,即可实现数据更新时自动同步刷新物化视图。当基表发生变化时,构建在基表上的实时物化视图将会自动更新。AnalyticDB PostgreSQL企业数据智能平台是构建数据智能的全流程平台,提供可视化实时任务开发 + 实时数据洞察,让您轻松平移离线任务,使用SQL和简单配置即可完成整个实时数仓的搭建。
143963 8
|
4月前
|
SQL 关系型数据库 MySQL
MySQL【实践 02】MySQL迁移到PostgreSQL数据库的语法调整说明及脚本分享(通过bat命令修改mapper文件内的SQL语法)
MySQL【实践 02】MySQL迁移到PostgreSQL数据库的语法调整说明及脚本分享(通过bat命令修改mapper文件内的SQL语法)
192 0
|
关系型数据库 测试技术 分布式数据库
PolarDB | PostgreSQL 高并发队列处理业务的数据库性能优化实践
在电商业务中可能涉及这样的场景, 由于有上下游关系的存在, 1、用户下单后, 上下游厂商会在自己系统中生成一笔订单记录并反馈给对方, 2、在收到反馈订单后, 本地会先缓存反馈的订单记录队列, 3、然后后台再从缓存取出订单并进行处理. 如果是高并发的处理, 因为大家都按一个顺序获取, 容易产生热点, 可能遇到取出队列遇到锁冲突瓶颈、IO扫描浪费、CPU计算浪费的瓶颈. 以及在清除已处理订单后, 索引版本未及时清理导致的回表版本判断带来的IO浪费和CPU运算浪费瓶颈等. 本文将给出“队列处理业务的数据库性能优化”优化方法和demo演示. 性能提升10到20倍.
780 4
|
存储 缓存 NoSQL
[译]解锁TOAST的秘密:如何优化PostgreSQL的大型列存储以最佳性能和可扩展性
[译]解锁TOAST的秘密:如何优化PostgreSQL的大型列存储以最佳性能和可扩展性
188 0

相关产品

  • 云原生数据库 PolarDB
  • 云数据库 RDS PostgreSQL 版
  • 下一篇
    云函数