Goと大規模分散システムの相性

https://www.youtube.com/watch?v=8IKxf98h65Y

1 comment | 2 points | by WazanovaNews 3年弱前 edited


Jshiike 3年弱前 edited | ▲upvoteする | link

Googleで分散システムの開発をてがけ、現在はソーシャルメディア mttr.toを立上げ中のBen Sigelmanが、Goを分散システムの開発に利用する場合の、メリットおよびチャレンジについて講演しています。

分散システムのあるべき姿

  • 分散システムの勘所は、最上位ビットをパフォーマンス的にも構造的にもうまく扱うことができるかというのがポイント。その効果が一番大きい。スループットの改善のような詳細は、自分もGoogleでそれに取組んだけれども、9ヶ月くらいたつとハードウェアの性能で解決される可能性が高い。また、構造的にというのは、なるべく小さなコンポーネントを組み合わせたシステムにできるかという意味。
  • Goのよいところは、
    • 両方、とくに後者によい。Railsだとアプリを複数個用意して並列処理するのは大変だったけど、Goだとシンプルにできて、標準ライブラリも読みやすいとかなどなど。パフォーマンスのメリットも手間入らずで享受できる。
    • 分散システムでは複数のメンバが開発に関わるため、自分が全てのコードを把握してない状況になる。そこで、静的型付け言語のメリットは大きい。他のチームをわずらわせずに自信をもってコードをいじれる。この点でもGoは活きる。
    • goroutineは、フローコントロール、特に複数の階層のフローコントロールの処理や、メモリが枯渇してクラッシュするのでなくプッシュバックするところなどエレガントに対処してくれる。先ほど会場の人から聞いた事例だけれども、三段階の複雑な仕組みを一つのGoサービスで置き換えて、Nginx、ロードバランサ、サービスを一つのGoバイナリで見れるようになり、パフォーマンスの問題もなく、リソースがなくなった時もクラッシュするのでなく500を返すという期待通りの処理ができるようになったという話があった。
  • Goがうまく対応できてないエリアとしては、
    • 大規模システムにおけるデバッグ
    • また、これはGoに限らない話だけれど、IPCは、自らドキュメントの役割も果たすように行われるべきだと思う。そういう意味では、Protocol Buffers / Thrift / Avroは重要。一方で、JSONのAPIを提供するアプローチは社外の人のためにはよいが、JSONのAPIをつくる際にそれがどうワークするかについてコードにコメントを残さなくてはいけないのは長期的に見ると悪い習慣。メンテ上の大きな問題につながる。シリアライゼーション、デシリアライゼーション、ハイスループットが要求されるシステムなどにとって頭の痛いことになる。

典型的な分散システムのアーキテクチャ

  • たくさんのサブシステムの結果をとりまとめる仕組み。Google検索におけるキャッシュミスは、数万のプロセスにヒットしなくてはいけない事態をもたらす。システム間のコミュニケーションコストが実装コストをはるかに上回る問題になる。
  • 遅延は平均値でなく、99パーセンタイルのようなロングテールのエッジケースの対処が重要。分散システムにおいて、1台のマシンで1%のリクエストが1秒以上かかるとすると、それが100台のマシンを取りまとめる仕組みであれば、63%のリクエストが1秒以上かかることになる。(「大規模分散システムのレスポンスを向上させる工夫」を参照ください。)
  • 分散トレースをしていてよく見られるのは、キューの問題やガベージコレクションにより、一つのリクエストの完了が他のリクエスト、特にメインのリクエストを待たせる状況。この数が増えたり、多層的に起きると、大きな問題になる。分散システムではクリティカルパスを特定し、それを最適化するのが最も重要。

ガベージコレクションと遅延

  • Goのガベージコレクションについては改善される計画もあり、基本的にはポジティブ。
  • 少しだけ心配なのは、ものすごくリアルタイム性を追求しようとすると問題になるのではないかという点。認識が間違っていたら指摘してほしいのだが、50msごとのブロックにおいて10msの間全てを止めて、そこでもし処理しきれなければ残りの40msを使って、おそらくグレードダウンしたスループットコストの高い並列処理をして遅延に対処しようとしているところ。10msであれば銀行のプロセスを5秒止めることにつながるなどという悪夢のような事態が起きないだろうという前提なのだと思う。大丈夫だと信じているが。また、いざとなればプログラム的には他にも対処方法はあるが、このあたりの事情もあり、クリティカルな大規模システムでの採用においては、まだちょっとナーバスになる。

