ようへいの日々精進XP

よかろうもん

ゴールデンウィークスペシャル:Elixir チュートリアル (2) 〜 JSON パース編〜

気になっていた Elixir という言語について、以下の資料を写経して引き続きチュートリアルしてみた。

www.slideshare.net

チュートリアル

チュートリアル環境

  • OS は Ubuntu Xenial でお届けします
ubuntu@ubuntu-xenial:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
  • Elixir は 1.4.1 でお届けします
ubuntu@ubuntu-xenial:~$ elixir --version
Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Elixir 1.4.1

JSON パースに必要なライブラリの導入

導入するライブラリ

以下のライブラリを導入する。

ライブラリ名 Github リポジトリ 用途
HTTPoison https://github.com/edgurgel/httpoison HTTP クライアント
Poison https://github.com/devinus/poison JSON パーサー

mix.exs を修正

sample/mix.exs を以下のように修正する。

--- mix.exs.original    2017-05-05 02:36:31.000000000 +0000
+++ mix.exs     2017-05-05 02:37:13.000000000 +0000
@@ -28,6 +28,9 @@
   #
   # Type "mix help deps" for more examples and options
   defp deps do
-    []
+    [
+      { :httpoison, "~> 0.7.2" },
+      { :poison, "~> 1.5" }
+    ]
   end
 end

Ruby だと Gemfile みたいなものですな、きっと。

以下を実行して各モジュールを取得する。

ubuntu@ubuntu-xenial:~$ mix deps.get

以下のように出力される。

Running dependency resolution...
Dependency resolution completed:
  hackney 1.3.2
  httpoison 0.7.5
  idna 1.0.3
  poison 1.5.2
  ssl_verify_hostname 1.0.6
