避坑指南:關於SPDK問題分析過程

【前言】

這是一次充滿曲折與反轉的問題分析,資料不多,代碼不少,經驗不多,概念不少,當內核態,用戶態,DIF,LBA,大頁內存,SGL,RDMA,NVME和SSD一塊兒迎面而來的時候,問題是單點的意外,仍是羣體的無奈?node

爲了加深記憶,也爲了分享出來給人以啓示,特記錄此次問題分析過程。segmentfault

【現象】

同事L在項目中須要使用NVMF寫盤,發現寫盤失敗,瘋狂打印錯誤碼:api

圖片中雖然截取的比較少,但實際是瘋狂的一直打印。安全

故障現象簡要描述一下就是:網絡

經過NVMF寫盤失敗,瘋狂打印錯誤碼15;數據結構

做爲對照,經過本地寫盤,一切正常。app

注:這裏的盤,都是指SSD盤。目前實驗室使用的型號是公司V3版本(HWE3xxx)。異步

【分析】

在這裏把涉及到的一些基本縮略語都記錄一下:函數

習慣了縮略語做爲名詞後,老是容易忽略其背後更多的含義,問題的分析,須要對這些有更深的理解,最初對這些理解不深,對數據處理流程不清晰,起步很艱難。工具

分析步驟(一)

在下發IO時,經過變換IO的大小,隊列深度,發現數據量較小時,則幾乎沒有問題,直接下發1M大小IO時,則必現。

所以,能夠明顯的推測出IO的大小與問題的出現緊密相關。

直接運行業務來驗證問題,過於笨重了,並且很是麻煩,將問題直接簡化爲,一個服務端和一個請求端,發現均能穩定復現,他們分別是:

1. 運行SPDK自帶的app,nvmf_tgt程序,這個就是NVMF的服務端了;

  • 進入spdk目錄後,配置好2M大頁;
  • 配置好nvmf.conf 配置文件,假設文件放在/opt/yy目錄下;配置文件參考附錄;
  • 運行./app/nvmf_tgt/nvmf_tgt -c /opt/yy/nvmf.conf;

2. 可使用兩種模式的請求端,

  • 一種是SPDK自帶的perf程序,路徑是./examples/nvme/perf/perf,會配置必要的參數; 注意:系統也自帶一個perf,不是系統自帶的那一個; Perf是一個測試工具,會隨機產生數據大量寫入,能夠驗證問題修復性,但不利於問題最初的分析;
  • 一種是自已改造nvme目錄下的helloworld程序(初始版本,由同事C提供,後來通過了一些改良,後續稱爲DEMO程序); 代碼見附錄;

由於都是運行在用戶態,因此開啓調試仍是很方便的。兩端同時開啓調試模式,進行單步跟蹤,發現錯誤碼是在異步模式下輪循獲得,如圖

函數名稱已經告知,是處理完成的結果;

調用是來自於這裏,383行:

在303行下斷點,根據棧信息(沒有有效信息,略)看,錯誤碼可能來自於SPDK的某個異步調用,也可能來自於設備,查遍SPDK代碼,發現根本沒有15這個錯誤碼的設置,基本推導爲是由SSD返回的。

根據最初的信息可知,IO的數據量大小會影響問題出現,IO數據量較小時不會出現,那麼分界點在哪裏呢?

採用二分法在DEMO程序上嘗試,發現LBA的個數爲15時,是分界點

那麼,怎麼用起來呢?

單步跟蹤,有一個參數進入視野,命名空間(NVME的協議規範吧,一塊SSD下有一個控制,有若干個命名空間)的sectors_per_max_io參數。

修改這個參數,能夠控制最後寫盤時的大小,在DEMO程序上試驗,問題消失。

可是當IO大小與深度較大,要麼出現內存不足錯誤碼,要麼錯誤依然出現,另外多盤場景下很是容易再現。

給出有條件解決辦法1:

(1) 修改如上位置;

(2) 業務下發時要求對IO的大小和下發的盤數進行限定;

實際使用時,由於必需多盤,要改形成單盤,很是困難,不是理想的解決方案。

另外還發現不一樣版本的盤,最小適配值不同,最安全值是7,可是後來主要選取一塊15爲安全線的盤來分析問題使用。

分析步驟(二)

爲了快速解決問題,開始嘗試普遍求助,這麼明顯的問題,別人有沒有遇到?

在遍訪hi3ms和搜遍google,以及請教相關能夠找到的同事,嘿,還真沒有第二例!

並且更爲奇怪的是,在Intel的基線報告中明明就有較大的IO數據量的NVMF測試,還有正常的結果。

