數據庫調優實踐案例linux
數據庫做爲基礎數據支撐層的核心部分,對於應用和平臺總體性能表現有着決定性的影響。所以,數據庫性能優化能夠說是最考驗DBA能力的工做了。本文咱們就由數據庫內核專家來,以 SequoiaDB 5.0 內核的部分性能優化爲例,帶領各位數據庫愛好者揭開數據庫性能優化的「神祕面紗」。數據庫
一般優化思路:緩存
提升數據庫性能的方式有不少,總結起來從易到難無外乎以下三種:性能優化
內核調優網絡
在數據庫內核的調優中,開發人員一般會跑必定的workload或benchmark,使用操做系統或三方提供的工具,持續監控系統各種資源的使用狀況,在高併發系統中,也會關注併發控制中使用的鎖和原子變量帶來的開銷。下面咱們經過TPCC場景下的逐步優化SequoiaDB內核的過程,來了解咱們是如何使用工具來定位優化數據引擎的。併發
1. CPU usage高併發
咱們常使用兩大神器觀察CPU使用狀況:top 和 perf。top能動態的顯示linux 系統中各進程/線程以及內存使用的彙總信息。工具
/性能
以上圖爲例,咱們知道這臺機器的CPU基本上被用滿了,其中系統CPU佔13%,用戶CPU佔81.7%。若是CPU出現過多的空閒,每每意味着系統要麼還能夠增長負載提升性能,要麼有瓶頸致使CPU上不去,好比說併發很差,太多等待,串行化太多。在這個例子中,咱們沒有看到等IO的狀況,idle的比例也很是小,這都是好的現象。在CPU用滿的狀況下,優化系統也意味着要儘可能減小開銷,讓系統能儘量的跑多點任務。須要注意的是,若是系統CPU太高,意味着CPU不是在執行跟程序邏輯相關的指令,也能夠理解爲是overhead。根據以往的經驗,這裏系統CPU佔比仍是偏高。使用線程模式,更進一步分析,咱們能夠看到潛在的問題多是在系統調用,context switch和併發控制的mutex上。優化
至於更精確的定位,就要perf出馬了。注意的是SequoiaDB 的代碼編譯時加入了debug symbol,這樣會帶來必定的性能損失,但可以極大的方便問題診斷和定位。
perf 是linux提供的一種基於event的性能蒐集分析工具,可以分析CPU/內存/鎖等資源的統計信息。perf自己已經提供了至關完整的文字的報表輸出功能。
好比這裏能看到system_call 也是跟sys_futex 相關的,一般是線程/進程同步共享資源互踩時形成的,還有部分是通信線程相關的。這樣咱們的方向就能夠從各類鎖衝突入手。Perf也能提供鎖衝突的信息。
爲了簡單直觀的分析結果,咱們還使用火焰圖(flame graph)來用圖形的方式展示結果,以利用更快的發現問題。下面兩張圖分別提供了CPU和鎖的使用統計:從中咱們發現的確有幾處熱的Latch/mutex。好比內存分配時使用共享內存池,這是會形成等鎖的現象,咱們能夠經過使用線程上獨享的內存池解決;還要部份內部表的物理鎖衝突嚴重,咱們經過增長鎖的控制粒度減小衝突;再有就是儘可能減小鎖內操做,好比內存分配,磁盤IO儘量的搬出熱鎖保護範圍。經過一系列優化,咱們實現了5%左右的性能提高。
CPU 火焰圖
鎖火焰圖
2. Memory allocation
內存是個好東西,如今計算機系統內存愈來愈大,軟件也儘可能經過使用內存來實現空間換時間以提升系統相應速度。可是動態內存分配經常成爲了高性能軟件的性能瓶頸。咱們經過perf 來抓取系統內存的使用狀況,並用火焰圖顯示出來:
這裏明顯看到的是不少動態內存分配發生在一個set的插入過程當中。Std::set內部使用的紅黑樹,每次結點的插入都要進行內存分配。爲了減小系統內存的動態分配與回收,SequoiaDB實現了一整套本身的內存管理機制。最開始儘可能在線程預分配好的內存池上分配空間,這點和tcmalloc的原理很接近,這時的開銷最小,內存事先已經從操做系統分配好了,並且本線程上分配是無鎖的。可是若是線程內存池用完了,咱們會到一個共享的預分配好的內存池上分配,這時會多一個鎖的開銷。但這兩處都用完了,咱們才向操做系統申請。從火焰圖上看,咱們基本上都走到向操做系統分配的分支中了。針對這種狀況,咱們優化了set的實現。當set中結點數量較小時,咱們用一個flat的較小的array存放數據,避免了動態內存分配。當結點數較多時,咱們再轉化成樹型結構以提升查找效率。可是咱們會提升線程上容許的緩衝池的大小,特別是小結構線程池大小。最終咱們避免了絕大多少的動態內存分配與回收,提高了系統性能。經過這塊的分析,咱們也反過來幫助肯定那些query會用到大量數據,並優化對應的query。
3. Cache line misses
你們知道現代CPU的主頻很是高,常有超過3GHz,執行指令速度很是快。可是咱們存儲訪問速度始終跟不上,高速的內存又很是貴,這就是現代CPU裏有幾級不一樣速度不一樣大小內存的緣由,常見的CPU內集成有L1,L2,L3級緩存。CPU執行時須要從緩存中獲取指令和數據。在咱們編譯程序的時候,編譯器會試圖優化程序,使得CPU能有效的重用或預提取數據和指令。當CPU在緩存中找不到合適的指令和數據時,就不等不從主存甚至磁盤上讀取他們,這樣的開銷很是大,咱們用CPU cache line miss來衡量這中狀況出現的頻繁程度。
咱們仍是經過perf命令來蒐集cache line miss的狀況,
詳細信息分解開來,最大一塊是由monitor引發的
而後咱們檢查monitor相關的代碼,發現代碼中有個switch語句公有14個分支,但最經常使用的一個分支放在了後面。咱們只須要將其挪到前面,咱們的miss就有顯著降低。
還有另一種狀況形成嚴重的cache line miss,就是使用原子變量,特別是頻繁使用的原子變量。由於一旦該變量被變動了,全部cache 裏的值都會變成無效,那麼CPU使用時必定會碰到cache line miss。咱們經過分析代碼邏輯,對於某些經常使用的確不須要時時精確的值,咱們能夠在程序邏輯開始存爲本地變量,避免過多的直接訪問。對於一些只須要單線程訪問的變量,咱們也避免使用原子變量。
小結:
上面咱們經過幾個例子,爲你們展示瞭如何經過系統工具進行數據庫內核性能優化,一樣的思路也能夠適用於其餘底層軟件的開發調試。在實際的實踐過程當中,除了使用合適的工具,更重要的是還要細心,有耐心和鑽研的精神,一步步的下手,從現象中抽絲剝繭,找到根本緣由。