【AWS】はじめてのCloudSearch/ElasticMapReduceでCSV形式に変換したログをCloudSearchで検索する

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

はじめに

こんにちは植木和樹です。今回はCloudSearchを使ったログの検索を試してみたいと思います。

前回のエントリ「はじめてのEMR/fluentdでS3にアップロードしたログをElastic MapReduceで検索・集計する」では、S3に置いたログファイルをElastic MapReduceを使って直接検索しました。ただこの方法だと、検索するたびに毎回重量級サービスのEMRを起動しなければなりませんし、なによりS3のログファイルを直接扱うため遅いです。

そこで方法を少し見直して、EMRでfluentdのログファイルをCSV形式に変換した後、そのデータをCloudSearchに与えることでインデックス化し全文検索ができるようにしてみたいと思います。

CloudSearchってなに?

CloudSearchを一言でいえば「Googleのような全文検索システムを提供してくれるサービス」です。

ドキュメント情報をSDF(Search Data Format)という形式にしてCloudSearchにアップロードすると、そこからインデックスを作成してくれます。「Search Endpoint」というエンドポイント(URL)が提供されるので、そのURLにhttp://search-example-xxxxxxxx.ap-southeast-1.cloudsearch.amazonaws.com?q=foobarというようにクエリーを投げるとfoobarにマッチするドキュメント情報をJSONやXML形式で返してくれます。

この返されたJSON(XML)をパースして検索結果ページを表示することで、サイト独自の検索サービスを提供することができます。CloudSearchはインデックス管理と検索窓口を提供してくれるサービスと考えていいでしょう。

準備するもの

  • 前回のエントリで「archivelog_201308」にてS3に出力したCSVファイル
  • CloudSearchを操作するためのコンソール(Amazon Linux EC2を使用しました)

CloudSearchセットアップ

CloudSearchを使うためにはまず「検索ドメイン」を作成します。検索ドメインごとにエンドポイントが提供され、インデックスの管理が行われます。またエンドポイントへのアクセス制御や、検索結果ランキングに影響する項目の「重み付け(Rank Expressions)」などもドメイン毎に管理されています。

2013年9月12日現在 TokyoリージョンではCloudSearchは提供されていません。そのためシンガポール(ap-southeast-1)リージョンを使用しています。

マネージメントコンソールからCloudSearchの画面を開き[Create Your First Search Domain]をクリックします。

20130912_cloudsearch_001

Search Domain Nameを入力します。今回はclassmethodドメインにしておきます。

20130912_cloudsearch_002

インデックスのフィールドを決めます。サンプルがあれば、そこから自動判定することができるのですが(EMRで処理したため)1行目にヘッダー情報のないCSVの場合は自動判定に失敗するようです。そのためここでは "Manual Configuration" を選び、後ほど設定することにします。

20130912_cloudsearch_003

20130912_cloudsearch_004

アクセス制御を設定します。CloudSearchでは「検索用エンドポイント」と「ドキュメント操作用エンドポイント」の2種類のサービスが提供されています。それぞれ毎にどのIPアドレスからのアクセスを許可するかを設定することができます。今回は "Allow everyone access to all services" をクリックし、どこからでもアクセスできるようにしておきます。

20130912_cloudsearch_005

確認画面が表示されますので[Confirm]をクリックします。

20130912_cloudsearch_006

ドメインの作成処理が開始されます。

20130912_cloudsearch_007

ドメインが作成されました。ただし初期化には30分近くかかります(!)。

20130912_cloudsearch_008

CloudSearchの画面に戻り、作成したドメインのステータスが「LOADING」から「ACTIVE」になれば初期化完了です。

20130912_cloudsearch_009

次にCSVファイルからSDF(Search Data Format)ファイルを作成して、ドメインにドキュメントをアップロードしてみましょう。

CloudSearch API Toolsをインストール

まずはコンソールとなるEC2にコマンドラインツールをインストールします。CloudSearchのapi-toolsはRPMでは提供されていないようです。ネットからアーカイブをダウンロードしてec2-userのホームディレクトリに展開します。

$ curl -O https://s3.amazonaws.com/amazon-cloudsearch-data/cloud-search-tools-1.0.2.3-2013.08.02.tar.gz
$ tar xzf cloud-search-tools-1.0.2.3-2013.08.02.tar.gz