* Getting httpoison (Hex package)
  Checking package (https://repo.hex.pm/tarballs/httpoison-0.7.5.tar)
  Using locally cached package
* Getting poison (Hex package)
  Checking package (https://repo.hex.pm/tarballs/poison-1.5.2.tar)
  Using locally cached package
* Getting hackney (Hex package)
  Checking package (https://repo.hex.pm/tarballs/hackney-1.3.2.tar)
  Using locally cached package
* Getting idna (Hex package)
  Checking package (https://repo.hex.pm/tarballs/idna-1.0.3.tar)
  Using locally cached package
* Getting ssl_verify_hostname (Hex package)
  Checking package (https://repo.hex.pm/tarballs/ssl_verify_hostname-1.0.6.tar)
  Using locally cached package

Ruby だと bundle install みたいなものですが、きっと。

Qiita API を呼び出す

Qiita API を呼び出すコードを書く

以下のように get() 関数を追加する。

defmodule Sample do
  ...
  def get() do
    HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir")
  end
end

Sample 関数を recompile() した後に Sample 関数を実行すると以下のように Qiita API から Elixr 関連記事を取得する。

iex(5)> Sample.get
...

{"X-Request-Id", "850ad9f5-9b62-4a65-8ffb-d1f37ffb511d"},
{"X-Runtime", "0.432123"}, {"X-XSS-Protection", "1; mode=block"},
{"transfer-encoding", "chunked"}, {"Connection", "keep-alive"}],
status_code: 200}
iex(6)>

おお、簡単ばい。

Body だけを出力したい

以下のように get() 関数を修正して Body だけを抜き出す。

defmodule Sample do
  def get() do
    response = HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir")
    body = body( response )
    Poison.decode!( body )
  end
  def body( %{ status_code: 200, body: json_body } ), do: json_body
end

改めて recompile() を実行して Sample 関数を実行すると Qiita API レスポンスから Body のみを取得出来る。

iex(17)> Sample.get
...

"user" => %{"description" => "", "facebook_id" => "", "followees_count" => 0,
  "followers_count" => 1, "github_login_name" => nil, "id" => "mdmom",
  "items_count" => 5, "linkedin_id" => "", "location" => "", "name" => "",
  "organization" => "", "permanent_id" => 152421,
  "profile_image_url" => "https://avatars.githubusercontent.com/u/18415389?v=3",
  "twitter_screen_name" => nil, "website_url" => ""}}]

おお。いい感じ。

ちなみに

status_code200 にマッチしたレスポンスを処理しているが、もし status_code200 以外の場合を考慮した場合にはどのように書けば良いのか。

defmodule Sample do
  def get() do
    response = HTTPoison.get!("https://qiita.com/api/v3/items?query=Elixir")
    if response.status_code == 200 do
      body = body( response )
      Poison.decode!( body )
    else
      response.status_code
    end
  end
  def body( %{ status_code: 200, body: json_body } ), do: json_body
end

こんな感じかな…おお、Ruby となんか似てる。こちらによると、cond do ~ という書き方もあるらしい。

defmodule Sample do
  def get() do
    response = HTTPoison.get!("https://qiita.com/api/v3/items?query=Elixir")
    cond do
      response.status_code == 200 ->
        body = body( response )
        Poison.decode!( body )
      response.status_code != 200 ->
        response.status_code
    end
  end
  def body( %{ status_code: 200, body: json_body } ), do: json_body
end

パイプ演算子

変数の受け渡しがちょっと煩わしい時がある

現状は以下のように Qiita からデータを取得して Body を抽出し JSON をデコードするという流れを変数で受け渡しを行っている。

defmodule Sample do
  def get() do
    response = HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir")
    body = body( response )
    Poison.decode!( body )
  end
  def body( %{ status_code: 200, body: json_body } ), do: json_body
end

まあ、別にコレでもいいかなーと思うけど。

パイプ演算子が便利

Elixir ではパイプ演算子を使えばスマートに書くことが出来る。

defmodule Sample do
  def get() do
    HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir")
    |> body
    |> Poison.decode!
  end
  def body( %{ status_code: 200, body: json_body } ), do: json_body
end

|> は前の処理の出力を次の処理の第一引数として渡すことが出来る。引数が複数ある場合には、第二引数を以降を () で括って指定が可能。

タイトルのみを抽出する

Qiita API のレスポンス Body

レスポンスを解析すると以下のような内容となっている。

[
%{ "body" => "body1", ..., "title" => "titile1" },
%{ "body" => "body2", ..., "title" => "titile2" },
%{ "body" => "body3", ..., "title" => "titile3" },
]

記事のタイトルのみを取得する

以下のように (1) 〜 (6) を追加する。

defmodule Sample do
  def get() do
    HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir")
    |> body
    |> Poison.decode!
    |> title_list( [] )                          # 追加 1
  end
  def body( %{ status_code: 200, body: json_body } ), do: json_body
  def title_list( [ head | tail ], titles ) do   # 追加 2
    %{ "title" => json_title } = head            # 追加 3
    added_titles = [ json_title ] ++ titles      # 追加 4
    title_list( tail, added_titles )             # 追加 5
  end
  def title_list( [], titles ), do: titles       # 追加 6
end

以下のような処理が追加されたことになる。

  1. パイプ演算子として [] は空のリストを引数として title_list() 関数を呼び出す
  2. JSON 解析した Body リストの先頭を head それ以降を tail で処理出来るようにする
  3. head で取得した Body リストから title キーをパターンマッチで抽出して json_title に保持する
  4. json_title[] で囲むことでリスト化し、titles に連結して added_titles に保持する
  5. tail に保持しているデータに対して 2 〜 4 の処理を繰り返し呼び出す
  6. tail が空になった際に呼ばれて、titles を出力する

recompile() を実行した後に関数を実行すると以下のように出力される。

iex(45)> Sample.get
["Amazon EC2上にRed Hatインスタンスを作成してElixir Reportをコンソールインストールする手順",
 "Mastodon を Node.js で遊んでみる", "CowboyのHelloWorldまで",
...
 "Elixirで弱々しいAI#3を作る「MeCab辞書差し替え |> CaboChaモジュールの作成」"]

おお。

例のごとく、Ruby で書くと以下のように書けると思う。

irb(main):001:0> require 'net/http'
=> true
irb(main):002:0> require 'json'
=> true
irb(main):003:0> res = Net::HTTP.get(URI.parse('https://qiita.com/api/v2/items?query=Elixir'))
=> ...
...
irb(main):004:0> titles = []
=> []
irb(main):005:0> JSON.parse(res).each { |body| titles << body['title'] }
irb(main):006:0> titles.reverse
=> ["Amazon EC2上にRed Hatインスタンスを作成してElixir Reportをコンソールインストールする手順", "Mastodon を Node.js で遊んでみる", "CowboyのHelloWorldまで", "[翻訳] Elixirでスモールデータを扱う", "Elixir製のプロジェクトをTravisCIで運用するための.travis.yml", "Amazon EC2上のRed Hat Enterprise LinuxにX11転送でElixir ReportをGUIインストールする手順", "Amazon EC2上のRed Hat Enterprise Linuxにリモートデスクトップ接続してElixir ReportをGUIインストールする手順", "Ectoでバリデーション", "【elixir】slackbotをつくり定期的にコメントさせる", "Laravel5系blade中でassetファイルのlastModified追加", "Ectoでカスタムバリデーションを追加する", "Google Compute EngineのRed Hat VMインスタンス作成と、X11転送でGUIインストーラをWindowsから操作する手順 - Elixir Report", "PhoenixでPlugを使用しアクションの前にフィルターを実装する", "Goの構造体にメ タ情報を付与するタグの基本", "Google Compute EngineでのRed Hat Enterprise Linux VMインスタンス作成と、Elixir Reportのコマンドインストール手順", "ElixirでSleep Sort書いてみた", "Elixirで弱々しいAI#1を作る「MeCabで文章パース」", "Elixirのソースコードにバイナリを埋めこむ方法とそのバイナリ表現の生成方法", "Elixirで弱々しいAI#2を作る「構文解釈してオウム返し(ついでにPhoenixでWebアプリ化)」", "Elixirで弱々しいAI#3を作る「MeCab辞書差し替え |> CaboChaモジュールの作成」"]

ということで

sample.ex

これまでのチュートリアルで作成した sample.ex は以下のようになった。

defmodule Sample do
  # def sort( values ) do
  #   Enum.sort( values )
  # end
  def sort( values ), do: Enum.sort( values )
  def match( %{ Yes: "we can" } ), do: "Barack Obama"
  def match( %{ Yes: need } ), do: need
  def match(_), do: "Yes...Not EXIST"
  def match_inner( input_map ) do
    %{ Yes: need } = input_map
    need
  end
  # def nums( [ head | tail ], rows ), do: nums( tail, [ head | rows ] )
  # def nums( [], rows ), do: rows
  def nums( [ head | tail ], rows ) do #...1
    nums( tail, [ head | rows ] )      #...2
  end
  def nums( [], rows ) do              #...3
    rows                               #...4
  end

  def sum_list( [ head | tail ], total ) do
    sum_list( tail, head + total )
  end
  def sum_list( [], total ) do
    total
  end

  def get() do
    HTTPoison.get!("https://qiita.com/api/v2/items?query=Elixir")
    |> body
    |> Poison.decode!
    |> title_list( [] )
  end
  def body( %{ status_code: 200, body: json_body } ), do: json_body
  def title_list( [ head | tail ], titles ) do
    %{ "title" => json_title } = head
    added_titles = [ json_title ] ++ titles
    title_list( tail, added_titles )
  end
  def title_list( [], titles ), do: titles
end

パイプ演算子

って便利だなと思ったけど、エラーハンドリングの方法が見つけられずに悩んでいるところ。

参考

2017 年 05 月 05 日(金)

佐世保

佐世保のゆーちゃんおじさん宅に。その前に道の駅させぼっくす 99 でりょーちゃんおばさんともお茶をしばく。

おばさん、おじさんともにすごく歓迎して下さって嬉しかった。

夜はおじさん宅で呑みながらワイワイ楽しい時間を過ごすことが出来て感謝。

バカという言葉ほど…

おじさんの息子(まさひろ)が酔っ払って言っていた言葉。忘れないうちにつぶやいておいた。

なるほどなって思った。

ゴールデンウィークスペシャル:Elixir チュートリアル (1) 〜パターンマッチ編〜

以前から

気になっていた Elixir という言語について、以下の資料を写経してチュートリアルしてみた。

www.slideshare.net

その前に Elixir (エリクサー) とは

そこには Erlang が…

  • Erlang の関数を呼び出せるため、Erlang のシステムにシームレスに統合出来る
  • Erlang 良い部分(並行性や信頼、耐障害性)をそのまま利用出来る(当然 Erlang の悪い部分も受け継いでしまっているらしい)

特徴

その他色々… こちらが参考になる。

エコシステム

  • ライブラリ管理に hex を利用する… Ruby だと gem
  • ビルドツールに mix を利用する… Ruby だと rake
  • 対話型シェルに iex を利用する… Ruby だと irb
  • Web Application Framework として Phoenix がメジャー… Ruby だと Rails

で、なんであんたは Elixir をやろうとしたの?

  • Ruby と似た文法ということで、以前から気になっていた
  • ギョームでたまに Erlang というワードが耳に入ってくる

チュートリアル

チュートリアル環境

  • OS は Ubuntu Xenial でお届けします
ubuntu@ubuntu-xenial:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"
  • Elixir は 1.4.1 でお届けします
ubuntu@ubuntu-xenial:~$ elixir --version
Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Elixir 1.4.1

Elixir のインストール

以下を実行して Erlang と Elixir をインストールする。

wget https://packages.erlang-solutions.com/erlang-solutions_1.0_all.deb && sudo dpkg -i erlang-solutions_1.0_all.deb
sudo apt-get update
sudo apt-get install esl-erlang
sudo apt-get install elixir

以下を実行して iex を起動することを確認する。

ubuntu@ubuntu-xenial:~$ iex
Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help)

