Hydraで書かれたコードをVertex AIでハイパーパラメータ調整できるようにした



初めまして、JX通信社のMLチームでインターンをしている田中です。これまで物体検知や深層距離学習を使った画像分類、自然言語の分類や生成などのAI作成、MLOpsなどをやっています。今回、MLエンジニアのヨンテさんのもとで文書生成AIのハイパーパラメータ調整の並列化に挑戦しました。

背景

JX通信社のMLチームでは「力を使うべき場所に注力しよう」の理念のもとに、機械学習用のテンプレートコードを作成しており、その中ではPyTorch LightningやHydraなどが使用されています。

理念やテンプレートコードについてはヨンテさんが書いた解説記事があるので読んでみてください。

tech.jxpress.net tech.jxpress.net

文書生成AIの学習は、1回の学習に数時間程度かかります。

ハイパーパラメータ調整の学習を一つのマシーンで直列で回していると、図1の上の例ように1回の学習時間×学習の試行回数以上の時間がかかり、数日以上かかってしまいます。

図1 ハイパーパラメータ調整時に一つのマシーンで学習した場合 (上の例)と、複数マシーンを並行して学習したとき(下の例)の完了までにかかる時間 の比較。Vertex AI hyperparameter tuningは学習を並行化できるだけではなく、並行学習を直列につなげることで、ベイズ最適化も実施することができ、最適なパラメーターをコスパよく高精度に探索することができます。*1

JX通信社で大事にしているバリューの一つにSpeedがあり、その理念をML側からも支えるために、学習をより早く終わらせ、事業の開発スピードを早めることを目指しています。

そこで、ハイパーパラメータ調整の際は、たくさんの実験を並列で回したいです 。このような並列学習は、フルマネージドMLサービスであるVertex AIを使用すれば簡単に実現できます(図1下の例)。

一方で、Vertex AI Trainingのハイパーパラメータ調整機能と、JX通信社のテンプレートコードで用いられているHydraの相性が悪く、そのまま使えなかったので、工夫して動くようにしました!

問題点

ハイパーパラメータ調整はVertex AI側がコマンドライン引数を用いてハイパーパラメータを学習コンテナに渡し学習が行われ、学習が終わった後に最適化の評価値を専用のライブラリを使用しVertex AIに伝えることで、ハイパーパラメータの最適化が行われます。

しかし、以下のようにVertex AIが渡すコマンドライン引数の形式とHydraが対応するコマンドライン引数の形式が違うためうまくできませんでした。

Vertex AIの公式ドキュメントに記載されている形式

argparse を推奨

python3 -m my_trainer --learning_rate learning-rate-in-this-trial

⚠️ 注意

自分が調査した限り、実際渡されている形式は上の形式ではなく

--learning_rate=learning-rate-in-this-trial

でした。

推奨されている argparseだとこの形式でも受け取れます。

Hydraの形式

python3 my_trainer.py learning_rate=learning-rate-in-this-trial

解決方法

解決方法はいくつか考えられます。

  1. Vertex AIがHydra形式に対応するのを待つ

    → 一番良いが、対応されるか分からない

  2. Vertex AIのハイパーパラメータ調整機能を使わず、オープンソースのハイパーパラメータ自動最適化フレームワークOptunaを使用する

    → 並列ではなく直列でHydra+Optunaで使用しており、一つのインスタンスで完結するため、さまざまな環境で動かすことができるので便利。しかし、同一マシーンではなく並列で回す際はSQLのサーバーを立てる必要があり大変。(Optunaの並列について)

  3. argparse など対応しているコマンドライン引数解析ライブラリを使う

    → 推奨されているが、パーサーの定義が大変、設定ファイルを使用できない

  4. Vertex AIが渡す引数の形式を変換する処理を挟んで実行する

    → 強引だが、導入コストが一番低い

今回は導入コストが一番低い4を採用しました。

Hydra形式へ変換し、本来の学習コードを実行するスクリプトの作成

まずは、Vertex AIからコンテナに渡される入力(コマンドライン引数)の変換を考えます。今回、コマンドライン引数の変換にはshell scriptを使いました。

shell scriptでのコマンドライン引数の受け取りですが、一番目の引数が欲しい時は $1 で取り、n番目は $nで取れます。今回は引数の個数は可変なため $@で引数全体をそのまま文字列として受け取ります。

次に受け取った文字列を正規表現で置換を行いHydra形式に変換します。

shell scriptなのでsedコマンドを使用して正規表現の置換を行います。

