こんにちは。
ビジネスソリューション開発グループ チャネルソリューションチームの長谷川と申します。
弊社では一部の処理や業務効率化にGo言語を使用していますので、今回はぐるなびにおけるGo言語導入の経緯や活用例をお話しします。
ビジネスソリューション開発グループについて
Go言語の話をはじめる前に、私の所属するビジネスソリューション開発グループについて簡単に説明させていただきます。
ビジネスソリューション開発グループのミッションは、ぐるなびのビッグデータの利活用。グループは分析を主業務とするデータソリューションチームと、分析されたデータを使い実際にサービスを開発・運用するチャネルソリューションチームの2チームで構成されます。
データソリューションチームは分析やバッチ処理の比重が高いためPython、R、Perlなどが使われ、チャネルソリューションチームではPHPが最も多く使われています。
このようにほとんどのメンバーがGo言語の経験がなかったのですが、そのような中でなぜGo言語を選択したのかをプロダクトごとに説明させていただきます。
Go言語を使用したプロダクト
現在、チャネルソリューションチームでGo言語を使用しているプロダクトは以下となります。
- 属性バナー
- ビッグデータ関連商品効果レポート用バッチ処理
- 非開発現場向け業務効率化ツール開発
それぞれのプロジェクト概要とGo言語を採用した理由を述べさせていただきます。
1. 属性バナー
Go言語を使用した最初のプロジェクトです。属性バナーとは、ビッグデータを用いてユーザの嗜好に合った飲食店の広告を表示するバナー商品を指します。
ぐるなびのサイトを閲覧いただいた際に、下記のようなバナー広告が表示されることがあります。
このバナーを表示するためのシステムを属性バナーシステムといい、アプリケーションサーバをGo言語で実装しました。
属性バナーの仕組み
ぐるなびのビッグデータはHadoopで管理され、分析チームはHadoop上の各種データを分析・解析し「新宿エリアでよく焼き肉を食べる」「梅田で串揚げをよく食べる」などのぐるなび固有の属性を会員一人ひとりに付与します。
回遊ログから属性を付与
分析された属性情報はユーザIDとマッピングされた状態でCassandraに保存され、ユーザのページアクセスごとにバナーサーバへの問い合わせを行います。バナーサーバはユーザ情報とページのコンテンツ情報をもとにCassandraで属性情報を検索、マッチした広告を表示します。
このようにユーザごとに最も最適な広告を表示させることで、純広告などのバナー商品に比べて高い広告効果が期待できます。
開発言語の選択について
バナーはメインコンテンツの一部を占有して表示する広告商品のため、それが原因で主コンテンツの表示速度が遅れると、コンテンツのUXを阻害してしまいます。
ユーザが不満を感じないように、バナーサーバは常に高速にレスポンスを返さなければなりません。
ぐるなびは月間6,000万UUアクセス以上の高いトラフィックがありますので、高アクセス時でも高速処理ができるようなバナーサーバが必要となります。
この話が来たタイミングがPixivさんが下記の記事を発表された直後ということもあり、「処理速度が速い」といわれるGo言語での開発を希望しました。
ピクシブ社内広告サーバーでのGoの開発・運用 #gocon /p_ads_server_gocon2015 // Speaker Deck
検証
開発当時、社内でGo言語の経験があるエンジニアがほとんどいなかったため、メンテナンス性や人材確保の観点からもPHPでの開発も検討しておりました。
そこで実際の処理を簡略化させたプログラムをPHPとGo言語の2パターン用意し、検証を実施することにしました。
- PHPはAPC=ONに、接続ライブラリにmemcacheを使用
- DBへのアクセスをボトルネックとしないため、広告情報をあらかじめmemcacheサーバに載せておき、広告情報を取得するように設計
- Cassandraはデータの分析が間に合わなかったため、検証環境ではスタブとした
- WebサーバはApacheを使用
- PHPはmod_php.soを、Go言語はアプリケーションサーバとして起動させ、リバースプロキシとして立てたApacheとソケット接続
- 検証にはJmeter使用
検証では、あらかじめ1万パターンのURLを用意しておき、Jmeterの同時接続数を10,20,30と徐々に増やしつつ、用意したURLへ順番にアクセスを繰り返して試験を実施したところ、下記のような結果になりました。
同一の条件でテストした結果、この案件ではGo言語のプログラムがPHPに比べて1.5倍ほど高速に稼働することが分かりましたので、Go言語の採用を決めました。
リリース後
属性バナーのリリースから2年近く経過しておりますが、特に大きな障害もなく安定して稼働しています。
また属性バナーのパフォーマンスが良かったこともあって、属性バナー以外の一部バナーについてもPHPからGo言語へ移行を開始しました。
既存のバナーサーバはファイルアクセスが大量に発生する作りになっていました。Go言語への切り替えと同時にファイルアクセスをなくし、キャッシュをmemcacheサーバに変更したことでパフォーマンスを劇的に改善することに成功しました。
下記は切替時のバナーサーバの負荷状況です。
Go言語に切り替えた8月下旬を境にロードアベレージが格段に下がっています。
同じタイミングでトラフィックは上がっていますので、処理が軽くなった結果より大量のアクセスを捌けていることがわかります。
その後も継続的に監視していますが、以前のように高ロードアベレージのまま推移するような状態は発生しておりません。
※10月頭に瞬間的にロードアベレージが上がってますが、これはキャッシュの全更新を行ったためです。現在はキャッシュをパージしても影響ないように修正済みです
2. ビッグデータ関連商品効果レポート用バッチ処理
こちらはGo言語を導入した2つめのプロダクトとなります。
ビジネスソリューション開発グループでは、先ほどご紹介した属性バナーの他に外部DSPを使用したバナー配信や、ユーザ属性を利用したメルマガの配信などを行っています。
こちらの効果測定は別のチームが実施しているのですが、社内外の複数サービスに分散されたデータを取りまとめる必要があり、そのデータ集約作業がコストになっていました。
そこで、データを一元化して可視化するための社内ツールとして、データ集約のバッチプログラムをGo言語で開発しました。
Go言語を選んだのは並列処理が容易に書けるという理由からでした。
先に述べたように、効果を測るためのデータは社内DBの他に外部DSPから提供されたAPIを実行する必要があります。
APIのコール回数は受注件数に応じて増減するのですが、多い時期ともなると1回のレポート作成で100回以上実行することがあり、そのまま直列で実行すると1時間くらいかかってしまいます。
この問題を解決すべく、Go言語のチャネルを使ってAPIのコールを並列化することにしました。
ただ単純に並列化するだけではAPI側の負荷が上がりますので、チャネルを使い並列処理の上限数を設定しています。
並列数上限の設定例
var sem = make(chan int, semMax) //上限数を設定 wg.Add(1) go func(period Period) { sem <- 1 // チャネルに値をセット。semのバッファサイズがsemMaxに達していた場合、値はセットされず待ち状態となる。 defer func() { <-sem // 処理が完了し、deferが呼ばれた時点でチャネルが解放され、上段のsem <- 1以降がが実行される。 wg.Done() }() }(val) wg.Wait()
現在はsemMax=5で並列実行していますが、処理時間は10分近くまで短縮できました。
3. 業務効率化
3つめは正式なプロダクトではないのですが、簡単な営業やサポート・運用などの業務を支援するようなツールをGo言語で作って配布しています。
Go言語の特徴としてシングルバイナリとマルチプラットフォーム対応があります。
単一のソースを簡単にWindows環境用にビルドできるため、単純な用途のツールであれば簡単に作ることができます。
一例を挙げると、ぐるなびコンテンツ内で特定の商品を目立たせるために個別のclassを適用するような場合があるのですが、それまではそれらがルール通り適用されているかどうかを目視でチェックしていました。
技術者であればSeleniumなどを使って機械的に確認することが出来ますが、非エンジニアの方にSeleniumの使い方をレクチャーするにも時間がかかってしまいます。
そこでこの作業を機械的に実施できるようgoqueryを使ったスクレイピングツールを作成し、利用していただくことにしました。
ツールとしてはとても単純ですが、Go言語ですとこういったツール類を簡単に作ることができます。
他にもあるルールに沿ったURLを大量に生成するツールや、フォルダのバックアップなどの簡易なツールもGo言語で作成して配布しています。
まとめ
このように、サービスからツールまで多岐に渡って開発できるのがGo言語の魅力だと思います。
ぐるなびでのGo言語活用はまだ多くないですが、これからもGo言語を使ったサービスを開発してゆきたいと思います。
お借りした素材:
The Go gopher was designed by Renee French.This gopher created by Takuya Ueda. Licensed under the Creative Commons 3.0 Attributions license.