対話シェルで操作してみる

対話シェル iex を起動して加算や list や map の操作を行ってみる。

iex(1)> 1+2
3
iex(2)> list = [ 123, "abc", 456, true ]
[123, "abc", 456, true]
iex(3)> Enum.sort( list )
[123, 456, true, "abc"]
iex(4)> map = %{ "key2" => "abc", "key1" => 123 }
%{"key1" => 123, "key2" => "abc"}
iex(5)> Enum.sort( map )
[{"key1", 123}, {"key2", "abc"}]
iex(6)>
BREAK: (a)bort (c)ontinue (p)roc info (i)nfo (l)oaded
       (v)ersion (k)ill (D)b-tables (d)istribution

list や map の操作に Enum モジュールを利用している。Enum モジュール には eachempty? 等の関数が用意されていて、さながら Ruby と見紛う。

関数を定義する

新規プロジェクトの作成

新規プロジェクトを以下のように作成する。

mix new sample

以下のように出力される。

* creating README.md
* creating .gitignore
* creating mix.exs
* creating config
* creating config/config.exs
* creating lib
* creating lib/sample.ex
* creating test
* creating test/test_helper.exs
* creating test/sample_test.exs

Your Mix project was created successfully.
You can use "mix" to compile it, test it, and more:

    cd sample
    mix test

