Taste of Tech Topics

Acroquest Technology株式会社のエンジニアが書く技術ブログ

Elasticsearch勉強会 第14回フィードバック

こんにちは、fujiiです。

第14回elasticsearch勉強会に参加してきました。

f:id:acro-engineer:20160108014007p:plain


途中、電車遅延の影響で、間に合わないかと焦りましたが、
何とかギリギリで到着でした。

冬なのに、汗かきましたよ。。。


さて、本題に戻り、勉強会の内容を紹介しますね。

ここがツラいよElasticsearch

リクルート高林さんによる「ここがツラいよElasticsearch」

リクルートさんが行っているElasticsearchの活用事例と、
実際に困った問題を教えてくれました。

前半は、リクルートさんのElasticsearchの活用事例の紹介でした。

こちらについては、以前、ブログで紹介しているので、
以下を参照ください。

Elastic{ON} Tokyo 2015レポート 〜事例紹介 その1 リクルートテクノロジーズ、Naver #elasticon - Taste of Tech Topics


で、本題のElasticsearchの辛いところですが、

1.バージョンアップが辛い。
2.Riverが非推奨になったのが辛い。
3.フィールドに「.」が使えないのが辛い。
4.Replication-Async非推奨が辛い。
5.ズレが辛い。

ということでした。


すごい勢いでバグFixや機能追加を行っているので、
頻繁に更新される一方、
APIも大幅に変更されて困った、
というバージョンアップの辛さはよく耳にしますね。


「ズレが辛い。」というのは気になるところですが、
クラスタを組んでいる場合に、リロケート、リバランス、レプリカ生成中に、
Primaryに更新が行われた場合に、Documentにズレが発生するというもののようです。

後で詳しい話を@ さんに聞きましたが、
consistencyの設定の関係があるそうです。

クラスタを組んでいる場合、
Elasticsearchのデフォルトでは、ノード数 / 2 + 1 のノードのデータが更新されれば、
更新に成功したとみなす動きをします。

例えば、3つのレプリカを作る場合、2ノードで更新処理が成功した場合に、
更新が成功したという動きです。

で、ネットワークの切断などで、
残りの1ノードの更新が失敗していたらどうなるかというと、
更新が失敗した状態で、動いてしまい、そのままでは復旧しないそうです。

これを防ぐには、consistencyをall にする(Index API)と解消されます。

ただし、すべてのレプリカの更新を待つことになるので、インデックスの更新が遅くなり、検索にも影響が出てきます。
システムの特性に合わせて使う必要がありますね。


リクルートさんでは、レプリカのDocumentのズレを検出したら、
もう一度、過去ログの投入からやり直したそうです。


リクルートさんの苦労が感じられました。

機械学習を利用した、ちょっとリッチな検索

PFN久保田さんによる「機械学習を利用した、ちょっとリッチな検索」


機械学習を使うと、どのようなメリットがあるのか、
機械学習はどのように始めればよいのか、
という内容でした。

今回は、記事の分類などで使われる「文書分類」を基に、
機械学習を使う方法を教えてくれました。

簡単に紹介すると、以下のサイクルを覚えるとよいとのこと。

1.ラベル定義
2.データ収集
3.教師データの作成
4.学習
5.適用

また、機械学習の結果をElasticsearchに投入する方法としては、
以下を利用しているとのことでした。

1.オフラインで適用してからElasticsearchに投入する。
2.Jubatusのプラグインを使う。


初心者向けにかみ砕いて説明してくれたので、
わかりやすかったです。

Lucene Query 再考 - Domain Specific Query 実装 -

最後は、Supershipの大川さんによる「Lucene Query 再考 - Domain Specific Query 実装 -」です。

QueryParserに関する基礎知識と
Supershipさんが取り組んでいる内容を説明してくれました。

Elasticsearch では QueryDSLでは、種々のパーサーを使えるが、
1.近傍検索を行うSpanQueryに対応していない。
2.Term結合やSynonymなどの揺らぎに対応できない。

という課題があり、Query Parserのプラグインを開発したとのことでした。


Qiitaにも関連する内容をあげているそうなので、合わせて参照ください。

Domain Specific Query Parser (Hetero Grammatical Query Parser) 実装 - Qiita



実際にPJで適用したときの苦労話も色々きけて、
勉強になりました。

引き続き、参加したいと思います♪

Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 データ分析で国内に新規市場を生み出す新サービス開発者WANTED! - Acroquest Technology株式会社の新卒・インターンシップ - Wantedly

Beatsのつくりかた - ヒープとGCを取得するJstatbeatsを実装してみた

Hello, world! @ です!
このエントリーはElasitcsearch Advent Calendarの20日目になります。


先日、Elastic{ON} Tour 2015 Tokyoが行われました。

