零からの

ゆるーく綴るブログ

Elasticsearchでインデックスを作成する

インデックスを作成するにあたり、mappingsやsettingsあたりを一緒に登録する方法。

インデックス作成

curl -H "Content-Type: application/json" -XPUT "http://localhost:9200/sample_index?pretty" -d '
{
  "settings": {
    "number_of_shards": 100
  },
  "mappings": {
    "properties": {
      "sample": { "type": "text" }
    }
  }
}
'

mappings確認

期待通りか確認する。

curl -XGET "http://localhost:9200/sample_index/_mappings?pretty"

{
  "sample_index" : {
    "mappings" : {
      "properties" : {
        "sample" : {
          "type" : "text"
        }
      }
    }
  }
}

大丈夫そう。

settings確認

次にsettingsも。

curl -XGET "http://localhost:9200/sample_index/_settings?pretty"

{
  "sample_index" : {
    "settings" : {
      "index" : {
        "creation_date" : "1595507722172",
        "number_of_shards" : "100",
        "number_of_replicas" : "1",
        "uuid" : "lCuqDV3KQZi_YuwM-mfx4g",
        "version" : {
          "created" : "7080099"
        },
        "provided_name" : "sample_index"
      }
    }
  }
}

こちらも大丈夫そう。

最後に

データを投入するとmappingsは自動で作成されるが、事前に定義することも可能であることがわかった。 またsettingsについても定義できることがわかった。今後kuromojiを使用して形態素解析を行う予定なので、その場合はこちらにkuromojiを設定することになるので事前に素振りできてよかった。

Elasticsearchにエイリアスを設定する

ESのインデックス作成後にマッピングの追加はできるけど変更はできないらしい。

ただ、プロダクトの状況によってはインデックスを変更する機会はあるはず。

その場合にどうしたらいいのかを調べていたらエイリアスというものを知ったのでメモ。

参考

エイリアスを設定する理由

プロダクト(ESを参照するサービス)からESのインデックスを直接参照してもいいけど、インデックスはマッピングを変更することができず再作成となる。その場合、プロダクトはインデックスの参照先を切り替えるリリースが発生するため効率的でない。

エイリアスを作成し、プロダクトがそれを参照することでインデックスのマッピングが変更(インデックス再作成)となったとしても、インデックスに設定したエイリアスを変更するだけで、参照先を変更することが不要となる。

ただし、エイリアス宛にドキュメントは登録できないのでプロダクト側の登録先のインデックスを変更する必要は生じる。

エイリアス作成

わかりやすくbank_aliasとする。

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/_aliases" -d '
{
  "actions" : [
    {
      "add" : {
        "index": "bank",
        "alias": "bank_alias"
      }
    }
  ]
}'

エイリアス確認

curl -XGET "http://localhost:9200/_alias?pretty=true"

インデックスを指定することも可能。

curl -XGET "http://localhost:9200/bank/_alias?pretty=true"

エイリアス変更

新しくインデックスを作成する。

curl -XPUT "http://localhost:9200/bank_v2?pretty"

この時点のエイリアスを確認する。

curl -XGET "http://localhost:9200/_alias?pretty=true"
{
  "bank" : {
    "aliases" : {
      "bank_alias" : { }
    }
  },
  "bank_v2" : {
    "aliases" : { }
  }
}

bank_v2にはエイリアスが設定されていないことが確認できる。

エイリアスを変更する。

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/_aliases" -d '
{
  "actions" : [
    {"remove": {"index": "bank", "alias": "bank_alias" }},
    {"add" :   {"index": "bank_v2", "alias": "bank_alias" }}
  ]
}'

エイリアスが変更されたことが確認できる。

curl -XGET "http://localhost:9200/_alias?pretty=true"
{
  "bank" : {
    "aliases" : { }
  },
  "bank_v2" : {
    "aliases" : {
      "bank_alias" : { }
    }
  }
}

余談

すでに作成済みのエイリアス名でインデックスを作成するとエラーになる。

