1. 程式人生 > 實用技巧 >23丨當Postgres磁碟讀引起IO高的時候,應該怎麼辦

23丨當Postgres磁碟讀引起IO高的時候,應該怎麼辦

在效能分析的人眼裡,效能瓶頸就是效能瓶頸。無論這個效能瓶頸出現在程式碼層、作業系統層、資料庫層還是其他層,最終的目的只有一個結果:解決掉!

有人可能會覺得這種說法過於霸道。

事實上,我要強調的效能分析能力,是一套分析邏輯。在這一套分析邏輯中,不管是作業系統、程式碼還是資料庫等,所涉及到的都只是基礎知識。如果一個人都掌握這些內容,那確實不現實,但如果是對一個性能團隊的要求,我覺得一點也不高。

在效能測試和效能分析的專案中,沒有壓力發起,就不會有效能瓶頸,也就談不上效能分析了。所以每個問題的前提,都是要有壓力。

但不是所有的壓力場景都合理,再加上即使壓力場景不合理,也能壓出效能瓶頸,這就會產生一種錯覺:似乎一個錯誤的壓力場景也是有效的。

我是在介入一個專案時,首先會看場景是否有效。如果無效,我就不會下手去調了,因為即使優化好了,可能也給不出生產環境應該如何配置的結論,那工作就白做了。

所以要先調場景。

我經常會把一個性能測試專案裡的工作分成兩大階段:

整理階段

在這個階段中,要把之前專案中做錯的內容糾正過來。不止有技術裡的糾正,還有從上到下溝通上的糾正。

調優階段

這才真是幹活的階段。

在這個案例中,同樣,我還是要表達一個分析的思路。

案例問題描述

這是一個性能從業人員問的問題:為什麼這個應用的update用了這麼長時間呢?他還給了我一個截圖:

從這個圖中可以看到時間在100毫秒左右。根據我的經驗,一個SQL執行100ms,對實時業務來說,確實有點長了。

但是這個時間是長還是短,還不能下結論。要是業務需要必須寫成這種耗時的SQL呢?

接著他又給我發了TPS圖。如下所示:

這個TPS圖確實……有點亂!還記得前面我對TPS的描述吧,在一個場景中,TPS是要有階梯的。

如果你在遞增的TPS場景中發現了問題,然後為了找到這個問題,用同樣的TPS級別快速加起來壓力,這種方式也是可以的。只是這個結果不做為測試報告,而是應該記錄到調優報告當中。

而現在我們看到的這個TPS趨勢,那真是哪哪都挨不上呀。如此混亂的TPS,那必然是效能有問題。

他還告訴了我兩個資訊。

  1. 有100萬條引數化資料;
  2. GC正常,dump檔案也沒有死鎖的問題。

這兩個資訊應該說只能是資訊,並不能起到什麼作用。另外,我也不知道他說的“GC正常”是怎麼個正常法,只能相信他說的。

以上就是基本的資訊了。

分析過程

照舊,先畫個架構圖出來看看。

每次做效能分析的時候,我幾乎都會先幹這個事情。只有看了這個圖,我心裡才踏實。才能明確知道要面對的系統範圍有多大;才能在一個地方出問題的時候,去考慮是不是由其他地方引起的;才能跟著問題找到一條條的分析路徑……

下面是一張簡單的架構圖,從下面這張架構圖中可以看到,這不是個複雜的應用,是個非常典型的微服務結構,只是資料庫用了PostgreSQL而已。

由於這個問題反饋的是從服務叢集日誌中看到的update慢,所以後面的分析肯定是直接對著資料庫去了。

這裡要提醒一句,我們看到什麼現象,就跟著現象去分析。這是非常正規的思路吧。但就是有一些人,明明看著資料庫有問題,非要瞪著眼睛跟應用伺服器較勁。

前不久就有一個人問了我一個性能問題,說是在壓力過程中,發現數據庫CPU用完了,應用伺服器的CPU還有餘量,於是加了兩個資料庫CPU。但是加完之後,發現數據庫CPU使用率沒有用上去,反而應用伺服器的CPU用完了。我一聽,覺得挺合理的呀,為什麼他在糾結應用伺服器用完了呢?於是我就告訴他,別糾結這個,先看時間耗在哪裡。結果發現應用的時間都耗在讀取資料庫上了,只是資料庫硬體好了一些而已。

因為這是個在資料庫上的問題,所以我直接查了資料庫的資源。

檢視vmstat,從這個結果來看,系統資源確實沒用上。不過,請注意,這個bi挺高,能達到30萬以上。那這個值說明了什麼呢?我們來算一算。

bi是指每秒讀磁碟的塊數。所以要先看一下,一塊有多大。

[root@7dgroup1 ~]# tune2fs -l /dev/vda1 | grep "Block size"
Block size:               4096
[root@7dgroup1 ~]#

那計算下來大約就是:

$(300000*1024)/1024/1024\approx293M$

將近300M的讀取,顯然這個值是不低的。

接下來檢視I/O。再執行下iostat看看。

從這個結果來看,%util已經達到了95%左右,同時看rkB/s那一列,確實在300M左右。

接著在master上面的執行iotop。

我發現Walsender Postgres程序達到了56.07%的使用率,也就是說它的讀在300M左右。但是寫的並不多,從圖上看只有5.77M/s。

