ちょっと話題の記事

Amazon API Gateway+AWS Lambda+Amazon Elasticsearch Serviceでサジェスト機能を実装する

2016.01.31

この記事は公開されてから1年以上経過しています。情報が古い可能性がありますので、ご注意ください。

はじめに

Elasticsearch検証担当の藤本です。

概要

ElasticsearchはSuggesterというサジェストを実装するための検索APIを提供しています。 検索方法は単純な文字列の一致だけでなく、Elasticsearch(Lucene)が持つ検索エンジンを活用でき、RDMS+ロジックによりゴリゴリ実装せずとも、よりユーザーが望む検索結果、結果順序を提供することができます。

今回はSuggesterの内、Completion Suggestterで実装しましたが、Suggesterは複数あり、更には設定オプションも豊富なため、多くの要件を満たせるでしょう。 ちなみに現在(v2.1)、SuggesterはCompletion suggester含め、4つ提供されています。

環境

今回はAWSのサービスを組み合わせて、サジェスト機能を実装します。

クライアントからElasticsearchに直接クエリを投げて実装することも可能ですが、Elasticsearchを外部公開することはセキュリティリスクや攻撃対象となってしまう恐れがあるため、API Gatewayをエンドポイントとします。 またLambdaで検索APIの加工、結果の加工を実装します。

Untitled(18)

  • Webサーバ:S3静的ホスティング
  • Suggestエンドポイント:Amazon API Gateway
  • ロジック:AWS Lambda
  • 検索エンジン:Amazon Elasticsearch Service

Amazon Elasticsearch Service設定

ドメイン作成

Amazon Elasticsearch Serviceのドメイン作成は[新機能]Amazon Elasticsearch Serviceがリリースされました!を参照。

index作成

indexを作成します。今回、SuggesterはCompletion Suggesterを利用します。Completion Suggesterは前方一致で検索結果を返します。Completion Suggesterを利用する場合、Typeにcompletionを指定します。 テストデータはDevlopers.IOの直近40件のブログタイトルを利用します。 index名はblogs、type名はtype、フィールド名はtitleとします。

curl -XPUT "http://search-**************.es.amazonaws.com/blogs"
curl -XPUT "http://search-**************.es.amazonaws.com/blogs/type/_mapping" -d'
{
  "type": {
    "properties": {
      "title": {
        "type": "completion"
      }
    }
  }
}'

AWS Lambda設定

Lambda Function作成

Lambda Function作成は【新機能】AWS LambdaがPythonに対応しました #reinventを参照。

Amazon Elasticsearch Serviceにアクセスするため、IAM RoleはAmazon ESへの操作権限を与えてください。

ソースコード

実装サンプルです。

import os
from botocore.awsrequest import AWSRequest
from botocore.auth import SigV4Auth
from botocore.endpoint import PreserveAuthSession
from botocore.credentials import Credentials

ES_ENDPOINT = "http://search-*****************.ap-northeast-1.es.amazonaws.com/"
INDEX = "blogs"
FIELD = "title"
SUGGEST_NAME = "comp-suggest"

def lambda_handler(event, context):
    data = '{"%s": {"text": "%s","completion": {"field": "%s"}}}' % (SUGGEST_NAME, event["text"], FIELD)
    
    credentials = Credentials(os.environ["AWS_ACCESS_KEY_ID"], os.environ["AWS_SECRET_ACCESS_KEY"], os.environ["AWS_SESSION_TOKEN"])
    request = AWSRequest(method="GET", url="%s/%s/_suggest" % (ES_ENDPOINT, INDEX), data=data.encode("utf-8"))
    SigV4Auth(credentials, "es", os.environ["AWS_REGION"]).add_auth(request)
    es_response = PreserveAuthSession().send(request.prepare())

    response = []
    for option in es_response.json()[SUGGEST_NAME][0]["options"]:
        response.append(option["text"])

    return response

Amazon API Gateway

API・Resource作成

API、Resource作成はAmazon API Gateway – API作成から動作確認までやってみるを参照。

今回は/のGETのみを実装しています。

Integration typeはLambda Functionを選択し、上記で作成したLambda Functionを指定してください。 またテキストボックスの入力値を受け取るため、ResourceのMappingも忘れずに実装してください。

CORS有効化

今回、HTMLはS3 WEB静的ホスティングエンドポイント、SuggestリクエストはAPI Gatewayエンドポイントとなるため、API GatewayにCORSの有効化を設定してください。 API GatewayのCORS有効化はAmazon API Gateway をクロスオリジンで呼び出す (CORS)を参照。

ResourceのMethodsペインのEnable CORSでもボタン一発でできそうです。

S3設定

HTMLファイル設定

テキストボックスだけあるHTMLファイルをS3に配置します。 SuggestのJavascript実装はjQueryUIのAutocompleteを利用します。

# cat index.html
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <link rel="stylesheet" href="http://code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css" />
  <script src="http://code.jquery.com/jquery-1.10.2.js"></script>
  <script src="http://code.jquery.com/ui/1.11.4/jquery-ui.js"></script>
  <script>
  $(function() {
    $("#suggest").autocomplete({
      source: function( request, response ) {
        $.ajax({
          url: "https://*********.execute-api.ap-northeast-1.amazonaws.com/prod",
          dataType: "json",
          data: {"text":$("#suggest").val()},
          success: function(data) {
            response(data);
          }
        });
      }
    });
  });
  </script>
</head>
<body>
<div class="ui-widget">
  <input type="text" id="suggest">
</div>
</body>
</html>

静的ホスティング、Publishの設定も忘れずに。

動作確認

それではS3のHTMLファイルにアクセスします。

jQuery_UI_Autocomplete_-_Default_functionality

シンプルなUIが表示されました。

文字入力します。

jQuery_UI_Autocomplete_-_Default_functionality

おー、「S」から始まるブログタイトルが表示されました。Frontの実装したことないからちょっと感動w 続けて文字入力を進めます。

jQuery_UI_Autocomplete_-_Default_functionality

候補が絞られました。

もちろん日本語にも対応しています。

jQuery_UI_Autocomplete_-_Default_functionality

まとめ

いかがでしたでしょうか? Elasticsearchは今まで苦労して実装していた検索ロジックをより簡単に、よりクレバーに実装することができます。今回は検索結果をそのまま返す実装となっており、普通に検索APIでよいのでは?という実装になっていますが、Suggesterは検索の入力値と検索結果を異なるフィールドで定義することやサイズ指定を行うことができます。

Elasticsearchは本当に多機能です。ドキュメントの機能を一つ一つ試すには時間がいくらあっても足りません。楽しいです!