次に環境変数を設定します。なんどもログインし直すなら~/.bash_profileに追記しておきましょう。また上で説明した通りシンガポールリージョンを使っているのでCS_ENDPOINTはap-southeast-1になります。IAM-Roleに対応していないので、CloudSearchをフルコントロールできるIAMユーザーを作成し、アクセスキーとシークレットアクセスキーを設定します。

$ export CS_HOME=~/cloud-search-tools-1.0.2.3-2013.08.02
$ export PATH=$CS_HOME/bin:$PATH
$ export CS_ENDPOINT=cloudsearch.ap-southeast-1.amazonaws.com
$ export AWS_CREDENTIAL_FILE=~/cred.txt
$ cat ~/cred.txt
AWSAccessKeyId=<ACCESS_KEY>
AWSSecretKey=<SECRET_KEY>

SDFファイルの作成とCloudSearchへのアップロード

CloudSearch api-toolsに含まれている cs-generate-sdf コマンドを使うとCSVファイルをSDFファイルに変換してくれます。ただEMRで作成したCSVファイルは1行目にヘッダー文字列がないため、CSV形式として自動判定してくれません。また拡張子がcsvでないとCSVと判断してくれないようです。そこでS3からCSVファイルをダウンロードしてヘッダー行と拡張子をつけてから処理します。ファイルのダウンロードには先日正式リリースされたAWS CLIを使ってみましょう。

$ mkdir csv
$ aws s3 sync --region ap-northeast-1 s3://cm-fluentd-emr/archives/2013/08/ csv
 download: s3://cm-fluentd-emr/archives/2013/08/7662e24f-e26d-4526-ad07-37b87640c75c_000000
   to csv/7662e24f-e26d-4526-ad07-37b87640c75c_000000
$ for file in `ls csv/*`; do
  echo $file;
  echo "version,dt,host,user,method,path,code,size,referer,agent" > $file.csv
  cat $file >> $file.csv
  rm $file
done
$ ls csv/
7662e24f-e26d-4526-ad07-37b87640c75c_000000.csv

それではcs-generate-sdfコマンドでSDFファイルを作成しましょう。

