天天看點

優化Postgres-x2 GTM【CSDN】

postgres-x2是一個基于pgsql、面向otlp的分布式資料庫,采用了shared-nothing的架構,目标是針對oltp\olap應用能做到可擴充的系統。源碼在github上:https://github.com/postgres-x2/postgres-x2 。

     最近在針對 postgres-x2做壓力測試。測試是在一台dell r510上進行的,該伺服器上有4顆x5650和64g記憶體,另外是兩塊老式的ssd,型号不詳,最大寫入速度100m左右。

     測試的版本是直接從github上拉下來的,直接編譯安裝。在這台伺服器上安裝了gtm、coordinator、datanode各一個,gtm和coordinator的資料目錄都在一個ssd上,datanode的資料目錄在另外的一個ssd盤上。以最大化利用磁盤資源。之是以在一台伺服器上進行測試,主要是想模拟一個網絡帶寬不受限的環境,也是為了簡化問題。

    coordinator的幾個重要配置如下:

conf代碼

1. max_connections = 1024    

2. shared_buffers = 2048mb # shared_buffer沒有必要設定過大  

3. checkpoint_segments = 64   

4. checkpoint_timeout = 20min # 讓checkpoint間隔盡量大   

5. logging_collector = on  

6. autovacuum = off  

7. min_pool_size = 100   # 連接配接池初始大小  

8. max_pool_size = 1024  

 datanode的配置如下

1. max_connections = 1024  

2. shared_buffers = 20480mb  

3. vacuum_cost_delay = 10  

4. vacuum_cost_limit = 10000  

5. checkpoint_segments = 256   

6. checkpoint_timeout = 10min    

7. logging_collector = on  

8. autovacuum_vacuum_cost_delay = 5ms              

 測試的主要方法是使用pgbench生成scale為1000的資料集合,大概有16g,主要的測試方法就是先執行checkpoint,将資料塊刷回磁盤,以減小checkpoint的影響,然後執行下面的指令:

bash代碼

1. pgbench -c n -j n -t 60  

 n是連接配接數,這個操作會模拟并發的n個客戶連續通路資料庫60秒,每個客戶需要在60秒内不停執行一個有着5條sql的事務:

1. begin;  

2. update pgbench_tellers set tbalance = tbalance + $1 where tid = $2;  

3. update pgbench_branches set bbalance = bbalance + $1 where bid = $2;  

4. select abalance from pgbench_accounts where aid = $1;  

5. insert into pgbench_history (tid, bid, aid, delta, mtime) values ($1, $2, $3, $4, current_timestamp);   

6. update pgbench_accounts set abalance = abalance + $1 where aid = $2;  

7. end;  

 測試的時候,n從32增大到768,取tps。結果如下:

優化Postgres-x2 GTM【CSDN】

 随着連接配接數的增加,tps幾乎是線性降低。datanode資料目錄所在的磁盤使用率也基本上從最高的60%降低到了20%,而整體的cpu使用率不怎麼飙升,隻是gtm看起來還比較忙。

      感覺gtm比較有問題,于是祭出了gprof來做性能分析,結果啥都沒有,google一下,發現是gtm屏蔽了sigprof信号,打上我的這個patch就ok了:

c代碼

1. diff --git a/src/gtm/libpq/pqsignal.c b/src/gtm/libpq/pqsignal.c  

2. index e3f482c..594540f 100644  

3. --- a/src/gtm/libpq/pqsignal.c  

4. +++ b/src/gtm/libpq/pqsignal.c  

5. @@ -119,6 +119,10 @@ pqinitmask(void)  

6.     sigdelset(&blocksig, sigcont);  

7.     sigdelset(&authblocksig, sigcont);  

8.  #endif  

9. +#ifdef sigprof  

10. +        sigdelset(&blocksig, sigprof);  

11. +        sigdelset(&authblocksig, sigprof);  

12. +#endif  

   記得使用--enable-profiling選項來重新生成makefile 或是直接編輯makefile加上 -pg選項,然後重新編譯一下gtm。拿pgbench運作一段時間之後,停掉gtm,在gtm的目錄下會有一個gmon.out,執行: 

1. gprof -b /usr/local/pgx2/bin/gtm gmon.out >  gtm.out  

在生成的gtm.out中,可以看到有兩個函數幾乎占用了cpu的大部分時間:

gmon代碼

1. each sample counts as 0.01 seconds.  

2.   %   cumulative   self              self     total  

3.  time   seconds   seconds    calls  ms/call  ms/call  name  

4.  31.41     49.90    49.90  8256962     0.01     0.01  gtm_gxidtohandle  

5.  15.00     73.72    23.82  3791614     0.01     0.01  gtm_gettransactionsnapshot  

6.   6.33     83.77    10.05  4305476     0.00     0.00  gtm_list_delete  

7.   5.07     91.83     8.06      790    10.20   181.29  gtm_threadmain  

8.   3.09     96.75     4.92 25006706     0.00     0.00  allocsetalloc  

9.   2.06    100.02     3.27 752039341     0.00     0.00  globaltransactionidprecedes  

10.   1.78    102.85     2.83 30641196     0.00     0.00  elog_start  

11.   1.66    105.49     2.64 11157165     0.00     0.00  pq_recvbuf  