Run "mix help" for more commands.

関数の作成

lib/sample.ex を以下のように記述する。

defmodule Sample do
  def sort( values ) do
    Enum.sort( values )
  end
end

関数のビルドと実行

記述したら、以下のコマンドを実行してビルドを行う。

cd sample
iex -S mix

iex が起動するので、以下のように作成した Sample 関数を実行する。

ubuntu@ubuntu-xenial:/vagrant/elixir/sample$ iex -S mix
Erlang/OTP 19 [erts-8.3] [source-d5c06c6] [64-bit] [smp:2:2] [async-threads:10] [hipe] [kernel-poll:false]

Interactive Elixir (1.4.1) - press Ctrl+C to exit (type h() ENTER for help)
iex(1)> Sample.sort( [ 5, 1, 3 ])
[1, 3, 5]
iex(2)> Sample.sort [ 5, 1, 3 ] #=> () は省略可能
[1, 3, 5]

おお。

関数の修正と再ビルド

関数は end を省略して 1 行でも記載することが出来る。

defmodule Sample do
  def sort( values ), do: Enum.sort( values )
end

関数を修正したら recompile() を実行して再ビルドする。

iex(3)> recompile()
Compiling 1 file (.ex)
:ok

改めて Sample 関数を実行する。

iex(4)> Sample.sort [ 5, 1, 3 ]
[1, 3, 5]
iex(5)> Sample.sort %{ "key2" => "abc", "key1" => 123 }
[{"key1", 123}, {"key2", "abc"}]

