閱讀92 返回首頁    go 阿裏雲 go 技術社區[雲棲]


車聯網案例,軌跡清洗 - 阿裏雲RDS PostgreSQL最佳實踐 - 窗口查詢

標簽

PostgreSQL , 窗口函數 , 車聯網 , 軌跡 , 軌跡清洗 , lag , lead


背景

車聯網中一個非常典型的場景是采集車輛的行駛軌跡,通常來說車輛的軌跡並不會實時上報,可能會堆積若幹條軌跡記錄,或者間隔多少時間上報一次。

一個典型的數據結構如下

(car_id, pos geometry, crt_time timestamp)  

車輛在行駛,行駛過程中會遇到堵車,紅綠燈,那麼上報的軌跡記錄可能是這樣的

1, 位置1, '2017-01-01 12:00:00'  
1, 位置1, '2017-01-01 12:00:05'  
1, 位置1, '2017-01-01 12:00:10'  
1, 位置1, '2017-01-01 12:00:15'  
1, 位置1, '2017-01-01 12:00:20'  
1, 位置2, '2017-01-01 12:00:30'  

也就是說,在同一個位置,因為堵車、等紅燈,可能會導致上傳多條記錄。

那麼就涉及到在數據庫中清洗不必要的等待記錄的需求,在一個點,我們最多保留2條記錄,表示到達這個位置和離開這個位置。

這個操作可以使用窗口函數實現。

當然從最佳效率角度來分析,軌跡清洗這個事情,在終端做是更合理的,一個位置的起始點,隻留兩條。

例子

1、設計表結構

create table car_trace (cid int, pos point, crt_time timestamp);  

2、生成1000萬測試數據,假設有1000量車,(為了讓數據更容易出現重複,為了測試看效果,位置使用25個點)

insert into car_trace select random()*999, point((random()*5)::int, (random()*5)::int), clock_timestamp() from generate_series(1,10000000);  

3、創建索引

create index idx_car on car_trace (cid, crt_time);  

4、查詢數據layout

select * from car_trace where cid=1 order by crt_time limit 1000;  
  
   1 | (3,1) | 2017-07-22 21:30:09.84984  
   1 | (1,4) | 2017-07-22 21:30:09.850297  
   1 | (1,4) | 2017-07-22 21:30:09.852586  
   1 | (1,4) | 2017-07-22 21:30:09.854155  
   1 | (1,4) | 2017-07-22 21:30:09.854425  
   1 | (3,1) | 2017-07-22 21:30:09.854493  
  
觀察到了幾個重複。  

5、使用窗口過濾單一位置記錄,最多僅保留到達這個位置和離開這個位置的兩條記錄。

這裏用到兩個窗口函數:

lag,表示當前記錄的前麵一條記錄。

lead,表示當前記錄的下一條記錄。

判斷到達點、離去點的方法如下:

  • 當前pos 不等於 前一條pos,說明這條記錄是當前位置的到達點。

  • 當前pos 不等於 下一條pos,說明這條記錄是當前位置的離去點。

  • 前一條pos 為空,說明這條記錄是第一條記錄。

  • 下一條pos 為空,說明這條記錄是最後一條記錄。

select * from   
(  
select   
  *,   
  lag(pos) over (partition by cid order by crt_time) as lag,   
  lead(pos) over (partition by cid order by crt_time) as lead   
from car_trace   
  where cid=1   
  and crt_time between '2017-07-22 21:30:09.83994' and '2017-07-22 21:30:09.859735'  
) t  
  where pos <> lag  
  or pos <> lead  
  or lag is null  
  or lead is null;  
  
 cid |  pos  |          crt_time          |  lag  | lead    
-----+-------+----------------------------+-------+-------  
   1 | (2,1) | 2017-07-22 21:30:09.83994  |       | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.839953 | (2,1) | (5,2)  
   1 | (5,2) | 2017-07-22 21:30:09.840704 | (3,1) | (4,4)  
   1 | (4,4) | 2017-07-22 21:30:09.84179  | (5,2) | (5,2)  
   1 | (5,2) | 2017-07-22 21:30:09.843787 | (4,4) | (1,5)  
   1 | (1,5) | 2017-07-22 21:30:09.844165 | (5,2) | (0,5)  
   1 | (0,5) | 2017-07-22 21:30:09.84536  | (1,5) | (4,1)  
   1 | (4,1) | 2017-07-22 21:30:09.845896 | (0,5) | (3,3)  
   1 | (3,3) | 2017-07-22 21:30:09.846958 | (4,1) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.84984  | (3,3) | (1,4)  
   1 | (1,4) | 2017-07-22 21:30:09.850297 | (3,1) | (1,4)  
   1 | (1,4) | 2017-07-22 21:30:09.854425 | (1,4) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.854493 | (1,4) | (3,2)  
   1 | (3,2) | 2017-07-22 21:30:09.854541 | (3,1) | (2,0)  
   1 | (2,0) | 2017-07-22 21:30:09.855297 | (3,2) | (4,1)  
   1 | (4,1) | 2017-07-22 21:30:09.857592 | (2,0) | (4,1)  
   1 | (4,1) | 2017-07-22 21:30:09.857595 | (4,1) | (0,4)  
   1 | (0,4) | 2017-07-22 21:30:09.857597 | (4,1) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.858996 | (0,4) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.859735 | (3,1) |   
(20 rows)  

未加清洗軌跡,得到的結果如下:

select   
  *,   
  lag(pos) over (partition by cid order by crt_time) as lag,   
  lead(pos) over (partition by cid order by crt_time) as lead   
from car_trace   
  where cid=1   
  and crt_time between '2017-07-22 21:30:09.83994' and '2017-07-22 21:30:09.859735';  
  
 cid |  pos  |          crt_time          |  lag  | lead    
-----+-------+----------------------------+-------+-------  
   1 | (2,1) | 2017-07-22 21:30:09.83994  |       | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.839953 | (2,1) | (5,2)  
   1 | (5,2) | 2017-07-22 21:30:09.840704 | (3,1) | (4,4)  
   1 | (4,4) | 2017-07-22 21:30:09.84179  | (5,2) | (5,2)  
   1 | (5,2) | 2017-07-22 21:30:09.843787 | (4,4) | (1,5)  
   1 | (1,5) | 2017-07-22 21:30:09.844165 | (5,2) | (0,5)  
   1 | (0,5) | 2017-07-22 21:30:09.84536  | (1,5) | (4,1)  
   1 | (4,1) | 2017-07-22 21:30:09.845896 | (0,5) | (3,3)  
   1 | (3,3) | 2017-07-22 21:30:09.846958 | (4,1) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.84984  | (3,3) | (1,4)  
   1 | (1,4) | 2017-07-22 21:30:09.850297 | (3,1) | (1,4)  
   1 | (1,4) | 2017-07-22 21:30:09.852586 | (1,4) | (1,4)  
   1 | (1,4) | 2017-07-22 21:30:09.854155 | (1,4) | (1,4)  
   1 | (1,4) | 2017-07-22 21:30:09.854425 | (1,4) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.854493 | (1,4) | (3,2)  
   1 | (3,2) | 2017-07-22 21:30:09.854541 | (3,1) | (2,0)  
   1 | (2,0) | 2017-07-22 21:30:09.855297 | (3,2) | (4,1)  
   1 | (4,1) | 2017-07-22 21:30:09.857592 | (2,0) | (4,1)  
   1 | (4,1) | 2017-07-22 21:30:09.857595 | (4,1) | (0,4)  
   1 | (0,4) | 2017-07-22 21:30:09.857597 | (4,1) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.858996 | (0,4) | (3,1)  
   1 | (3,1) | 2017-07-22 21:30:09.859735 | (3,1) |   
(22 rows)  

使用lag, lead清洗掉了停留過程中的記錄。

被跟蹤對象散落導致的掃描IO放大的優化

因為業務中涉及的車輛ID可能較多,不同車輛匯聚的數據會往數據庫中寫入,如果不做任何優化,那麼不同車輛的數據進入數據庫後,可能是交錯存放的,也就是說一個數據塊中,可能有不同車輛的數據。

那麼在查詢單一車輛的軌跡時,會掃描很多數據塊(掃描IO放大)。

優化思路有兩種。

1、業務端匯聚分組排序後寫入數據庫。例如程序在接收到車輛終端提交的數據後,按車輛ID分組,按時間排序,寫入數據庫(insert into tbl values (),(),...();)。這樣的話,同樣車輛的數據,可能會盡可能的落在同一個數據塊內。

2、數據庫端使用分區,重組數據。例如,按車輛ID,每輛車、或者車輛HASH分區存放。

以上兩種方法,都是要將數據按查詢需求重組,從而達到降低掃描IO的目的。

這個方法與《PostgreSQL 證券行業數據庫需求分析與應用》的方法類似,有興趣的朋友可以參考。

最後更新:2017-07-23 21:32:38

  上一篇:go  我的考駕照之路
  下一篇:go  分區索引的應用和實踐 - 阿裏雲RDS PostgreSQL最佳實踐