怎麼在這裏就有問題呢?

不一樣點:

  • Intel確定使用Intel的盤;
  • 這兒用的是公司的盤;

難道是由於這個?

硬件上,理論上沒有這麼大差別吧。

通過一番探索發現,當把硬盤格式化爲不帶DIF時,NVMF也是正常的,若是格式化爲帶DIF的,即512+8格式時,問題就會出現;

SO,Intel爲啥沒有問題,基本已經肯定,他們用的是不帶DIF格式,同時發現不帶DIF,時延會快一點點,這很好理解。

有一個疑惑,始終沒有答案,爲何本地寫沒有出現,而NVMF寫會出現呢?

這是須要回答的最重要的問題。

做爲基礎,須要先簡單瞭解一下NVME的寫盤。

這個過程是異步的;

寫盤前,程序將數據按照隊列(好比SGL)準備好,而後通知SSD,程序就完事了;

而後是SSD會到機器中把數據取出寫入盤中,處理完成後,而後通知程序,程序檢查結果隊列。

能夠看出,當前說的寫盤,主要是指將數據按照隊列準備好就完成了,後面一段是由SSD設備來處理的。

有了這個基礎,能夠較快理解本地寫盤了,調用SPDK API後,由SPDK準備隊列,而後提交,真正把數據存起來的事情是SSD裏控制器作的。。。

可是NVMF寫盤呢?畢竟中間有段網絡,是怎麼處理的。。。

爲了便於分析,因此選擇改造DEMO,主要是perf比較複雜,隨機的LBA和大數據量對分析有較大幹擾。

在DEMO程序中,指定在0號LBA開始提交數據,並且每次提交17塊數據(總長度17*520=8840)。

那爲啥數據塊指定17呢?

由於15及如下是不會出現問題的,根據前面的分析,這塊SSD的正常分界線是15,而16是2的4次方,在計算機中2的N次方過於特殊,所以選擇普通的17。

其次,保證其它地方徹底同樣,僅在初始化時,造成兩種模式,一種是本地寫,一種是NVMF寫;

如圖,手動直接改變紅框裏的參數,由tr_rdma和tr_pcie,能夠在兩種模式中切換;

這樣的目的是,能夠造成徹底的對比,對齊全部能對齊的條件,分析在NVMF的哪一個環節出現問題。

在初步單步跟蹤了一下調用過程,能夠梳理出本地寫與NVMF寫的基本處理流程:

本地寫:

  1. 在請求端,申請了一塊連續的內存1M大小,塊大小以4K大小對齊;
  2. 將其中的17個塊(也就是1M大小隻用了17*520字節)經過調用SPDK的API進行寫盤;
  3. SPDK的API會調用以PCIE模式接口(系統初始化時,註冊的回調函數,在初始化入口時,上面圖中紅框的參數決定了會走向PCIE對應接口);
  4. 準備數據隊列,提交SSD寫盤請求,返回;
  5. 輪循處理完成的接口,獲取到寫盤成功通知;

NVMF寫:

請求端側:

(1)在請求端,申請了一塊連續的內存1M大小,塊大小以4K大小對齊;

(2)將其中的17個塊(也就是1M大小隻用了17*520字節)經過調用SPDK的API進行寫盤;

(3) SPDK的API會調用以RDMA模式接口(同上,初始化時,註冊了RDMA的回調函數,上圖中紅框的參數決定了,這裏的調用走向RDMA對應接口);

(4)準備數據隊列,經過RDMA網絡傳送到服務端,返回;

服務端側:

(5) 服務端的RDMA在輪循(poll)中收到數據到來的通知;

(6)組裝數據結構,便於內部API調用;

(7)數據一路調用bdev,spdk,nvme的api,地址被轉換爲物理地址,最後調用pcie的數據接口提交;

(8)而後按規範按下提交門鈴,返回;

兩側異步(提交請求後,只能異步等待結果打印)打印結果:

(9)請求端輪循處理完成的接口,若是錯誤會出現打印;

經過debug能夠看到錯誤碼是15

(10)服務端輪循處理完成的接口,若是錯誤,會出現打印:

反覆對本地和NVMF下發數據(上面0開始,17塊數據),逐個流程與參數對比(雙屏提供了較大的便利),確實發現很多異同點:

(1)本地寫的過程與NVMF寫的請求端過程,幾乎同樣,不一樣的是本地寫的數據提交是到SSD,NVMF請求端的寫調用RDMA的接口;

(2) NVMF服務端有很長的調用棧(有30層深),而本地寫根本不存在這個過程;

(3)NVMF服務端在通過系列調用後,最後走到了像本地寫盤同樣的函數調用,nvme_transport_qpair_submit_request;