map もキー名でソートすることが出来る。

パターンマッチ

引数でのマッチ

Sample 関数に以下を追加する。

def match( %{ Yes: need } ), do: need

追加したら recompile() する。

iex(7)> recompile()
Compiling 1 file (.ex)
:ok

以下のように Sample 関数を実行する。

iex(8)> Sample.match %{ No: "-", Yes: "we can", NA: "N/A" }
"we can"

マップの Yes キーの値を関数の引数だけで実装出来る。ちなみに、Ruby でやろうとすると、以下のような書き方になるんだろうか。

irb(main):001:0> def match(args = {})
irb(main):002:1> p args[:Yes]
irb(main):003:1> end
=> :match
irb(main):004:0> match({ No: "-", Yes: "we can", NA: "N/A" })
"we can"
=> "we can"

あー、改めて、関数の引数だけで値が取り出せるのは便利かも。

複数関数の呼び分け

値でもマッチさせることも可能なので、以下を追加して recompile() する。

def match( %{ Yes: "we can" } ), do: "Barack Obama" # 追加する
def match( %{ Yes: need } ), do: need

Sample 関数を実行してみる。

iex(2)> Sample.match %{ No: "-", Yes: "we can", NA: "N/A" }
"Barack Obama"

ほー。マップのキーをチェックして処理を分岐することが出来ている。これも Ruby でやろうとすると以下のような書き方になるんだろうか。

irb(main):001:0> def match(args = {})
irb(main):002:1> if args[:Yes] == "we can"
irb(main):003:2> p "Barack Obama"
irb(main):004:2> end
irb(main):005:1> end
=> :match
irb(main):006:0> match({ No: "-", Yes: "we can", NA: "N/A" })
"Barack Obama"
=> "Barack Obama"

マッチしない場合

キーが存在しない場合には、以下のようなエラーとなる。

iex(4)> Sample.match %{ No: "-", NA: "N/A" }
** (FunctionClauseError) no function clause matching in Sample.match/1
    (sample) lib/sample.ex:24: Sample.match(%{NA: "N/A", No: "-"})

以下のように関数の引数に _ を指定すると、その他としてマッチ出来るようになる。

def match( %{ Yes: "we can" } ), do: "Barack Obama"
def match( %{ Yes: need } ), do: need
def match(_), do: "Yes...Not EXIST" # 追加する

recompile() して関数を再実行する。

iex(5)> recompile()
Compiling 1 file (.ex)
:ok
iex(6)> Sample.match %{ No: "-", NA: "N/A" }
"Yes...Not EXIST"

例の如く、Ruby で書くと以下のようになるんだろうか。

irb(main):010:0> def match(args = {})
irb(main):011:1> p "Yes...Not EXIST" unless args.has_key?(:Yes)
irb(main):012:1> end
=> :match
irb(main):013:0> match({ No: "-", NA: "N/A" })
"Yes...Not EXIST"
=> "Yes...Not EXIST"

関数内でマッチ

関数の引数では無く、関数内でマッチさせる場合には以下のように書く。

defmodule Sample do
  def match_inner( input_map ) do
    %{ Yes: need } = input_map
    need
  end
end

以下のように引数マッチと同じ結果となる。

iex(10)> Sample.match_inner %{ No: "-", Yes: "we can", NA: "N/A" }
"we can"

引数マッチ版の方がシンプルな気がする。

リストのパターンマッチ

リストは先頭を head で、以降を tail でパターンマッチすることが出来る。

iex(1)> [ head | tail ] = [ 5, 8, 3, 1 ]
[5, 8, 3, 1]
iex(2)> head
5
iex(3)> tail
[8, 3, 1]

わかり易い言葉で書くと head はリストの最初の要素を取り出すことが出来て、tail はその残りを取り出すことが出来る。

以下のようにも書くことが出来るようだ。

