Webサービスにおける外部APIの使用(その1) - キャッシュ

kashima
2012/02/03 14:30

ここ最近のWebサービスでは、Googleを始めとする他のサービスが提供するAPIを使用して、そのデータを活用するというのが当たり前になってきており、また、そのためのライブラリ等の基盤も整っています。今回はそうしたライブラリの使い方からちょっとだけ先に進んで、外部APIのアクセス数制限を回避するためのキャッシュについて説明します。

はじめに

はじめまして。Retty株式会社の鹿島と申します。今回からこのblogに記事を書かせていただくことになりました。今後、読者の皆さんのご意見なども取り入れつつ、何か役に立つような内容を書いていければと思っていますので、よろしくお願いします。

この記事の内容ですが、我々が開発しているRettyというWebサービスでの実例を通じて、教科書にはあまり載っていないtips、落とし穴等を紹介したいと思います。対象読者として以下のような方を想定しています。

  • Webサービス開発に興味がある人
  • これからはじめようと思っている人(比較的初心者の方)

具体的には以下のような経験があることを前提としています。

  • (言語を問わず)プログラミングの経験
  • Facebook等のWebサービスの使用経験

このサイトに来ている方の大多数は、この前提をクリアしているだろうと思います。

Rettyのサービス概要

Rettyのサービスページ

Rettyのサービスページ

まずは、我々が開発しているRettyというサービスについて少し紹介させて下さい。Rettyは「行ったお店を共有する、記録するソーシャルグルメサイト」です。具体的な機能は以下のようになります。

  • 行ったお店のレビューを投稿する
  • 「友達」や「嗜好の合う人」をフォローして、その人達の投稿をタイムライン(TL)上で閲覧する
  • 行きたい店があった場合には、行きたいリストに追加する
  • スマートフォンで、現在地付近の人気のお店、行きたいリストに追加したお店を検索する

サービスはWeb、iPhone、Androidのマルチプラットフォームで展開していますが、以下のように使い分けている方が多いようです。

  • TLの閲覧やお店の行きたいリストへの追加 → Web
  • 現在地付近のお店検索 → スマートフォン
  • 投稿 → Web、スマートフォン両方

Webサービス開発において特に必要なこと(技術者の視点から)

今回は最初なので、まず「Webサービス開発で特に必要なこと」について技術者の視点から考えてみたいと思います。

まず、これはスタートアップ企業全般に言えることですが、人的リソースがとにかく足りません。Rettyは3人(+サポートメンバー)の技術者で開発をしています。比較的恵まれているようにも思えますが、Web、iPhone、Androidと3つのプラットフォームを同時に展開しているので、実際にはあまり余裕がありません。

また、Webサービスの世界は動きが速いため、競合サービスが数多く登場します。競合するサービスに負けないため、現在のシステムの良いところ、悪いところを的確に把握し、素早く改善する必要があります。受託開発や業務系システムと大きく異なるのがこの点だと思います。

このような状況下で重要なことは、以下のような点だと考えています。

  1. ユーザー行動の把握

    サイトの素早い改善のためには「ユーザーがどのような行動をしたのか」、もう少し具体的に書くと「どの機能がよく使われているか・使われていないか」「どの機能がユーザーのサイト利用を促進しているか」というような事柄について把握しておく必要があります。これ以外にも、ユーザーからフィードバックを得るチャネルを数多く用意しておくことも大切だと感じています。

  2. 重要な機能の開発に注力

    ユーザーが増えてくるとさまざまな要望をいただきます。また内部でも「こんな機能を追加してはどうか」というアイディアが数多く出てきます。そんな中で自分達の方向性に合致する重要な機能だけを選んで実装し、時にはそれまでに実装したものを捨てる勇気が必要になります。

  3. 外部サービス・APIの活用

    リソースが限られており、自分達だけでできない部分も多いので、前述の通り必要に応じて外部サービス・APIを使用します。とはいえ、コア機能は外部サービスに頼らないで自分達で作る必要もあります。

  4. フレームワーク等の活用

    これは一般的なことなので、ここでは詳しく書きません。

  5. 短いリリースサイクル

    この点に関してもあまり異論はないかと思います。Rettyでは大きめのアップデートは1~2週間に1度、小さな修正は随時実施しています。

今回はこれらのうち「外部APIの活用」について書いていきます。

外部APIの活用

ここ最近のほとんどのWebサービスが何らかの形で外部のAPIを使用しています。大雑把に分類したものが以下の表1です。ここに書いた以外にも多数のサービスがAPIを提供しており、有名なサービスであればほとんど全てが何らかの形でAPIを公開しているのではないでしょうか。

