PostgreSQL類微博FEED系統-設計與效能指標

德哥發表於2018-04-18

標籤

PostgreSQL , feed , 微博 , 推送 , 分割槽 , 分片 , UDF , 挖掘 , 文字挖掘


背景

類微博系統,最頻繁用到的功能:

A,D,E使用者關注B使用者。  
  
B使用者推送訊息。  
  
A,D,E使用者接收訊息。  
  
A,D,E使用者消費訊息。涉及消費排序演算法。  

之前寫過一篇《三體高可用PCC大賽 – facebook微博 like場景 – 資料庫設計與效能壓測》

LIKE相關場景,用PostgreSQL來設計,效能槓槓的。

本文則是與訊息推送、訊息消費相關的場景。

以內容2048字為例。

設計

為了滿足高效率的推送與消費,設計時,需要考慮到分割槽。分割槽後,也便於將來做較為透明的分庫。

例如可以按使用者的UID進行雜湊分割槽。

1 hash 分割槽表

建立訊息推送表

create table tbl_feed(  
  uid int8,   -- 使用者ID  
  from_uid int8,   -- 被關注使用者ID  
  ts timestamp,    -- 被關注使用者傳送該訊息的時間  
  content text,    -- 被關注使用者傳送該訊息的內容  
  status int       -- 訊息被當前使用者閱讀的狀態, 0 初始狀態, 1 已消費  
);    

建立partial index,因為消費時,只關心沒有被消費的記錄。

create index idx_tbl_feed_1 on tbl_feed(uid,ts) where status=0;    

建立1024個分割槽

do language plpgsql $$    
declare    
begin    
  for i in 0..1023 loop    
    execute format(`create table tbl_feed_%s (like tbl_feed including all , constraint ck_tbl_feed_%s check(abs(mod(uid,1024))=%s)) inherits(tbl_feed)`, i, i, i);    
  end loop;    
end;    
$$;    

2 寫入 UDF

目前RDS PG 10的分割槽表寫入效率和查詢效率不是特別理想,為了達到較好的寫入效率,建議可以先使用UDF,動態拼接SQL。

create or replace function ins_feed(int8, int8, timestamp, text, int) returns void as $$  
declare  
  i int := abs(mod($1,1024));  -- 動態拼接表名  
begin  
  execute format(`insert into tbl_feed_%s(uid,from_uid,ts,content,status) values(%s,%s,%L,%L,%s)`, i, $1,$2,$3,$4,$5);  
end;  
$$ language plpgsql strict;  

寫入效能

假設有20億使用者,隨機輸入1個使用者,並推送一條2048個英文字的訊息。

PG 10,單例項,寫入 19.5 萬行/s,瓶頸主要在寫WAL日誌的LOCK上。

set uid random(1,2000000000)  
select ins_feed(:uid,:uid+1,now()::timestamp,repeat(md5(`a`),64),0);  
  
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 56 -j 56 -T 120  
transaction type: ./test.sql  
scaling factor: 1  
query mode: prepared  
number of clients: 56  
number of threads: 56  
duration: 120 s  
number of transactions actually processed: 23464891  
latency average = 0.286 ms  
latency stddev = 0.486 ms  
tps = 195379.681306 (including connections establishing)  
tps = 195404.169885 (excluding connections establishing)  
statement latencies in milliseconds:  
         0.001  set uid random(1,2000000000)  
         0.285  select ins_feed(:uid,:uid+1,now()::timestamp,repeat(md5(`a`),64),0);  

消費 UDF

目前RDS PG 10的分割槽表寫入效率和查詢效率不是特別理想,為了達到較好的寫入效率,建議可以先使用UDF,動態拼接SQL。

create or replace function get_feed(int8, int, text) returns setof tbl_feed as $$  
declare  
  i int := abs(mod($1,1024));   -- 動態拼接表名  
begin  
return query execute format(`with tmp as   
(  
update tbl_feed_%s set status=1 where ctid = any (array(  
  select ctid from tbl_feed_%s where status=0 and uid=%s order by ts limit %s  -- 每次消費N條,按時間先或後消費都可以,都會走索引  
))   
returning *  
)  
select * from tmp order by %s`,  -- 排序演算法可以寫成UDF,或引數傳入, 本例使用ts排序    
i, i, $1, $2, $3  
);    
end;  
$$ language plpgsql strict;   

消費例子

postgres=# select * from get_feed(642960384,10,`from_uid`);  
-[ RECORD 1 ]------------------------------------------------------------------------------------------------  
uid      | 642960384  
from_uid | 642960385  
ts       | 2018-03-05 19:41:40.574568  
content  | 0cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc17  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b  
9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e2697726610cc175b9c0f1b6a831c399e  
2697726610cc175b9c0f1b6a831c399e269772661  
status   | 1  

消費效能

為了觀察到實際的消費,即每次消費都有至少20條被真實消費掉,這裡先生成一批密集的資料再測。