$ mkdir sdf
$ cs-generate-sdf --source ./csv/*.csv --output ./sdf --exclude-metadata
Processing /home/ec2-user/./csv/7662e24f-e26d-4526-ad07-37b87640c75c_000000.csv
(Using field 'version' as version というメッセージが続く)

$ ls sdf/
1.sdf

$ head -20 sdf/1.sdf

SDFはJSON形式になっています。

[ {
  "type" : "add",
  "id" : "d__home_ec2_user___csv_7662e24f_e26d_4526_ad07_37b87640c75c_000000_csv_1",
  "version" : 1378951384,
  "lang" : "it",
  "fields" : {
    "dt" : "2013-08-28T07:16:41+00:00",
    "host" : "59.xxx.xxx.xxx",
    "path" : "/",
    "method" : "GET",
    "referer" : "-",
    "code" : "403",
    "agent" : "Mozilla/5.0 (Macintosh; Intel Mac OS X 10_8_4) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/28.0.1500.71 Safari/537.36",
    "user" : "-",
    "size" : "3839"
  }
}, {
  "type" : "add",
  "id" : "d__home_ec2_user___csv_7662e24f_e26d_4526_ad07_37b87640c75c_000000_csv_2",
  "version" : 1378951385,

JSON形式のファイル1.sdfが作成されました。fieldsにApacheログの各フィールドが含まれていることが分かります。

さてこのSDFファイルをclassmethodドメインにアップロードすることでインデックスを作成したいのですが、ドメイン作成時にどのフィールドをインデックスにするかの指定をスキップしていました。そこでcs-configure-from-sdfコマンドを使ってインデックス情報を適当に設定してみましょう。

$ cs-configure-from-sdf --domain-name classmethod --source ./sdf/1.sdf
Connecting to CloudSearch in region [ap-southeast-1]
Detected source format for ./sdf/1.sdf:json
Analyzing ./sdf/1.sdf
-----------------------------------------------------------------------------------
Existing field configuration for the domain - classmethod :
-----------------------------------------------------------------------------------
Detected field configurations from all the sources :
dt                                                             text (Result)
host                                                        literal (Search Facet)
path                                                           text (Result)
method                                                      literal (Search Facet)
referer                                                        text (Result)
code                                                           uint ()
agent                                                       literal (Search Facet)
user                                                        literal (Search Facet)
size                                                           text (Result)
-----------------------------------------------------------------------------------
New proposed field configuration for the domain - classmethod :
dt                                                             text (Result)  [NEW]
host                                                        literal (Search Facet)  [NEW]
path                                                           text (Result)  [NEW]
method                                                      literal (Search Facet)  [NEW]
code                                                           uint ()  [NEW]
referer                                                        text (Result)  [NEW]
user                                                        literal (Search Facet)  [NEW]
agent                                                       literal (Search Facet)  [NEW]
size                                                           text (Result)  [NEW]
-----------------------------------------------------------------------------------
Configure [classmethod] with analyzed fields y/N: y
Configuring fields
Defined 9 field configurations.

各フィールドをどのように扱うかが右端のカッコで示されています。

  • Search ... 検索対象となるフィールド
  • Result ... 検索結果に表示するフィールド
  • Facet ... 検索結果をさらに絞り込み検索する際のフィールド

インデックスのタイプには3種類があり、それぞれ特徴があります。

タイプ 設定値 Search Facet Result 備考
text 文字列 常に対象 検索フィールドを選択しなかった場合に常に検索対象になる
ResultまたはFacetどちらかに指定できる
literal 完全一致文字列 FacetまたResultどちらかに指定できる
uint 正の整数値 常に対象 常に対象 常に対象 重み付け(Rank Expressions)に使用できる

詳しくはドキュメントを参照してください(Amazon CloudSearch Developer Guide)。上記の例だとhostmethoduseragentも検索フィールドに含め、dtpathreferersizeが検索結果に表示されます。(Agentはliteralでなくtextが適当だと思うのですが・・・)

それではSDFをアップロードしてインデックスの作成を行いましょう。アップロードにはcs-post-sdfコマンドを用います。またアップロードしただけではインデックス処理が実行されませんのでcs-index-documentsコマンドを使ってインデックス処理をキックしてあげましょう。

$ cs-post-sdf --domain-name classmethod --source ./sdf/1.sdf
Connecting to CloudSearch in region [ap-southeast-1]
Processing: ./sdf/1.sdf
Detected source format for ./sdf/1.sdf as json
Status: success
Added: 13
Deleted: 0

$ cs-index-documents --domain-name classmethod

インデックス化処理には非常に時間がかかります(20分以上)。マネージメントコンソールでドメインのステータスが"PROCESSING"から"ACTIVE"になるのを待ちましょう。

20130912_cloudsearch_101

検索ドメインのステータスが"ACTIVE"になったら準備完了です。適当な文字列で検索してみましょう。ここでは検索フィールドを指定していないので、すべてのtextフィールドが検索対象になります。

20130912_cloudsearch_102

pathに"aaaa"が含まれているログ行がマッチしたため検索結果に表示されています。

20130912_cloudsearch_103

おまけ

cs-generate-sdf --helpで出力される--sourceオプションの使い方には「 Accepts Apache-ant style wildcards such as */** for files and S3 prefixes.」とあるので、当初S3のとあるフォルダ以下のファイルすべてを対象にする際に、次のように指定したのですがドキュメントがマッチしませんでした。

$ cs-generate-sdf --source s3://cm-fluentd-test/2013/*/** --output ./output

*/**の構文はローカルディレクトリのファイルを指定するときのみ使えるようです。S3の場合は単純に上位フォルダを指定すれば、下位フォルダのファイルはすべて対象になります。

$ cs-generate-sdf --source s3://cm-fluentd-test/2013/ --output ./output

まとめ

今回はCloudSearchを使ってログファイルの全文検索を行ってみました。最初はインデックスタイプやSDFのフォーマットが分からずとまどいましたが、コツをつかむと高速で柔軟な検索が作れて楽しいです。

しかしSDFを作るためには一旦ログファイルをローカルに保存して、いくつかの前処理をする必要があります。ログファイルが大量になると、ファイルをバッチ処理するところで一工夫が必要になりそうです。

今回のCloudSearchで取り上げなかった「ドキュメントのバージョニング」や「検索フィールドの重み付け」「日本語検索」をもう少し詰めれば、日本語サイトでも独自の検索システムが実用的かつ手頃に構築できそうです。特にバージョニングについてはバッチ処理でインデックスを更新する上で避けられない部分なので、引き続き調査を行いたいと思います。