表1:主なWebサービスが外部に提供しているAPI

  主な機能 主なサービス提供者
SNS系 認証(OAuth) ソーシャルグラフの取得 (フォロー、フレンドの関係) ユーザーコンテンツの作成、取得 (日記、写真など) Facebook、Twitter、 mixi、foursquare
EC系 商品情報の検索、アフィリエイト Amazon、 楽天
情報提供、検索系 データの検索、データの表示 Google、Yahoo、はてな
ユーザーコンテンツ系 データの登録(写真、ドキュメント) データの取得、表示 Google Docs、Flickr、 instagram、evernote

以下、Rettyで使っているものを中心にいくつか紹介していきます。

Facebook

最近のWebサービスでは、サーバー側にユーザー情報を直接持たず、OAuthという仕組みを使用してFacebookやTwitterなどのアカウントと連動することで認証を行うタイプのサービスが数多くあります。思いつくままにいくつか挙げると、foursquare、Yelp、Wondershake、ソーシャルランチなどがあります。 認証以外でのFacebookの主な使用法としては、

  • ユーザーの基本情報(氏名、プロフィール文章等)の取得
  • ウォールへの書き込み
  • メッセージ送信

などが挙げられます。基本的には、ユーザーがFacebook上でできることは、ほとんどがアプリケーションからも実行可能です。詳細はFacebook APIのドキュメントを参照して下さい。

RettyではFacebook APIを用いて、OAuthによる認証、ユーザー情報の取得、フレンドの取得、ウォールへの書き込み等を行っています。

Twitter

FacebookではなくTwitterと連動するWebサービスも数多く存在します。Twitter APIでできることはFacebookとほぼ同様で、以下のものです。

  • 認証(OAuth)
  • ユーザーの基本情報、フォロー、フォロワーの取得
  • ツイート

Twitterと連動しているサービスには、例えばATNDやLivlisなどがあります。Rettyでは、Facebookと同様にTwitterも使用しています。

Google Maps

詳しい説明は必要ないと思いますが、Google Maps APIは非常に多くのサービスで使用されています。Rettyではお店の位置を表示するために使用しています。

Amazon

Amazonでは書籍をはじめとする多種多様な商品を扱っています。商品情報DBの検索やアフィリエイトなどにAPIを使用できます。

foursquare

最近では位置情報を使用した新しいサービスが増えていますが、foursquareのAPIを使用しているサービスもそれに伴い増えています。4SQ App Galleryでそれらを見ることができます。

Rettyではお店情報の検索にfousquareのAPIを使用しています。

外部APIとただ連携するだけであれば、最近では情報・ライブラリが豊富に揃ってきているのでそれほど難しくありません。ただ、書籍やWebサイトなどであまり触れられていない問題もいくつかあります。今回はそのひとつとしてrate limitsについて書いていきます。

rate limitsとは

各種サービスのAPIドキュメントを読んだことがある方はご存知かと思いますが、各APIによって投げられるリクエストの数に制限があります。その制限を超えると

  • APIが一時的に使用不可となる
  • (頻繁にlimitを超えると)以後のアクセスが拒否される

など、サービス継続にとって致命的な事態となります。

rate limits対策

サービス継続にとって致命的なrate limit超過を避けるには何か対策を施す必要がありますが、Rettyでは主に以下の2つの対策をとっています。

  • キャッシュ(cache)
  • クライアントからのAPI呼び出し

今回はこのうち「キャッシュ」に焦点を当ててお話していきます。

外部APIにおけるキャッシュ使用とは以下のようなことを指します。

  • 外部APIを呼び出して返って来た結果をシステム内部のキャッシュに保存
  • 「同じリクエスト」があった場合は外部APIを再度呼び出さずに内部のキャッシュに保存した結果を返す

基本的にはリクエストに対するレスポンスをキャッシュに保存しておき、「同じリクエスト」が来た場合にはキャッシュに保存したデータを返します。

キャッシュを使用するメリットとしては以下の2つです。

  • 外部API呼び出し回数の低減
  • レスポンスタイムの低減(外部API呼び出しにかかる時間 > キャッシュからの読み出し時間)

今回はRettyにおけるfoursquare検索結果のキャッシュを例に、説明していきます。

キャッシュの設計

保存場所、実装方式の選択

キャッシュしたデータを保存する場所・バックエンドとしては以下のようなものが挙げられます。

  • メモリ
  • ファイル
  • リモートのサーバー

また、キャッシュの実装方式としても以下のようなものが挙げられます。

  • 自作ライブラリ(単純な例では連想配列を使用)
  • ライブラリ(各種バックエンドを意識せずに扱える)
  • DBサーバー等の汎用データストア
  • memcached等のキャッシュ専用サーバー

