故障分析 | 奇怪!內存明明夠用,MySQL 卻出現了 OOM

作者:劉開洋
愛可生交付服務部團隊北京 DBA,主要負責處理 MySQL 的 troubleshooting 和我司自研數據庫自動化管理平臺 DMP 的日常運維問題,對數據庫及周邊技術有濃厚的學習興趣,喜歡看書,追求技術。
本文來源:原創投稿
*愛可生開源社區出品,原創內容未經授權不得隨意使用,轉載請聯繫小編並註明來源。

問題

前幾天遇到一個奇怪的問題,服務器內存明明夠用,結果在對 MySQL 進行測壓的時候卻出現了 OOM,是 Linux 內核出錯了嗎?

具體現象如下:使用 sysbench 對 mysql 進行壓測,併發 50、80 均正常輸出,當併發達到 100 時開始報 OOM。

[root@BJDB-01 ~]# sysbench /usr/share/sysbench/oltp_read_write.lua --mysql-host=BJDB-02 --mysql-port=3306 --mysql-user=root --mysql-password=*** --mysql-db=test --table-size=100000 --tables=5 --threads=100 --db-ps-mode=disable --auto_inc=off --report-interval=3 --max-requests=0 --time=360 --percentile=95 --skip_trx=off --mysql-ignore-errors=6002,6004,4012,2013,4016,1062 --create_secondary=off run
sysbench 1.0.17 (using system LuaJIT 2.0.4)
 
Running the test with following options:
Number of threads: 100
Report intermediate results every 3 second(s)
Initializing random number generator from current time
 
Initializing worker threads...
 
 
FATAL: mysql_store_result() returned error 5(Out of memory (Needed 48944 bytes))
FATAL: 'thread_run' function failed: /usr/share/sysbench/oltp_common.lua:432: SQL error,errno = 5, state = 'HY000': Out of memory (Needed 48944 bytes)
FATAL: mysql_store_result() returned error 5(Out of memory (Needed 48944 bytes))
FATAL: 'thread_run' function failed: /usr/share/sysbench/oltp_common.lua:432: SQL error,errno = 5, state = 'HY000': Out of memory (Needed 48944 bytes)

MySQL 中 error log 的報錯爲:

······
2021-03-16T09:13:00.692622+08:00 343 [ERROR] [MY-010934] [Server] Out of memory (Needed 708628 bytes)
2021-03-16T09:13:00.692702+08:00 343 [ERROR] [MY-010934] [Server] Out of memory; check if mysqld or some other process uses all available memory; if not, you may have to use 'ulimit' to allow mysqld to use more memory or you can add more swap space
2021-03-16T09:13:59.832037+08:00 374 [ERROR] [MY-000000] [InnoDB] InnoDB: Assertion failure: ut0ut.cc:678:!m_fatal
InnoDB: thread 140375101384448
InnoDB: We intentionally generate a memory trap.
InnoDB: Submit a detailed bug report to http://bugs.mysql.com.
InnoDB: If you get repeated assertion failures or crashes, even
InnoDB: immediately after the mysqld startup, there may be
InnoDB: corruption in the InnoDB tablespace. Please refer to
InnoDB: http://dev.mysql.com/doc/refman/8.0/en/forcing-innodb-recovery.html
InnoDB: about forcing recovery.
01:13:59 UTC - mysqld got signal 6 ;


分析

對 MySQL OOM 我們一步步進行分析,首先應該查看 ulimit 限制,

[root@BJDB-02 ~]# ulimit -a
core file size          (blocks, -c) 0
data seg size           (kbytes, -d) unlimited
scheduling priority             (-e) 0
file size               (blocks, -f) unlimited
pending signals                 (-i) 23045
max locked memory       (kbytes, -l) 64
max memory size         (kbytes, -m) unlimited
open files                      (-n) 1024
pipe size            (512 bytes, -p) 8
POSIX message queues     (bytes, -q) 819200
real-time priority              (-r) 0
stack size              (kbytes, -s) 8192
cpu time               (seconds, -t) unlimited
max user processes              (-u) 23045
virtual memory          (kbytes, -v) unlimited
file locks                      (-x) unlimited

系統並沒有對 ulimit 進行限制,100 個併發量在我們的配置之內,那就不是 ulimit 配置的問題,接下來分析下內存的使用情況,

在復現 MySQL OOM 的過程中,查看對應內存使用,通過 top 和 free 命令進行監控,得到以下信息,

[root@BJDB-02 ~]# free -m
              total        used        free      shared  buff/cache   available
Mem:          16047        1958        8956         8        5132       13920
Swap:          5119           0        5119
  
[root@BJDB-02 ~]# top
top - 17:21:30 up 32 min,  4 users,  load average: 0.00, 0.04, 0.11
Tasks: 226 total,   2 running, 224 sleeping,   0 stopped,   0 zombie
%Cpu(s):  2.2 us,  0.6 sy,  0.0 ni, 97.2 id,  0.0 wa,  0.0 hi,  0.0 si,  0.0 st
KiB Mem :  16432236 total,  9164596 free,  2012156 used,   5255484 buff/cache
KiB Swap:        5242876 total,    5242876 free,        0 used.  14247508 avail Mem
 
  PID USER      PR  NI    VIRT    RES    SHR S  %CPU %MEM     TIME+ COMMAND
 7654 root      20   0   13.7g   1.7g  14312 S   1.3 10.3   0:00.27 mysqld

這裏看我們的內存使用很正常,free 和 available 都很多,swap 都沒有使用,唯一存在異常的是虛擬內存有點高,我們接着分析:

進一步查看一下 /proc/meminfo,具體分析一下內存的使用情況,其中以下兩個參數引起了注意:

[root@BJDB-02 ~]# cat /proc/meminfo | grep Commit
CommitLimit:     13458992 kB
Committed_AS:    13244484 kB

一般來說 CommitLimit 的值是要比 Committed_AS 的值要小的,結合現在內存的使用,我們應該注意到一個 OS kernel 參數。

[root@BJDB-02 ~]# cat /etc/sysctl.conf |grep vm.overcommit_memory
vm.overcommit_memory=2

將 vm.overcommit_memory 調整爲 0,壓測時 MySQL OOM 消失了。

這三個參數值是什麼意思呢?它和內存使用的關係是什麼?內存真的夠用嗎?通過翻看 Linux 的內核文檔我們來進行詳細說明。

分析 vm.overcommit_memory 的使用

首先解釋下 overcommit 的意思是指操作系統承諾給進程的內存大小超過了實際可用的內存。

從內核版本 2.5.30 開始,這個參數的解釋爲:

overcommit_memory:This value contains a flag that enables memory overcommitment.

  • When this flag is 0, Heuristic overcommit handling, the kernel attempts to estimate the amount of free memory left when userspace requests more memory. Obvious overcommits of address space are refused. Used for a typical system. It ensures a seriously wild allocation fails while allowing overcommit to reduce swap usage. root is allowed to allocate slightly more memory in this mode. This is the default.

  • When this flag is 1, Always overcommit, the kernel pretends there is always enough memory until it actually runs out. Appropriate for some scientific applications. Classic example is code using sparse arrays and just relying on the virtual memory consisting almost entirely of zero pages.

  • When this flag is 2, Don't overcommit, the kernel uses a "never overcommit" policy that attempts to prevent any overcommit of memory. The total address space commit for the system is not permitted to exceed swap + a configurable amount (default is 50%) of physical RAM. Depending on the amount you use, in most situations this means a process will not be killed while accessing pages but will receive errors on memory allocation as appropriate.

中文釋義:

  • 當這個標誌爲 0 時,表示試探性的 overcommit,當用戶空間請求更多內存時,OS kernel 會預估剩餘的空閒內存量,如果內存申請特別大就會被拒絕。例如 malloc() 一次性申請的內存大小就超過了 swap 和 physical RAM 的和,就會遭到 kernel 拒絕 overcommit。

  • 當這個標誌爲 1 時,kernel 會假裝一直有足夠的內存,直到實際用完爲止。

  • 當這個標誌爲 2 時,kernel 使用“永不過度提交”的策略,試圖阻止任何內存的過度提交。



從含義中分析,如果我們將 vm.overcommit_memory 的值設爲 2,就很有可能出現內存申請的值超過我們的閾值,就會受到禁止。該閾值是通過內核參數 vm.overcommit_ratio 或 vm.overcommit_kbytes 間接設置的,從對應參數解釋中得到公式如下:

CommitLimit = Physical RAM * vm.overcommit_ratio + Swap                  // vm.overcommit_ratio 是內核參數,默認值是 50,表示物理內存的 50%。

測試環境中 Physical RAM 的值約爲 16G,Swap 的值約爲 5G,計算下來可正對應 CommitLimit 的值 13G。

/proc/meminfo 中的 Committed_AS 表示所有進程已經申請的內存總大小,而我們查詢的 free 和 top 下的內存則是進程已經分配的內存。

Committed_AS 是 OS kernel 對所有進程在最壞情況下需要多少 RAM/swap 的預估,才能保證工作負載不會出現 OOM,因此會存在過度申請提交內存的現象。

這個值是系統所有運行的程序所申請的內存大小,並不代表着分配使用的大小,而且各個程序申請的內存是可共享的。

雖然 Committed_AS 的數值與虛擬內存 VIRT 的大小很相似,目前沒有找到官方說明他們之間的聯繫,經過多次測試,它的大小和虛擬內存並沒有關係。


總結

如果 Committed_AS 超過 CommitLimit 就表示發生了overcommit,超出越多表示 overcommit 越嚴重,kernel 的 killer 進程會挑一部分進程幹掉,如果內存不夠還會繼續被 killer 幹掉,MySQL 在內存使用中佔用最大,首當其衝。

測試環境查看 Committed_AS 和 CommitLimit 的參數值爲:CommitLimit: 13458992 kBCommitted_AS: 13244484 kB。兩者已經十分接近,在vm.overcommit_memory=2 的情況下非常容易發生 MySQL 的 OOM,因此需要將 vm.overcommit_memory 的值設爲 0,具體需要根據環境變更。

附:還有兩個參數與我們這次的內存分配有關係,不過影響不大,有興趣的同學可以自行谷歌:admin_reserve_kbytes 和 user_reserve_kbytes。

參考:

https://www.kernel.org/doc/Documentation/vm/overcommit-accounting

https://www.kernel.org/doc/Documentation/sysctl/vm.txt

http://lwn.net/Articles/28345/

https://www.win.tue.nl/~aeb/linux/lk/lk-9.html#ss9.6


文章推薦:


故障分析 | 如何提高 MHA 的網絡容忍能力?(下)

故障分析 | 如何提高 MHA 的網絡容忍能力?(上)



社區近期動態




本文關鍵字:#MySQL OOM# #內核參數#
  點一下“閱讀原文”瞭解更多資訊

本文分享自微信公衆號 - 愛可生開源社區(ActiontechOSS)。
如有侵權,請聯繫 [email protected] 刪除。
本文參與“OSC源創計劃”,歡迎正在閱讀的你也加入,一起分享。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章