以下のようなコマンドにすることで、引数の形式については

--key=value--key value 両方とも受け取れるようにします。

sed -r 's/--([^= ]*)[= ]([^ ]*)/\1=\2/g'

sedコマンドはVimの置換と同様に /で区切りs/正規表現/置換/フラグと言う形式で(expr)が \1, \2と対応しています。そして最後の gは複数回置換するフラグです。また、sedコマンドは最短一致に対応しておらず、マッチしたいものの後ろにある文字以外を繰り返すという処理で対処しています。( [^ ]* のところなど)

  • 実行例
❯ echo "--lr=0.001 --batch-size 64" | sed -r 's/--([^= ]*)[= ]([^ ]*)/\1=\2/g'

lr=0.001 batch-size=64

最後に変換した引数を使って本来の学習コードを実行できるようにします。

$(expr)はexprを実行した結果を文字列として受け取れるので、それを用いて

python train.py $(echo $@ | sed -r 's/--([^= ]*)[= ]([^ ]*)/\1=\2/g') # train.pyはHydraを用いたコード

とすると、変換した引数を本来の学習コードの実行コマンドの後ろに追加できます。

あとはこれを train.shなどでファイルを作成し、Vertex AIの設定ファイルの実行コマンドに ./train.shなどと記載すれば完成です。設定ファイルの実行コマンドの記載場所は HyperparameterTuningJob.trialJobSpec.workerPoolSpec.containerSpec.commandです(参考)。

HydraのコードでVertex AIのハイパーパラメータ調整ができた

このような工夫を行った結果、Vertex AIでハイパーパラメータ調整を並列に計算することができました(図2~4)。図3,4には、複数の色の線が同時に存在しており、これは複数インスタンスが並行して学習していることを意味してます。

これにより、これまで数日かかってしまう学習も、数時間で終えることができました!

図2 ハイパーパラメータ調整の各トライアルと、そのトライアルで利用されたハイパーパラメータの値。具体的な変数名は隠してます。

図3 Vertex AIのハイパーパラメータ調整時のCPU使用率の遷移。それぞれの線の色は各トライアルに対応している。

図4 Vertex AIのハイパーパラメータ調整時のGPU使用率の遷移。それぞれの線の色は各トライアルに対応している。

まとめ

Vertex AIのハイパーパラメータ調整ジョブの時、スクリプトを挟み正規表現でパラメータの形式を変換することで、Vertex AIから渡されるハイパーパラメータをHydraが受け取れる形式に変換することができました。

これにより、Hydraを使用した既存のコードを使用しながら並列でハイパーパラメータ調整できるようになり、学習時間の短縮ができました。

今回の方法以外により良い方法があるかもしれません。私自身まだまだ、勉強中なので改善点などアドバイス頂けられたら幸いです。

補足:チームでR&Dに取り組む工夫

このブログでご紹介しましたが、JX通信社ではPytorch Lightningをベースとした、テンプレートコードを用いて学習を行うことで、チームで効率よく実験ができるようにしています。

興味があれば以下のブログも読んでみてください

tech.jxpress.net tech.jxpress.net

我々とともに挑戦する仲間を求めています

我々とともに成長しながら、より良い社会のためのMLを開発したい仲間を社員・インターン問わず積極的に募集しています!また、MLエンジニアはもちろん、あらゆる職種のエンジニアを求めています!

正社員、インターン、そして副業・復業として体験的に働くことで、当社のカルチャーや働き方等を知っていただいたうえで正式入社を見極めていただくことが可能な「おためし入社」制度などもあります!ほんの少しでも興味を持たれた方はこちらを覗いてみてください!


*1:⚠️ Vertex AIを利用するさらなる利点

ハイパーパラメータ調整をする方法は主に以下の2つに分けられます。

①実験前に予め、ハイパーパラメータを決めて実験を行い、その中で最も性能の良いパラメータを採用する方法 (Grid searchなどが有名)

②とあるハイパーパラメータを与え実験し、その結果を元に更に次のハイパーパラメータ決めて実験を行い、を繰り返すことで最適なハイパーパラメータを探索する方法 (ベイズ推定などが有名)

①の場合は探索するハイパーパラメータは実験前から決まっているため、すべての実験を一度に並行で回せばよく、図1の下の例の1回目の実験で終わります。しかし、場当たり的にハイパーパラメータを選んでいるため、無駄な計算も多くなってしまい、最適なハイパーパラメータを得るためには膨大なコストがかかる可能性があります。

