2016年8月5日金曜日

スケーラブルな開発と Conway’s law

Twitter で自分が関わっているサービスの開発者の数は、この 4 年間で約 5 倍に増えた。エンジニアの数とサービスの開発・運用について考えてみたい。

2, 3 人の小さいチームが作ったサービスが徐々に複雑になり、アーキテクチャの大きな変更がないまま、複数のチームに属する数十〜数百人のエンジニアが関わるようになる、ということはよくある。このときの問題点として、次の 3 つが考えられる。

コードレビュー

数十人が関わるサービスは大抵コードが複雑で、全体像をしているのは一部のエンジニアだけという状況になる。そして、その一部のエンジニアが「コアチーム」となり、コード変更の決定権を持つ。
エンジニア全体の増加に比べて、コアチームのメンバーは増えない(システムの複雑性が上がっていくので、全体を理解しているエンジニアの数が比例しない)ので、コアチームのレビューが開発のボトルネックになる。

デプロイ・インシデントレスポンス

エンジニアが増えると、一つのデプロイに含まれる変更の数が増える。当然、デプロイが失敗する確率(機能的なバグやパフォーマンス低下など)が上がる。また、失敗した時にどの変更をロールバックすべきかの判断が難しい。デプロイできるのはコアチームだけになり、また、デプロイの頻度が下がる。

リソースプランニング

そのサービスが使用するリソースの予測が難しくなる。
一つのサービスの中にいくつもの機能が含まれるので、ダウンストリームのサービスへのリクエスト数、CPU / ネットワーク使用量、ストレージのキャパシティの見積もりが非線形になる。



これらに共通する原因は、機能・モジュールごとのアイソレーションが無いことだ。
アイソレーションのわかりやすい実装例として、micro service があるが、もしこの複雑なサービスを複数の単純なサービスに分解できるのであれば、これらの問題は解決する。
それぞれの機能を一つのサービスとして実装し、それぞれのサービスは個別のチームが運用すればよい。
難しいのは、micro service に分解するのが現実的ではないケースだ。例えば、機能間でやりとりされるデータ量が大きい場合、別のサービスに分けると、ネットワークがボトルネックになったりする。あるサービスを micro service に分解できるかどうかは、機能間のデータ量、同期処理・非同期処理の切り分け、リアルタイム性などに依存する。

Conway’s law という法則があって、意訳すると「システムの構造は組織の構造を反映する」というものだ。


組織の構造をシステムに反映させ(またはシステムの構造にあった組織を作り)、機能・モジュール間のアイソレーションを提供する。これは、エンジニアの数に対してスケーラブルな開発環境を保つために、コアチームが解決すべき重要な問題だと思う。

2016年8月4日木曜日

コードレビューで心掛けていること

Twitter の Ads チームは、これまでに働いてきた環境に比べて、コードレビューをずっと重視している。
4 年間を振り返って、自分が学んだことをまとめておきたい。

コードレビューの目的は、ソフトウェアの品質を高めることだ。品質にはいろいろな観点があるが、正しく動く、メンテナンスしやすい、設計が正しい、の 3 点でカバーできると思う。

正しく動く

この観点でのレビューには、ロジカルなバグ、テストカバレッジ、エッジケースを考慮してるかなどの確認が含まれる。
テストカバレッジのチェックやマイクロベンチマークは、理想的には自動化して、変更のマージ前にチェックできると良い・・・が現実的にはいろいろ難しいのでレビューでカバーすることになる。
具体的には、null チェックしてるか、メソッド中の条件分岐がユニットテストでカバーされてるか、無駄にオブジェクトを生成してないか、適切に例外を処理しているか、などを見る。

メンテナンスしやすい

大きなサービスだと、運用を担当するエンジニアが機能開発をするエンジニアと別(またはサブセット)ということがよくあるので、オペレーションの観点からのレビューが重要になる。
この機能がバグってたときに、サービスとしてどのように振る舞うべきかを考え、サービス全体を落とすべきなのかその機能だけを無効にするのか、その機能が動いていないことをどのように誰に通知するのか、などを考える。メンテナンスという観点では、コードの読みやすさも大切なので、長すぎるコードやおかしな変数名・クラス名は直してもらう。
これが良いことかどうかは別の機会にまとめたい(追記:まとめた)が、運用を担当するエンジニアは、リリースする機能やリリースのタイミングについて強い決定権を持つべきで、同時に、ある機能をリリースする / しないことによるリスク・ベネフィットのバランスを持たなくてはいけない。

設計が正しい

多分これが一番重要で難しいが、「この変更にバグがなくメンテナンス性も高いと仮定して、そもそもこれは正しい設計なのか」という観点が重要だ。
このレビューが難しい理由は、正しい設計を思いつくには、現在・将来のサービス設計、この機能のビジネス的な意味、どういう方向に発展していくのか、といった幅広い理解が必要だからだ。レビューワが変更のバックグラウンドを知っているケースもあるし、優れたレビューワだと「この変更を読んだところ、あなたがやりたいことは XXX だと思うのだが、だったらこのようにコードを書いた方が良いのではないか?」と、コードを読む→変更の理由を理解する→正しい設計を提案する、とリバースアセンブルしたりする。

理想的には、コードを書く前にコミッタとレビューワが議論すると良いのだが、すべての機能開発でそれができるわけではない(コミッタにとっては自明な変更でもレビューワにとってはそうではないことはよくある )ので、コードレビューの時点で差し戻されることもある。上述のとおり、コードを書き直すことのインパクトをレビューワは理解するべきで、なんでもかんでも差し戻せば良いというものでもない。ただし、「このように書き直すには X 日かかるが、リリースが X 日遅れるとビジネス上どういうインパクトがあるのか?」と聞くと「じゃあ書き直すわ」となることは経験上よくある。概算で良いので定量化(X 日)することで議論しやすくなると思う。

この観点でレビューするときに、最終形が明確に見えていないときがままある。「最終的にどういうコードになるかはっきりとはわからないけど、この方向で書き直すと良さそうだ」と感じてコメントするときもあるし、そのようなコメントをもらうときもある。結果として良い設計になることもあるし、「XXX という理由でその方向では書き直せなかった」ということもあるが、「多くの場合に、結果的に正しい設計にたどり着く方針を、最終形が明確に見えてない時点で指摘できる」レビューワは本当にすごいと思う。



何度も書いているが、コードレビューする際には、この機能をリリースすることのベネフィットを理解することが最も大切だ。バグがないこと、サービスが安定すること「だけ」を目標にすると「新しい機能は一切リリースしない」のが合理的な解になってしまう。そうすると、重箱の隅をつつくようなコードレビューになり、コミッタとレビューワが対立することになる。レビューワはコミッタと同じ方向を向いて、「この変更のどこを修正すれば、この機能をリリースできるのか」と考えると生産的なレビューになると思う。