iex(10)> list = [1,2,3]
[1, 2, 3]
iex(11)> hd(list)
1
iex(12)> tl(list)
[2, 3]

Ruby だと以下のように書くんだろうか。

irb(main):014:0> list = [ 5, 8, 3, 1 ]
=> [5, 8, 3, 1]
irb(main):015:0> list.first
=> 5
irb(main):016:0> list[-3..-1]
=> [8, 3, 1]

個人的には Elixir も Ruby も直感的な気がする。各コード量的には微々たるものだけど Elixir の方が少ないかもしれない。

リストのパターンマッチ(2)

headtail を利用することで、リストの巡回(再帰呼び出し)が可能となる。

defmodule Sample do
  def nums( [ head | tail ], rows ) do #...1
    nums( tail, [ head | rows ] )      #...2
  end
  def nums( [], rows ) do              #...3
    rows                               #...4
  end
end

関数を実行すると以下のように出力される。

iex(6)> Sample.nums( [ 5, 8, 3, 1 ], [] )
[1, 3, 8, 5]

少し理解に苦しんだので、以下のように整理した。

  1. リストの先頭を head で、残りを tail でマッチさせる
  2. tail に入っている残りの値の先頭を head でマッチさせる
  3. 2 の tail が空になったら 3. が呼ばれで 4. の rows が出力される

正直言って、まだボヤーっとしているので、headtail はもう少し弄る。

ちなみに、Ruby だと以下のように書くのかな…

irb(main):006:0> list = [5, 8, 3, 1]
=> [5, 8, 3, 1]
irb(main):007:0> new_list = []
=> []
irb(main):008:0> list.each { |l| new_list << l }
=> [5, 8, 3, 1]
irb(main):009:0> new_list.reverse
=> [1, 3, 8, 5]

リストのパターンマッチ(2) + 1

もすこし再帰呼び出しをやりたいので、list に格納されている数値の合計を求めてみる。

defmodule Sample do
  def sum_list( [ head | tail ], total ) do #...1
    sum_list( tail, head + total )          #...2
  end
  def sum_list( [], total ) do              #...3
    total                                   #...4
  end
end

おお、なんか理解が出来てきたけど整理。

  1. リストの先頭を head で、残りを tail でマッチさせる(初期値は 0)
  2. 1 の tailheadtotal に格納されている数値を加算(tail が空になるまで繰り返す)
  3. 2 の tail が空になったら 3. が呼ばれて 4. の total が出力される

Ruby だと inject メソッドを使って以下のように書くはず。

irb(main):010:0> list = [5, 8, 3, 1]
irb(main):011:0> list.inject(0) { | sum, l | sum + l }
=> 17

おお、シンプル。

ということで

sample.ex

今回のチュートリアルで作成した sample.ex は以下のようになった。

defmodule Sample do
  # def sort( values ) do
  #   Enum.sort( values )
  # end
  def sort( values ), do: Enum.sort( values )
  def match( %{ Yes: "we can" } ), do: "Barack Obama"
  def match( %{ Yes: need } ), do: need
  def match(_), do: "Yes...Not EXIST"
  def match_inner( input_map ) do
    %{ Yes: need } = input_map
    need
  end
  # def nums( [ head | tail ], rows ), do: nums( tail, [ head | rows ] )
  # def nums( [], rows ), do: rows
  def nums( [ head | tail ], rows ) do #...1
    nums( tail, [ head | rows ] )      #...2
  end
  def nums( [], rows ) do              #...3
    rows                               #...4
  end

  def sum_list( [ head | tail ], total ) do
    sum_list( tail, head + total )
  end
  def sum_list( [], total ) do
    total
  end
end

Elixir とは (再掲)

引き続き…

参考にさせて頂いた資料はまだまだ続くので、引き続き写経を続けていくぞー。

参考

2017 年 05 月 04 日(木)

ジョギング

日課

  • (腕立て x 30 + 腹筋 x 30) x 3

読書

Ruby スクリプティングテクニック を読み進めている。訳が少し複雑な感じで読みづらい部分があるが、少し修正すれば使えそうなツールを題材にしていたり、Test::Unit の話しが出てきたりして面白くなってきた。