世界各地で行われたイベントの中で最後となる東京では、無料イベントにも関わらず、
セッションは充実しているし、資料が印刷されて、しかも翻訳までされているし、
ランチに人形町今半のお弁当が配られるし
帰るときにもらったノベルティグッズが、なんとElasticロゴ入りモバイルバッテリーだったり、
とんでもないホスピタリティあふれるイベントでした。

Acroquestもelastic社のパートナーとして提携して、今回もデモスポンサーとして参加しましたが
それ以外にも何かイベントの盛り上げをお手伝いができないかなと思い、リアルタイムブログに挑戦してみました。

当日のブログ一覧 : http://acro-engineer.hatenablog.com/search?q=%23elasticon

同僚のサポートもあり、いずれもセッション終了から数分以内にはレポートをあげることができました!


ということでこのAdvent Calendar、元々はElastic{ON}のことを書こうかと思っていたのですが、
既に全部レポートしちゃったので、代わりに、Beatsの作り方について紹介したいと思います。

Beatsのつくりかた

Beatsとは何ぞやというところは、以前にPINOKIOが書いたエントリーを見てください。
acro-engineer.hatenablog.com

Beatsの作り方は、公式ドキュメントのDeveloper Guideにまとまっています。
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html

この手順に沿いつつ、JavaのヒープサイズやGCの回数をjstatコマンドで取得する、
「Jstatbeat」を作ってみたいと思います。


なお、今回作成したコードは、Githubで公開しています。
https://github.com/cero-t/jstatbeat

こんな感じのダッシュボードを作ることができます。
f:id:acro-engineer:20151220045735p:plain
サクッと作れる割に、良い感じです。


ちなみにBeatsシリーズはGo言語で書かれているため、Goの開発環境を事前に作っておく必要があります。
僕はIntelliJ + golang-pluginを使っています。

Goは文法的にも取っつきやすく、チュートリアルも充実しており、習得しやすいと感じています。
クロスプラットフォーム向けコンパイルや、メモリフットプリントの小ささなどメリットも大きいので、
まだGoを学んでない方は、この機会にぜひ習得してみてください。

1. 概要を把握する

まずは簡単にBeatsの実装を知るために、Topbeatのソースコードを確認します。
https://github.com/elastic/beats/tree/master/topbeat

Topbeatの本体ソースコードは、4つしかありません。
各ソースの代表的な部分とともに紹介します。

