Elasticsearch で Index の再構築を行う場合、ダウンタイムを低減するために Alias 機能を利用するのが常道のようです。

今回 Elasticsearch を Go言語で利用するにあたり、elastic を利用します。elastic は Elasticsearch の クライアントライブラリです。

なお、Elasticsearch は REST API が整備されているので、ライブラリ や SDK がなくとも利用は平易です。JSON の処理を手間に感じるようであれば、ライブラリの利用を検討するとよいと思います。

アイデア概要

Alias を利用するにあたり、Index の命名を以下のようにします。

  • Index名 = Alias名 - タイムスタンプ (などのなにか)

具体的な状態を以下に示します。

{
  "test1-1467527980": {
    "aliases": {
      "test1": {}
    }
  },
  "test2-1467527980": {
    "aliases": {
      "test2": {}
    }
  }
}

今回は簡略化のために UnixTime のタイムスタンプを採用していますが、衝突可能性が気になる場合は UUID v1 などで生成するのが宜しいと思います。

コード例

全ての Index を再構築するコード例は以下のとおりです。Elasticsearch のバージョンは 2.3.3、対応する elastic のバージョンは v3 です。

package main

import (
	"fmt"
	"strings"
	"time"

	"github.com/pkg/errors"
	"gopkg.in/olivere/elastic.v3"
)

// ReBuildElasticsearchIndex は以下の処理を行います
// 1. Reindex
// 2. Alias の付け替え
// 3. 古い Index の削除
func ReBuildElasticsearchIndex() error {

	client, err := elastic.NewClient(
		elastic.SetURL("YOUR_ELASTICSEARCH_HOST"),
		elastic.SetSniff(false),
	)
	if err != nil {
		return errors.Wrap(err, "failed to create Elascitseach client.")
	}

	// Index と Alias の一覧を取得
	aliases, err := client.Aliases().Do()
	if err != nil {
		return errors.Wrap(err, "failed to get aliases.")
	}

	// 処理前の状態の確認
	fmt.Println(aliases)
	// => &{map[test2-1467527586:{[{test2}]} test1-1467527586:{[{test1}]}]}

	for index := range aliases.Indices {
		alias := strings.Split(index, "-")[0]
		newIndex := fmt.Sprintf("%s-%d", alias, time.Now().Unix())

		if _, err := client.Reindex(index, newIndex).Do(); err != nil {
			return errors.Wrap(err, "failed to re-index.")
		}

		if _, err := client.Alias().Remove(index, alias).Add(newIndex, alias).Do(); err != nil {
			return errors.Wrap(err, "failed to replace indices.")
		}

		if _, err := client.DeleteIndex(index).Do(); err != nil {
			return errors.Wrap(err, "failed to delete a index.")
		}
	}

	// 処理後の状態の確認
	afterAliases, err := client.Aliases().Do()
	if err != nil {
		return errors.Wrap(err, "failed to get aliases.")
	}
	fmt.Println(afterAliases)
	// => &{map[test2-1467527599:{[{test2}]} test1-1467527599:{[{test1}]}]}

	return nil
}

func main() {

	if err := ReBuildElasticsearchIndex(); err != nil {
		fmt.Printf("%+v", err)
	}
}

コード中ではカジュアルに古い Index を削除してしまっていますがこれで本当に大丈夫かどうかはわかりません。また Index の一覧を無条件で処理しているので、古い Index の削除に失敗していたりすると問題が起きる可能性があります。開発中で頻繁に Index の再構築を行いたいという用途には充分利用できるコードだと思います。