ゴールデンウィークスペシャル:小ネタ道場一本勝負 〜 川原洋平探検隊が Elasticsearch の奥地に住まう猫に遭遇する! 〜

この記事は

川口浩探検隊へのオマージュです。

www.amazon.co.jp

cat API と何か!?(効果音:ジャーン)

www.elastic.co

cat API は Elasticsaerch のノードやインデックスの各種情報を取得する事が出来る便利 API で、Elasticsearch のトラブルシューティングやリソースの検討等に有用である!(効果音:ジャーン)ちなみに、個人的には以下のように各インデックスのシャード数やドキュメント数を取得出来るが嬉しい!(効果音:ジャーン)

ubuntu@ubuntu-xenial:~$ curl localhost:9200/_cat/indices?v
health status index   uuid                   pri rep docs.count docs.deleted store.size pri.store.size
yellow open   twitter VSZ8zCTBS86Awm6n3pmckw   5   1          2            0      9.1kb          9.1kb

果たして Elasticsearch に猫が住んでいるのだろうか…(効果音:ジャーン)

尚、cat API の詳細についてはこちらのブログで紹介されている!(効果音:ジャーン)

本当に猫が住んでいるのか!?(効果音:ジャーン)

試した環境

ubuntu@ubuntu-xenial:~$ curl localhost:9200/
{
  "name" : "u0y-s7V",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "p20emC22SyG96ziu0qwl5w",
  "version" : {
    "number" : "5.3.2",
    "build_hash" : "3068195",
    "build_date" : "2017-04-24T16:15:59.481Z",
    "build_snapshot" : false,
    "lucene_version" : "6.4.2"
  },
  "tagline" : "You Know, for Search"
}

にゃー!

川原洋平探検隊は遂に見た!(効果音:ジャーン)

ubuntu@ubuntu-xenial:~$ curl localhost:9200/_cat
=^.^=
/_cat/allocation
/_cat/shards
/_cat/shards/{index}
/_cat/master
/_cat/nodes
/_cat/tasks
/_cat/indices
/_cat/indices/{index}
/_cat/segments
/_cat/segments/{index}
/_cat/count
/_cat/count/{index}
/_cat/recovery
/_cat/recovery/{index}
/_cat/health
/_cat/pending_tasks
/_cat/aliases
/_cat/aliases/{alias}
/_cat/thread_pool
/_cat/thread_pool/{thread_pools}
/_cat/plugins
/_cat/fielddata
/_cat/fielddata/{fields}
/_cat/nodeattrs
/_cat/repositories
/_cat/snapshots/{repository}
/_cat/templates

ん…

f:id:inokara:20170503093903p:plain

cat だけに…猫が住んでいたのであった!!(効果音:ジャーン)

そして、とうとう、川原洋平探検隊は…

ソースコードの中に猫を発見したのであった!(効果音:ジャーン)

github.com

f:id:inokara:20170503130114p:plain

以上

川原洋平探検隊が Elasticsearch の奥地に住んでいる猫を発見したメモでした。

小ネタ道場一本勝負 〜 Ubuntu xenial で Elasticsearch が Cannot allocate memory で起動しない場合 〜

試した環境

ubuntu@ubuntu-xenial:~$ cat /etc/lsb-release
DISTRIB_ID=Ubuntu
DISTRIB_RELEASE=16.04
DISTRIB_CODENAME=xenial
DISTRIB_DESCRIPTION="Ubuntu 16.04.2 LTS"

ubuntu@ubuntu-xenial:~$ free
              total        used        free      shared  buff/cache   available
Mem:        1015992       49948      397164        3440      568880      770376
Swap:             0           0           0

ubuntu@ubuntu-xenial:~$ curl localhost:9200/
{
  "name" : "u0y-s7V",
  "cluster_name" : "elasticsearch",
  "cluster_uuid" : "p20emC22SyG96ziu0qwl5w",
  "version" : {
    "number" : "5.3.2",
    "build_hash" : "3068195",
    "build_date" : "2017-04-24T16:15:59.481Z",
    "build_snapshot" : false,
    "lucene_version" : "6.4.2"
  },
  "tagline" : "You Know, for Search"
}

インストール直後