一方、②の方法では、戦略的にハイパーパラメータを選んでいるため最適なパラメーターをコスパよく高精度に探索することができます。しかし、前の学習結果に依存して次の実験で探索するハイパーパラメータが決まるため、全ての学習を一度に並行で回す事はできません。

並行学習を直列でつなぐことができれば、並行学習とベイス最適化の両方の恩恵を受けることができますが、一般的には実装が非常に大変です。しかし、Vertex AIを利用することで並行学習の直列化をほとんど苦労することなく実施することができます (図1 下の例)。

リスク検知SaaSを支えるマルチモーダル・マルチタスクなExplainable AI

皆様こんにちは!JX通信社で機械学習エンジニアを担っているファンヨンテです。

弊社提供のビックデータ リスク情報サービスFASTALERTでは、Deep Learningを使ってSNSの投稿をリアルタイムに解析し、火事や事故などのリスク情報の検知を行っています。

SNSの投稿には、文字だけでなく、画像、動画などの情報も含まれているため、SNS解析にはよくマルチモーダルなAIモデルが用いられます。今回は「SNS の投稿からのリスク情報の判定」というタスクをテーマに、マルチモーダルなAIモデルの判定根拠の可視化や、精度を上げるための工夫などをご紹介します。

FASTALERT(ファストアラート)について

「FASTALERT」は、SNSをはじめとする各種ビッグデータから、AIがリスク情報を検知・配信するビックデータ リスク情報サービスです。報道に必要不可欠なツールとしてNHKと全ての民放キー局、全ての一般紙に採用されるなど、国内の大半の報道機関に浸透しています。また、最近では防災やBCP、障害監視やサプライチェーンのリスク管理など広範なニーズに対応する情報ツールとして、政府・自治体やインフラ企業をはじめとする幅広い業種の顧客に導入されています。URL:https://fastalert.jp

f:id:yoooongtae:20220408110109p:plain

図1FASTALARTについて

リスク検知タスクの課題

SNSからリスク情報を検知するというタスクの性質上、AIモデルには以下の4点の性質が求められます。

  1. 画像やテキスト等あらゆる情報を統合して判定したい!(マルチモーダル)
  2. リスク度だけでなく、リスク情報のカテゴリー(災害、事件、事故など)まで予測したい!
  3. リスク情報の検知漏れが起こってはならない!
  4. モデルに説明可能性をもたせたい!

マルチモーダルでマルチタスクができるAIのモデルアーキテクチャ

冒頭にも書いた通り、SNSの投稿にはテキストや画像、動画などの情報が含まれています。

また、FASTALERTでは、リスク情報を検知して配信するだけでなく、カテゴリー(災害、事件、事故などの区分)まで解析して、フィルタリングできるようにしています。リスク度の判定+リスクのカテゴリー予測を1つのモデルでマルチタスクで予想させると、ビジネス的な要件を満たすだけでなく、リスクの検知力自体を上げる効果もあります

まとめると、SNS からのリスク検知タスクに対しては、

  • 入力データ
    • テキスト
    • 画像
  • 出力データ
    • リスク検知
    • カテゴリー(災害、事件、事故などの区分)

が可能な”マルチモーダル””マルチタスク”ができるAI】が最適です。

AIの説明可能性

リスク情報を検知するというAIの性質上、AIの判断根拠を可視化することで、人間による監視や改善に役立てることができます。しかし、一般に公開されている多くのAIは”ブラックボックス”と言われ、判断根拠をうまく示すことができません。したがって、今回は判断根拠を示せるAIを開発し、実験を行いました。

AIの判断根拠を理解するためには、”挙動の理解”または”仕組みの理解”2つの方針があります。

”挙動の理解”は何かを変化させたときの出力の変化を見ることで、判断根拠を理解する方針であり、Grad_CAM, LIME, SHAP等が有名な方法です。しかし、マルチモーダルなモデルだと根拠の解釈が直感的ではないことや、計算時間が余計にかかり、リアルタイム性が失われてしまうことなどから、実装を見送りました。

”仕組みの理解”はAIモデルの仕組みを理解する事で判断根拠を見出す方針であり、Attentionが有名な方法です。Attentionを用いると、

  • 注目箇所が可視化されるので解釈があまり困らない
  • forwardの1回の計算で完結するので、計算時間が余計にかからない

のメリットがあるため、今回はAttentionを用いてAIが着目した部分を明瞭化し、判断根拠の可視化を行うことにしました。

実験

リスク検知力について

