エンジニアリング

Canonical、Elastic、Googleが結集、Linuxのデータ破損問題を解決

Elasticはイノベーションと新機能のリリースに継続して取り組んでいます。 新機能をリリースする際は、その機能が十分にテストされ、堅実で信頼性があることの確認も行います。そこでバグや、その他の問題が見つかることもあります。

以前、Elastic Stackの新機能のテスト中に、特定のLinuxカーネルでSSDディスクに影響するLinuxカーネルバグが発見されました。このブログ記事では一連の調査内容と、調査を支援してくださった親身なパートナーであるGoogle Cloud、ならびにCanonicalと協働したストーリーをご紹介します。

調査の成果として、この問題を解決する新しいUbuntuカーネルがリリースされています。

事のはじまり

さかのぼること2019年2月、私たちはElasticsearchの標準ベンチマークツールであるRallyを使い、重いアップデートを伴うインジェストワークロードで10日間にわたるクラスター横断レプリケーション(CCR)安定性テストを実施していました。Elasticsearchのバージョンは当時最新だった7.0台の枝番で、リリース前のものでした。数日後、フェイルとなったそのテスト結果から次のようなElasticsearchのログレポートが得られました。

… 
Caused by: org.apache.lucene.index.CorruptIndexException: checksum failed (hardware problem?)

Elasticsearchはインデックスの破損を検知すると[1]、複製からの復元を試みます。多くのケースでこの復元はシームレスに実行され、ユーザーがログをレビューしない限り、破損に気づくことはありません。しかし複製によって冗長性を確保していても、特定の状況下でデータロスが生じる可能性は0ではありません。そこで私たちは、この破損が生じた理由を把握し、問題を修復する方法を探ろうとしました。

当初、Elasticsearchの新機能であるCCR/ソフトデリートが問題の原因として疑われました。しかし、この疑いはすぐに見当違いであると判明しました。1つのクラスターだけを使い、CCR/ソフトデリート機能を無効化したテストでもインデックスの破損が再現されました。さらに調査すると、CCR/ソフトデリートのベータリリースを未搭載の、先行するElasticsearchバージョンでも問題が再現されることがわかりました。これはCCR/ソフトデリートがインデックス破損の原因ではないことを示唆しています。

ここで私たちは別の点に気づきました。インデックス破損が生じたテストはGoogle Cloud Platform(GCP)で実施され、Canonical Ubuntu Xenial imageローカルの複数のSSD4.15-0-*-gcpカーネルを使っていました。ところが、同一のオペレーティングシステムとSSDディスク(4.13または4.15のいずれかのHWEカーネル)をベアメタル環境で使用すると、問題が再現されないのです。

この問題とその影響に関するデータをさらに入手したいと考え、私たちはすぐさまElastic Cloudに飛びつきました。並列のワークストリームを構築し、さまざまなファイルシステムをテストしたり、RAIDを無効化したり、最後は後続のメインラインカーネルも使って多数の可能性を排除しました。しかし、いずれの条件でも破損を回避することはできませんでした。

Google Cloudサポートの協力と調査

当時私たちは、再現の手間を軽減し、またパートナーであるGoogleが環境の問題について手軽に調査を開始できるよう、シンプルな再現スクリプトを作成していました。

Elasticが提供したスクリプトを使ったGCPのサポートチームでも確実に問題が再現され、次のようなエラーメッセージが取得されました。

org.apache.lucene.index.CorruptIndexException: codec footer mismatch (file 
truncated?): actual footer=-2016340526 vs expected footer=-1071082520 
(resource=BufferedChecksumIndexInput(MMapIndexInput(path="/home/dl/es/data/nodes/0/indices/IF1vmFH6RY-MZuNfx2IO4w/0/index/_iya_1e_Lucene80_0.dvd")))

