Amazon API Gateway+AWS Lambda+Amazon Elasticsearch Serviceでサジェスト機能を実装する
はじめに
Elasticsearch検証担当の藤本です。
概要
ElasticsearchはSuggesterというサジェストを実装するための検索APIを提供しています。 検索方法は単純な文字列の一致だけでなく、Elasticsearch(Lucene)が持つ検索エンジンを活用でき、RDMS+ロジックによりゴリゴリ実装せずとも、よりユーザーが望む検索結果、結果順序を提供することができます。
今回はSuggesterの内、Completion Suggestterで実装しましたが、Suggesterは複数あり、更には設定オプションも豊富なため、多くの要件を満たせるでしょう。 ちなみに現在(v2.1)、SuggesterはCompletion suggester含め、4つ提供されています。
環境
今回はAWSのサービスを組み合わせて、サジェスト機能を実装します。
クライアントからElasticsearchに直接クエリを投げて実装することも可能ですが、Elasticsearchを外部公開することはセキュリティリスクや攻撃対象となってしまう恐れがあるため、API Gatewayをエンドポイントとします。 またLambdaで検索APIの加工、結果の加工を実装します。
- 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ファイルにアクセスします。
シンプルなUIが表示されました。
文字入力します。
おー、「S」から始まるブログタイトルが表示されました。Frontの実装したことないからちょっと感動w 続けて文字入力を進めます。
候補が絞られました。
もちろん日本語にも対応しています。
まとめ
いかがでしたでしょうか? Elasticsearchは今まで苦労して実装していた検索ロジックをより簡単に、よりクレバーに実装することができます。今回は検索結果をそのまま返す実装となっており、普通に検索APIでよいのでは?という実装になっていますが、Suggesterは検索の入力値と検索結果を異なるフィールドで定義することやサイズ指定を行うことができます。
Elasticsearchは本当に多機能です。ドキュメントの機能を一つ一つ試すには時間がいくらあっても足りません。楽しいです!