1. /main.go (https://github.com/elastic/beats/blob/master/topbeat/main.go)
メイン関数です。実質的にはbeatを起動する処理を1行書くだけです。

func main() {
	beat.Run(Name, Version, topbeat.New())
}

2. /beat/config.go (https://github.com/elastic/beats/blob/master/topbeat/beat/config.go)
設定ファイルと1:1のパラメータを持つ構造体です。

type TopConfig struct {
	Period *int64
	Procs  *[]string
	Stats  struct {
		System     *bool `yaml:"system"`
		Proc       *bool `yaml:"process"`
		Filesystem *bool `yaml:"filesystem"`
		CpuPerCore *bool `yaml:"cpu_per_core"`
	}
}

3. /beat/sigar.go (https://github.com/elastic/beats/blob/master/topbeat/beat/sigar.go)
Topの情報を作成するための、gosigarのラッパー関数群です。詳細は割愛します。

4. /beat/topbeat.go (https://github.com/elastic/beats/blob/master/topbeat/beat/topbeat.go)
処理の本体です。一番重要な関数群です。

func (tb *Topbeat) Config(b *beat.Beat) error {
	// 略
}

func (tb *Topbeat) Setup(b *beat.Beat) error {
	// 略
}

func (t *Topbeat) Run(b *beat.Beat) error {
	// 略
}

func (tb *Topbeat) Cleanup(b *beat.Beat) error {
	return nil
}

func (t *Topbeat) Stop() {
	close(t.done)
}

この5つのメソッドを実装することで、beat本体となるBeaterを実装することができます。

参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_developing_the_beat_specific_code


自分でbeatを作る際には、libbeatという共通ライブラリを利用することで実装量を抑えることができるため、
1、2、4に相当するコードだけを書けば良いわけです。

参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_overview


要するに周辺作業はlibbeatがすべてやってくれるので、
個別のbeatはデータを取るところに注力すれば良いわけです。

2. libbeatのソースをgo get

よし作ってみよう、という気になったら、まずはlibbeatの取得をします。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_getting_ready

公式ドキュメントの通りにtopbeatをgo getで取ってくれば、libbeatも一緒に取得できます。

go get github.com/elastic/topbeat

これで開発の準備ができました。

3. 設定ファイルと、設定の構造体を作る

まずは設定ファイルの作成と、その設定を読み込む処理を作成します。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#config-method


Jstatbeatでは「GC情報を取得する間隔(ミリ秒)」と、jpsを実行した時に取れる「プロセス名」の
2つを設定できるようにします。

設定ファイルはtopbeatのものをコピーして、設定部分だけ変えるのが良いでしょう。
https://github.com/elastic/beats/blob/master/topbeat/etc/topbeat.yml

/etc/jstatbeat/jstatbeat.yml

input:
  interval: 5000
  name: "Elasticsearch"

  # 略


そして、この設定ファイルに対応する構造体を作ります。

/beat/config.go

package beat

type JstatConfig struct {
	Interval *string `yaml:"interval"`
	Name     *string `yaml:"name"`
}

type ConfigSettings struct {
	Input JstatConfig
}


続いて、本体となるJstatbeatクラスのうち、設定ファイルを読み込むConfig関数を作成します。

/beat/jstatbeat.go

package beat

// import文は省略

type Jstatbeat struct {
	// from configuration
	interval string
	name     string // (1)

	// state
	pid     string
	isAlive bool
}

func (jsb *Jstatbeat) Config(b *beat.Beat) error { // (2)
	var config ConfigSettings
	err := cfgfile.Read(&config, "") // (3)
	if err != nil {
		logp.Err("Error reading configuration file: %v", err)
		return err
	}

	jsb.name = *config.Input.Name // (4)

	if config.Input.Interval != nil {
		jsb.interval = *config.Input.Interval
	} else {
		jsb.interval = "5000"
	}

	return nil
}


(1) Jstatbeat自身のフィールドとして interval と name を定義しておき
(2) Config関数の中で
(3) 設定ファイルを読み込んで
(4) 読み込んだ値を、フィールドに代入します。

これでConfig関数は完成です。
ね、簡単でしょう?

4. 起動時の処理を作る

続いて、起動時の処理を行うSetup関数を作成します。
起動時の処理では、jpsコマンドを実行して、監視対象となるJavaプロセスのプロセスIDを取得します。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#setup-method


/beat/jstatbeat.go

type Jstatbeat struct {
	// from configuration
	interval string
	name     string

	// state
	pid     string // (1)
	isAlive bool
}

func (jsb *Jstatbeat) Setup(b *beat.Beat) error { // (2)
	cmd := exec.Command("jps") // (3)

	stdout, err := cmd.StdoutPipe()
	if err != nil {
		logp.Err("Error get stdout pipe: %v", err)
		return err
	}

	cmd.Start()

	// TODO: handle error when 'jps' command cannot be executed.

	scanner := bufio.NewScanner(stdout)
	for scanner.Scan() {
		line := scanner.Text()
		items := strings.Split(line, " ")

		if len(items) == 2 && items[1] == jsb.name { // (4)
			jsb.pid = items[0]
			break
		}
	}
	cmd.Wait()

	if len(jsb.pid) == 0 {
		logp.Err("No target process: %v", jsb.name)
		return errors.New("No target process: " + jsb.name)
	}

	return nil
}

(1) Jstatbeatのフィールドとして pid を定義しておき
(2) Setup関数の中で
(3) jpsコマンドを実行して
(4) 設定で指定した name に一致するプロセスの pid を取得します

5. メインの処理を作る。

いよいよ、一番のメイン処理となるRun関数の実装です。
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#run-method


処理が少し長いので、いくつかのブロックに分けて説明します。

/beat/jstatbeat.go

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	jsb.isAlive = true

	cmd := exec.Command("jstat", "-gc", "-t", jsb.pid, jsb.interval) // (1)
	stdout, err := cmd.StdoutPipe()

	if err != nil {
		logp.Err("Error get stdout pipe: %v", err)
		return err
	}

	cmd.Start()
	defer killProcess(cmd) // (2)

	// 略

	return nil
}

func killProcess(cmd *exec.Cmd) {
	if cmd.Process != nil {
		err := cmd.Process.Kill()
		if err != nil {
			logp.Err("Error killing jstat process: %v", err)
		}
	}
}

(1) jstatコマンドに-gcオプションと-tオプションをつけて実行します。
また、pidやintervalは、それぞれSetupとConfigで取得したものを渡します。

(2) コマンドを実行した後は、エラーなどで処理が止まってしまって良いように
deferでプロセス停止の関数を呼ぶようにしておきます。


/beat/jstatbeat.go

var blanks = regexp.MustCompile(`\s+`)

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	jsb.isAlive = true

	// 略

	scanner := bufio.NewScanner(stdout)
	var keys []string
	var version string
	for jsb.isAlive && scanner.Scan() { // (3)
		line := scanner.Text()

		values := blanks.Split(line, -1) // (4)

		if len(values) > 2 && values[0] == "Timestamp" {
			keys = values // (5)

			if strings.Contains(line, "CCSC") { // (6)
				version = "java8"
			} else {
				version = "java5"
			}

			continue
		}

		// 略
	}

	return nil
}

(3) jstatの結果を一行ずつ読み出しながら
(4) 正規表現を使って、空白で値を分割しています。
(5) 1行目(見出し行)の場合は、分割した値をkeysとして保持しておきます。
(6) また念のため、jstatで取れた情報から、JVMJava5〜7なのか、Java8だったのかを判断しています。
この判断結果は、elasticsearchのtypeとして利用します。


/beat/jstatbeat.go

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	// 略

	for jsb.isAlive && scanner.Scan() {
		line := scanner.Text()

		values := blanks.Split(line, -1)

		// 略

		event := common.MapStr{ // (7)
			"@timestamp": common.Time(time.Now()),
			"type":       version,
		}

		for i, key := range keys { // (8)
			if len(key) == 0 {
				continue
			}
			event[key] = toFloat(values[i+1]) // (9)
		}
		b.Events.PublishEvent(event) // (10)
	}

	return nil
}

func toFloat(str string) float64 {
	value, err := strconv.ParseFloat(str, 64)

	if err != nil {
		logp.Err("Cannot parser to float. Ignore this value: %v", err)
		return 0
	}

	return value
}

(7) jstat実行結果の2行目以降に対して、行に対応する内容をMapとして作成し、
(8) すべての項目に対して
(9) 項目 = 値となるように、Mapに格納しています。また値はstringではなくfloat64に変換してから渡しています。
(10) そして、それをlibbeatのPublishEvent関数を使って送信します。


要はRun関数では、Mapを作ってPublishEventする、ということです。

Java5とJava8の両方に対応させるため、若干、処理がややこしくなっていますが、
簡単なbeatを作るだけなら、ホントに十数行で済むんじゃないかと思います。

6. 終了時の処理を作る

残るCleanup関数とStop関数は、まとめて紹介します。
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#cleanup-method
https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#stop-method


Cleanup関数は処理が終わるとき(正常時、異常時とも)の処理を書きますが、
ここでは特にやることがないので、何の処理も書いていません。

Stop関数は、SIGTERMを受信した時の処理を書きます。
ここではフラグを落として、ループが終わるようにしています。

/beat/jstatbeat.go

type Jstatbeat struct {
	// from configuration
	interval string
	name     string

	// state
	pid     string
	isAlive bool // (1)
}

func (jsb *Jstatbeat) Run(b *beat.Beat) error {
	jsb.isAlive = true // (2)

	// 略

	for jsb.isAlive && scanner.Scan() { // (3)

	// 略
}

func (jsb *Jstatbeat) Cleanup(b *beat.Beat) error {
	return nil
}

func (jsb *Jstatbeat) Stop() {
	jsb.isAlive = false // (4)
}

(1) フィールドとして定義した isAlive を
(2) Run関数で処理の前に true にしておき
(3) ループ中は必ず確認するようにして
(4) Stop関数が呼ばれた際に false にします。
こうすることで、SIGTERM受信時には処理が中断できるようになります。

7. main関数の作成

最後にメイン関数の作成です。
Jstatbeatのインスタンスを作成して、それをlibbeatに渡すだけです。
参考: https://www.elastic.co/guide/en/beats/libbeat/current/new-beat.html#_the_main_function


/beat/jstatbeat.go

func New() *Jstatbeat { // (1)
	return &Jstatbeat{}
}

/main.go

package main

import (
	jsatbeat "./beat" // (2)
	"github.com/elastic/libbeat/beat"
)

func main() {
	beat.Run("jstatbeat", "0.1", &jsatbeat.Jstatbeat{}) // (3)
}

(1) go言語お作法に従って、Jstatbeatのインスタンスを作成するNew関数を用意しておきます。
(2) また、beatという名前空間がlibbeat被るため、jstatbeatというエイリアスをつけておき
(3) main関数では、実質1行で処理を書いてしまいます。
このRunの第一引数で渡した"jstatbeat"は、設定ファイル名や、Elasticsearchのindex名などでも
利用するため重要です。index名で使う都合上、名前は英語の小文字だけにしてください。


これで本体ソースコードは完成です。
ほら、簡単でしょう?

8. いざ実行!

main.go、beat/config.go、beat/jstatbeat.goの3つのソースコードと、
jstatbeat.ymlを /etc/jstatbeat/jstatbeat.yml におけば、いよいよ実行です。

go run main.go

これでエラーなく動作するようであれば、Elasticsearchで確認しましょう。

http://(Elasticsearchのアドレス):9200/_cat/indices にアクセスして
jstatbeat-yyyy.MM.dd なindexができていれば成功です。

動作しない場合には、elasticsearchのログか、
Macの場合は /var/log/system.log 辺りを見ると、エラーが出ている可能性があります。


これだけでも動作自体には問題ないのですが、index定義をjsonとして作っておいたほうがより親切です。
topbeatのtemplateを参考にすると良いです。
https://github.com/elastic/beats/blob/master/topbeat/etc/topbeat.template.json

9. ダッシュボード

最後に、kibanaでダッシュボードを作りましょう。
今回は、こんな感じのダッシュボードを作ってみました。

f:id:acro-engineer:20151220045735p:plain

なお手動でダッシュボードを作ったあと、dashboardやvisualizationを
kibana上でexportしておくと、別の環境でもimportすることができます。

なのでexportしたものを配布物と一緒に提供するのが良いかと思います。

公開しました。

ということで、意外とさっくりとbeatを作れることが分かってもらえたかと思います。

今回は簡単にjstatを実行しただけなので、言ってしまえば別に、
jstatをファイルに出力してfilebeatやlogstashで送ってしまえばいいって話になります。

もう少し役立つものにするためには、複数プロセスや複数サーバに対応するとか
あるいはJMX経由でもう少し情報を取ってくるとか、そういうことが必要かと思います。
ただ逆に、そういうものを集める方法を統一するために、Beatsがピタっとハマりそうです。


冒頭にも書きましたが、今回作成したコードは、Githubで公開しています。
https://github.com/cero-t/jstatbeat

また、Windows/Mac/Linuxのそれぞれ向けのバイナリや
ダッシュボードの設定ファイルも含んだファイルをリリースしています。
https://github.com/cero-t/jstatbeat/releases

よかったら参考にしてください。


それでは、
Stay beats, See you!


Acroquest Technologyでは、キャリア採用を行っています。


  • 日頃勉強している成果を、AWSHadoop、Storm、NoSQL、SpringBoot、HTML5/CSS3/JavaScriptといった最新の技術を使ったプロジェクトで発揮したい。
  • 社会貢献性の高いプロジェクトに提案からリリースまで携わりたい。
  • 書籍・雑誌等の執筆や対外的な勉強会の開催を通した技術の発信や、社内勉強会での技術情報共有により、技術的に成長したい。
  • OSSの開発に携わりたい。

 
少しでも上記に興味を持たれた方は、是非以下のページをご覧ください。
 キャリア採用ページ

Elastic{ON} Tokyo 2015レポート 〜Elasticスタックを使用したセキュリティ関連のユースケース #elasticon

本日最後のセッション、
Elasticsearchをセキュリティ用途で使えないかと注目している皆さんにとっては
インパクトある内容だったと思うのが、このセッション。

elasticのNicholas Limさんによる
「Security Use Cases with Elastic Stack」です。

f:id:acro-engineer:20151216170512j:plain

セッション中には、デモを交えた事例が紹介されました。

一つめが、AntiVirusのイベントログを分析することで
ウイルス感染した件数、感染したホスト数、影響を受けたユーザ数などを可視化するものです。
(食らった後の話かー!)

続いて、IDS/IPSのログから、イベントの総数や、攻撃者数、標的数などを可視化するダッシュボード。
また、Firewallのイベントログから、ソースIP、攻撃先IPとポート数などを可視化するダッシュボード。
(いずれも、私たちが作ろうとしているものに近いです、参考になる!)


そしてWatcherを通じて、メールやHipChatに通知を送ることができます。
HipChatとつなぐというアイデアは、星野リゾート様の事例にも出てきましたが
最近はチャットツールAPI経由でのアクセスが当たり前になってきているので
通知先としてもかなり有効です。


またセキュリティログを分析する際、情報ソースとなるFirewallやミドルウェア
プロダクトごとにログ形式は異なっており、自前で解析する必要があります。

そこで推奨されていたのが、標準に則ることです。
STIX/CEF/CIM/LEEFなどのセキュリティロギングの標準に従ってログ出力し
その解析をElasticsearchで行うというものです。


私はまだセキュリティ関係は勉強中なので、改めて復習します!(><)


さー、LTやでー!

Elastic{ON} Tokyo 2015レポート ~ElasticスタックとTipsの紹介 #elasticon

ElasticスタックとTipsの紹介

ここからは、テクニカルセッションです。
まずはelastic大谷さんによる、「ElasticスタックとTipsの紹介」です。

f:id:acro-engineer:20151216163256j:plain

冒頭に「Elasticsearch使ってるひとー」っていうアンケートというか客いじりがあったんですが
8割ぐらいが手を挙げるぐらい、今日はElasticsearchユーザの多いイベントでした。

Elastic{ON}とは言え、すごいですね。

Logstash

まずは、Logstashの紹介です。

セッションでも度々出てきたLogstashですが、
ここではApacheアクセスログの設定を例にして、具体的な設定が紹介されました。

grokでパースして、
dateで日付フォーマットを指定して、
useragentでユーザエージェントを加工して、
geoipで、IPアドレスから緯度と経度を取りました。

Tipsというよりはむしろこれこそが標準的な設定なので、
まずはここから始めましょう!
(個人的には、この機能をElasticsearch側にもつけて欲しいです!)

Elasticsearch

次はElasticsearchのノウハウ。
今回、説明されたのは、以下の4つでした。

1. Index template
2. multi_field
3. dynamic template
4. numeric_detection


Index templateは、要するにRDBMSで言うところのDDLみたいなものです。
スキーマレスなElasticsearchですが、ここは数値にしたいとか、ここはgeoipにしたいとか
また全文検索のような構文解析分かち書き)しなくて良い、という定義をしたくなります。
そのような定義ができるのが、index templateです。
https://www.elastic.co/guide/en/elasticsearch/reference/current/indices-templates.html

multi_fieldは、一つのフィールドに対して、複数のフィールド定義を持たせるものです。
たとえば「国名」というフィールドに対して「分かち書きした国名」と「分かち書きしてない国名」の
両方を定義する、というようなことができます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/_multi_fields.html

ただ、このような定義を、いちいち全部のindexに定義するのは面倒なものです。
そこで使えるのがdynamic templateです。
たとえば「stringはすべて分かち書きしない」とか
「こういう名前に一致するものは全部(日付ではなく)文字列とする」というような定義ができます。
https://www.elastic.co/guide/en/elasticsearch/reference/current/dynamic-templates.html

最後のnumeric_detectionは、文字列ではなく数値として定義するような機能です。
(ドキュメントが、見つからなかった・・・)

Kibana

次はKibanaアプリのSense。

すごく平たく言えば、
Elasticsearch用にカスタマイズされたREST API用クライアントです。

コマンドやindex名、クエリなどの補完が効くので、
ようやくクエリを学び始めた私みたいなエンジニアには便利すぎる機能です。
curlコマンド用の文字列としてコピーする機能もあるので、重宝します。


最後は、visualize-spy。
これ凄く気づきにくい機能なんですが、KibanaのVisualizationって
一番下に ▲ というボタンがあり、これをクリックすると、
Elasticsearchへのリクエスト、レスポンスなどを直接確認することができます。

Kibanaの出力が期待通りにならないから
ちょっと中を見てみたい、という時に使える機能です。
また、Kibanaが発行しているクエリを見て勉強するためにも使えます。

それにしても、気づきにくい機能ですよね。
spyなどと言わず、もっと目立って!


というTipsが多いセッションですが、いかがでしたか?
知らなかった、これ試したい、というものが多ければ私も嬉しいです!

Elastic{ON} Tokyo 2015レポート 〜事例紹介 その3 星野リゾート、ゴールドマンサックス #elasticon

事例紹介その3は、星野リゾートと、ゴールドマンサックスです。
これで事例紹介は最後となります。

星野リゾートにおけるELKを活用した可視化と共有の取り組み

続いては、星野リゾートの久本英司さんによる
星野リゾートにおけるELKを活用した可視化と共有の取り組み」です。

私たちも一緒に取り組みをさせていただいています!(><)


星野リゾートは創業101年のホテル・旅館の運営会社で、
メディアなどに取り上げられる機会も増えて、認知度が非常に高まっています。

一般的に、ホテルや旅館は縦割り組織が多く、
フロントはフロント、清掃は清掃、レストランはレストランという形ですが、
星野リゾートでは、いわゆる「多能工」で、スタッフはマルチタスクで動いています。

そのほかにも、全員で経営判断をできるようにするとか、フラットな組織文化にするなどして、
顧客満足と生産性の両立を目指しています。


特に経営判断に必要なのが、情報共有です。

星野リゾートでは積極的な情報共有を行っていますが、
既存のシステムが古くて使い物にならないものがあったり、
KPIを収集する仕組みが世の中の変化についていけないとか、
あるいはセキュリティ面での課題があり、改善が必要となりました。

改善のアプローチとして、あらゆる経営情報を蓄積しておける場所が必要となり
ここにAcroquestからの提案で、Elasticsearchを利用することにしました。

手書きの絵やマインドマップを使ったビジョン共有を行い、
必要最小限のダッシュボードを作って見せてフィードバックを受け取り、
また改善したものを見せてはディスカッションしてフィードバックを受け取り、
ということを数ヶ月繰り返しました。

そのようなものを利用したところ、

1泊宿泊制限が解除されたタイミングで急に予約数が増えることが分かった
 → 制限が解除されるまで、多少、予約が少なくても大丈夫

とか、
他にも予約が入るタイミングが年々早くなっていること、
会員数が順調に伸びていることなどが、はっきり可視化されました。


将来構想としては、あらゆるデータを投入し、
Timelionや、Sparkとの連動、HipChatなどとの連携を進めたり、
たとえば大量キャンセルなどのタイミングでアラートを上げるなどの
業務に直結する部分のシステム化を進めようとしています。

またIoTの導入による生産性や満足度の向上、
需要予測・稼働予測、稼働調整なども視野に入れています。


実際に動いているものを見たい方は、ぜひAcroquestのブースまで!(>▽<)ノ

Goldman Sachs Engineering

続いては、事例紹介としては最後となります、
Goldman SachsのIan Macleanさんによる「Elastic in Equity Finance」です。

ワークフローやトレーディングオーダーの検索、法規文書やレジュメの全文検索
JVMやリソースなどのメトリクス、アラート、データ解析など
幅広い範囲でElasticsearchを利用しています。

GitHubからElasticsearchのソースをクローンして内部でビルドしており、
独自プラグインを用いて、セキュリティやバックアップの強化を行っているとのことです。
(マジか!)


Elasticsearchのメリットとして、サポートを挙げているところが興味深かったです。
設計レビューやパフォーマンスチューニング、パッチの適用など
サポートで得られるメリットが大きいとのことです。


事例としては、オーダーの検索が説明されました。
オーダー情報はSybaseに保存していましたが、とにかくレコードが多すぎたため
DBをかなり分割しなくてはいけなくなりました。

また検索も時間が掛かり、数時間かかるものがありました。
そのため、意味のある分析をするのが難しかったのです。
(そうですよね、検索が遅いと、分析を試すこともなかなかできない)


そこでElasticsearchを導入しました。
履歴データをElasticsearchに入れ、取引時間中のデータもインデックスしておき、
分析や可視化に利用します。Aggregationを使った高速な分析も行うようにしました。

そして、独自に「Sharp-X」という名の、管理/分析ダッシュボードを作成しました。
オーダーのデータを市場ごとに分析できるダッシュボードです。
履歴データと現在のデータを比較した分析や、取引でエラーが起きてないかどうかの確認などができます。

結果、アーキテクチャソースコードも、かなりシンプルなものになりました。


Elasticsearchは、開発が容易で、スケーラブルな、Game changerです。
今後、RDBMSから、HadoopとElasticsearchを用いたシステムにを置き換えることを計画しています。
(なんか私たちとやりたことが似てる!)


どちらも刺激的なセッションですね。
星野リゾート様のセッションは技術寄りよりも経営寄りの興味深い話でしたし、
Goldman Sachsは力の入れ方に圧倒される一方で、次にやりたいことなどが
非常によく似ているので、ぜひディスカッションさせてもらいたいなと思いました。


さて、事例紹介は以上となり、次はテクニカルセッションです!

Elastic{ON} Tokyo 2015レポート 〜事例紹介 その2 日本経済新聞 #elasticon

記事検索とログ解析でのElasticsearch活用事例

続いては、日本経済新聞社デジタル編成局の梅崎裕利さんによる
「記事検索とログ解析でのElasticsearch活用事例」です。

f:id:acro-engineer:20151216142913j:plain

新聞記事の全文検索とか、なかなか胸が熱くなるタイプのヤツですね。

検索の需要がある中でSolrとElasticsearchの検証を進める中、
気がついたらElasticsearchがメインの仕事になっていたとのこと。
これ、Elasticsearchあるあるですかね。


コンテンツ検索とログ可視化の両方で使えるから、という理由でElasticsearchを採用したそうです。

コンテンツ検索は、200万のデータ(5GB)で、1日数千の更新頻度、リクエストは秒間100回ぐらい。
検索APIは、Django API + Elasticsearchで、マスターはMySQLに別途保存。
本番稼働を始めてから半年以上運用しているようです。

Kuromoji、ICU normalizerの利用、ngramと形態素解析の両方でインデックス作成、
また完全一致用にnot_analyzed(文節や単語で分かち書きしないやつです)も残しています。
(なるほど、検索のために全力だ)

また、新聞記事をスマホで撮ると、関連記事を検索する「もっと日経」というサービスが提供されていまして、
こちらは写真の画像をOCRして、Elasticsearchを用いて検索しています。
OCRの課題はあるそうですが、これは面白いサービスですね。


一方、アクセスログ解析の方は、いわゆるELK/EFK構成(Elasticsearch + Logstash/Fluentd + Kibana)

アクセスログは、1日約3億件(120GB)で、1週間分を保持。
r3.xlargeを6台で運用し、物理メモリ180GB(うちヒープは72GB)という構成。

それで月額20万円程度、もしスポットインスタンスが使えれば月約3万円ぐらいになること。
24時間分のログ集計に、10秒〜1分ぐらいかかるそうです。
検索回数が多くないので、この程度の台数で済んでいるとのこと。
(かなりサイジングの参考になる事例ですやん!)


アクセスログから、HTTPエラーやファイルごとの帯域の可視化、
どの地域からのアクセスが多いかなどを分析しているとのこと。

何よりも、これまでエンジニアが秘伝のソースで解析していたログを
Kibanaで可視化することで、URLや画像で共有できるようになったことが大きな改善ポイント。

また解析に掛かる時間も、当初10分〜1日以上というバッチだったところから
長くとも1分ぐらいになっている点で、フィードバックも早くなりました。

「Kibanaは楽しいから、気づいたら時間がすごい経ってる」というのは、私も共感するところです!


ということで、ここで休憩に入りました。


きゃー、LTの準備が進んでないー!!

Elastic{ON} Tokyo 2015レポート 〜事例紹介 その1 リクルートテクノロジーズ、Naver #elasticon

つづいて、各企業の事例紹介に移ります。
Elastic{ON}は、この東京だけでなく、Elastic{ON} Tourとして世界中をツアーしているのですが、
いずれの地域でも、必ずたっぷり事例紹介が行われています。

リクルート流Elasticsearchの使い方

そんな事例紹介の第一弾は、
株式会社リクルートテクノロジーズの中原裕成さんによる
リクルート流Elasticsearchの使い方」です。


検索基盤や通知基盤でElasticsearchを利用

全社検索基盤「QASS」や、全社プッシュ通知基盤「Pusna-RS」、
またそれらを含む様々なサービスの可視化などでElasticsearchを利用しています。


元々はApache Solrで、各サービスごとに検索エンジンクラスタを組んでいたが、
当時の構成の都合上、検索性能 / 更新性能に限界がありました。

検索品質を向上させるため、全サービス共通の、検索サービスの共通基盤を作ることにして、
その時に盛り上がっていたElasticsearchを採用することにしました。
それで出来たのが全社共通検索基盤「QASS」です。

Spark / Hadoopを用いた分析基盤があるため、これでelasticsearchの検索結果を分析し、
専門チームがelasticsearchの検索結果の継続的品質向上を行っているとのことです。
(何それすごい!)


続いてPusna-RSですが、これはリアルタイムなプッシュ通知を送るシステムです。

データをDynamoDBに保存しているのですが、単純な投入や全件取得は高速なのですが
検索条件を指定した検索は苦手で、そこでelasticsearchを利用することにした。

同じデータを、DynamoDBとelasticsearchの両方に投入し、用途に応じて使い分けている。
(マジか!!)


そして、それぞれのサービスは可視化しており、QASSの検索結果の指標や
プッシュ通知の登録状況や配信状況を可視化しています。

運用ノウハウ

ありがたいことに運用ノウハウの共有もありました。
1. プラグイン機構の利用
2. スナップショットの利用
3. Re-indexの手法


elasticsearchにで提供されているプラグイン機構を用いて、検索を最適化する、
スナップショット機能を利用してS3やHDFS、ディスクなどにスナップショットを残す、
スナップショットのリストアはJenkinsを用いて行うなどしています。

またre-indexですが、考え方的にはブルーグリーンデプロイメントと同様で
クラスタごと再構築を行い、データを流し込んでから、クラスタの切り替えを行っているそうです。


あと、バージョンアップでそれなりに内部構造も変わるので
バージョンアップは計画的に、とのことでした
(分かる!!)

Searching and Alerting for application logs with Elasticsearch at Naver

2社目は、NaverのJaeik Leeさん、Seungjin Leeさんによる
「Searching and Alerting for application logs with Elasticsearch at Naver」です。

最初にLINEを使っている人、という質問がありましたが、ほぼほぼ全員が手を挙げる感じでした。
ちなみにこのブログの写真も、他のメンバーに撮ってもらってLINEで共有しています。


さて、そんなNaver社のElasticsearchの使い方ですが、
元々、エラーログの収集や解析をするシステムを利用していたところ
収集や検索性能に限界を感じて、elasticsearchで再構築をしたとのことです。

スケールとしては、8クラスタ、229ノード、毎日2TBのログが集まっており
現在までに160TBのログが集まっているとのこと。
(半端ないんですけど、何すかそれ?)


Kafka + Kafka Riverを使ってElasticsearchにデータを流し込んでいたところ
パフォーマンス問題や、不安定さ、デバッグの難しさがあったため
Kafka + Stormの構成に変更し、改善させたそうです。

クラスタはマスターノード群、データノード群、クライアントノード群(or ロードバランサ)に分けるほか
直近1週間のデータはSSD、それより古いものはHDDに入れて、保存しているとのことです。


通知の仕組みとして、パーコレーターAPIを用いてこれに合致したものを
Kafkaに送り、そこで判定を行って通知を投げているそうです。
(パーコレーターAPIの一番正しい使い方ですよねこれ)

そしてKafka側で、設定された閾値と時間(たとえば5分間に10回、とか)に従って
ログイベントの通知判定を行って、通知しているとのこと。
通知ルールを変えた際には、Kafkaに通知して、キャッシュをクリアすることにしているとのこと。


2社とも相当なスケールの事例紹介で、少ないノードでなんとか回してる私としては圧倒される感もありますが
リカバリの方法や、通知の方法など、ぜひ取り入れたい学びもありました。

さぁ、続いてのセッションも楽しみですよ!