本文從NUMA的介紹引出常見的NUMA使用中的陷阱,繼而討論對於NUMA系統的優化方法和一些值得關注的方向。mysql
文章歡迎轉載,但轉載時請保留本段文字,並置於文章的頂部 做者:盧鈞軼(cenalulu) 本文原文地址:http://cenalulu.github.io/linux/numa/linux
NUMA簡介
這部分將簡要介紹下NUMA架構的成因和具體原理,已經瞭解的讀者能夠直接跳到第二節。git
爲何要有NUMA
在NUMA架構出現前,CPU歡快的朝着頻率愈來愈高的方向發展。受到物理極限的挑戰,又轉爲核數愈來愈多的方向發展。若是每一個core的工做性質都是share-nothing(相似於map-reduce的node節點的做業屬性),那麼也許就不會有NUMA。因爲全部CPU Core都是經過共享一個北橋來讀取內存,隨着核數如何的發展,北橋在響應時間上的性能瓶頸愈來愈明顯。因而,聰明的硬件設計師們,先到了把內存控制器(本來北橋中讀取內存的部分)也作個拆分,平分到了每一個die上。因而NUMA就出現了!github
NUMA是什麼
NUMA中,雖然內存直接attach在CPU上,可是因爲內存被平均分配在了各個die上。只有當CPU訪問自身直接attach內存對應的物理地址時,纔會有較短的響應時間(後稱Local Access
)。而若是須要訪問其餘CPU attach的內存的數據時,就須要經過inter-connect通道訪問,響應時間就相比以前變慢了(後稱Remote Access
)。因此NUMA(Non-Uniform Memory Access)就此得名。算法
咱們須要爲NUMA作什麼
假設你是Linux教父Linus,對於NUMA架構你會作哪些優化?下面這點是顯而易見的:sql
既然CPU只有在Local-Access時響應時間纔能有保障,那麼咱們就儘可能把該CPU所要的數據集中在他local的內存中就OK啦~數據庫
沒錯,事實上Linux識別到NUMA架構後,默認的內存分配方案就是:優先嚐試在請求線程當前所處的CPU的Local內存上分配空間。若是local內存不足,優先淘汰local內存中無用的Page(Inactive,Unmapped)。 那麼,問題來了。。。緩存
NUMA的「七宗罪」
幾乎全部的運維都會多多少少被NUMA坑害過,讓咱們看看究竟有多少種在NUMA上栽的方式:
- MySQL – The MySQL 「swap insanity」 problem and the effects of the NUMA architecture
- PostgreSQL – PostgreSQL, NUMA and zone reclaim mode on linux
- Oracle – Non-Uniform Memory Access (NUMA) architecture with Oracle database by examples
- Java – Optimizing Linux Memory Management for Low-latency / High-throughput Databases
究其緣由幾乎都和:「由於CPU親和策略致使的內存分配不平均」及「NUMA Zone Claim內存回收」有關,而和數據庫種類並無直接聯繫。因此下文咱們就拿MySQL爲例,來看看重內存操做應用在NUMA架構下到底會出現什麼問題。
MySQL在NUMA架構上會出現的問題
幾乎全部NUMA + MySQL
關鍵字的搜索結果都會指向:Jeremy Cole大神的兩篇文章
- The MySQL 「swap insanity」 problem and the effects of the NUMA architecture
- A brief update on NUMA and MySQL
大神解釋的很是詳盡,有興趣的讀者能夠直接看原文。博主這裏作一個簡單的總結:
- CPU規模因摩爾定律指數級發展,而總線發展緩慢,致使多核CPU經過一條總線共享內存成爲瓶頸
- 因而NUMA出現了,CPU平均劃分爲若干個Chip(很少於4個),每一個Chip有本身的內存控制器及內存插槽
- CPU訪問本身Chip上所插的內存時速度快,而訪問其餘CPU所關聯的內存(下文稱Remote Access)的速度相較慢三倍左右
- 因而Linux內核默認使用CPU親和的內存分配策略,使內存頁儘量的和調用線程處在同一個Core/Chip中
- 因爲內存頁沒有動態調整策略,使得大部份內存頁都集中在
CPU 0
上 - 又由於
Reclaim
默認策略優先淘汰/Swap本Chip上的內存,使得大量有用內存被換出 - 當被換出頁被訪問時問題就以數據庫響應時間飆高甚至阻塞的形式出現了
解決方案
Jeremy Cole大神推薦的三個方案以下,若是想詳細瞭解能夠閱讀 原文
numactl --interleave=all
- 在MySQL進程啓動前,使用
sysctl -q -w vm.drop_caches=3
清空文件緩存所佔用的空間 - Innodb在啓動時,就完成整個
Innodb_buffer_pool_size
的內存分配
這三個方案也被業界廣泛承認可行,同時在 Twitter 的5.5patch 和 Percona 5.5 Improved NUMA Support 中做爲功能被支持。
不過這種三合一的解決方案只是減小了NUMA內存分配不均,致使的MySQL SWAP問題出現的可能性。若是當系統上其餘進程,或者MySQL自己須要大量內存時,Innodb Buffer Pool的那些Page一樣仍是會被Swap到存儲上。因而又在這基礎上出現了另外幾個進階方案
- 配置
vm.zone_reclaim_mode = 0
使得內存不足時去remote memory分配
優先於swap out local page
echo -15 > /proc/<pid_of_mysqld>/oom_adj
調低MySQL進程被OOM_killer
強制Kill的可能- memlock
- 對MySQL使用Huge Page(黑魔法,巧用了Huge Page不會被swap的特性)
從新審視問題
若是本文寫到這裏就這麼結束了,那和搜索引擎結果中大量的Step-by-Step科普帖沒什麼差異。雖然咱們用了各類參數調整減小了問題發生機率,那麼真的就完全解決了這個問題麼?問題根源到底是什麼?讓咱們回過頭來從新審視下這個問題:
NUMA Interleave真的好麼?
爲何Interleave
的策略就解決了問題? 借用兩張 Carrefour性能測試 的結果圖,能夠看到幾乎全部狀況下Interleave
模式下的程序性能都要比默認的親和模式要高,有時甚至能高達30%。究其根本緣由是Linux服務器的大多數workload分佈都是隨機的:即每一個線程在處理各個外部請求對應的邏輯時,所須要訪問的內存是在物理上隨機分佈的。而Interleave
模式就偏偏是針對這種特性將內存page隨機打散到各個CPU Core上,使得每一個CPU的負載和Remote Access
的出現頻率都均勻分佈。相較NUMA默認的內存分配模式,死板的把內存都優先分配在線程所在Core上的作法,顯然廣泛適用性要強不少。
也就是說,像MySQL這種外部請求隨機性強,各個線程訪問內存在地址上平均分佈的這種應用,Interleave
的內存分配模式相較默認模式能夠帶來必定程度的性能提高。 此外 各類 論文 中也都經過實驗證明,真正形成程序在NUMA系統上性能瓶頸的並非Remote Acess
帶來的響應時間損耗,而是內存的不合理分佈致使Remote Access
將inter-connect這個小水管塞滿所形成的結果。而Interleave
剛好,把這種不合理分佈狀況下的Remote Access請求平均分佈在了各個小水管中。因此這也是Interleave
效果奇佳的一個緣由。
那是否是簡簡單單的配置個Interleave
就已經把NUMA的特性和性能發揮到了極致呢? 答案是否認的,目前Linux的內存分配機制在NUMA架構的CPU上還有必定的改進空間。例如:Dynamic Memory Loaction, Page Replication。
Dynamic Memory Relocation 咱們來想一下這個狀況:MySQL的線程分爲兩種,用戶線程(SQL執行線程)和內部線程(內部功能,如:flush,io,master等)。對於用戶線程來講隨機性至關的強,但對於內部線程來講他們的行爲以及所要訪問的內存區域實際上是相對固定且能夠預測的。若是能對於這把這部份內存集中到這些內存線程所在的core上的時候,就能減小大量Remote Access
,潛在的提高例如Page Flush,Purge等功能的吞吐量,甚至能夠提升MySQL Crash後Recovery的速度(因爲recovery是單線程)。 那是否能在Interleave
模式下,把那些明顯應該彙集在一個CPU上的內存集中在一塊兒呢? 很惋惜,Dynamic Memory Relocation這種技術目前只停留在理論和實驗階段。咱們來看下難點:要作到按照線程的行爲動態的調整page在memory的分佈,就勢必須要作線程和內存的實時監控(profile)。對於Memory Access這種很是異常頻繁的底層操做來講增長profile入口的性能損耗是極大的。在 關於CPU Cache程序應該知道的那些事的評論中我也提到過,這個道理和爲何Linux沒有全局監控CPU L1/L2 Cache命中率工具的緣由是同樣的。固然優化不會就此停步。上文提到的Carrefour算法和Linux社區的Auto NUMA patch
都是積極的嘗試。何時內存profile出現硬件級別,相似於CPU中 PMU 的功能時,動態內存規劃就會展示很大的價值,甚至會做爲Linux Kernel的一個內部功能來實現。到那時咱們再回過頭來審視這個方案的實際價值。
Page Replication 再來看一下這些狀況:一些動態加載的庫,把他們放在任何一個線程所在的CPU都會致使其餘CPU上線程的執行效率降低。而這些共享數據每每讀寫比很是高,若是能把這些數據的副本在每一個Memory Zone內都放置一份,理論上會帶來較大的性能提高,同時也減小在inter-connect上出現的瓶頸。實時上,仍然是上文提到的Carrefour也作了這樣的嘗試。因爲缺少硬件級別(如MESI協議的硬件支持)和操做系統原生級別的支持,Page Replication在數據一致性上維護的成本顯得比他帶來的提高更多。所以這種嘗試也僅僅停留在理論階段。固然,若是能獲得底層的大力支持,相信這個方案仍是有極大的實際價值的。
到底是哪裏出了問題
NUMA的問題? NUMA自己沒有錯,是CPU發展的一種必然趨勢。可是NUMA的出現使得操做系統不得不關注內存訪問速度不平均的問題。
Linux Kernel內存分配策略的問題? 分配策略的初衷是好的,爲了內存更接近須要他的線程,可是沒有考慮到數據庫這種大規模內存使用的應用場景。同時缺少動態調整的功能,使得這種悲劇在內存分配的那一刻就被買下了伏筆。
數據庫設計者不懂NUMA? 數據庫設計者也許從一開始就不會意識到NUMA的流行,或者甚至說提供一個透明穩定的內存訪問是操做系統最基本的職責。那麼在現狀改變很是困難的狀況下(下文會提到爲何困難)是否是做爲內存使用者有義務更好的去理解使用NUMA?
總結
其實不管是NUMA仍是Linux Kernel,亦或是程序開發他們都沒有錯,只是還作得不夠極致。若是NUMA在硬件級別能夠提供更多低成本的profile接口;若是Linux Kernel可使用更科學的動態調整策略;若是程序開發人員更懂NUMA,那麼咱們徹底能夠更好的發揮NUMA的性能,使得無限橫向擴展CPU核數再也不是一個夢想。
- Percona NUMA aware Configuration
- Numa system performance issues – more than just swapping to consider
- MySQL Server and NUMA architectures
- Checking /proc/pid/numa_maps can be dangerous for mysql client connections
- on swapping and kernels
- Optimizing Linux Memory Management for Low-latency / High-throughput Databases
- Memory System Performance in a NUMA Multicore Multiprocessor
- A Case for NUMA-aware Contention Management on Multicore Systems