ナカザンドットネット

Android Developer's memo

Uber Eatsの障害についての事実をReact Nativeの観点から確認する

本日、Uber Eatsで大規模障害がありました。React Native絡みのようなので、今わかっている範囲の事実だけメモしておこうと思います。

公式アナウンス

ユーザーの声

Twitterを眺めている限りでは、店舗側に配布されているアプリ(レストランアプリ)が利用できなくなり、受注の確認ができなくなっていたようです。

React Nativeっぽい

Uber EatsチームがReact Nativeを採用していることは、次の公式エンジニアブログでも言及されています。

eng.uber.com

本件で報告が上がっていた赤い画面は、React Nativeのデバッグモードで console.error() や最上位まで到達してしまった例外の内容を表示するための、RedBoxと呼ばれる開発補助のための機能です。レストランアプリもReact Native製と見て問題ないでしょう。

2つの事実を確認する

それでは、React Nativeの観点で、今回報告されている画面から読み取れる内容について解説します。主に次の2件について言及します。

  • RedBoxが表示されている
  • Textコンポーネントについてのエラーが出ている

RedBoxが表示されている

まずは、赤い画面(RedBox)が出ている件についてです。繰り返しになりますが、

本件で報告が上がっていた赤い画面は、React Nativeのデバッグモードで console.error() や最上位まで到達してしまった例外の内容を表示するための、RedBoxと呼ばれる開発補助のための機能

です。

React Nativeでアプリ開発をしたことがある方なら日常的に目にしているものですが、そうでない方はほぼ見たことないと思います。React Nativeは多くの有名アプリで使われているにもかかわらず、です。

というのも、RedBox(とconsole.warnを司るYellowBox)はリリース用にビルドした際に無効化され、ストアに公開された時点で表示されなくなっているために、ユーザーとしてアプリを触る場合には目にすることがないからです。

次の公式ドキュメントでも言及されています。

facebook.github.io

RedBoxes and YellowBoxes are automatically disabled in release (production) builds.

今回の障害では、Uber EatsのレストランアプリにRedBoxが表示されていました。

誤ってなのか、もともとそういう運用なのかはわかりませんが、今回障害のあったレストランアプリはデバッグビルドされたものだったことが見て取れます。

Textコンポーネントについてのエラーが出ている

RedBoxは console.error() なので、そのとき発生したエラーのメッセージとスタックトレースが表示されています。メッセージの内容を見てみましょう。

Invariant Violation: Text strings must be rendered within a <Text> component.

これはブラウザ開発に慣れ親しんだ方がReact Nativeを触り始めたときによくやるエラーで、簡単に再現することができます。↓の "Tap to play" を押すと、実行結果を見ることができます。

今回起きていたのは、JSX内で <Text> コンポーネント以外の場所に文字列を書くと発生するエラーです。ブラウザなら <div> の直下にテキストを書いても怒られませんが、React Nativeで <View> の直下にテキストを書くと怒られるのです。((最終的にAndroidのTextViewやiOSのUIViewに落とし込まないと表示できないと考えると、妥当な仕様です。))

これは静的解析では見つけづらいエラーなので、ちょっと同情はします。しかし、言ってしまえば凡ミスの類いなので、特に今回の障害でReact Nativeのヤバさが露見したとか、そういうことではないように思います。

追記

使ったことなかったけど、eslint-plugin-react-nativeの react-native/no-raw-text ルールで検出できそう。

github.com

感想と邪推

というわけで、Twitterで報告されていた内容から読み取れる範囲の事実について解説しました。ここからは私個人の意見や妄想の話で、事実に基づく話ではないものが混ざっています。眉に唾をつけて読んでください。

デバッグビルドの話はアプリの運用ルールの話なので、良いとも悪いとも断言できない部分もあるのですが、普通はやらないです。というのも、デバッグ中というのは、APKファイルの中にJSファイルを内包するのではなく、パソコン側で立ち上げた開発サーバーからJSファイルを逐次にダウンロードして画面に描画するため、配布に向いていないはずだからです。

……もしかして、開発サーバーを公開サーバーの中で起動しておいて、配布したデバッグビルドのアプリからアクセスさせている……? そうすれば、Hot Reloadの要領で、最新の実装がリアルタイムでエンドユーザーの手元に届くから……? そんなことある……? 簡易Code Pushとして技術的には実現可能かもしれないけど、本当にやるか……?

妄想はできるのですが、憶測を見てきたかのように語るのもよくないので、口を噤むことにしましょう。

追記:Textコンポーネントのエラーが起きるパターン

ちょっとだけ複雑なパターンもあるようです。

例えば、こんなコンポーネントがあったとします。

type Props = {
  message: string;
};
const Component: React.FC<Props> = ({ message }) => (
  <View>
    {message && <Text>message</Text>}
  </View>
);

これを <Component message=""> のように呼び出した場合、JSX部分の条件式が評価されて次のようになります。

<View>
  ""
</View>

こうなるとViewの直下にテキストがきてしまうので、アウトです。空文字はfalthyなので、短絡評価の結果として左オペランドだけが残った形ですね。Twitterを見ていると、このケースがあるある案件のようです。

とはいえ、想像の域を出ないので、実際にこういう実装をした結果として起きたエラーだというわけではありません。

おわりに

今回の件は、React Native関連の障害としては初めてと言っていいレベルの大規模なものになったように思います。しかしながら、React Nativeのせいというにはちょっと運用のほうが前衛的すぎるんじゃないかなという感想を持ちました。

こんな事例を挙げて、訳知り顔で「これだからReact Nativeみたいなツールは信用できない」みたいなことを言う人が出てくるのも業腹なので、そうではないよ、と言うために筆を取った次第です。

それにしても、本当にどういう運用をしているのか純粋に(割とポジティブな感情で)興味が出てきました。いつかReactConfとかApp.js Confあたりで、今回の障害についてUber Eatsのエンジニアから振り返りセッションがあったりすると面白いのになあ、と思っています。