MySQL - VirtualBoxのホスト共有フォルダをdatadirにしたらはまった

ことの発端

これはWindows上のVirtualBoxのお話。

datadirがVirtualBoxのホスト共有フォルダに「なってしまっていた」。 必要なファイルをまとめてホストにおいてまるっとDockerで適当にマウントしたのが諸悪の根源だったりする。

気になるので非Docker環境で試してみた。

2020-01-17T13:59:10.540801Z 0 [Warning] [MY-010159] [Server] Setting lower_case_table_names=2 b
ecause file system for /vagrant/mysql/ is case insensitive
2020-01-17T13:59:11.246091Z 0 [ERROR] [MY-012592] [InnoDB] Operating system error number 95 in 
a file operation.
2020-01-17T13:59:11.246215Z 0 [ERROR] [MY-012596] [InnoDB] Error number 95 means 'Operation not
 supported'
2020-01-17T13:59:11.246374Z 0 [ERROR] [MY-012646] [InnoDB] File ./undo_001: 'Linux aio' returne
d OS error 195. Cannot continue operation
2020-01-17T13:59:11.246445Z 0 [ERROR] [MY-012981] [InnoDB] Cannot continue operation.

^q^

(ログのタイムスタンプはUTCだから仕事サボってたわけじゃないよ)

対策

innodb_use_native_aio=0 で回避可能。

細かい話は続きから。

細かい話

今回試した環境は Win10 64bit / VirtualBox 6.0.14 / CnetOS 8 / MySQL 8.0.19 です。

Operating system error number 95

似てる事象の記事はだいたい22っぽい*1*2MySQLリファレンスマニュアルのエラーコード一覧*3を見ると22は「無効な引数」ですが,95はここには載っていないのでLinuxのヘッダを見ます。95はEOPNOTSUPPで,確かに「Operation not supported」ですね。

追記:そもそもこれらの記事はWindows環境だったので,下記のようにファイルシステムの差によるもののようです。

EOPNOTSUPPでLinuxカーネルのソースをgrepすると,システムコールの関数ではなくファイルシステムの実装側で返す値のようです。ここらへんみると,本当にサポートされていないオペレーションのときに返ってくるらしい(ちなみに記事執筆時点ではext4に大きめの変更が入ってる最中らしいのでmasterブランチだと300行くらいずれます)。

ということで,共有フォルダはVirtualBoxのvboxsfなのでコードを落としてきてみてみます regops.c

/*
 * Now now we reject async I/O requests.
 */
if (!is_sync_kiocb(kio)) {
  SFLOGFLOW(("vbsf_reg_read_iter: async I/O not yet supported\n")); /** @todo extend FsPerf with AIO tests. */
  return -EOPNOTSUPP;
}

とのこと。native aioは使えないらしい。

InnoDB

せっかくなのでInnoDBのコードもチラ見してnative aioが無効になるのか確認します。 ログのメッセージは「Linux aio」なので一応aio_readとかでgrepしてみましたが使われておらず,native aioのようです。

storage/innobase/os/os0file.ccをみると,Linux向け,Win向けのaioのコードがたくさん書いてあります。実行環境がnative aioに対応しているかをチェックする処理もありますが,今回は環境は対応していてdatadirのファイルシステムだけ対応していないという状況だったのでスルーされていたみたいです。

handlerを見るとsrv_use_native_aioがフラグらしい。 もう一度os0file.ccを見てみます

dberr_t os_aio_handler(ulint segment, fil_node_t **m1, void **m2,
                       IORequest *request) {
  dberr_t err;

  if (srv_use_native_aio) {
    srv_set_io_thread_op_info(segment, "native aio handle");
    // 略
  } else {
    srv_set_io_thread_op_info(segment, "simulated aio handle");
    // 略
  }

  return (err);
}

おっ!それっぽいですね!これで安心して使えそうです*4

本当は細かいI/Oの仕組みまで追いかけたいですが,それはまた別のお話。

まとめ

ちゃらっと調べるつもりがLinuxVirtualBoxのソースとにらめっこする羽目になりました*5。夜ふかしには注意しましょう。