今回作成したモデルでは、全リスク情報ツイートの内99%を検知できるような再現率を達成することができました。弊社のFASTALERTでは、AIだけでなく最終的には有人による24時間体制の監視も加えて、リスク情報を漏れなく素早く検知し、より高品質な配信を実現しています。

モデルの判断根拠について

リスク情報を検知する判断根拠となる箇所を、わかりやすく可視化することで、さらに改善のための指標を得やすいようにしました。

以下に、火事、水害、横転事故のTweetを解析した例をあげました。画像は左がオリジナルで右がAIの注目箇所を表示しています。赤みが強いほど、AIがより注目したことを意味しています。

※ 以下の事例は実際のツイートではなく、ブログでの公開用に擬似的に作成したデータです(学習自体は実際のツイートで行っています)

f:id:yoooongtae:20220408110826p:plain
図2 火事についてのツイートを模したデータ。https://www.flickr.com/photos/sbeebe/7974757077から引用(licence: CC BY 2.0)。

f:id:yoooongtae:20220408111122p:plain
図3 水害についてのツイートを模したデータ。https://pxhere.com/en/photo/852904から引用(licence: CC 0.0)

f:id:yoooongtae:20220408111350p:plain
図4 横転事故についてのツイートを模したデータ。https://pxhere.com/en/photo/619517から引用(licence: CC BY 0.0)

これら結果を眺めていると、AIは各単語の関連性や、画像の情報と総合して判断箇所を注目していることがわかります。例えば、図2の火事の情報では、テキストは”消防車”と”火事”が、画像は炎の領域が注目されていることがわかります。この結果と図4の横転事故の情報を見比べてみましょう。すると”消防車”は、横転事故のときはあまり注目度が高くないのが見て取れます。図3の大雨の情報と、図4の横転事故を見比べても”道”という単語の注目度は変化してます。更に、興味深い点として、図4の横転事故のテキストの中で、最初に現れる”事故”は注目されていますが、後半の”事故”には注目されていないことがわかります。つまり、同じ文章内でも、重要度の区別をAIができていることを意味しています。

このように単純な単語のマッチングでは対応できない、複雑な言語の表現に対応した,マルチモーダルで説明可能なAIを作成することができました。

補足1:その他の前処理の工夫

リスク情報を漏らさないことを目的として、前処理をいくつか行ってます。その一つとして、絵文字の扱いを紹介します。

SNSにはテキスト情報の中に絵文字が含まれることがあります。自然言語処理を行う際にはノイズになるため絵文字は削除されることも多いのですが、リスク情報を検知する上では絵文字は重要な情報となりえます。 例えば以下のようなテキスト情報はリスク情報を含む可能性があります。

  • 東京⚡️やばい → 落雷・豪雨
  • 神田駅前🧑‍✈️👩‍✈️いっぱいいる。なにごと? → 事件
  • 東名高速で🚕 が🔥 → 交通事故

詳細な技術の説明は省きますがFASTALARTのAIはこれらの絵文字の情報も取りこぼすことなくリスク検知に役立てることで、情報の検知漏れがないようにしています。

補足2:チームでR&Dに取り組む工夫

JX通信社のMLチームにはインターン生がおり、今回の実験に関しても、インターンの小川遼人さんにサポートしていただきました。

過去にもエンジニアブログでご紹介しましたが、Pytorch Lightningをベースとした、テンプレートコードを用いて学習を行うことで、チームで効率よく実験ができるようにしています。

興味があれば以下のブログも読んてみてください tech.jxpress.net

tech.jxpress.net

我々とともに挑戦する仲間を求めています

我々とともに成長しながら、より良い社会のためのMLを開発したい仲間を社員・インターン問わず積極的に募集しています!また、MLエンジニアはもちろん、あらゆる職種のエンジニアを求めています!

正社員、インターン、そして副業・復業として体験的に働くことで、当社のカルチャーや働き方等を知っていただいたうえで正式入社を見極めていただくことが可能な「おためし入社」制度などもあります!ほんの少しでも興味を持たれた方はこちらを覗いてみてください!

slack-goとZapierで障害対応初動を自動化した話

f:id:TatchNicolas:20220316183046p:plain

サーバサイド開発やインフラ周りをいじっているたっち(TatchNicolas)です。今回はJX通信社における障害対応フローの改善について書きます。

はじめに

TL;DR;

  • slack-goとZapierを組み合わせて、障害対応時の提携作業を自動化するツールを作った
  • 「自動化しよう」という意見がでやすい場自体を仕組みとして整備したことが改善のきっかけになった