deb パッケージ をダウンロードしてインストール後に確認してみると…

ubuntu@ubuntu-xenial:~$ sudo service elasticsearch status
● elasticsearch.service - Elasticsearch
   Loaded: loaded (/usr/lib/systemd/system/elasticsearch.service; disabled; vendor preset: enabled)
   Active: failed (Result: exit-code) since Tue 2017-05-02 23:48:36 UTC; 4s ago
     Docs: http://www.elastic.co
  Process: 10100 ExecStart=/usr/share/elasticsearch/bin/elasticsearch -p ${PID_DIR}/elasticsearch.pid --quiet -Edefault.path.logs=${LOG_DIR} -Edefault.path.data=${DATA_DIR} -Edefault.path.conf=${CONF_DIR} (code=exited, status=1/FAILURE)
  Process: 10097 ExecStartPre=/usr/share/elasticsearch/bin/elasticsearch-systemd-pre-exec (code=exited, status=0/SUCCESS)
 Main PID: 10100 (code=exited, status=1/FAILURE)

May 02 23:48:36 ubuntu-xenial systemd[1]: Started Elasticsearch.
May 02 23:48:36 ubuntu-xenial elasticsearch[10100]: OpenJDK 64-Bit Server VM warning: INFO: os::commit_memory(0x000000008a660000, 1973026816, 0) failed; error='Cannot allocate memory' (errno=12)
May 02 23:48:36 ubuntu-xenial elasticsearch[10100]: #
May 02 23:48:36 ubuntu-xenial elasticsearch[10100]: # There is insufficient memory for the Java Runtime Environment to continue.
May 02 23:48:36 ubuntu-xenial elasticsearch[10100]: # Native memory allocation (mmap) failed to map 1973026816 bytes for committing reserved memory.
May 02 23:48:36 ubuntu-xenial elasticsearch[10100]: # An error report file with more information is saved as:
May 02 23:48:36 ubuntu-xenial elasticsearch[10100]: # /tmp/hs_err_pid10100.log
May 02 23:48:36 ubuntu-xenial systemd[1]: elasticsearch.service: Main process exited, code=exited, status=1/FAILURE
May 02 23:48:36 ubuntu-xenial systemd[1]: elasticsearch.service: Unit entered failed state.
May 02 23:48:36 ubuntu-xenial systemd[1]: elasticsearch.service: Failed with result 'exit-code'.

ふむ。

ということで、技あり

/etc/elasticsearch/jvm.options で JVM オプションを修正してヒープサイズを修正する。

root@ubuntu-xenial:/etc/elasticsearch# diff -u jvm.options.bk jvm.options
--- jvm.options.bk      2017-05-02 23:51:22.295369994 +0000
+++ jvm.options 2017-05-02 23:51:36.788119994 +0000
@@ -19,8 +19,8 @@
 # Xms represents the initial size of total heap space
 # Xmx represents the maximum size of total heap space

--Xms2g
--Xmx2g
+-Xms512m
+-Xmx512m

 ################################################################
 ## Expert settings

実際にどの程度の値に設定すれば良いのかについては、以下のドキュメントに記載されている。

www.elastic.co

ざっくり要約すると…(Powered by Google 翻訳)

  • minimum heap size (Xms) と maximum heap size (Xmx) は同じ値にしましょう
  • Elasticsearch で使用可能なヒープサイズが多いほど、キャッシュに使用できるメモリが増えるばい
  • ただし、ヒープサイズを割り当て過ぎると長い GC が発生する可能性があることに注意しませう
  • maximum heap size (Xmx) は物理メモリの 50% を超えないようにして、カーネルファイルシステムのキャッシュ用に十分な物理メモリが残っていることを確認しませう
  • maximum heap size (Xmx) を 32GB に近い値に設定しない方が良いです(超意訳ですいません … compressed oops の絡み)
  • じゃあ、Xmx の最大値だと 26GB 〜 30GB くらいにしておくと安全でせう(超意訳ですいません)

がっつりメモリがある場合でも、メモリの割り当てには注意しましょうってことですな。

有難うございました

改めて JVM について勉強が必要な事がわかったので、今回は「技あり」ってことで。