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等內容,都是非常重要的分析點,在做項目之前,我建議先把這樣的參數給收拾一遍,不要讓參數配置成為性能問題,否則得不償失。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM