先週土曜日に開かれた「Google App Engine ハッカソン」に参加しました。
場所はATNDの開発などを行っているリクルートメディアテクノロジーラボ。こじんまりとしながらもオシャレで快適な空間でした。
・Google App Engineハッカソン(東京) on Zusaar
Google App EngineはHello Worldアプリをデプロイしたことがある程度でほとんど初心者なのですが、Twitter4JのGAE対応最適化を一気にやってしまおうと思い参加しました。
・Togetter – 「Google App Engine ハッカソン #ajnhack & appengine ja night #17 #ajn17」
ベースとなるのは@takawitterコントリビュートのURLFetchServiceを使った非同期通信コンポーネント。Twitter4J自体AsyncTwitterという非同期処理を行うインターフェースが用意されていますが、内部でスレッドを起こします。なのでスレッドの作成が許されていないGoogle App Engineでは使えません。
一方、URLFetchService#fetchAsync()を使うとGAE環境が用意しているスレッドでサーブレットのスレッドとは別に非同期でHTTP通信を行ってくれます。
URLFetchService#fetchAsync()について詳しくは以下のサイトで説明されています。
・非同期URLFetch、FetchAsync()の使い方【Google App Engine】
レスポンスを返すまで30秒という制限があるGAE環境で平行して複数のAPIコールが行えるようになるので便利ですね。
Twitter4JはHTTP通信部分を差し替えるフックポイントが用意されているのですが、APIコールと同時にオブジェクトの初期化を行うため、単にURLFetchService#fetchAsync()を使うだけではだめです。オブジェクト初期化時にHTTP通信のレスポンスを取得してしまい、レスポンスを完全に受け取るまでブロックされてしまい結局同期APIコールになってしまうからです。
当日は遅延初期化を行うラッパを作成することでTwitter4Jの公開インターフェースは変えずに透過的に内部でURLFetchServiceを利用する仕組みの実証コードを作るところまでできました。(それに伴う大幅なリファクタリングも必要でした)
通常ステータスのアップデートには3000ms〜5000msくらいかかるところ、URLFetchService#fetchAsync()を使ったパターンではなんと1〜2msほどで完了するようになりました!(実際の処理は別スレッドで行われているわけですが)
あとは大量のgetterがあるデータクラス群の遅延初期化用ラッパクラスを作ればいいのですが、単調で面倒な作業なのでインターフェースからラッパクラスを生成するスクリプトをゴリゴリ作りました。(bashスクリプトとかsedは結構ニガテ)
・twitter4j-gae/src/test/resources/generate-lazy-object.sh at master from yusuke/twitter4j – GitHub
今回の成果はすでにスナップショットリリースに反映済みです。
関連リンク:
・[#TFJ-610] improve performance on Google App Engine with URLFetchService#fetchAsync() – JIRA