curl -XPUT "http://localhost:9200/bank_alias?pretty"
{
  "error" : {
    "root_cause" : [
      {
        "type" : "invalid_index_name_exception",
        "reason" : "Invalid index name [bank_alias], already exists as alias",
        "index_uuid" : "_na_",
        "index" : "bank_alias"
      }
    ],
    "type" : "invalid_index_name_exception",
    "reason" : "Invalid index name [bank_alias], already exists as alias",
    "index_uuid" : "_na_",
    "index" : "bank_alias"
  },
  "status" : 400
}

複数のインデックスに同じエイリアスを設定することも可能。

この場合、1つのエイリアスの検索に対し複数のインデックスから合致する内容が取得できる。

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/_aliases" -d '
{
  "actions" : [
    {"add" :   {"index": "bank", "alias": "bank_alias" }}
  ]
}'
curl -XGET "http://localhost:9200/_alias?pretty=true"
{
  "bank" : {
    "aliases" : {
      "bank_alias" : { }
    }
  },
  "bank_v2" : {
    "aliases" : {
      "bank_alias" : { }
    }
  }
}

最後に

エイリアスを理解することで、参照元に影響することなく柔軟にインデックスの見直しを図ることが可能となる。

Getting Started with Elasticsearch

ESを使用することになったので、このあたりを見ながら簡単にメモを残す。

環境構築

Dockerを利用したのでdocker-compose.ymlを以下のように設定した。

version: '3'
services:
  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:7.8.0
    container_name: elasticsearch
    environment:
      - TZ=Asia/Tokyo
      - discovery.type=single-node
    ports:
      - 9200:9200

実行方法は以下。

docker-compose up -d

起動後したか確認する。

curl -XGET "http://localhost:9200/_cat/health?v"

インデックス

ESではインデックスの中にドキュメント(データ)を登録していく。

RDBのテーブル、レコードのようなイメージ

インデックス作成

curl -XPUT "http://localhost:9200/customer?pretty"

登録済みのインデックスを確認する

curl -XGET "http://localhost:9200/_cat/indices?v"

インデックス削除

curl -XDELETE "http://localhost:9200/customer?pretty"

すべてのインデックスを一括で削除することも可能。

curl -XDELETE "http://localhost:9200/*"

ドキュメント

インデックス内に登録するデータをドキュメントと呼ぶ。

ドキュメントの登録

ホスト/インデックス/タイプ の順で指定する。

タイプについては、ES7ではインデックスに複数のタイプが登録できないようなので、_doc固定で良いのかも。

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/customer/_doc?pretty" -d '{"customer": "John Doe"}'

なお、ホスト/インデックス/タイプ/ID の順で指定することで登録することも可能。

この場合は、POSTでなくPUTとなる。

curl -H "Content-Type: application/json" -XPUT "http://localhost:9200/customer/_doc/1?pretty" -d '{"customer": "John Doe"}'

ドキュメント取得

登録時のレスポンスに含まれる"_id"を指定する。

curl -XGET "http://localhost:9200/customer/_doc/<_idの値>?pretty"

ドキュメント削除

登録時のレスポンスに含まれる"_id"を指定する。

curl -XDELETE "http://localhost:9200/customer/_doc/<_idの値>?pretty"

大量データ登録

もちろん、大量データ登録も可能。

こちらからaccounts.jsonのデータをダウンロードして登録する。

curl -H "Content-Type: application/json" -XPOST "http://localhost:9200/bank/_bulk?pretty&refresh" --data-binary "@accounts.json"

検索

ESの醍醐味であろう検索。

Queryを駆使することで柔軟な検索が可能になる。

特定のフィールド検索

スペースで区切ることでOR検索が可能。

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/bank/_search?pretty" -d '
{
  "query": { "match": { "address": "mill lane" } }
}'

複雑な検索

Boolean queryを使用すると複雑な検索も可能。

それぞれがTrueになるドキュメントを取得する。