これらの選択の基準としては色々ありますが、代表的なものとして以下を挙げておきます。

  1. 機能
  2. 性能
  3. ライブラリ・モジュールが簡単に使用可能か(インストールが容易か、プログラムが楽か)
  4. サーバーが簡単に使用可能か(パッケージが用意されている、インストール済み等)

Rettyではこのうち3、4を重視し、現在は以下の2つをキャッシュに使用しています。

  • Zend FrameworkのZend Cacheモジュール+APC(Alternative PHP Cache)
  • MySQL

今回、例に挙げているfoursquareの結果のキャッシュでは後者を使用しています。APCに関しては次回以降で書こうと思います。

キャッシュのキーと内容

次にキャッシュを検索する際のキーと、キャッシュする内容について説明します。先ほど「『同じリクエスト』があった場合は外部APIを再度呼び出さずに内部のキャッシュに保存した結果を返す」と書きましたが、何をキーにして「同じリクエスト」と判定すれば良いのでしょうか。 まずはキャッシュするデータに関して考えてみます。Rettyでは以下のような場面でfoursquare APIを使用しています。

  • スマートフォン → 現在地周辺のお店検索
  • Web → エリアとキーワードによるお店検索

foursquareに投げるリクエストとしてはそれぞれ以下のようになります。

  • スマートフォン
    • 現在地の情報(lat、lng)[1]
    • キーワード(オプション)
  • Web
    • エリア・駅の位置情報(lat、lng)
    • キーワード
[1]lat=latitude=緯度、lng=longitude=経度

以下の擬似コードのように、lat、lng、キーワードの3つの組み合わせをキャッシュのキーとしてその結果を返す方法が考えつきます。

$key = "$lat:$lng:$keyword";
//キャッシュの読み出し
if (($result = getMyCache($key)) === false) {
  //キャッシュなしの場合・・・
  //foursquareのAPIを呼び出して
  $result = callFoursquareAPI($lat, $lng, $keyword);
  //結果をキャッシュに保存
  saveCache($key, $result);
}
return $result;
キャッシュの有無をチェックする際ある程度の幅を持たせて検索する

キャッシュの有無をチェックする

この方法で単純に実装してしまうと、スマートフォンの場合に期待したようには上手く動作しません。なぜかと言うと、全く同じ位置(=同じlat、lng)からお店検索が呼び出されることはほとんどないため、キャッシュにヒットすることもほとんどないからです。

そのため、キャッシュの有無をチェックする際にlat、lngにある程度の幅を持たせて検索する必要があります。例えば、100m以内で実行した検索結果があればそれを返す、といった感じです。

またキーワードに関しては、それを単純にキーにしてしまうと「焼肉」と「焼き肉」が違うキャッシュになってしまうという問題があります、Rettyでは、全文検索エンジンであるSolrと組み合わせてこの辺を解決していますが、これに関してはここでは触れません。

キャッシュする内容

キャッシュする内容には2通りが考えられます。

  1. APIから返ってきたJSONをそのまま
  2. JSONをパースして、お店情報を1件ずつ個別に

用途にもよりますが、今回は前者を採用します。理由は、現在使用している情報(店名、位置情報など)以外の情報(例えばチェックイン数)が必要となった時に対応しやすいからです。

設計のまとめ

  1. キャッシュのキーはlat、lng、キーワード
  2. lat、lngで近くのデータがキャッシュにあればそれを使用する
  3. キャッシュにはfoursquareから返ってきたJSONをそのまま保存する

これらの要件のうち、特に2番を実現するためには、機能として位置情報での検索ができるシステムが必要です。幸いにもMySQLにはSpatial Extensionsという機能がありますので、次回にこれを用いた実装例について説明します。

まとめ

今回は以下について説明しました。

  • 私が開発に携わっているRettyというWebサービスの内容、およびWebサービス開発上で必要と思われる事項
  • 最近のWebサービスで使われている主な外部APIについて
  • 外部APIのアクセス数制限の超過を避けるためのキャッシュの設計

次回はMySQLSpatial Extensionsを使用して、位置情報を検索する方法について説明します。

Bookfair

O'Reilly Japanのセレクションフェア、全国の書店さんで開催
[ブックフェアのページへ]

Feedback

皆さんのご意見をお聞かせください。ご購入いただいた書籍やオライリー・ジャパンへのご感想やご意見、ご提案などをお聞かせください。より良い書籍づくりやサービス改良のための参考にさせていただきます。
[feedbackページへ]