2017年6月23日、サイバーエージェントはエンジニア向けのイベントである「オレシカナイト Vol.2 アドテク×golang勉強会」を開催しました。本稿ではその模様をレポートします。
新しい技術への挑戦に付きものの“つまずきポイント”を共有
オレシカナイトは、サイバーエージェントが運営するメディアの広告部門である、メディアディベロップメント事業本部(以下、MDH)のエンジニアが、新規技術に挑戦する中で踏み抜いてしまった“地雷”を共有するというもの。なお、オレシカナイトは、今回で2度目の開催となります。
最初に、2014年からMDHのアドテクノロジー局 局長を務め、現在は株式会社AJAの取締役である小越崇広氏が、開会の挨拶としてMDHの業務内容や今回のオレシカナイトの趣旨について説明しました。
小越氏はMDHについて「自社メディアのマネタイズの専門組織」だと説明し、具体的なメディアとして国内最大のブログメディアである「Ameba」や、インターネット放送局として話題を集める「AbemaTV」などを挙げました。そしてMDHの価値観の1つに「技術的挑戦」があると話し、次のようにオレシカナイトの趣旨を語りました。
「MDHでは新しい技術を使ったり、課題を解くために新たなプロセスを試したりするといった挑戦を積極的に行っています。ただ、新しい技術を使うと必ず“つまずきポイント”があります。そこで、どうせなら我々が取り組んできたことの苦労をみなさんにお裾分けしたいと考え、オレシカナイトを実施しています。今回に関してはGo言語を使ったプロダクトが多かったので、我々がGo言語を使ってつまずいたところ、あるいは感じたところを共有させていただきます」(小越氏)
新卒エンジニアがgolangに触れて感じたこと
小越氏の挨拶に続いて登壇したのは、新卒で入社して2年目のエンジニアである大江善渡氏です。大学時代、分散処理関連の研究を行っていたという大江氏は、配属当初は社内レポートツールを担当し、現在はDSPの計測サーバー部分の開発に従事しています。今回は「1年目でgolangとScalaを触った話」というタイトルで発表を行いました。
大学時代は主にJavaを使っていたという大江氏は、当時のMDHのサーバーサイドエンジニアが主に使っていたScalaについての感想から話し始めます。その中で印象に残ったのは、「開発以前にScalaの言語仕様で苦しめられました。苦労する分、習得する報酬も大きいのですが、Scalaを身に付けるのにどれくらいの時間がかかるのやらと、当時はかなり焦りました」と感想を述べた部分。Scalaには多くのメリットがある一方、習得するにはそれなりのハードルをクリアしなければならないことが分かります。
一方、golangについては他言語に比べて習得が容易ではないかとしました。
「Scalaの後に触った影響もありますが、かなりラーニングコストが低いのかなと印象を持ちました。言語仕様がシンプルで、習得するのがほかの言語に比べて比較的簡単なのではないでしょうか。具体的には、golangには継承やGenerics、例外処理といった機能が備わっていません。これらの正しく使うことが難しく、乱用することの弊害が生まれやすい可能性があるものが取り払われている印象です。また継承とかがないので、既存のコードだとかをすべて読むのに、それほどの時間を割く必要がない。そこもメリットだと感じています」(大江氏)
新たな言語にチャレンジする際、ひととおり使いこなせるようになるまでにどれくらいの時間がかかるのかは、誰しも気になるところでしょう。その点について、若い大江氏ならではの視点でストレートにScalaとgolangを比較した説明は分かりやすく、とても参考になりました。
低遅延が求められるDSPの開発でハマった“落とし穴”
2人目の片田雄樹氏は2015年に新卒入社したエンジニアで、2016年からMDHにてDSPチーム責任者兼エンジニアを務めています。今回の発表は「DSPでgolangの屍を超えた話」と題し、自身が開発責任者を務める「AmebaDSP」をgolangで開発したことによって生じた課題と、それをどう乗り越えたかについて話しました。
片田氏はDSPを開発する言語として気軽にgolangを選択したと話しつつ、レスポンス問題に苦しめられたと言います。DSPには、リクエストを受けてから100ms以内にレスポンスを返さなければならないというノルマがありますが、これがなかなかクリアできなかったのです。その問題を乗り越える上での苦労や解決策などを紹介しました。
参考になったのは、ライブラリを見直したという部分です。当然ながらライブラリもレスポンスに大きく影響しますが、選定の際には「広く使われているライブラリだから」などといった理由で、安直に決めてしまうことが少なくありません。しかし、片田氏は実装までしっかりチェックすべきだと指摘します。
「ライブラリを使うときに実装を見ないといけないと思いつつ、実際には見ない人が多いと思います。ただ実装する上では、ちゃんと見た方がいいというのが今回の教訓です。golangはそれほど読みづらい言語ではないので、読むことで余計なトラブルを回避できる可能性は高まるでしょう」(片田氏)
またパフォーマンスチューニングの要はプロファイルを取ることだったとした上で、golangの「pprof」は非常に優秀だと評価します。そして「echo-contrib/echopprof」を使ったプロファイル取得の手順も片田氏は説明しました。
特にアドテク領域において、レスポンスは極めて重要な要素となります。それだけに、参加者も真剣に片田氏の言葉に耳を傾けていました。
Scalaとgolangにおける末尾再帰最適化
3番目の登壇者は森拓真氏です。SSP広告配信サーバーや運用管理画面の機能開発を担当している森氏は「Java、ScalaをやってきてGoに思うこと golangで末尾再帰最適化は使えるか」というタイトルで発表しました。
まず森氏は、末尾再帰最適化について次のように説明します。
「階乗のプログラムを再起で実装した関数を書いて、大きな値を入力するとスタックオーバーフローになってしまいます。これを末尾再帰の形にすれば、Scalaのコンパイラが最適化してくれて、スタックオーバーフローが起きない形にしてくれます。これが末尾再帰最適化と呼ばれるものになります」(森氏)
Scalaでは末尾再帰処理に対応していますが、golangではどうなのでしょうか。森氏が過去のメーリングリストで確認したところによれば「いくらかのケースは対応しています。ただ言語としてすべてのケースを最適化するようにコンパイラを変更するつもりはない」とコア開発者が回答していたそうです。そこで実際に特定の数まで立ち上げる末尾再帰関数で検証した結果、期待が裏切られてスタックオーバーフローになったとのこと。また、再起とループ、そして末尾再帰で書いた関数でパフォーマンスを比較したところ、末尾再帰で書いた関数は再帰よりも結果はよかったものの、ループには遠く及ばない結果だったとしました。また、アセンブリコードを見ても最適化されている様子はなかったようです。
こうした検証結果から「golangでは末尾再帰は最適化されません。Scalaやほかの関数型言語で日常的に末尾再帰を書いている人にはつらいかもしれませんが、事実です。諦めて末尾再帰はforやgoto文に書き換えましょう。またコア開発者が最適化を追加する予定はないと言ったこと、ここ数年で末尾再帰最適化のトピックが立っていないことを考えると、本当に追加されないんだろうなと思っています」と結論を述べました。
森氏の説明した末尾再帰最適化のように、言語仕様などで気になる部分があっても、実際に調査するのは時間がかかるからと先送りにすることはないでしょうか。こうしたなかなか表に出てこない情報を得ることができるのは、オレシカナイトのような勉強会に参加する大きなメリットだと改めて感じました。
APIサーバー開発に役立つgoaとは何か
広告関連システムのサーバーサイドエンジニアで、最近golangを始めたという渋江一晃氏は、「マイクロサービスのためのフレームワークgoaのご紹介」と題して発表しました。
渋江氏は冒頭で「APIサーバーを作ることになったが、ルーティングやバリデーション、ドキュメントなど考えることが数多くあり、“ガワ”を作るだけでも大変」と話し始め、最終的にマイクロサービスのためのフレームワークである「goa」を採用したと説明しました。
その選定理由の1つとして、goaではデフォルトでswaggerの生成ができると渋江氏は話します。同様にgolangで使えるswaggerを生成可能なツールとしてはgo-swaggerもあり、goaの採用を決定する前に両者が比較されたようです。その上でgoaを選定した理由として「go-swaggerは日本語のドキュメントがあまりなかったこと。DSLが読みやすかったこと。そしてドキュメントもgo-swaggerと比較して多かったこと」を挙げました。
実際にgoaを使う際に参考になると感じたのは、エラーハンドリングについての説明です。渋江氏はgoaのエラーハンドリングにおいて、自動生成されるエラーではなくアプリ独自のエラーで返したいという要望があったことから、goaの中の人に問い合わせたところ解決策が返ってきたと言います。
「たとえばパラメータの型がおかしいといった際は決まったエラーメッセージが返ってくるので、そのエラーメッセージを奪い取り、特定のメッセージが含まれていたらオリジナルのエラーを返してあげる、そういったことを教えてもらいました」(渋江氏)
golangのコミュニティは確実に広まっていますが、一方で周辺ツールやそれについてのドキュメントが十分に整備されているとは言い難い状況です。そうした中で、渋江氏によるgoaの解説はとても参考になったようで、参加者の注目度も高いセッションでした。
golangを使うときはコインの裏側も知っておくべき
「Goにおけるテスト可能な設計 Javaとの比較」というタイトルで発表したのは、広告配信システムのバックエンド開発を担当する大澤翔吾氏です。今回のオレシカナイトの中でも特に示唆に富んだセッションであり、参加者も真剣に耳を傾けていました。
冒頭、大澤氏は結論として「golangはJavaのような既存の歴史ある言語と比較すると、テストという観点においては制約や守るべきルールも多い。なので『なんかGoってイケてるじゃん、採用しよう』とかってやってしまうと、ちょっと後で痛い目を見るかもしれないです」と語り、さらに次のように続けました。
「たとえばJavaは古くて冗長で生産性が低い、しかしgolangは新しいし簡潔だし生産性が高い、こういう印象を持っている人はいるのではないかなと思っています。でも、こういう印象はそれぞれの言語のコインの片側しか見ていないわけです。golangは新興の言語としては異常にシンプルで機能も少ない。特異な思想を実現するために、何を犠牲にしたのか。そのコインの裏側をみなさんに知ってほしい」とセッションの目的を説明しました。
そこで大澤氏が持ち出したのはユニットテスト(単体テスト)です。これを実施する際、重要になるのが依存性の分離で、上位のユニットをテストする際、依存しているユニットは本番のものを使うのではなく、テスト専用の実装、いわゆるモックに置き換えるなどして依存性を分離します。何らかのエラーが生じたとき、その原因がテスト対象にあるのか、それとも依存しているユニットにあるのかの見極めを容易にすることが依存性の分離の目的です。
これを実現するためには、多態性(ポリモーフィズム)、依存性の注入、そしてモック実装の自動生成が必要であると大澤氏は話しました。Java、そしてgolangでこれらを実現する仕組みについて言及し、両者の違いを次のようにまとめました。
「このような依存性の分離はオブジェクト指向の世界で培われてきたプラクティスであり、Javaのようなリッチな言語と同じ方法論をgolangでも適用すると、やはりどうしてもボロが出ます。golangは新しいんだけど機能的には古い言語で、Javaのように歴史もあって機能も豊富でといった言語と同じ方法でやろうとすると、やれることとやるべきではないことの間に隙間が生まれるんです。そのため、設計の制約を理解し、制約に沿ったプログラミングを実践しなければなりません」(大澤氏)
最後に大澤氏が述べたのは、こうした課題をエンジニア自身が理解し、それを乗り越えるための意思を持つことの重要性です。
「golangのよさは機能の少なさみたいなところがポイントで、ただコインの裏には言語機能による設計意図の強制が効かないことがあります。そのため、テスト可能な設計という方法論への理解と、それをちゃんと実践するという意思力がgolangを使う上では求められます」(大澤氏)
今回実施されたオレシカナイト Vol.2では、golangをテーマとしてさまざまな“落とし穴”が語られました。昨今では、こうしたトピックをまとめた記事がインターネットに公開されることも少なくありません。ただ、実際に経験した人たちが目の前で語っているのを聞いたり、直接プレゼンテーションを行ったエンジニアに質問したりすることで、より深く理解することできると改めて実感しました。
またgolangについて、興味は持っているが実際にプロジェクトで使った経験はないというエンジニアも多いでしょう。そうした人たちにとって、パフォーマンス面で躓いた経験を紹介した片田氏と、golangにおけるテストについて突っ込んだ解説をした大澤氏のセッションは大いに参考になったのではないでしょうか。
なおサイバーエージェントでは、こうしたエンジニア向けのイベントを今後も随時実施していくとのことなので、興味があるテーマがあればぜひ参加してみてはいかがでしょうか。