詳細はこちら

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/bank/_search?pretty" -d '
{
  "query": {
    "bool": {
      "must": [
        { "match": { "age": "40" } }
      ],
      "must_not": [
        { "match": { "state": "ID" } }
      ]
    }
  }
}'

範囲指定はこうする。

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/bank/_search?pretty" -d '
{
  "query": {
    "bool": {
      "must": { "match_all": {} },
      "filter": {
        "range": {
          "balance": {
            "gte": 20000,
            "lte": 30000
          }
        }
      }
    }
  }
}'

集約

Groupingすることも可能。

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/bank/_search?pretty" -d '
{
  "size": 0,
  "aggs": {
    "group_by_state": {
      "terms": {
        "field": "state.keyword"
      }
    }
  }
}'

ソート指定

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/bank/_search?pretty" -d '
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ]
}'

RDBのようにOFFSET, LIMITを指定することも可能。

curl -H "Content-Type: application/json" -XGET "http://localhost:9200/bank/_search?pretty" -d '
{
  "query": { "match_all": {} },
  "sort": [
    { "account_number": "asc" }
  ],
  "from": 10,
  "size": 10
}'

最後に

以上を知っておけばとりあえずESを完全に理解した気になれる。

ここからは先は状況に応じてリファレンスをピックアップしていくのが良さそう。

Ruby on Jets Dockerfile修正

以下の記事でDockerfileをalpineイメージから作成していたがデプロイでapply2files not supportedのようなエラーが発生するためbusterイメージに変更した。 (エラーメッセージは失念…)

FROM node:12.16.0-buster as builder

FROM ruby:2.5-buster

RUN apt-get update && \
    apt-get install -y default-mysql-client rsync zip

RUN useradd -m jets && \
    gpasswd -a jets sudo && \
    echo "jets:jets" | chpasswd

ENV YARN_VERSION=1.22.0
COPY --from=builder /opt/yarn-v$YARN_VERSION /opt/yarn/
COPY --from=builder /usr/local/bin/node /usr/local/bin/
RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn && \
    ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

USER jets
ENV APP_HOME=/app
WORKDIR $APP_HOME

COPY ./Gemfile* $APP_HOME/
RUN bundle install

Ruby on Jets チュートリアル(CRUDの作成)

Ruby on Jets デモプロジェクト作成後、 http://localhost:8888 に表示される手順に沿ってpostsのCRUDを作成する。

jets generate scaffold post title:string
jets db:create db:migrate
jets server
open http://localhost:8888/posts
jets help

MySQLコンテナの作成

MySQLを使用するためdocker-compose.ymlを修正する。

Ruby on Jetsのプロジェクトを作成した際に.env.developmentファイルが作成されるのでそれを読み込むようにする。

version: '3'
services:
  app:
    build:
      context: .
      dockerfile: ./docker/ruby/Dockerfile
    volumes:
      - .:/app
      - bundle-data:/usr/local/bundle
    ports:
      - "8888:8888"
    depends_on:
      - db
    command: bundle exec jets server --port 8888 --host 0.0.0.0
  db:
    image: mysql:5.7
    ports:
      - "3306:3306"
    env_file:
      - .env.development
volumes:
  bundle-data:
    driver: local

.env.developmentに以下を追加する。

MYSQL_RANDOM_ROOT_PASSWORD=yes
MYSQL_DATABASE=jets_db
MYSQL_USER=jets_user
MYSQL_PASSWORD=jets_password
DB_HOST=db # docker-composeで定義したサービス名

config/database.ymlを修正する(database, username, password)。

default: &default
  adapter: mysql2
  encoding: utf8mb4
  pool: <%= ENV["DB_POOL"] || 5  %>
  database: <%= ENV['MYSQL_DATABASE'] || 'app_development' %>
  username: <%= ENV['MYSQL_USER'] || 'root' %>
  password: <%= ENV['MYSQL_PASSWORD'] %>
  host: <%= ENV["DB_HOST"] %>
  url: <%= ENV['DATABASE_URL'] %> # takes higher precedence than other settings
  reconnect: true