結合上面幾個圖,我們後面的優化方向就是:降低讀取,提高寫入

到這裡,我們就得說道說道了。這個Walsender Postgres程序是幹嗎的呢?

我根據理解,畫了一個Walsender的邏輯圖:

從這個圖中就可以看得出來,Walsender和Walreceiver實現了PostgreSQL的Master和Slave之間的流式複製。Walsender取歸檔目錄中的內容(敲黑板了呀!),通過網路傳送給Walreceiver,Walreceiver接收之後在slave上還原和master資料庫一樣的資料。

而現在讀取這麼高,那我們就把讀取降下來。

先檢視一下幾個關鍵引數:

這兩個引數對PostgreSQL非常重要。checkpoint_completion_target這個值表示這次checkpoint完成的時間佔到下一次checkpoint之間的時間的百分比。

這樣說似乎不太好理解。畫圖說明一下:

在這個圖中300s就是checkpoint_timeout,即兩次checkpoint之間的時間長度。這時若將checkpoint_completion_target設定為0.1,那就是說CheckPoint1完成時間的目標就是在30s以內。

在這樣的配置之下,你就會知道checkpoint_completion_target設定得越短,集中寫的內容就越多,I/O峰值就會高;checkpoint_completion_target設定得越長,寫入就不會那麼集中。也就是說checkpoint_completion_target設定得長,會讓寫I/O有緩解。

在我們這個案例中,寫並沒有多少。所以這個不是什麼問題。

但是讀取的I/O那麼大,又是流式傳輸的,那就是會不斷地讀檔案,為了保證有足夠的資料可以流式輸出,這裡我把shared_buffers增加,以便減輕本地I/O的的壓力。

來看一下優化動作:

checkpoint_completion_target = 0.1
checkpoint_timeout = 30min
shared_buffers = 20G
min_wal_size = 1GB
max_wal_size = 4GB

其中的max_wal_size和min_wal_size官方含義如下所示。

max_wal_size (integer):

Maximum size to let the WAL grow to between automatic WAL checkpoints. This is a soft limit; WAL size can exceed max_wal_size under special circumstances, like under heavy load, a failing archive_command, or a high wal_keep_segments setting. The default is 1 GB. Increasing this parameter can increase the amount of time needed for crash recovery. This parameter can only be set in the postgresql.conf file or on the server command line.

min_wal_size (integer):

As long as WAL disk usage stays below this setting, old WAL files are always recycled for future use at a checkpoint, rather than removed. This can be used to ensure that enough WAL space is reserved to handle spikes in WAL usage, for example when running large batch jobs. The default is 80 MB. This parameter can only be set in the postgresql.conf file or on the server command line.

請注意,上面的shared_buffers是有點過大的,不過我們先驗證結果再說。

優化結果

再看iostat:

看起來持續的讀降低了不少。效果是有的,方向沒錯。再來看看TPS:

看這裡TPS確實穩定了很多,效果也比較明顯。

這也就達到我們優化的目標了。就像在前面文章中所說的,在優化的過程中,當你碰到TPS非常不規則時,請記住,一定要先把TPS調穩定,不要指望在一個混亂的TPS曲線下做優化,那將使你無的放矢。

問題又來了?

在解決了上一個問題之後,沒過多久,另一個問題又拋到我面前了,這是另一個介面,因為是在同一個專案上,所以對問問題的人來說,疑惑還是資料庫有問題。

來看一下TPS:

這個問題很明顯,那就是後面的成功事務數怎麼能達到8000以上?如果讓你蒙的話,你覺得會是什麼原因呢?

在這裡,告訴你我對TPS趨勢的判斷邏輯,那就是TPS不能出現意外的趨勢。

什麼叫意外的趨勢?就是當在執行一個場景之前就已經考慮到了這個TPS趨勢應該是個什麼樣子(做嘗試的場景除外),當拿到執行的結果之後,TPS趨勢要和預期一致。

如果沒有預期,就不具有分析TPS的能力了,最多也就是壓出曲線,但不會懂曲線的含義。

像上面的這處TPS圖,顯然就出現意外了,並且是如此大的意外。前面只有1300左右的TPS,後面怎麼可能跑到8000以上,還全是對的呢?

所以我看到這個圖之後,就問了一下:是不是沒加斷言?

然後他查了一下,果然沒有加斷言。於是重跑場景。得到如下結果:


從這個圖上可以看到,加了斷言之後,錯誤的事務都正常暴露出來了。像這種後臺處理了異常並返回了資訊的時候,前端會收到正常的HTTP Code,所以才會出現這樣的問題。

這也是為什麼通常我們都要加斷言來判斷業務是否正常。

總結

在效能分析的道路上,我們會遇到各種雜七雜八的問題。很多時候,我們都期待著效能測試中的分析像破案一樣,並且最好可以破一個驚天地泣鬼神的大案,以揚名四海。

然而分析到了根本原因之後,你會發現優化的部分是如此簡單。

其實對於PostgreSQL資料庫來說,像buffer、log、replication等內容,都是非常重要的分析點,在做專案之前,我建議先把這樣的引數給收拾一遍,不要讓引數配置成為效能問題,否則得不償失。