彷佛是個顯然的結論,NVME OVER RDMA實際是,數據通過了RDMA傳輸後,仍是NVME OVER PCIE;

(4)本地寫時,只有1個SGL,這個SGL裏面只有1個SGE,NVMF的請求端在調用RDMA前,也是隻有1個SGL,這個SGL裏也只有1個SGE;

(5) NVMF服務端的在寫盤前,只有1個SGL,可是這個SGL裏有2個SGE;

整個過程,用圖來描述以下:

如圖:

這是一個重要的發現,基本能夠解釋爲何解決辦法1部分場合是有效的(15的安全線內數據大小小於8k,保證1個SGL裏只有1個SGE),但沒法解釋有一些場合失敗。

捋一下,就清楚多了:

RDMA在NVMF的請求端拿到的數據是1個SGL內含1個SGE,通過RDMA後,從NVMF服務端拿到的數據是1個SGL內含2個SGE。

至此,彷佛基本「鎖定」了肇事者了,就是RDMA了!

可是,在翻閱RDMA的資料,SSD的資料後,發現1個SGL裏,1個SGE,2個SGE根本是自由的,自由的。。。

雖然,RDMA在接收數據後,將1個SGE分紅2個SGE,有引發問題的嫌疑,可是從資料介紹看,彷佛不能直接構成問題。

爲了驗證1個SGL裏多個SGE是否是問題,又開始改造DEMO了,構造了寫數據前,將數據分爲多個SGE了,如圖:

先試了試NVMF,發現能夠復現,和前面的NVMF沒有什麼兩樣,

接下來試了試本地,發現沒有問題,也就是說,疑問沒有消除。

分析步驟(三)

山重水複疑無路,只好推倒,從頭再來分析,一次偶然的NVMF下發中發現,2個SGE的地址中,第2個SGE的地址在前,第1個SGE的地址在後,而後密切關注,即使在DEMO程序中,這個地址的前後也有必定的隨機,多數時候是順序的,少數時候是顛倒的,可是不管怎樣,1個SGE與另1個SGE中是不連續,也就是SGE1與SGE2之間有空洞。

立刻構造相同的形態,

寫本地,發現重現了!

這是一個「重要發現」!本地也能重現!

幾乎能夠順利成章的推論出,是否NVMF不是關鍵!那麼也就排除了RDMA的嫌疑了!

寫盤時,若是多個SGE的數據區徹底連續,則沒有問題,若是多個SGE的數據區不連續,則會出現問題。

那麼,很容易推導出問題所在點,當前用的這個SSD不支持不連續的SGE!難道是SSD?!

而後。。。(此處略去一段文字不表。。。)

。。。

。。。

是的,SSD沒有問題,有問題的是那個8192的長度,正確的應該是8320!

8320是什麼,8192是什麼?

8192是512 * 16;

8320是520 * 16;

看看,以前一直不理解那個刷屏的錯誤提示,什麼叫「DATA SGL LENGTH INVALID」,這個含糊不清的提示,也有不少可能,既多是SGL裏的SGE個數不對,也多是SGE裏的長度不對,還多是裏面的長度字段讀寫不對,還多是寄存器出錯,還可能內存被踩。。。

可是,真相就是,SGE裏的數據長度沒有和BLOCK的基本大小520對齊!如今用的格式是帶DIF區的,512+8=520!

那個提示是告訴你,數據塊沒有對齊,SGE裏的長度無效!

當各個點針對性的改好了這個基本參數時,

DEMO的本地正常了,

DEMO的NVMF也正常了,

彷佛真相大白了。。。

然而,還沒高興幾分鐘,使用perf下發1M的IO時,問題又復現了!

分析步驟(四)

細心的跟蹤後發現,雖然問題復現了,可是沒有之前刷屏那麼多了,並且經過單步發現,只要SGE數據的地址是以FF000結尾的,就會出現問題。

回溯這個地址,能夠看到,來源於RDMA在收到數據後就出現了,偶爾會出現FF000結尾的,因此能夠解釋錯誤刷屏沒有那麼密集了。

看起來,仍是RDMA有問題啊~

繼續分析能夠發現,這些地址,實際也不是RDMA臨時分配的,而是從緩衝隊列裏獲取的。

基本能夠認爲,緩衝隊列中有不少可供選擇,偶爾會拿到FF000結尾的這種來作緩衝,只要這種地址就會出現問題。

那麼,爲何這種地址就會出現問題呢?

還記得前面有一個步驟嗎?設置2M大頁內存,SPDK是基於DPDK的,DPDK內存隊列是要求大頁內存的,最經常使用的是2M大頁。

