/var/log/messagesを確認したところ、以下のエラーメッセージが出力されていました: XFS: possible memory allocation deadlock in kmem_alloc (mode:0x250)
Linuxカーネルのソースコードを調べたところ、該当する実装は以下の通りです:
void *
kmem_alloc(size_t size, xfs_km_flags_t flags)
{
int retries = 0;
gfp_t lflags = kmem_flags_convert(flags);
void *ptr;
do {
ptr = kmalloc(size, lflags);
if (ptr || (flags & (KM_MAYFAIL|KM_NOSLEEP)))
return ptr;
if (!(++retries % 100))
xfs_err(NULL,
"possible memory allocation deadlock in %s (mode:0x%x)",
__func__, lflags);
congestion_wait(BLK_RW_ASYNC, HZ/50);
} while (1);
}
コード解析:
- メモリ確保に成功した場合、すぐにポインタを返す。
- 確保に失敗した場合、HZ/50ジフィー待機後に再試行し、成功するまでループを続ける。
- 100回失敗するごとに、"possible memory allocation deadlock in %s (mode:0x%x)"を出力する。
- メモリが不足している場合、永久にこのループ内でブロックされることを示す。
modeパラメータは、要求されるメモリフラグ (GFP_IO|GFP_WAIT|GFP_NOWARN) を示します。
ログを確認したところ、"XFS: possible memory allocation deadlock in kmem_alloc (mode:0x250)"が長時間連続して出力されていました。以前はこのメッセージが稀にしか表示されませんでした。
使用されているファイルシステムはXFSです。調査により以下の情報を得ました:
大きな連続ページがほぼ存在しない状態であることが分かります。XFSのメモリ確保処理では、以下のような大きな連続メモリブロックの要求が存在するためです:
6000: map = kmem_alloc(subnex * sizeof(*map), KM_MAYFAIL | KM_NOFS);
推測される原因は、少量の空きメモリは存在するものの、それらが非常に断片化されており、比較的大きな連続メモリ要求を満たせないことです。
既にecho 1 > /proc/sys/vm/drop_cachesを実行していました。別のマシンの状態を確認します:
/proc/buddyinfoは、Linuxのbuddyシステムによる物理メモリ管理のデバッグ情報です。Linuxはbuddyアルゴリズムを使用して物理メモリの外部断片化問題を解決し、空きメモリを2のべき乗サイズで11個のブロックリストに分類します(1、2、4、8、16、32、64、128、256、512、1024ページ単位)。NUMAアーキテクチャでは、各ノードにローカルメモリがあり、buddyinfo内のNode0はノードIDを示します。各ノードのメモリは、DMA、Normal、HighMemなどのゾーンに分割されます。
Normalゾーンに注目すると、2列目の値100は、システム内のNormalゾーンで利用可能な連続2ページのメモリが100 * 2 * PAGE_SIZEであることを示します。3列目は52で、連続4ページのメモリが52 * 2^2 * PAGE_SIZEです。
cat /proc/buddyinfo
Node 0, zone DMA 23 15 4 5 2 3 3 2 3 1 0
Node 0, zone Normal 149 100 52 33 23 5 32 8 12 2 59
Node 0, zone HighMem 11 21 23 49 29 15 8 16 12 2 142
5列目以降では、44 * 16 * PAGE_SIZEのページブロックのみが残り、その後は1 * 32 * PAGE_SIZE、1 * 64 * PAGE_SIZE、2 * 128 * PAGE_SIZEとなり、256や512のページブロックは存在しません。
したがって、問題発生時にはメモリの断片化が進行しており、アプリケーション実行時にXFSが大きな連続メモリブロック(例: kmem_alloc(subnex * sizeof(*map), KM_MAYFAIL | KM_NOFS))を要求すると、メモリが割り当てられません。例えば、docker psやdocker execコマンドを実行すると、kmem_allocのループ内で永続的にブロックされ、メモリが断片化されたままであるため確保できず、コマンドも応答しなくなります。これにより、lsやsshなどのコマンド(必要なページサイズが小さい)は正常に動作する理由が説明できます。
echo 1 > /proc/sys/vm/drop_cachesを実行すると、断片化されたページが再編成され、その後大きなページメモリを確保できるようになり、ブロックが解消されます。
しかし、毎回このコマンドを実行するのは現実的ではないため、以下の対策を採用しました:
参考: http://www.cnblogs.com/itfriend/archive/2011/12/14/2287160.html
Linuxはmin_free_kbytesパラメータを提供しており、システムがメモリ回収を開始するしきい値を決定し、空きメモリを制御します。値が高いほど、カーネルは早期にメモリ回収を開始し、空きメモリ量が増加します。
/proc/sys/vm/min_free_kbytesの値を4Gバイトに設定:
echo 4194304 > /proc/sys/vm/min_free_kbytes
現在の値は90Mバイトです。
設定後の効果:
メモリ断片化の状態が大幅に改善されました。
この修正後、docker psコマンドが応答しなくなる現象は再発しませんでした。