development:
  <<: *default
  database: <%= ENV['MYSQL_DATABASE'] || 'app_development' %>

test:
  <<: *default
  database: app_test

production:
  <<: *default
  database: app_production
  url: <%= ENV['DATABASE_URL'] %>

Jets generate and migrate

あとは一番上の手順を参考にコマンドを実行し、http://localhost:8888/posts にアクセスする。

docker-compose run --rm app bundle exec jets generate scaffold post title:string
docker-compose run --rm app bundle exec jets db:migrate # MySQLコンテナ作成時にdatabaseも作成するため、db:createは不要
docker-compose up
open http://localhost:8888/posts
docker-compose exec app bundle exec jets help # コマンド一覧

以下の画面が表示される。

f:id:kzp:20200123001634p:plain
Posts

Ruby on Jets デモプロジェクト作成

Ruby on Jets 環境構築に続き、プロジェクトを作成する。

プロジェクト作成

カレントディレクトリにプロジェクトを作成する。

docker-compose run --rm app bundle exec jets new .

rootでの実行とgitコマンドについてエラーメッセージが出るので、Dockerfileを修正する。

Don't run Bundler as root. Bundler can ask for sudo if it is needed, and installing your bundle as root will break this application for all
non-root users on this machine.
Fetching https://github.com/tongueroo/webpacker.git
sh: git: not found

Retrying `git clone 'https://github.com/tongueroo/webpacker.git' "/usr/local/bundle/cache/bundler/git/webpacker-34bfa510f2df7739e222f010552da83abb125db1" --bare --no-hardlinks --quiet` due to error (2/4): Bundler::Source::Git::GitCommandError Git error: command `git clone 'https://github.com/tongueroo/webpacker.git' "/usr/local/bundle/cache/bundler/git/webpacker-34bfa510f2df7739e222f010552da83abb125db1" --bare --no-hardlinks --quiet` in directory /app has failed.sh: git: not found

Retrying `git clone 'https://github.com/tongueroo/webpacker.git' "/usr/local/bundle/cache/bundler/git/webpacker-34bfa510f2df7739e222f010552da83abb125db1" --bare --no-hardlinks --quiet` due to error (3/4): Bundler::Source::Git::GitCommandError Git error: command `git clone 'https://github.com/tongueroo/webpacker.git' "/usr/local/bundle/cache/bundler/git/webpacker-34bfa510f2df7739e222f010552da83abb125db1" --bare --no-hardlinks --quiet` in directory /app has failed.sh: git: not found

Retrying `git clone 'https://github.com/tongueroo/webpacker.git' "/usr/local/bundle/cache/bundler/git/webpacker-34bfa510f2df7739e222f010552da83abb125db1" --bare --no-hardlinks --quiet` due to error (4/4): Bundler::Source::Git::GitCommandError Git error: command `git clone 'https://github.com/tongueroo/webpacker.git' "/usr/local/bundle/cache/bundler/git/webpacker-34bfa510f2df7739e222f010552da83abb125db1" --bare --no-hardlinks --quiet` in directory /app has failed.sh: git: not found

Git error: command `git clone 'https://github.com/tongueroo/webpacker.git'
"/usr/local/bundle/cache/bundler/git/webpacker-34bfa510f2df7739e222f010552da83abb125db1" --bare --no-hardlinks --quiet` in directory /app has
failed.
FROM ruby:2.5-alpine

RUN apk update && \
    apk add --no-cache build-base libxml2-dev libxslt-dev git

RUN adduser -S jets && \
    echo "jets ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
    echo 'jets:jets' | chpasswd

USER jets
ENV APP_HOME=/app
WORKDIR $APP_HOME

COPY ./Gemfile* $APP_HOME/
RUN bundle install

Dockerfileの修正後、再度ビルドを実行する。

docker-compose build
docker-compose run --rm app git --version
git version 2.24.1
docker-compose run --rm app whoami
jets

カレントディレクトリにプロジェクトを作成する。 README.md、Gemfileを既に作成しているため、forceオプションを指定する。