這些緩衝就是從DPDK那些大頁裏獲取的,而FF000就是靠近2M邊界的,通常的緩衝使用也沒有啥問題,可是SSD不接受跨大頁的空間,所以在準備提交隊列時,若是遇到要跨大頁的,將這個SGE作切分,1分爲2,以FF000結尾的地址上只能存4096字節,所以一個SGE裏4096,餘下的放在下一個SGE裏,而4096又不是520的對齊倍數,因此出問題了。

針對性的解決辦法是,在獲取地址前,加一個判斷,若是是這種地址就跳過。

修改!

驗證!

屏住呼吸。。。

可是,再一次出乎意料,用perf在大IO下測試依然有問題!

不氣餒,再戰!

打開日誌(由於是異步,並且是大數據量測試,因此只好在關鍵地方增長日誌,記錄下這些地址分配細節,主要地點,一個是提交請求時,見上面的文件和代碼行,就不貼代碼了,一個是入RDMA收到數據最開始拿到的地方,還有一個是完成時的結果),繼續分析。

一下就看到,還有一種地址分配異常,也會造成SGE中長度問題,如圖:

再一次在獲取地址的位置進行修改屏蔽之,將兩種要跳過的直接合一。

如圖(471~475,另外在nvmf_request_get_buffers函數中須要配置進行跳過處理):

修改!

驗證!

各用例測試經過!

問題消失!

提供第2個解決辦法,按如上代碼,能夠完全解決問題。

雖然問題解決了,跳過一些特殊地址,有一些浪費,

可是總感受這種改法太土了!能夠消除問題,可是隱隱感受不爽!

分析步驟(五)

有沒有其它方法?

帶着疑問繼續挖。

既然RDMA只是使用緩衝的隊列,那就有一個地方是分配這種緩衝隊列的,分配出來卻不用,明顯有點浪費,那至少能夠作到,分配的時候就不要分配這種數據吧。

一路回溯,終於找到申請的地方,可是甚是複雜,容後慢慢消化吧。

發現有段文字描述很長,和地址的分配很相關,

帶着這些信息再來單步查看分配緩衝過程,大體推測修改過程當中的一個參數,就能夠影響到後面的處理流程了。

紅框1爲代碼默認參數,修改成紅框2的,紅框2兩個參數的含義爲單生產者單消費者,DEMO程序中徹底匹配這個模式。

修改!

驗證!

RDMA在獲取SGE地址時,是單向增加的。

問題消失!

一個參數消除掉問題,對比起來,溫馨多了!

【小結】

(1)問題最後的解決辦法就是: NVMF的配置文件中須要顯性設置IOUnitSize的大小,與所用的Block大小成整數倍對齊,當前使用520的Block,建議設置爲8320;修改建立內存池參數;最後圖中的一個參數便可。

(2) 過程很是曲折,可是隻要不放棄,跟着代碼,再翻閱資料,大膽假設,當心求證,不斷迭代,終能找到問題所在;若是對相關概念與處理過程熟悉,會大幅度節約時間;

(3)最後安利一下,VSC,配上Remote – SSH,能夠直接在呈現Linux機器上的代碼,進行可視化調試,在代碼裏任意穿梭,哪裏疑惑點哪裏,對本次分析問題有極大的幫助;

附錄:

Nvmf的配置文件以下

[Global]
[Nvmf]
[Transport]
  Type RDMA
  InCapsuleDataSize 16384
  IOUnitSize 8192
[Nvme]
  TransportID "trtype:PCIe traddr:0000:04:00.0" Nvme0
  TransportID "trtype:PCIe traddr:0000:05:00.0" Nvme1
  TransportID "trtype:PCIe traddr:0000:82:00.0" Nvme2
[Subsystem1]
  NQN nqn.2020-05.io.spdk:cnode1
  Listen RDMA 192.168.80.4:5678
  SN SPDK001
  MN SPDK_Controller1
  AllowAnyHost Yes
  Namespace Nvme0n1 1
[Subsystem2]
  NQN nqn.2020-05.io.spdk:cnode2
  Listen RDMA 192.168.80.4:5678
  SN SPDK002
  MN SPDK_Controller1
  AllowAnyHost Yes
  Namespace Nvme1n1 1
[Subsystem3]
  NQN nqn.2020-05.io.spdk:cnode3
  Listen RDMA 192.168.80.4:5678
  SN SPDK003
  MN SPDK_Controller1
  AllowAnyHost Yes
           Namespace Nvme2n1 1

點擊關注,第一時間瞭解華爲雲新鮮技術~

相關文章
相關標籤/搜索