set uid random(1,4096)  
select ins_feed(:uid,:uid+1,now()::timestamp,repeat(md5(`a`),64),0);  
  
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 56 -j 56 -T 120  

隨機輸入一個隨機使用者,每次消費20行。平均每秒消費 2.7 萬次。

# set uid random(1,2000000000)  
測試時使用 set uid random(1,4096) 
select * from get_feed(:uid,20,`ts`);  
  
pgbench -M prepared -n -r -P 1 -f ./test.sql -c 56 -j 56 -T 45  
transaction type: ./test.sql
scaling factor: 1
query mode: prepared
number of clients: 56
number of threads: 56
duration: 45 s
number of transactions actually processed: 1195840
latency average = 2.106 ms
latency stddev = 2.707 ms
tps = 26560.345111 (including connections establishing)
tps = 26572.467067 (excluding connections establishing)
statement latencies in milliseconds:
         0.001  set uid random(1,4096) 
         2.105  select * from get_feed(:uid,20,`ts`);

小結

PG 11後,分割槽表的寫入、查詢效率會大幅提升。將來可以直接使用分割槽表,避免使用UDF動態SQL來訪問分割槽。

PostgreSQL內建UDF(plpgsql, plpython, pljava, plv8 等)功能,可以支援任意排序演算法的擴充套件。

單例項效能指標:

推送 | 消費
19.5 萬行/s | 54 萬行/s , 2.7 萬次/s (平均每次消費20行)

其他輔助技術

1、partial index,只對關心的資料建立索引。

2、流計算,結合流計算實現實時統計,實時資料轉換,實時歸納、清洗等。

3、brin 索引,對TS欄位,可以使用時序索引。索引小,效能好。類似業務:

《PostgreSQL 時序最佳實踐 – 證券交易系統資料庫設計 – 阿里雲RDS PostgreSQL最佳實踐》

4、plproxy,實現分片。阿里雲將會提供類似DRDS的PG中介軟體服務,使得PG的分庫分表透明化。

《阿里雲ApsaraDB RDS for PostgreSQL 最佳實踐 – 4 水平分庫(plproxy) 之 節點擴充套件》

《阿里雲ApsaraDB RDS for PostgreSQL 最佳實踐 – 3 水平分庫(plproxy) vs 單機 效能》

《阿里雲ApsaraDB RDS for PostgreSQL 最佳實踐 – 2 教你RDS PG的水平分庫(plproxy)》

5、gpdb mpp,將來如果需要對FEED資料進行挖掘,可以使用HDB PG(Greenplum)。MPP架構,OLAP效能非常棒。類似案例:

《打造雲端流計算、線上業務、資料分析的業務資料閉環 – 阿里雲RDS、HybridDB for PostgreSQL最佳實踐》

6、gin 倒排,文字搜尋。實現文字全文檢索。

《PostgreSQL 全文檢索之 – 位置匹配 過濾語法(例如 `速度 <1> 激情`)》

《PostgreSQL UDF實現tsvector(全文檢索), array(陣列)多值欄位與scalar(單值欄位)型別的整合索引(類分割槽索引) – 單值與多值型別複合查詢效能提速100倍+ 案例 (含,單值+多值列合成)》

《PostgreSQL – 全文檢索內建及自定義ranking演算法介紹 與案例》

《用PostgreSQL 做實時高效 搜尋引擎 – 全文檢索、模糊查詢、正則查詢、相似查詢、ADHOC查詢》

7、海明,rum,pg_trgm,文字相似搜尋

《海量資料,海明(simhash)距離高效檢索(smlar) – 阿里雲RDS PosgreSQL最佳實踐》

《17種文字相似演算法與GIN索引 – pg_similarity》

《PostgreSQL結合餘弦、線性相關演算法 在文字、圖片、陣列相似 等領域的應用 – 3 rum, smlar應用場景分析》

《PostgreSQL結合餘弦、線性相關演算法 在文字、圖片、陣列相似 等領域的應用 – 2 smlar外掛詳解》

《PostgreSQL結合餘弦、線性相關演算法 在文字、圖片、陣列相似 等領域的應用 – 1 文字(關鍵詞)分析理論基礎 – TF(Term Frequency 詞頻)/IDF(Inverse Document Frequency 逆向文字頻率)》

《聊一聊雙十一背後的技術 – 毫秒分詞算啥, 試試正則和相似度》

《PostgreSQL 文字資料分析實踐之 – 相似度分析》

8、plpython, madlib, 文字挖掘

《一張圖看懂MADlib能幹什麼》

《[轉載]易上手的資料探勘、視覺化與機器學習工具: Orange介紹》

9、PPC大賽

《三體高可用PCC大賽 – facebook微博 like場景 – 資料庫設計與效能壓測》

《阿里雲 PostgreSQL 產品生態;案例、開發實踐、管理實踐、學習資料、學習視訊》

《PostgreSQL 傳統 hash 分割槽方法和效能》


相關文章