今回の話の背景

少し前に、ビープラウドさんとのイベントにて、JX通信社NewsDigestチームのCI/CDおよび障害対応についてお話しさせていただきました。*1

そのなかで、「障害対応時に専用のSlackチャンネルを都度作成していること」「Notionテンプレを使って情報の整理をしていること」*2を紹介しました。イベント後も何か障害やヒヤリハットが起こると少しずつフローが改善されていき、またNewsDigest以外のプロダクトチームでも同様のフローが採用されたりと自然と社内に浸透していきました。

JX通信社のポストモーテムでは、「発生した障害に対する技術的な振り返り、再発防止」の他に「対応のマニュアル、対応中のメンバーの振る舞い」に対する振り返りをする項目を明示的に設けています。その結果、「この手順、自動化したいな」という声が挙がったことから、ZapierとGolangを使って障害対応の初動を自動化する仕組みを作りました

f:id:TatchNicolas:20220316175706p:plain:w400
ポストモーテムで自動化の案がでたときのメモ

何をつくったか

任意のチャンネルで @incident-bot 障害発生 <プロダクト略称> <障害のひとこと説明> のような形式でSlackに書き込むと、

  • 障害対応Slackチャンネル作成(#incident-yyyy-mm-dd-<プロダクト略称>-<障害のひとこと説明>)
  • 情報をまとめるNotionページの作成
  • 関係者への連絡(然るべき常設Slackチャンネルへ新規作成した障害対応slackチャンネルとNotionページを投下)

を自動で瞬時に行ってくれます。

f:id:TatchNicolas:20220315230506p:plain:w400 f:id:TatchNicolas:20220315230514p:plain:w600

こういった作業は障害発生時のような急いでいる時ほど速く確実に行いたいので、自動化にはもってこいですね。

どう作ったか

はじめは、以前社内勉強会*3でも取り上げた https://github.com/slack-go/slack を使って適当な場所へデプロイしてサクッと完成させようと思ったのですが、Notion APIを使ってみたところ執筆時点でページ単位のAPIキーの発行に対応しておらず、あまり気軽にキーを発行するわけにもいきませんでした。

そこで社内で相談したところ「Zapier越しになら記録もいい感じに残るし使ってOK」との助言をもらったので、「じゃあいっそのことZapierで完結させるか!チャチャっと終わらせたいし!」と作り始めました。

Zapierの困りごと

Zapierは、SlackもNotionもネイティブに対応しており、Slack連携でチャンネル作成やメッセージ送信、Notion連携でページの作成などの処理を簡単に作ることができます。なのでポチポチしていくだけで簡単に今回の用件を満たすものが作れそうだと思ったのですが、Slackチャンネル作成の機能が日本語に対応していなかったので、 冒頭の例のようなチャンネルを作ろうとすると #incident-yyyy-mm-dd-nd-_ のように日本語がアンダースコアで置き換えられてしまいました。

これにより困ることが2つあって、一つは急いでいるときにチャンネル名から何が起こっているのかを特定できないことです。障害対応中はアラートを流しているチャンネルや修正のためのCI/CDの状態を流しているチャンネルなど、いろいろなチャンネルを行き来します。なので日本語でチャンネルを検索できた方が便利ですし、チャンネルが作成されたことを通知された人も何が起こっているのかパッとみてイメージしやすいので、日本語チャンネル名対応は諦めたくありませんでした。

もう一つは、あまり考えたくないですが同じ日に同じプロダクトで障害が複数発生*4した場合、日本語部分がアンダースコアになってしまうとチャンネル名が被ってしまいます。するとチャンネル作成がエラーになってしまいますし、ランダムな接尾辞などを付与するのもなんだかイケてないので困りました。

そこで「チャンネルを作る」というアクションは今後も変わることが少ないと想定されるので、チャンネル作成の部分は当初の予定通りGolangで開発したbotに任せ、それ以外をZapierに分担させることにしました。

JXならではの工夫

JX通信社は、私が所属しているプロダクトであるニュースアプリのNewsDigestのほか、To BサービスのFASTALERT、KAIZODEがあり、それぞれが共通して使う社内基盤であるXWireというシステムも存在します。

また弊社の文化として、各プロダクトのチームは技術選定やインフラ管理などを自分たちの意思と責任で実践しており、たとえば「本番デプロイのためのAWSの権限をどう管理・取得するか」のような本当に会社全体で守るべきルール以外はかなり大きな裁量がプロダクト開発チームに与えられています

一方で、他プロダクトの良いと思った開発手法やツールなどは積極的にお互いに真似していく文化もあります。私はこれを個人的に「ゆるやかで自然発生的な標準化」と呼んでいます。

なので、「障害が発生したときの動きを自動化したい」といっても、プロダクトごとに以下の設定が異なりますし、今後も違いが出てくるかもしれません。

  • 障害対応時に作成するNotionページにどんな情報を含めるか、どんなTODOを含めるか*5
  • 障害対応Slackチャンネルに誰を招待するか
  • 障害の発生を誰に通知するか
  • その他、「追加で○○がしたい」etc...

そこで、プロダクトごとに異なる部分をZapierのPath*6を使って表現することにしました。

結果的に、ツールの細かな挙動もZapierの管理画面からポチポチと変更できるので、「障害対応チャンネルに招待する人を変えたい」「通知先を変えたい」などの変更も気軽にできるようになりました

まとめ

ノーコードでポチポチ気軽に変更できる部分と、コードを書いて作る部分を組み合わせて、「ゆるやかで自然発生的な標準化」の文化を活かしつつも、共通する部分を自動化して運用負荷を下げようと試みました。ノーコードのツールを使うのは初めてで、変更差分の管理やコメントを入れにくいなど慣れない部分もありましたが、Zapierが思ったよりも高機能で驚きました。

今後使われていく中で機能を落としたり追加したり改修が増えてくると、「やっぱり普通にbotに寄せよう」「いやZapier使い倒そう」など作り方自体を変えていくかもしれませんが、現時点では「制約のなかで欲しいものをチャチャっと作る」を実現するのにちょうどいい塩梅にできたかなと思います。幸い、今回の仕組みを作ったあとでこれが必要になるようなトラブルはまだ発生していないので、実際に使ってもらいながら改善していきたいと思います。

今回のツールは障害発生時に使われるものなので出番が少ないほど嬉しい性質のものではありますが、こういった改善のアイディアが出てくるのはポストモーテムで考える範囲を広げて「人の動き方をもっと良くするにはどうすればよいか」について議論することを明示的に組み込んだ一つの成果ではないかと思います。

障害対応フローについては他社さんでも色々な工夫をされていると思うので、「こういう事例しってるよ」「ウチではこんなことしてるよ」など知見がありましたら、ぜひ教えていただければ嬉しいです。

*1:https://speakerdeck.com/tatchnicolas/cdtozhang-hai-dui-ying

*2:詳しくは↑の註釈のURLから資料を見ていただきたいですが、アラートチャンネルでそのままコミュニケーションを始めるとアラートと人間の会話が混ざってしまいますし、Slackだけでは情報の整理という意味で不足を感じたのでSlack+Notionを障害対応時のツールとして両方使っています

*3:https://tech.jxpress.net/entry/slack-app-101

*4:対応フローを整備したときに指針として「false positiveには寛大になろう」と明文化して障害発生を宣言することのハードルを下げているため、実際は何もなくともチャンネル名が重複する可能性を考慮しました

*5:障害対応ページをまとめるページもプロダクトごとに分かれているため、挿入先データベースの指定も異なります。

*6:https://zapier.com/help/create/customize/add-branching-logic-to-zaps-with-paths

AWS 上のシステムでリージョン切り替えの避難訓練を年末にやってみた

f:id:nsmr_jx:20220104095954j:plain あけましておめでとうございます。 サーバーサイドエンジニアの @kimihiro_n です。

今日はAWSに載っているシステムの避難訓練を実施したことについて書いてみようと思います。

弊社が提供している FASTALERT というサービスでは、全国の災害や事件などを検知して報道機関や自治体、インフラを支える企業などにリスク情報として提供しています。 リスク情報を提供するという性質上、情報検知の素早さや網羅性に加えて「システムの可用性」も重要なサービスの要素となっています。

FASTALERT の多くのシステムは AWS の東京リージョンで動いており、複数データセンターを活用した冗長化(マルチAZ)がされています。 しかし、例えば大規模地震のような広域かつ被害の大きい災害の場合、東京リージョン全体にわたって問題が発生する可能性があります。 首都直下型の大きな地震は今後30年以内に70%の確率で発生すると予想されており*1東京リージョンのみに頼らない構成が必要であると考えています。

また災害に限らず、AWSの特定のサービスで障害が発生した場合でも、別のリージョンへ切り替えることで素早く復旧できるケースもあるため、マルチリージョンでシステムを構成しておくことは可用性を高めてくれます。

マルチリージョン化の進め方

AWS のマルチリージョン化については、主要なコンポーネントから少しずつ進めていきました。 FASTALERT のシステムはマイクロサービス的に機能ごとに複数のコンポーネントに分かれており、止まってしまうと困る部分を優先的にリージョン単位で冗長化していきました。

実際の作業としては、リージョン間でネットワークの相互疎通できるよう土台を整え、それからデータベースやアプリケーションの冗長化に手をつけていきました。

要となるデータベースは Amazon Auroraを利用しているので、グローバルデータベース機能が利用できました。 グローバルデータベースを活用すると、データベースのリードレプリカが別リージョンへ簡単に作成できます。 また東京リージョンで障害が発生した際には、フェイルオーバー操作によって書き込み用の Writer へと昇格することが可能です。

コンポーネントの冗長化にあたっては、費用の面もあるのでホットスタンバイするものとコールドスタンバイにしておくものなど適宜使い分けるようにしています。 データベースなどリージョン切り替えに時間を要するものはホットスタンバイとして常時動かしておき、ECSで動くサービスなど素早く起動して利用できるものは停止した状態で冗長化しています。

システム自体の冗長化に加えて、欠かせないのが移行手順のドキュメント化です。 東京リージョンがまるごと利用困難になるレベルの災害を想定すると、東京にいるエンジニアも対応できない状況になっている可能性が高いです。 こうした非常時に、リモートで動けるエンジニアへバトンタッチ出来るようしっかりとしたドキュメントを用意しておく必要があります。

避難訓練の実施

個々のコンポーネントの冗長構成と動作確認は適宜行っていたのですが、全体を通して「東京リージョンの機能がほぼ使えなくなった」シナリオでの検証はやったことがありませんでした。 シナリオを想定してみることで、どの順序で何をすべきかや、どれくらい時間がかかるかなどを洗い出すことが出来ます。

ちょうど年末年始の休みで開発のキリがいいタイミングがあったため、避難訓練を実施してみることにしました。

ちなみにAWS のマネジメントコンソールは使える仮定でやっていました。実際になってみないと何が使えて何が使えないのかは分からないですが、何もかも使えないことに対処しようとすると無限に工数と費用がかかってしまうため、出来る範囲で対処できるケースを増やしていくのが大事だと思います。

避難訓練は、メンバーの1人に画面共有してもらい、マニュアル通りにリージョンの切り替え作業を行ってもらう形で実施しました。 他のメンバーは手順が正しいことを確認しつつ、サービスの状態を監視したり、どのタイミングで何を操作したかの記録を取っていきました。 マニュアルで分かりづらい点や不具合、エラーがでた箇所なども適宜メモしています。

避難訓練を実施してみて

元の状態に戻すところまで含めて1時間ぐらいで終わるかなと思っていたのですが、実際は2時間かかってしまいました

時間がかかった要因としては、マニュアル通りにうまくいかなかったことがあったことが挙げられます。 ドキュメントを作成してからシステム自体に変更が入りそのままでは動かなくなってしまっているケースや、ターゲットのリージョンで操作すべき項目を東京リージョン側で操作してしまったケースなどがありました。 練習なので落ち着いて調査出来ましたが、実際に障害が発生しているときだと大変です。 事前に不備を訓練で洗い出すことが出来たのはよかったです。

またこうすればもっと省力化できそう、みたいなアイデアも出てきたので避難訓練を実施した価値は十分あったかなと思います。 大規模な災害は起こってほしくないですが、万が一の際でも安定して提供できるサービスを作っていきたいですね。

爆速開発を目指して NewsDigest を Flutter にリプレイスします

f:id:jazzsasori:20211128104040p:plain
爆速開発を目指して NewsDigest を Flutter にリプレイスします

JX通信社 Engineering Manager の @jazzsasori です。
最近アークナイツというソシャゲに課金してしまいましたが妻には内緒にしています。

弊社は NewsDigest という無料ニュースアプリを運営しています。
NewsDigest は記者が業務で愛用するほど、その圧倒的スピードに強みがある速報アプリです。また、一般的なニュース分野での速報に加えて、報道はされにくいが個人にとって価値の高い情報も to B 向けのリスク情報SaaS である FASTALERT と連携して即時に伝える、社会派ニュースアプリです。
現在 (2021/11) 500万ダウンロードを突破しており、今後もさらにユーザーを伸ばそうとしています。

なぜリプレイスを行うのか

サービスとしては 2015年 にストアで公開されたので今日現在 (2021年12月) アプリとしてはもうすぐ8年目となります (すごい)。
iOS でいくと Swift 1 → 2 → 3 → 4 という移行も乗り越えてきました。

技術的負債という課題

7年以上運用していると、どれだけ気をつけていてもいわゆる「技術的負債」が溜まってしまいました。
時が経つにつれて技術的負債の課題は深刻なものになっていきました。
例えばある機能を改修する際、

  • 技術的負債となっている部分が解決すれば1日で終わるタスクに2日かかる
  • 簡単に終わると思っていた改修箇所が実は同じ変更内容を2箇所に適用しなければならなかった

など、よくある技術的負債による工数の肥大化が発生していました。

※ NewsDigest はさまざまな立場で多くのエンジニアの方に改修していただいた歴史があります。
技術的負債に関して否定的なことも記述はしておりますが、関わっていただいたメンバーの方に最大限の尊敬と感謝の念を込めて書いています。

爆速開発できるコードベースをつくりたい

技術的負債の側面に加え、我々が描く未来をより速く実現したいと考えています。
例えば弊社の提供する最新感染状況マップ・感染者数情報 は多くの方に価値を感じていただき、テレビ番組などでは多く特集いただきました。
このようなユーザーにとって価値の高い情報をお届けしつつ、多くの方にご利用いただくことにより、たくさんのフィードバックをいただき、さらにサービスを改善していきたいと考えています。

我々は技術的負債の解消・今後の開発速度の向上という二つの観点から Flutter によるフルリプレイスを行う という選択をしました。

Flutter を選択した背景

NewsDigest という大規模でかつユーザーの多いニュースアプリをリプレイスする技術選定としてはさまざまな可能性を模索しました。

  • 継続してSwift / Kotlin で開発
  • React Native
  • Kotlin Multi Platform
  • ...

特に React Native に関しては弊社の Frontend では React を多く使うこともあり、相性がよいのではないか、という議論もありました。

会社としても大きな投資となるため、経営層・マネージャー層で議論を進めていたところ、弊社のエンジニアから「NewsDigest をもし Flutter で書き直すならこんな感じかなと思って書いてみました」という連絡がありました。リポジトリを覗いてみるとけっこう書き進めてくれていて...というストーリーがあり、 チームメンバーからのボトムアップで Flutter という技術選択をしました。

Flutter でどうリプレイスを進めているのか

今回のテーマとしては「技術的負債の解消」という大きなテーマがあります。
今後の開発速度を爆速にするため、なるべく技術的負債が溜まりにくくするため、アーキテクチャにはこだわっています。

具体的には、まず大きな考え方として Clean Architecture の考え方を参考にしつつ Onion Architecture をベースとしたアーキテクチャを選択しています。例えば社内のドキュメントには SOLID 原則 にのっとってコードを書くように案内するようなものもあります。

また、FAT な Widget を作らないようにすることも重要です。
(私も iOS で 2000行を超える FatViewController を書いたこともあります...)
こちらは Atomic Design の考え方にのっとって Widget を設計しています。
何をもって molecules か、何をもって organisms か、というのは難しい問題ですが、考え方をすり合わせながら・ドキュメント化しながらコーディングルールを明確化していっているのが現状です。

ステート管理については Riverpod を採用しています。

今後どう進めていくのか

NewsDigest の規模のアプリをフルリプレイスするのには工数がかかります。
数ヶ月の工数をかけて出来るだけ正しい形、今後の開発速度が爆速になるよう + 負債が溜まりにくくなるような形でリプレイスを完遂しようとしています。
エンジニアだけではなく、セールスメンバーにも理解いただきつつチーム一丸となってリリース目標を立てて進めています。

正直なところ忙しいプロジェクトではあります。
一方で多くのユーザーの方にご利用いただいているニュースアプリを根本から改善する、という挑戦的な面白いプロジェクトでもあり、メンバーにはモチベーション高く挑んでいただいています。  

読んでいただいている Flutter エンジニアの方、ぜひ一度弊社のお話を聞いていただけないでしょうか?
フリーランス、正社員問わず募集しています。
こちらからご応募可能なのでぜひお気軽にご連絡ください 🙇

open.talentio.com


NewsDigest を使ってみたいという方は下記よりダウンロードください 🙇‍♂️
iPhone 版 :
app.adjust.com

Android 版: app.adjust.com