docker-compose run --rm app bundle exec jets new . --force

MySQLインストールエラー

MySQLに関するエラーが発生した。

Gem::Ext::BuildError: ERROR: Failed to build gem native extension.

    current directory: /usr/local/bundle/gems/mysql2-0.5.3/ext/mysql2
/usr/local/bin/ruby -I /usr/local/lib/ruby/site_ruby/2.5.0 -r ./siteconf20200119-7-19w4mew.rb extconf.rb
checking for rb_absint_size()... yes
checking for rb_absint_singlebit_p()... yes
checking for rb_wait_for_single_fd()... yes
checking for -lmysqlclient... no
-----
mysql client is missing. You may need to 'sudo apt-get install libmariadb-dev', 'sudo apt-get install libmysqlclient-dev' or 'sudo yum install
mysql-devel', and try again.
-----

ライブラリが足りていなかったので追加する。

FROM ruby:2.5-alpine

RUN apk update && \
    apk add --no-cache build-base libxml2-dev libxslt-dev git mariadb-dev

RUN adduser -S jets && \
    echo "jets ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
    echo 'jets:jets' | chpasswd

USER jets
ENV APP_HOME=/app
WORKDIR $APP_HOME

COPY ./Gemfile* $APP_HOME/
RUN bundle install

Dockerfileの修正後、再度ビルドを実行する。

docker-compose build
docker-compose run --rm app bundle exec jets new . --force

Yarnインストール

Yarnのインストールを行う必要がある。

Yarn is not installed or has not been detected. Please double check that yarn has been installed.
To check:

    which yarn

If it is not installed, you can usually install it with:

    npm install -g yarn

Dockerfileを修正する。

FROM node:alpine as builder

FROM ruby:2.5-alpine

RUN apk update && \
    apk add --no-cache build-base libxml2-dev libxslt-dev git mariadb-dev

RUN adduser -S jets && \
    echo "jets ALL=(ALL) NOPASSWD:ALL" >> /etc/sudoers && \
    echo 'jets:jets' | chpasswd

ENV YARN_VERSION=1.21.1
COPY --from=builder /opt/yarn-v$YARN_VERSION /opt/yarn/
COPY --from=builder /usr/local/bin/node /usr/local/bin/
RUN ln -s /opt/yarn/bin/yarn /usr/local/bin/yarn && \
    ln -s /opt/yarn/bin/yarnpkg /usr/local/bin/yarnpkg

USER jets
ENV APP_HOME=/app
WORKDIR $APP_HOME

COPY ./Gemfile* $APP_HOME/
RUN bundle install

Dockerfileの修正後、再度ビルドを実行する。

docker-compose build
docker-compose run --rm app which yarn
docker-compose run --rm app yarn --version

Yarnのインストール確認後、再度プロジェクトを作成する。

docker-compose run --rm app bundle exec jets new . --force

以下のメッセージが表示されたら完了。

================================================================
Congrats 🎉 You have successfully created a Jets project.

Cd into the project directory:
  cd .

To start a server and test locally:
  jets server # localhost:8888 should have the Jets welcome page

Scaffold example:
  jets generate scaffold post title:string body:text published:boolean
  jets db:create db:migrate

To deploy to AWS Lambda, edit your .env.development.remote and add a DATABASE_URL endpoint.
Then run:

  jets deploy

Bundle用ボリューム作成

最後にdocker-compose.ymlを修正する。

  • 毎度bundle installが必要になるのでインストール先専用のボリュームを作成
  • Jets起動コマンドを追加
version: '3'
services:
  app:
    build:
      context: .
      dockerfile: ./docker/ruby/Dockerfile
    volumes:
      - .:/app
      - bundle-data:/usr/local/bundle
    ports:
      - "8888:8888"
    command: bundle exec jets server --port 8888 --host 0.0.0.0
volumes:
  bundle-data:
    driver: local

起動

Dockerを起動する。

docker-compose up

以下の画面が表示される。

f:id:kzp:20200122080450p:plain
Ruby on Jets Welcome