この問題が生じているときIOのスループットは160MB/秒を上回り、IOPSは16,000でした。この現象は複数回のテストにわたり、一貫して確認されました。Elasticsearchはデフォルトでストレージの一部にメモリーマップトファイルを使用します。私たちは一部のファイルアクセスがより大規模なページフォールトを引き起こすことでディスクへのIO操作が増加し、最終的にこの問題の引き金となっている可能性を疑いました。そこでページフォールトの発生を抑制するため、GCPインスタンスのメモリーを32GBから52GBに強化しました。メモリーを増加すると問題は再現されず、IOスループットは50MB/秒、IOPSは4000になりました。 

さらにGCPとベアメタル環境間の差異に気づいたとき、私たちは最初のブレークスルーに到達しました。このGCPカーネルはmulti-queue block layer(blk_mq)と呼ばれる機能を有効化しており、一方、ベアメタルカーネルはこれを有効化していませんでした[2]。さらに、ある特定のバージョン[3]以降、Ubuntu Linux -gcpイメージ上のblk_mq[4]をカーネルオプションで無効化することができなくなっていることが判明し、事態はより複雑な様相を呈しました。するとGCPサポートは、GCPのUbuntuイメージをエクスポートしてからそれをVIRTIO_SCSI_MULTIQUEUE guestOS機能(マルチキューSCSI[5]を有効化する機能)なしに再作成することで、blk_mqを無効化するという方法を提示してくれました。

2つ目のブレイクスルーは、ベアメタルでこの破損を再現しようとする試みで生じました。明示的にblk_mqを有効化している場合のみ、破損を再現できたのです。より古いカーネル、4.13.0-38-genericを使う場合でも結果は同じでした。さらに私たちは、NVMeディスクにはこの問題が生じないことも検証しました。

この時点で、次の2つの条件を満たす場合のみ破損が生じることを突き止めることができたのです。

  • SSDドライブがSCSIインターフェースを使用する(NVMeディスクは影響を受けない)
  • blk_mqが有効化されている

NVMeディスクのみを使うことに加え、GCPサポートは2つの回避策を提示しました。インスタンスのメモリーを増やす、あるいはマルチキューSCSIを無効化したカスタムインスタンスイメージを作成するというものです。

Canonicalの協力と支援

いくつかの回避策を確保したものの、私たちは満足していませんでした。

  • GCPのUbuntuイメージ固有の問題ではなく、ベアメタルでも生じる問題だったためです。
  • どのカーネルがこの問題を起こしているのか、わかりませんでした。
  • 後続の新しいカーネルですでに修正されているかどうかもわかりません。

こうした疑問を解消するべく、私たちはパートナーであるCanonical社に連絡をとり、調査を依頼しました。

CanonicalはElasticが提供した再現スクリプトを使って大規模なテストを開始し、はじめにSSDドライブ(マルチキューI/Oスケジューラーをnone、またはmq-deadlineのいずれかで使用)を使用するUbuntuメインラインカーネル>=5.0では破損が起きないことを確認しました。

次のステップは、カーネルバージョンをさかのぼり、破損を生じるカーネルと生じないものの最小の差分を特定する作業です。複数の並列テスト環境を使用し(テスト期間が5日間だったため)、Canonicalは破損の修正を含む最初のUbuntuメインラインカーネルが4.19.8であることを突き止めました[6]。

Canonicalのバグトラッカーは、4.15.0カーネルとその派生バージョンへのバックポートをLP#1848739に記載しています。さらに詳しい情報は、こちらの記事kernel.org bugでご覧いただくことができます。

必要なすべてのバックポートを含むパッチ適用済みのGCPカーネルで問題を修正できることをElasticとCanonicalが確認した後、このバックポートはUbuntu 4.15.0メインカーネルにマージされ、最終的に派生バージョンのすべてのカーネル(-gcpを含む)にこの修正が適用されました。

まとめ

現在Elasticは、3つの主要なソリューションに進化をもたらすElastic Stackの新機能開発を進めています。この開発に携わるエンジニアやパートナーは卓越した才能に恵まれ、ユーザーにあらゆる懸念を抱かせないよう、入念にプロジェクトを進めています。テストで何か問題が見つかれば、Elastic、および緊密なパートナーのネットワークがあらゆる手段を尽くして解消にあたり、可能な限り最高のエクスペリエンスをユーザーに提供します。