12.   1.62    108.07     2.58 13648719     0.00     0.00  internal_flush  

 在花了半天看了這兩個耗時的函數,大概有點眉目:

   1,當coordinator上啟動一個事務時,回去gtm申請一個一個事務id (xid) 和存放事務相關資訊的gtm_transactioninfo資料結構,并把這個資料結構的指針放入一個全局的連結清單gtmtransactions.gt_open_transactions中,gtm将事務id傳回給coordinator

   2,事務執行時,會将xid發給gtm去擷取快照。gtm會首先調用gtm_gxidtohandle函數去獲得對應的gtm_transactioninfo資料結構的指針,gtm_gxidtohandle函數會周遊全局連結清單gtmtransactions.gt_open_transactions來擷取。然後将該gtm_transactioninfo資料結構的指針傳給gtm_gettransactionsnapshot函數來獲得快照:周遊gtmtransactions.gt_open_transactions中的每個元素,擷取全局最小的xmin,和活躍的事務id(小于最近送出事務的最大id),放入快照并傳回給coordinator。

   3,事務結束時,coordinator将xid傳回給gtm,gtm根據xid查找對應的gtm_transactioninfo資料結構,将其回收,并删除gtmtransactions.gt_open_transactions中的對應item。

    簡單看來,這兩個耗時的函數都是o(n)級别的複雜度,n 是gtmtransactions.gt_open_transactions的長度,也就是目前正在進行的事務數。是以,随着連接配接數的增加,coordinator和datanode内部鎖競争的加劇,會導緻事務逐漸的積壓起來,讓gtmtransactions.gt_open_transactions長度變得越來越長,是以堵住了很多擷取事務快照的事務。最終就是剛才描述的情形:事務等待gtm,磁盤使用率急劇降低。

      是以,從直覺出發,其實可以直接用開放式hash表來優化gtm_gxidtohandle函數,key是事務id,value是對應的gtm_transactioninfo的指針,将這個的函數的操作複雜度降低到o(1)。但是,發現效果并不好:因為gtm_gettransactionsnapshot函數依然還是要去周遊所有的目前事務擷取最小事務xmin和快照。

      在經過一個禮拜的調整之後,直接采用教科書的方法:使用一個avl樹來存儲gtm_transactioninfo的指針,比較大小的方法就是對比其中的gxid;另外一個使用avl樹來存最小的xmin。簡單來說就是分别以xmin和事務id為主鍵建立兩個類似btree的索引,以滿足查找需求。最終去掉了gtmtransactions.gt_open_transactions,将這兩個耗時的函數的複雜度降低到了o(log(n)),n是目前系統内的事務。

        采用前面的測試方法,來擷取tps。和前面的對比圖如下:

優化Postgres-x2 GTM【CSDN】

可以看到優化之後的gtm響應時間基本維持在o(log(n)),進而使得tps在連接配接數增大時,tps沒有像目前版本下降得這麼劇烈(678個連接配接時,tps是目前版本的幾乎5倍),datanode的資料目錄所在磁盤利用使用率機會基本維持在65%~60%之間。這樣看來,基本上能改善gtm的擴充能力。

       當然,這個優化并非是完美的,因為在執行簡單事務并且連接配接數不多的情況下,tps和原有的版本幾乎相同,但是,一旦事務變得複雜,在gtm中停留的時間增大,或是連接配接數增大後同時執行的事務數量多了之後,優化之後的gtm相對現有的gtm會穩定很多。

       最後,為了驗證工作,選了三台相同配置的r510,配置和前面所述相同。一台上裝gtm,其他兩台上配置了coordinator和datanode各一個(配置也和前面相同)。

       生成了scale為1000的資料,32g,使用前面的測試方法,但是分别執行在一張大表上的簡單主鍵查詢:

1. \setrandom aid 1 :1000  

2. select abalance from pgbench_accounts where aid = :aid;  

和基于主鍵的更新

sql代碼

1. \setrandom aid 1 10000000  

2. \setrandom delta -5000 5000  

3. update pgbench_accounts set abalance = abalance + :delta where aid = :aid; 

 在兩台coordinator同時上執行。

      最終, 簡單select操作的總qps最大值是45000(每個coordinator上的qps是22500),簡單update操作的總 tps最大值是 35000(每個coordinator上最大的qps是17500)。碰巧的是獲得最大值時,每台coordinator上執行pgbench的連接配接數都是64。而随着連接配接數增大到一定程度,優化之後的gtm會比目前的gtm 結果高50%以上。

        從測試看來,在64個連接配接數的情況,增大一個伺服器(部署上datanode和coordinator),可以增加17500的tps,網絡流量增加20mb/s。是以如果真的想達到10w的tps,預計需要10台左右這樣的伺服器用來部署coordinator和datanode。

     但是我們需要解決兩個問題:一方面,我們需要子網的交換機來提供200mb/s以上的帶寬連接配接各個伺服器,這是首先必須解決的問題,當然也是比較容易解決的問題;另外一方面,對于gtm來說,我們必須采用類似的優化來增大gtm的可擴充能力,因為如果每個coordinator上使用64個連接配接,那麼對于10台的叢集來說,系統内操作的連接配接數就是640了,如果還采用目前的gtm,tps qps會急劇下降,這是根本沒法做到的。

<b>本文來自雲栖社群合作夥伴“dbgeek”</b>