チーフエンジニアの加辺です。
今日は珍しいトラブルに出会ったので紹介します。
起こった問題
ある環境ではEC2によりサーバを運用しており、アプリケーションをデプロイサーバでビルドし、その成果物をアプリケーションサーバへコピーすることでデプロイとしていました。
ここで新規サーバを作成していたところ、一部のアプリケーションサーバで見慣れないエラーが発生し、アプリケーションが起動しないという事象が確認されました。調べたところ、デプロイサーバはt3、アプリケーションサーバはt3aファミリーが利用されていることがわかり、アプリケーションサーバをt3ファミリーへ変更したところ、問題が発生しなくなることが分かりました。
さて、これはどのような機序によるものでしょうか。
調査
記事タイトルで答えをほとんど書いてしまっていますし、t3, t3aを知っていれば明らかですが、これはIntelとAMDの違いです。
本アプリケーションはRubyで記述されており、Rubyは以下のようなログを書き出していました。適宜抜粋します。
2021-12-10 23:58:11.000759500 /path/to/app/shared/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:112: [BUG] Illegal instruction at 0x00007f1758f39b10
2021-12-10 23:58:11.000789500 ruby 2.6.6p146 (2020-03-31 revision 67876) [x86_64-linux]
2021-12-10 23:58:11.000790500
2021-12-10 23:58:11.000941500 -- Control frame information -----------------------------------------------
2021-12-10 23:58:11.000962500 c:0053 p:---- s:0283 e:000282 CFUNC :open
2021-12-10 23:58:11.001018500 c:0052 p:0022 s:0277 e:000276 BLOCK /path/to/app/shared/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:112 [FINISH]
2021-12-10 23:58:11.001079500 c:0051 p:---- s:0268 e:000267 CFUNC :each
2021-12-10 23:58:11.001131500 c:0050 p:0113 s:0264 e:000263 BLOCK /path/to/app/shared/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:109 [FINISH]
2021-12-10 23:58:11.001191500 c:0049 p:---- s:0257 e:000256 CFUNC :map
2021-12-10 23:58:11.001243500 c:0048 p:0069 s:0253 e:000252 METHOD /path/to/app/shared/bundle/ruby/2.6.0/gems/ffi-1.15.4/lib/ffi/library.rb:99
0x00007f1758f39b10番地に不正な命令があるとのことです。このログだけを見ると libffi に原因があるように見えます。
ところが実際にこの番地が何者かを確認してみると、libffiではありませんでした。
2021-12-10 23:58:11.022654500 7f1758eb6000-7f17590a3000 r-xp 00000000 103:01 25313550 /path/to/app/shared/bundle/ruby/2.6.0/gems/sassc-2.2.1/lib/sassc/libsass.so
sasscがmapされていました。さらにこの共有オブジェクトの中身を調べ、どんな命令なのか見てみます。
0x00007f1758f39b10 - 0x7f1758eb6000 = 0x83B10です。
0000000000083b10 <_ZN4Sass10ExpressionC1ENS_11ParserStateEbbbNS0_4TypeE>:
83b10: 62 f1 fe 08 6f 84 24 vmovdqu64 0x8(%rsp),%xmm0
83b17: 08 00 00 00
83b1b: 48 8b 44 24 18 mov 0x18(%rsp),%rax
83b20: 48 c7 47 08 00 00 00 movq $0x0,0x8(%rdi)
83b27: 00
ということで、VMOVDQU64という命令であることがわかりました。
新しい命令
これを調べると、AVX-512の命令であることがわかりました。
アライメントされていないメモリー位置からパックド int64 要素をロードします。
通常はAMD64でもIntel64の命令はサポートされているはず…と思ったのですが、AVX-512の命令は2021年3月に発表されたZen-4以降でようやくサポートされたばかりだったようです。
https://www.techpowerup.com/279129/amd-zen-4-microarchitecture-to-support-avx-512
This would make "Zen 4" the first AMD microarchitecture to support AVX-512.
Intel64でsasscをコンパイルした際にAVX-512の命令が入ってしまい、それをAMD64で動かそうとした結果だったようです。
とりうる対応
いくつもありますが、例として以下のような方法が考えられます。
- CPU familyをIntel64に揃える(AVX-512はSkylake-SP以降)
- デプロイサーバをAMD64にする(今回の問題の逆パターンも考えられ、問題が起こる可能性が0になるわけではない)
- ビルドするとき(今回であればgemをbundleinstall)にgccのオプションに --disable-avx512 を付ける
- AWSがAMD EPYCのプロセッサを全てZen-4以降に置き換えてくれるのを待つ
- 各サーバでnativeなバイナリをビルドするようにする
今回、手っ取り早くできる&コストが抑えられる対応として、2つ目の方法をとりました。