GoogleならびにCanonicalが緊密に協働してくださったおかげで私たちは問題の根本原因を解明することができ、その結果、以下の修正済みHWE Ubuntuカーネルがリリースされています。

  • AWS:2020年2月21日リリース、linux-aws - 4.15.0-1061.65で始まるバージョン
  • Azure:2020年1月6日リリース、linux-azure - 4.15.0-1066.71で始まるバージョン
  • GCP:2020年2月5日リリース、linux-gcp - 4.15.0-1053.57で始まるバージョン
  • Generic:2020年2月17日リリース、linux - 4.15.0-88.88で始まるバージョン

上記以降のバージョンを使用すると、有効化されたSCSI blk-mqとSSDディスクを同時に使用しても、破損を回避することができます。

お使いの環境でこのデータ破損を回避できるかの確認もしなくていい方法をお探しの場合は、Elastic Cloudをご検討ください。Elastic Cloudはもちろん対応済みで、ユーザーのデータは保護されています。

脚注

[1]運用コストが高くなることから、Elasticsearchはチェックサムを常時検証することはしません。しかしシャードのリロケーションやスナップショットの撮影中など、特定のアクションはより高頻度にチェックサムの検証をトリガーします。この挙動により、特定のアクションが原因で生じる、潜在的かつサイレントな破損を明らかにします。

[2]blk_mqがSCSI、またはデバイスマッパーディスクで使用中かどうかを確認するには、cat /sys/module/{scsi_mod,dm_mod}/parameters/use_blk_mqを使用します。

[3] https://patchwork.kernel.org/patch/10198305以降、blk_mqはSCSIデバイスに強制適用され、カーネルオプション経由で無効化することはできません。このパッチはUbuntu linux-gcpでバックポートされています。

[4] blk_mqを無効化するには、以下のパラメーターをそのカーネルにパスする必要があります。たとえば、via grub:GRUB_CMDLINE_LINUX="scsi_mod.use_blk_mq=N dm_mod.use_blk_mq=N"など。このオプションをNに設定すると有効化できますが、[3]に留意してください。

[5] Ubuntuイメージを再作成してVIRTIO_SCSI_MULTIQUEUE guestOS機能を無効化するgcloudコマンドの例は、次の通りです。

# gcloud compute images export --image-project ubuntu-os-cloud --image-family ubuntu-1604-lts --destination-uri=gs://YOUR_BUCKET/ubuntu_16.04.tar.gz 
# gcloud compute images create ubuntu-1604-test --family ubuntu-1604-lts --source-uri=gs://YOUR_BUCKET/ubuntu_16.04.tar.gz

[6]バックポート

    - blk-mq: quiesce queue during switching io sched and updating nr_requests 
    - blk-mq: move hctx lock/unlock into a helper 
    - blk-mq: factor out a few helpers from __blk_mq_try_issue_directly 
    - blk-mq: improve DM's blk-mq IO merging via blk_insert_cloned_request feedback 
    - dm mpath: fix missing call of path selector type->end_io 
    - blk-mq-sched: remove unused 'can_block' arg from blk_mq_sched_insert_request 
    - blk-mq: don't dispatch request in blk_mq_request_direct_issue if queue is busy 
    - blk-mq: introduce BLK_STS_DEV_RESOURCE 
    - blk-mq:Rename blk_mq_request_direct_issue() into 
      blk_mq_request_issue_directly() 
    - blk-mq: don't queue more if we get a busy return 
    - blk-mq: dequeue request one by one from sw queue if hctx is busy 
    - blk-mq: issue directly if hw queue isn't busy in case of 'none' 
    - blk-mq: fix corruption with direct issue 
    - blk-mq: fail the request in case issue failure 
    - blk-mq: punt failed direct issue to dispatch list