分散実行トレース

  • 大規模分散システムにおいては、一つのリクエストが数千のプロセスを巻き込むこともある。コードを見ても、ログを見ても、どうしてエラーが起きたのか、何が起きているのかわからない。
  • ログ処理のコストが高くなりすぎることもある。また、Goが並列処理が得意だからといって、ログを並列処理で大量に蓄積しても、相関が不明では理解できない。
  • 相関の問題、大量のホスト、アクセス問題が掛け合わさることで、プロダクトの規模が一定以上になると、分散実行トレースが大きな課題となる。プロセスをまたいで何が起きているか、ベーシックなタイミングの情報がほしくなる。
  • これにうまく対処するには、
    • wire protocolをうまくコントロールすること。この点ではGoは問題ない。
    • 基本的なfunction callにはコードのフットプリントを必要としないこと。複数のチームにまたがるエンジニアの工数をかけて、全てのcallが分散実行トレースに対応できるように仕込むのは現実的ではない。SDKやAPIにまでそれを期待することは無理。クロスfunctionのcallには透明性が必要。
    • 実行キューの統合。どのようなシステムの構成であれ、キューに入って、処理され、アウトプットされる流れが一連のものであることをトレースは把握できなくてはいけない。
  • そこで必要となる下記三点のうち、最後の二つはGoが不得意。特にスレッドローカルは。
    • RPCシステムにおけるカスタムコード
    • スレッドローカル変数に頼ってfunction callにおけるコードの追加を避ける
    • 特別に実行可能な実装。何がキューにきてもどうにか操作できることで、トレースコードが付与され、後々に検索が可能なこと。
  • Goに、スレッドローカルと同義のものを追加するのがよいと思う。大規模な分散システムにおいて、自分が書いたライブラリから大量の別のライブラリにコールする際、相手側が分散実行とレースについて期待とおりに準備してくれているとは考えづらい。しかし、goroutineはとにかく軽量にという設計思想なので、ローカルスレッド的なものをGoの開発チームが追加してくれるかは期待薄。自分はそれだと、大規模システムでどうやってデバッグすればよいのかわからない。また、新しいパラメータを追加してチャネルをつくり、「スレッドローカルかトレース情報をトラックして、何かがキューに入る前か、キューから外れるタイミングでコールバックできるようにしてほしい。」と仕込む方法も考えられる。Goの開発チームは好きなんだけど、主義が強くて、残念ながらこの点には関心を示してもらえない。
  • まとめると、Goは分散システムに開発においてはよい立ち位置にいて、遅延の問題も来年には解決していると思う。残りは大規模分散システムのデバッグのみ。

質疑応答

  • Goがカバーするのと構造的に似たツールをつくり、つくろうとしているビルドのバイナリ上で実行し、必要なタイミングで使うというアイデアはどうか?
    • すごく面白いアイデアだと思う。Googleのときにあったのは、最初のリクエストがきてから、レイジーに生成されるものができあがってしまい、数百万のRPCが存在することになって、結局カスタマイズする必要ができてということがあったけど、うまく自動でできるようになればワークすると思う。
  • 全てをトレースしているのか、サンプルだけか?
    • 答えは両方。新しいリクエストが開始される度に、そのツリーの頂上で64ビットのランダムな番号を付与。そのリクエストが分散システムのツリーを下って伝達されていくときにその番号を渡していく。番号は常に渡されるが、ログが取得されるかどうかは、サンプルで決めたタイミングのときのみ。一定率の間隔でサンプルをとるという手法もあるし、興味あるパターンを定義して、それにあてはまるケースは継続的にその箇所のログを取得しつづけるという工夫もできる。
  • 具体的に取得する情報としては何を重視しているか?
    • 分散システムにおいては圧倒的にRPCが重要。いつリクエストがきて、いつ結果がでてきて、いつツリーの親に戻るのか。他には、ネットワークでどれだけ時間がかかったか、RPCがどれだけサーバに残っているかなど。

#golang


ワザノバTop200アクセスランキング


Back