Memento memo.

Today I Learned.

VegetaでPhoenixとRailsの負荷テスト

以前会社の同期とご飯を食べていた時、 HTTP負荷テストツールのVegetaというものを紹介してもらったので試してみました。

github.com

GitHubのプロジェクトページを開くとイカしたサイヤ人の王子が目に飛び込んできます。

https://camo.githubusercontent.com/417a39e5a142e0877be0a7a6d7a66cb77ea21e8c/687474703a2f2f666330392e64657669616e746172742e6e65742f667334392f692f323030392f3139382f632f632f73736a325f7665676574615f62795f7472756e6b7332342e6a7067

Golang製ツールで、CLIで比較的簡単に扱えるのが特徴のようです。 大量のリクエストを投げるコマンドが vegeta attack です。 大量にエネルギー弾を撃ちまくるイメージですね。 ドラゴンボールの負け確フラグです。 "王子戦法"、またの名を"グミ撃ち"というらしいです。

dic.nicovideo.jp

Vegeta で負荷をかける

とりあえず使ってみます。他所のサーバに負荷をかけると本当に怒られるのでやめましょう。自前で用意したサーバかlocalhostに向かって実行しましょう。

負荷テストの対象は最近ハマってるPhoenix (v1.2.1, Elixir 1.2.6)と、比較用のRails (v5.0.0.1, Ruby 2.2.3)です。

手持ちのMBPのローカルでPhoenixRailsをnewした状態でサーバを立てます。 そしてlocalhostのルートURLに向かってリクエストを飛ばします。

※ 条件は超適当です。

VegetaのREADMEを読みつつ、以下のようなコマンドで rate を変えながら結果グラフを作成してみます。

$ echo "GET http://localhost:3000" | vegeta attack -duration=10s -rate=100 | vegeta report -reporter=plot > rails-100.html

vegeta attack のオプションで以下の内容を指定しています。

  • duration=10s ... 10秒間負荷をかける(今回は全試行で固定値)
  • rate=100 ... 秒間リクエスト数(変動値)

Rails => Phoenixの順で軽く検証してみます。

Rails

※Rails5のデフォルト設定のままPumaを使っています。
チューニングすればもう少しなんとかなるかもしれません。

30req/s

f:id:shotat_jp:20160925171754p:plain

余裕ですね。20ms付近で安定しています。

50req/s

f:id:shotat_jp:20160925171802p:plain

まだまだいけそうです。

80req/s

f:id:shotat_jp:20160925171810p:plain

ダメになりました。 半分くらいの処理が詰まって10秒遅れて処理されてます。(リクエスト受け付けずにVegetaがRetryしてるんでしょうか???HTTPステータスコード等を全く見てないので正確な挙動は不明です。すみません。。。) 通常のレスポンスも70ms程度まで落ち込んでいます。

100req/s

f:id:shotat_jp:20160925171816p:plain

すごいことになりました。

Phoenix

次いでPhoenixです。

30req/s

f:id:shotat_jp:20160925171412p:plain

余裕です。

100req/s

f:id:shotat_jp:20160925171516p:plain

まだまだいけます。Railsはここで死んでました。

500req/s

f:id:shotat_jp:20160925171524p:plain

レイテンシが若干大きくなってますが、概ね100ms以下で捌き切ってます。

800req/s

f:id:shotat_jp:20160925171533p:plain

レイテンシが100ms前後安定してます。

1000req/s

f:id:shotat_jp:20160925171540p:plain

エラーがいろいろ発生し始めました。 最初の2秒くらいは頑張ってますね。

2000req/s

f:id:shotat_jp:20160925171548p:plain

半分くらいエラーになってしまいました。お疲れ様でした。

まとめ

条件は超ざっくりですが、ローカル環境では

くらいいけました。Phoenixの方が10倍くらいパフォーマンス良いみたいな通説があるんですが、大体そんな感じの結果です。

分散環境でもPhoenix + Elixirは強いはずなので、高速で大量のリクエストを捌く必要のあるシステム(ソシャゲやtwitter)に向いてる気がします。通信回数控えめなエンタープライズ系だとメリットより導入コスト・リスクの方が上回るような感覚です。

キャパシティプランニング ― リソースを最大限に活かすサイト分析・予測・配置

キャパシティプランニング ― リソースを最大限に活かすサイト分析・予測・配置

あと、あまり負荷テストとかやったことがないので、 このあたりもう少し勉強していきたいです。 グラフの解釈等間違っていたらご指摘いただけると幸いです。

プログラミングElixir

プログラミングElixir

PhoenixのChannelを使う

www.phoenixframework.org

Phoenixのガイドを眺めていて一番気になったのがChannelだったので、上記の公式ガイドに沿ってChannelを使ってみました。結構端折っています。

プログラミングElixir

プログラミングElixir

概要をつかむために公式ガイドの一番上のところだけ訳してみます。

Channels are a really exciting and powerful part of Phoenix that allow us to easily add soft-realtime features to our applications. Channels are based on a simple idea - sending and receiving messages. Senders broadcast messages about topics. Receivers subscribe to topics so that they can get those messages. Senders and receivers can switch roles on the same topic at any time.

Since Elixir is based on message passing, you may wonder why we need this extra mechanism to send and receive messages. With Channels, neither senders nor receivers have to be Elixir processes. They can be anything that we can teach to communicate over a Channel - a JavaScript client, an iOS app, another Phoenix application, our watch. Also, messages broadcast over a Channel may have many receivers. Elixir processes communicate one to one.

The word "Channel" is really shorthand for a layered system with a number of components. Let's take a quick look at them now so we can see the big picture a little better.

適当訳:

チャネルはPhoenixの中でも本当に面白くて強力なところであり、簡単にソフトリアルタイム性をアプリケーションに持たせることができます。チャネルはメッセージの送受信という、単純なアイデアに基いています。senderはtopicについてブロードキャストし、receiverはtopicを購読することによってメッセージを受け取ることができます。sender, receiverはいつでも役割を交代することができます

ElixirはMessage Passingに基いているため、どうしてメッセージの送受信に他の仕組みを利用するのか疑問に思うことでしょう。チャネルでは、senderもreceiverもElixirのプロセスである必要はありません。チャネルと通信するものはJavaScriptiOSでも何だってよいのです。また、Elixirのプロセスが1対1で通信するのに対し、チャネルにおけるメッセージブロードキャスティングはreceiverが複数になり得ます。

"Channel"という言葉は多くのコンポーネットをもった多層システムを簡略に表したにすぎません。 全体像をもっとよくつかめるようになるために、もう少し覗いてみましょう。

以下、各partsの説明(省略)

  • Socket Handlers
  • Channel Routes
  • Channels
  • PubSub
  • Messages
  • Topics
  • Transports
  • Transport Adapters
  • Client Libraries

手を動かすのが一番早い理解につながるので、 例の如く適当にプロジェクトとDBを作成します

プロジェクト作成

$ mix phoenix.new channel_sample
$ cd channel_sample
$ mix ecto.create

SocketとChannelの設定

lib/hello_phoenix/endpoint.ex の4行目付近ですでにsocketが定義されています。

defmodule ChannelSample.Endpoint do
  use Phoenix.Endpoint, otp_app: :channel_sample

  socket "/socket", ChannelSample.UserSocket
...

ChannelSample.UserSocket 自体は web/channels/user_socket.ex で定義されています。5行目のコメントアウトを外して、channelの設定をします。

defmodule ChannelSample.UserSocket do
  use Phoenix.Socket

  ## Channels
  channel "room:*", ChannelSample.RoomChannel

Channelモジュールの実装

HelloPhoenix.RoomChannel モジュールはまだ存在しないので、web/channels/room_channel.ex ファイルを作成し、以下の内容でモジュールを定義します。

defmodule ChannelSample.RoomChannel do
  use Phoenix.Channel

  def join("room:lobby", _message, socket) do
    {:ok, socket}
  end

  def join("room:" <> _private_room_id, _params, _socket) do
    {:error, %{reason: "unauthorized"}}
  end
end

<> は文字列結合をしています)

認可のために、join/3 関数を定義する必要があります。 今回は"room:lobby" topicだけは誰でも入れるようにし、private_roomのことは考えないこととします。

クライアント側の設定

web/static/js/socket.js に最低限の実装が最初からあるので、中身を確認します。

// web/static/js/socket.js
...
socket.connect()

// Now that you are connected, you can join channels with a topic:
let channel = socket.channel("room:lobby", {})
channel.join()
  .receive("ok", resp => { console.log("Joined successfully", resp) })
  .receive("error", resp => { console.log("Unable to join", resp) })

export default socket

上記のように57行目付近を let channel = socket.channel("room:lobby", {}) に変更します。

また、web/static/js/app.js 末尾行のコメントアウトを外してsocket.jsを有効にします。

import socket from "./socket"

Phoenixがライブリロードされ、ブラウザコンソールに以下のように出力され、 socket通信が確立されていることがわかります。 (Phoenixを起動していない場合は $ mix phoenix.server します)


f:id:shotat_jp:20160924160548p:plain


web/templates/page/index.html.eex を修正して、入力フォームとメッセージ表示用のコンテナを作成します。

<div id="messages"></div>
<input id="chat-input" type="text"></input>

socket.js ファイルを修正して、socket通信でメッセージの受送信ができるようにします。

...
let channel = socket.channel("room:lobby", {})
let chatInput = document.querySelector("#chat-input")
let messageContainer = document.querySelector("messages")

chatInput.addEventListener("keypress", event => {
  if(event.keyCode === 13){
    channel.push("new_msg", {body: chatInput.value})
    chatInput.value = ""
  }
})

channel.on("new_msg", payload => {
  let messageItem = document.createElement("li");
  messageItem.innerText = `[${Date()}] ${payload.body}`
  messagesContainer.appendChild(messageItem)
})

// let channel = socket.channel("topic:subtopic", {})
channel.join()
  .receive("ok", resp => { console.log("Joined successfully", resp) })
  .receive("error", resp => { console.log("Unable to join", resp) })

export default socket

RoomChannelモジュールを修正します。 socketでメッセージが飛んできた際のフック関数として handle_in を実装すれば大丈夫です。

broadcast! で接続中のクライアント全員にメッセージを送ります。

handle_out ではクライアントごとにフィルタリング処理を行ったり、Interceptor的な役割をもっています。今回は何もしていません。

defmodule ChannelSample.RoomChannel do
  use Phoenix.Channel

  def join("room:lobby", _message, socket) do
    {:ok, socket}
  end

  def join("room:" <> _private_room_id, _params, _socket) do
    {:error, %{reason: "unauthorized"}}
  end

  def handle_in("new_msg", %{"body" => body}, socket) do
    broadcast! socket, "new_msg", %{body: body}
    {:noreply, socket}
  end

  def handle_out("new_msg", payload, socket) do
    push socket, "new_msg", payload
    {:noreply, socket}
  end
end

これでChatができるようになりました。


f:id:shotat_jp:20160924165910p:plain

Sample

こちらにChat Appのサンプルがあるようです。

http://phoenixchat.herokuapp.com/

Heroku上で動いています。逆に対応してるPaaSはHerokuだけかもしれないです。

Programming Phoenix: Productive, Reliable, Fast

Programming Phoenix: Productive, Reliable, Fast

まとめ

Channelが手軽につかえていい感じです。 当然普通にRails的な使い方もできるのでなかなか汎用性高いのでは?と思いました(小並感)

Effective Ruby 第四章 例外 まとめ

Effective Ruby 第三章 コレクション まとめ - Memento memo. の続きです。

今回はRubyの例外についてです。

例外は、以下の2つの異なる言語機能をまとめたものだと考えられます。

  • エラーの説明
  • 制御フロー

raiseにはただの文字列ではなくカスタム例外を渡す

  • raise に文字列だけを渡さず、例外のクラス名を指定する
  • または例外クラスを(パラメータと共に)インスタンス化してraiseに渡す
  • カスタム例外クラスにinitializeメソッドを定義するときはsuperを呼び出す
raise("something wrong") # NG
raise(CustomError, "something wrong") # OK
raise(CustomError.new(param)) # OK
raise(CustomError.new(param), "something wrong") # OKだが、第一引数のオブジェクトのメッセージが失われる
  • 新しい例外クラスは、標準例外クラス(主にStandardError)を継承し、慣例的に名称をXxxErrorのようにする。
  • 1つのプロジェクトに複数例外クラスを作る場合はStandardErrorを継承した基底クラスを作成し、個々の例外クラスはそこから継承する。

出来る限り最も対象の狭い例外を処理する

  • 修復プロセスがわかっている特定の例外(DB、NW接続エラー等)だけをrescueで捕まえる
  • 限定された例外(詳細度の高いもの)から順にrescueする
  • 汎用例外クラスをrescueで補足してはダメ。ensure節を作るか、上流で処理すること。
  • rescue節内で例外が発生すると新しい例外が現在の例外を押しのけてしまう。その場合、新しい例外より元の例外をraiseするのが良い。

リソースはブロックとensureで管理する

  • メモリはGCで処理されるが、メモリ以外のリソース(オープンしたファイル等)の自動的な開放は保証されない。
  • ensure節でリソースを確実に開放する
  • ensure節の式はbegin本体と同じスコープだが、変数が初期化されていない可能性に注意する
# ensureによるリソース管理
begin
  file = File.open(file_name, 'w')
  ...
ensure
  file.close if file 
end
  • ブロックで抽象化して同等の処理が可能
File.open(file_name, 'w') do |file|
  ...
end
  • File::openと同じようなブロック&ensureパターンを独自のリソース管理クラスでも実装すると良い(以下のような実装)

Lock.rb

ensureは最後まで実行して抜ける

  • ensure節の中で制御文(return, throw, next, break)などの制御文は絶対に使ってはならない
  • ダメ、絶対

retryで回数上限を設け、頻度を変化させ、オーディットトレイルを残す

  • 無条件のretryは無限ループに等しい危険性がある
  • retry実行の境界変数はbegin節の外のスコープに定義する
  • retryを使うときはaudit trail(監査証跡)を作ること。エラーのイベント連鎖を必ずログに残す。
  • retry時のディレイはrescue節の中で値を(指数関数的に)増やしていくことを検討する。

retry.rb

スコープから飛び出したいときはraiseではなくthrowを使う

  • catchとthrowは例外とは関係ない。gotoの安全versionと考える方が近い。
  • 複雑な制御フローが必要な場合はraiseよりthrowを使う。throwの場合スタック上位にオブジェクトを送ることができる。
  • ただしthrow、catchを多用してはならない。出来る限り単純な制御構造(return)を使う。

ネストの深いループから一撃で脱出したい場合はthrowすると良いみたいです。ただ、ネストの深いコードは循環的複雑度が高くなっちゃうのでそもそもそういったコードは避けるべきですね。


Effective Rubyは八章構成なので、前半戦終わりです。

次はメタプログラミングです。一番重そうな章です。

Effective Ruby

Effective Ruby

The actor model in 10 minutes [日本語訳]

10分でわかるアクターモデルです。 Erlang, Elixir, Scalaあたりを触るときに理解すべき内容です。

元ネタ: www.brianstorti.com

Introduction

我々のCPUは速くなりません。 CPUには今やたくさんのコアを積んでいますが、 全てのハードウェア資源を利用したければ コードを並行的に走らせる必要があります。 マルチスレッドの利用が進むべき道でないことは、 数十年もの追跡不能バグや開発者の憂鬱が示しています。 しかし恐れることはありません。 素晴らしいいくつかの代替案があります。 今回はその中の一つを紹介しましょう。 それはActor Modelです。

Model

Actor Modelは並行計算を取り扱うための概念的なモデルです。 システムコンポーネントがどのように振る舞い、相互作用すべきかについてのいくつかの原則が定義されています。 Actor Modelを使っている最も有名な言語はErlangでしょう。 Actor Modelがどのように他の言語やライブラリで実装されているかについての話はしません。Actor Modelそれ自体に焦点を当てていきましょう。

Actors

Actorは、計算において最も基本的な(primitiveな)ユニットです。 Actorは、メッセージを受け取り、受け取ったメッセージに基いて何らかの計算を行う"もの"です。

イデア自体はオブジェクト指向言語のそれによく似ています。 オブジェクトはメッセージを受け取り(method call)、受け取ったメッセージ(call された method)に基いて何かを行います。

Actor ModelとOOPとの最も大きな違いは、 Actorは完全に独立しており、決してメモリを共有しないことです。 注目すべきは、Actorは決して他のActorからは直接書き換えることのできないprivateなステートを維持できる、ということです。

One ant is no ant

Actorは一つでは意味がありません。複数でシステムを形成します。 Actor Modelにおいては、全てはActorであり、Actorは他のActorがメッセージを送るためのアドレスを持つ必要があります。

Actors have mailboxes

複数のActorは同時に走る一方、一つのActorは受け取ったメッセージを連続的に(シーケンシャルに)処理する、というの理解することが重要です。 つまり、3つのメッセージを同じ一つのActorに送ったとして、 受け取った側のActorは単に一つずつシーケンシャルに処理します。 3つのメッセージを並行処理させたい場合は、3つのActorを作成し、それぞれに一つずつメッセージを送る必要があります。

複数メッセージは非同期的にActorに送られるので、他のメッセージを処理している間、それらをどこかに格納しておく必要があります。 メッセージの格納先こそが、メールボックスです。

http://www.brianstorti.com/assets/images/actors.png Actorたちは非同期メッセージを送り合いコミュニケーションを取ります。それらメッセージは、処理されるまでの間、他のActorのメールボックスにしまわれています。

What actors do

メッセージを受け取ったActorは、次の3つのうちのどれか一つを行うことができます。

  • 更なるActorを作成する
  • 他のActorにメッセージを行う
  • 次受け取るメッセージと何をすべきかを指定する

上2つは非常に直感的ですが、3つ目は興味深いですね。 先に述べた通り、Actorはprivateな状態を保持することができます。 "次受け取るメッセージと何をすべきかを指定する"というのは、 より明確にいうと、Actorが状態を変化させる方法です。

計算機のように振る舞うActorがあり、 最初のstateが 0 であると想定してください。 Actorが add(1) というメッセージを受け取った場合、 元のstateを変化させてしまう代わりに、 次受け取るメッセージに対して stateが 1 になるように指定します。

Fault tolerance

Erlangは"let it crash"という思想を取り入れました。 "全ての単一障害点について考えるのは不可能であるため、 起こりうる全ての可能性を予想し、それぞれ対処する方法を見つけるような 防御的プログラミングを行う必要はない"、という考えです。 Erlangの手法は、すぐにcrashさせる代わりに、この致命的なコードを誰か(スーパーバイザ)に監視させることです。スーパーバイザの単一責務は、crashが起きた場合に何をすべきか(例として、crashしたユニットを正常な状態にresetする)を把握することです。 これを可能にしているのがActor Modelです。

全てのコードはプロセス(ErlangがActorと呼んでいるもの)の中で走ります。 プロセスは完全に独立・隔離されているので、プロセスのstateが他のプロセスの影響を受けることはありません。 スーパーバイザ(スーパーバイザもプロセス = Actor)は、 監視対象のプロセスがクラッシュすると通知され、何かの処理を行うことができます。

これらの仕組みにより自己修復(self heal)機能を持ったシステムを作ることが可能です。 つまり、あるActorが何らかの理由で例外的なstateになりcrashした場合、 スーパーバイザはcrashしたActorを再び整合性の取れたstateにすることができます。具体的な戦略はいくつかありますが、一般的なのはActorを初期状態にしてrestartすることです。

Distribution

メッセージを送るActorがローカルに存在するのか、離れたノードに存在するのかについては全く重要ではない、というのが Actor Modelの他の興味深い側面です。

考えてみてください。Actorがメールボックスと内部状態を持ち、メッセージに応答するだけのコード片だとしたら、実際にActorが動いているマシンが何であるかなんて誰が気にかけるでしょうか。メッセージがそこに届きさえすればそれで問題ありません。

おかげで、我々は複数のマシンを使ったシステムを構築でき、一つのノードに障害が起きたとしても、復旧が容易になります。

プログラミングElixir

プログラミングElixir

PhoenixのTemplate Engineを Slime (Slim) にする

Slime

PhoenixのデフォルトのTemplate Engineは erbライクなeexです。 RailsだとerbよりSlimを使うケースも多いと思いますが、 PhoenixでもSlimライクなTemplate Engineが存在しました。

その名もSlimeです。ロゴがスライムっぽいですね。

github.com

多分発音も"スリム"じゃなくて"スライム"だと思います。

Phoenixに適用する

基本設定

適当なPhoenixのプロジェクトの mix.exs に依存関係を追記します。

...
  defp deps do
    [{:phoenix, "~> 1.2.1"},
     {:phoenix_pubsub, "~> 1.0"},
     {:phoenix_ecto, "~> 3.0"},
     {:postgrex, ">= 0.0.0"},
     {:phoenix_html, "~> 2.6"},
     {:phoenix_live_reload, "~> 1.0", only: :dev},
     {:gettext, "~> 0.11"},
     {:cowboy, "~> 1.0"},
     {:phoenix_slime, "~> 0.6.0"} # here
   ]
  end

config/config.exs にも適当に設定書きます。

# Configure slime
config :phoenix, :template_engines,
  slim: PhoenixSlime.Engine,
  slime: PhoenixSlime.Engine

依存関係を追記したので本体をfetchしてきます

$ mix deps.get

watch対象に追加

my_app/config/dev.exs を修正。ライブリロードを有効にするために、slim, slimeのファイルをwatch対象に追加します。

# Watch static and templates for browser reloading.
config :slime_sample, SlimeSample.Endpoint,
  live_reload: [
    patterns: [
      ~r{priv/static/.*(js|css|png|jpeg|jpg|gif|svg)$},
      ~r{priv/gettext/.*(po)$},
      ~r{web/views/.*(ex)$},
      ~r{web/templates/.*(eex|slim|slime)$} # here
    ]
  ]

Generator Tasks

$ mix help | grep -i phoenix

とすると、phoenix関係のmix taskがドバドバ表示されます。

$ mix help | grep -i phoenix
mix local.phoenix            # Updates Phoenix locally
mix phoenix.digest           # Digests and compress static files
mix phoenix.gen.channel      # Generates a Phoenix channel
mix phoenix.gen.html         # Generates controller, model and views for an HTML based resource
mix phoenix.gen.html.slime   # Generates controller, model and views for an HTML based resource using Slime templates
mix phoenix.gen.json         # Generates a controller and model for a JSON based resource
mix phoenix.gen.layout.slime # Generates a default Phoenix layout file in Slime
mix phoenix.gen.model        # Generates an Ecto model
mix phoenix.gen.presence     # Generates a Presence tracker
mix phoenix.gen.secret       # Generates a secret
mix phoenix.new              # Creates a new Phoenix v1.2.1 application
mix phoenix.routes           # Prints all routes
mix phoenix.server           # Starts applications and their servers

出てこない場合は mix compile なり mix phoenix.routes なりすると buildが走って一緒にtaskが生成されると思います。

slime関係で新しくできたtaskは

mix phoenix.gen.html.slime   # Generates controller, model and views for an HTML based resource using Slime templates
mix phoenix.gen.layout.slime # Generates a default Phoenix layout file in Slime

の2つです。

とりあえず動かしてみます。

User users と書いている部分は 単数形 複数形 で入力します。 生成されるcontroller名はrailsと違って単数形ですね。

$ mix phoenix.gen.html.slime User users --no-model
* creating web/controllers/user_controller.ex
* creating web/views/user_view.ex
* creating test/controllers/user_controller_test.exs
* creating web/templates/user/edit.html.slim
* creating web/templates/user/form.html.slim
* creating web/templates/user/index.html.slim
* creating web/templates/user/new.html.slim
* creating web/templates/user/show.html.slim

Add the resource to your browser scope in web/router.ex:

    resources "/users", UserController

見事にslimファイルが生成されましたね。slimeはどこに行ったんでしょうか。

生成されたslimの中身は以下のようになってます。

h2 Listing users

table.table
  thead
    tr
      th
  tbody
    = for user <- @users do
      tr
        td class="text-right"
          = link "Show", to: user_path(@conn, :show, user), class: "btn btn-default btn-xs"
          | &nbsp;
          = link "Edit", to: user_path(@conn, :edit, user), class: "btn btn-default btn-xs"
          | &nbsp;
          = link "Delete", to: user_path(@conn, :delete, user), method: :delete, data: [confirm: "Are you sure?"], class: "btn btn-danger btn-xs"

= link "New user", to: user_path(@conn, :new)

ページの表示

とりあえずメッセージの通りに web/router.exresources "/users", UserController を追記します。

--no-model にしたせいかサーバ起動しようとするとめっちゃ怒られるので user_controller の余計なアクションを一旦全部削って以下のようにします。

defmodule SlimeSample.UserController do
  use SlimeSample.Web, :controller

  alias SlimeSample.User

  def index(conn, _params) do
    render(conn, "index.html", users: [])
  end
end

[error] Postgrex.Protocol (#PID<0.212.0>) failed to connect: ** (Postgrex.Error) FATAL (invalid_catalog_name): database "slime_sample_dev" does not exist みたいなエラーが出てきます。 DB作ってないので $ mix ecto.create で作りましょう。

とりあえずこれで

$ mix phoenix.server

して、 http://localhost:4000/users にアクセスすれば、ページが表示されるかと思います。

f:id:shotat_jp:20160921234438p:plain

Railsで作ったアプリケーションをPhoenixに移植するのが捗ります。

プログラミングElixir

プログラミングElixir

Programming Phoenix: Productive, Reliable, Fast

Programming Phoenix: Productive, Reliable, Fast

slimeのsyntax highlightのvimプラグインは提供されているようです。

github.com

Effective Ruby 第三章 コレクション まとめ

Effective Ruby 第二章まとめ その2 - Memento memo. の続きです。

第三章はコレクションです。コレクションをうまく扱えるかどうかはコードの可読性・保守性にダイレクトに効いてくる気がするので、言語ごとのクセを把握しておきたいです。

Effective Ruby

Effective Ruby

コレクションを書き換える前に引数として渡すコレクションのコピーを作っておく

  • Rubyのメソッド引数は値渡しではなく参照渡し。例外あり。(Fixnumは値渡しされる)
  • cloneとdupメソッドでオブジェクトのコピー作成ができる。たいていdupを使ったほうがいい。ただしshallow copyになってしまうので注意。
  • dupと比較して、cloneは"レシーバのフリーズ状態"と"特異クラス"を含めてコピーする
  • Marshalでシリアライズ、デシシアライズすればdeep copyになるが、メモリ効率、パフォーマンスの観点が悪いので注意。

破壊的メソッドを使って要素を直接書き換えるようなコードを避ければ大半は回避できそうな気がします。

nil、スカラオブジェクトを配列に変換するにはArrayメソッドを使う

  • Array(foo)とすると、fooがnilでもスカラオブジェクトでも配列に変換できる。
  • Array(foo)のfooにHashを渡してはいけない
foo = nil
puts Array(foo).map(&:to_s)
#=> no error
puts foo.map(&:to_s)
#=> error!!!

ある程度柔軟に引数渡せるようになるのがありがたいです。

要素が含まれているかどうかの処理を効率よく行うために集合を使うことを検討する

  • include?メソッドで要素が含まれてどうかの判定を繰り返し行うような場合は、include?のレシーバをSetクラスのインスタンスにすると高速になる。
  • SetはArrayと同じInterfaceを持つ。
  • require('set') して使う。

Array, HashだけじゃなくてSet, Rangeの存在も忘れないようにしましょう。

reduceを使ってコレクション畳み込む

  • select, mapメソッドよりreduceを使った方がメモリ効率が良い。
  • accumulatorの初期値は必ず与えること(コレクションが空の時の挙動を制御するため)

selectやmapだと効率が悪い、みたいなことが結構書いてあったのですが、内部実装どうなってるんでしょう。確かにreduceはあまり無駄がなさそうですが、向き不向きがある気がするのでいろんなパタンでベンチマーク取ってみようかと思います。

畳み込み自体の概念や実際の使い方については、HaskellやElixirみたいな関数型言語系の解説書を読むと理解が格段に深まるのでElixir本を読みましょう。

Hashのdefault値を利用することを検討する

  • Hashのデフォルト値・デフォルトブロックを使うことを検討する(以下参照)
h = {} # デフォルト値なし
i = Hash.new(0) # デフォルト値あり
j = Hash.new{|hash, key| hash[key] = []} # デフォルトブロック(危険な挙動があるので注意)
  • Hashのキー存在チェックは has_key? メソッドを使う。デフォルト値がnilとは限らないため。
  • デフォルト値より Hash#fetch の方が安全な場合がある
h = {}
h.fetch(:a)
#=> KeyError
h.fetch(:a, "default_value")
#=> "default_value"

Hash#fetch が一番柔軟でよく使う気がします。

Collection classからの継承よりも委譲を使う

  • 継承より委譲を使うこと。
  • 委譲するには require('forwardable') して Forwardable をextendする
  • def_delegatorsクラスメソッドで委譲ターゲットと委譲メソッドを定義する。

"継承より委譲"はよく聞きますね。OOPへの深い理解がないと適切に継承を使うのは難しいです。

Phoenix環境構築(Install・Server起動・ページの追加まで)

ElixirのWebフレームワーク Phoenixの入門 環境構築についてです。

公式Docの

に載っているものを簡略化 & 日本語化したものです。

Elixir

Elixirについての詳しい概要はこちらの記事が参考になります。

qiita.com

入門書はこちらの書籍が非常に面白かったです。というか他の日本語書籍は現時点で存在しないと思います。

プログラミングElixir

プログラミングElixir

Install

公式ページにいろいろ載ってますが、Mac + Homebrewの場合は

$ brew install elixir

でInstallできます。

上記コマンドでElixirをinstallすることで、 elixir コマンド、ElixirのREPL環境を実行する iex コマンド、ElixirのプロジェクトのCLIユーティリティの mix が使えるようになります。またErlangも一緒にinstallされます。

Phoenix

Install

Elixir & mix & Erlang がinstall済みであること

PhoenixのInstall

# package managerの hex を導入する
$ mix local.hex

# mixでPhoenixのinstall
mix archive.install https://github.com/phoenixframework/archives/raw/master/phoenix_new.ez

その他必要なもの

Node.js (>= 5.0.0)

Homebrewやnvmで適当に

PostgreSQL

オプションでMySQLも可能。デフォルトはPostgreSQL

参考: Homebrewを使ったPostgreSQLのインストール(Mac OS El Capitan)

Projectの作成

create project

$ mix phoenix.new hello_phoenix
# Fetch and install dependencies? [Yn] 聞かれるのでとりあえず 'Y' を押下するとbrunchがinstallされる
$ cd hello_phoenix

ディレクトリ構造はこんな感じです。

❯ tree -L 2
.
├── README.md
├── brunch-config.js
├── config
│   ├── config.exs
│   ├── dev.exs
│   ├── prod.exs
│   ├── prod.secret.exs
│   └── test.exs
├── lib
│   ├── hello_phoenix
│   └── hello_phoenix.ex
├── mix.exs
├── package.json
├── priv
│   ├── gettext
│   └── repo
├── test
│   ├── channels
│   ├── controllers
│   ├── models
│   ├── support
│   ├── test_helper.exs
│   └── views
└── web
    ├── channels
    ├── controllers
    ├── gettext.ex
    ├── models
    ├── router.ex
    ├── static
    ├── templates
    ├── views
    └── web.ex

よく触るファイル系はだいたい web に入っています。

ectoのsetup

軽量ORMのectoはRailsでいうActiveRecord的なポジションです。

config/dev.exs の設定の設定をいじってpostgresに接続できるようにしてください。role/passwordが"postgres/postgres"でCREATEDB権限があればいけるはずです。

...
# Configure your database
config :hello_phoenix, HelloPhoenix.Repo,
  adapter: Ecto.Adapters.Postgres,
  username: "postgres",
  password: "postgres",
  database: "hello_phoenix_dev",
  hostname: "localhost",
  pool_size: 10
$ mix ecto.create

上記コマンドでエラーがでなければ成功です。

Phoenix server 起動

$ mix phoenix.server

でserver起動できます。デフォルトでは http://localhost:4000 にアクセスすればページにアクセスすることができます。

f:id:shotat_jp:20160919221029p:plain

こんな感じの画面が出ます。

ページの追加

mix phoenix.gen.html といったmix task( Mix Tasks · Phoenix )で必要なファイル群の生成ができるみたいなのですが、今回はガイドに沿って確認しながら手でちまちま作っていきます。

routingの設定ファイル

routingの設定ファイルは web/router.ex です。 中身を見てみましょう。

defmodule HelloPhoenix.Router do
  use HelloPhoenix.Web, :router

  pipeline :browser do
    plug :accepts, ["html"]
    plug :fetch_session
    plug :fetch_flash
    plug :protect_from_forgery
    plug :put_secure_browser_headers
  end

  pipeline :api do
    plug :accepts, ["json"]
  end

  scope "/", HelloPhoenix do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
  end

  # Other scopes may use custom stacks.
  # scope "/api", HelloPhoenix do
  #   pipe_through :api
  # end
end

get "/", PageController, :index の行で ( web/controllers/page_controller.ex で定義された) HelloPhoenix.PageController moduleの index 関数を呼び出す、ということが容易に読み取れます。

routingの追加

http://localhost:4000/hello ページを作成します。

まず web/router.ex ファイルの20行目付近にroutingを定義します。

  scope "/", HelloPhoenix do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/hello", HelloController, :index
  end

controllerの作成

次に以下の内容の web/controllers/hello_controller.ex を作成します。

defmodule HelloPhoenix.HelloController do
  use HelloPhoenix.Web, :controller

  def index(conn, _params) do
    render conn, "index.html"
  end
end

conn はrequest情報が詰まったstructです。_paramsはrequest parametersですが、今回使わないので先頭に _ をつけています。

viewの作成

以下の内容の web/views/hello_view.ex を作成します。

defmodule HelloPhoenix.HelloView do
  use HelloPhoenix.Web, :view
end

xxx_controllerxxx_view の xxx部を一致させる必要があるので注意です。 viewの役割はtemplateのレンダリング & データの取得, 加工のヘルパー関数の提供です。

templateの作成

Elixirの標準テンプレートエンジンは Embedded Elixir(eex)です。

web/templates/hello/index.html.eex に以下の内容のファイルを作成します。

<div class="jumbotron">
  <h2>Hello World, from Phoenix!</h2>
</div>

helloページの表示

再びサーバを立ち上げ、 http://localhost:4000/hello にアクセスするとHelloページが表示されます。 サーバを再起動しなくとも変更は反映されます。

また、レイアウトファイルは web/templates/layout/app.html.eex に存在しています。

動的ページの追加

Pathパラメータを使ってページをレンダリングします。

routing設定

web/router.ex にroutingを追加しましょう。

  scope "/", HelloPhoenix do
    pipe_through :browser # Use the default browser stack

    get "/", PageController, :index
    get "/hello", HelloController, :index
    get "/hello/:messenger", HelloController, :show
  end

controller設定

/web/controllers/hello_controller.ex に showアクションを追加します。

  def show(conn, %{"messenger" => messenger}) do
    render conn, "show.html", messenger: messenger
  end

template設定

web/templates/hello/show.html.eex を作成し、以下の内容を記述します。

<div class="jumbotron">
  <h2>Hello World, from <%= @messenger %>!</h2>
</div>

これで http://localhost:4000/hello/phoenix にアクセスすると "Hello World, from phoenix!" と表示されるかと思います。

まとめ

PhoenixRailsライクなMVCで非常にわかりやすいですね。 今後は公式ガイド読みつついろいろ試してみるつもりです。

Programming Phoenix: Productive |> Reliable |> Fast

Programming Phoenix: Productive |> Reliable |> Fast

Effective Ruby 第二章 クラス、オブジェクト、モジュール まとめ その2

Effective Ruby 第二章まとめ その1 - Memento memo. の続きです。

Effective Ruby

Effective Ruby

構造化データにはHashではなくStructを使う

  • 新しいクラスを作るほどでもない構造化データを扱う場合はStructを使う
  • Struct::newを定数に代入し、定数をクラスのように扱う
  • ブロックでメソッドも定義できる
Person = Struct::new(:height, :weight, :age) do
  def bmi
    weight / (height * height)
  end
end

hoge = Person.new(1.58, 45, 20)

# getterでアクセスできる
puts hoge.height
# メソッド呼び出し
puts hoge.bmi

Hashで存在しないキーを指定するとnilになってしまってデバッグが辛くなるのでError吐いてくれるStructの方が良さ気です。

モジュールにコードをネストして名前空間を作る

  • 特にライブラリ等を作成する場合は、名前の衝突を避けるためにモジュール内に定義をネストして名前空間を切る。
  • 名前空間の構造はディレクトリに合わせる
  • トップレベル定数( Array とか)を使う場合に曖昧さが残る場合は"::"を使ってフル修飾する( ::Array

ライブラリ使う際は気を付けましょう。普通に開発する場合はあまり気にならないかも?

様々な等値の違いを理解する

  • "==", "===", "equal?", "eql?"の4種類の比較方法がある
  • "equal?"メソッドは厳格にオブジェクト(object_id)を比較する。オーバーライド禁止。
  • "eql?"メソッドはHashキーとして使われているオブジェクトの比較に使われる。たいていの場合は"=="の別名として定義した方が良い。
  • "=="メソッドは2つのオブジェクトが同じ値かどうかを緩やかにテストする。
  • "==="メソッドはcase式のwhen節をテストする。左被演算子がwhenに与えられる引数、右被演算子がcaseに与えられる引数であることに注意。

"<=>"とComparableモジュールで比較を定義する

  • "<=>"演算子を実装し、Comparableモジュールをincludeする
  • "<=>"演算子は、比較できない場合はnilを返す

Version.rb

サンプルはこんな感じでした。

protectedメソッドを使ってprivateな状態を共有する

  • privateな状態はprotectedメソッドで共有する
  • レシーバを明示してprotectedメソッドを呼び出せるのは、同じクラスのオブジェクトか共通のスーパークラスからprotectedメソッドを継承してるオブジェクトだけ

public interfaceは必要最低限にしたいので、関連クラスのオブジェクト間同士の処理はprotectedでの記述を検討しましょう。

クラス変数よりもクラスインスタンス変数を使うようにする

  • クラス変数はグローバル変数と同様の問題(並行処理など)があるため、クラスインスタンス変数を使う。
  • クラスはオブジェクトのため、専用のprivate instance変数セットを持っている。
  • Singletonを作成したい場合は include(Singleton) する

Effective Ruby 第二章 クラス、オブジェクト、モジュール まとめ その1

いつぞやの続きです。

shotat.hateblo.jp

Effective Ruby

Effective Ruby

一記事一章にまとめようとしたところ二章目から量が多くて詰んだので適当なところで切ることにしました。

Effective Ruby 第二章のテーマは"クラス、オブジェクト、モジュール"です。

Rubyは純粋オブジェクト指向言語とも呼ばれていて、全てがオブジェクトです。クラス、数値リテラルを含め全てオブジェクトです。クラス自体がオブジェクトなのです。

用語

  • オブジェクト: 変数の入れ物(※)
  • インスタンス変数: オブジェクトに状態を表現する変数
  • インスタンス: 特定のクラスの実例
  • クラス: メソッドと定数の入れ物
  • インスタンスメソッド: クラスのインスタンス(オブジェクト)のふるまいを表現する
  • クラス変数: クラスに格納された変数
  • クラスオブジェクト: (クラス自体がオブジェクト)
  • クラスメソッド: クラスオブジェクトのメソッド
  • スーパークラス: クラス階層内の親クラスの別名
  • モジュール: 制限付きクラス(newとかがない)
  • 特異クラス(singleton class): クラス階層に現れる不可視クラス。全てのオブジェクトが一つ持つ。
  • 特異メソッド(singleton methods: 特異クラスに格納される、クラスメソッドや特定オブジェクト専用メソッド
  • レシーバ: メソッドが呼び出されるオブジェクト

(※)オブジェクトが変数の入れ物っていうのはちょっと違うような気が。 少なくとも変数と振る舞いを両方カプセル化しています。

Rubyの継承階層について

  • オブジェクトは継承階層を使ってメソッドを探す
  • 階層ルートまで辿ってもメソッドが見つからなければ method_missing メソッドを探す
  • includeメソッドでmoduleをmixinした場合、特異クラス(不可視)がクラス階層に挿入される
  • singleton_classメソッドはレシーバのための特異クラスを返す
  • singleton_methodsメソッドは特異クラスに格納された特異メソッドを返すメソッド

コード書いて実験するのがわかりやすいですね。

class Hoge
  def foo
    puts 'bar'
  end
end

hoge0 = Hoge.new
hoge1 = Hoge.new
hoge2 = Hoge.new
def hoge2.foo
  puts 'piyo'
end

hoge1.foo
#=> bar
hoge2.foo
#=> piyo

# 特異クラス
p hoge0.singleton_class
#=> #<Class:#<Hoge:0x007fa93d81c480>>
p hoge1.singleton_class
#=> #<Class:#<Hoge:0x007fa93d81c458>>
p hoge2.singleton_class
#=> #<Class:#<Hoge:0x007fa93d81c430>>
### 全部違う(それはそう)

# 特異メソッド
p hoge1.singleton_methods
#=> []
p hoge2.singleton_methods
#=> [:foo]

superのふるまいが複数あることに注意する

  • 継承階層の上位メソッドをオーバーライドする際はsuperキーワードで上位メソッドを呼び出せる
  • 引数・括弧なしでsuperを呼ぶと呼び出し元のメソッドに渡された全ての引数を渡してオーバーライドされるメソッドの呼び出しをするのと同じ。暗黙的な挙動のため注意。
  • 引数を渡したくない場合は super() のように括弧をつける

基本的に括弧つけた方がよさげです。

サブクラスの初期化にはsuperを呼び出す

  • Rubyのサブクラスのinitializeメソッドは自動的にスーパークラスのinitializeのメソッドを呼び出さない。
  • initializeメソッドも通常のメソッドルックアップ規則が当てはまる(継承階層をたどる)
  • 明示的にinitializeメソッドを定義するときはinitializeメソッド内でsuperを呼ぶ。initialize_copyとかでも同様。

紛らわしい構文に注意

  • Rubyはメソッド名の末尾に "?", "!", "=" 等が使えるが、 "=" は構文的に別なので注意(セッターメソッド)
  • 代入と異なり、セッターメソッドは明示的なレシーバが必要
hoge = 1 # 代入
foo.bar = 2 # セッターメソッド呼び出し
self.a = 3 # セッターメソッド呼び出し
  • セッターメソッド以外は明示的にレシーバ指定しなくてOKなので、不要なselfを連発してはいけない。

続きます。

OSSへContributionする際のPull Request出す手順とか

最近PhoenixとかElixir面白そうだなーと思って勉強しているんですが、 PhoenixのCONTRIBUTING.md の説明がOSS Contribution guideとして分かりやすかったのでメモ。

github.com

詳細は、上記リンク読むとわかります。コマンドとかも載ってます。

要点をまとめると以下です。

  • Repositoryをforkする
  • fork元を "upstream" 、(fork先を"origin")にする
  • fork先のRepositoryのmasterに変更をcommitしない。feature branchを切ってmasterはupstreamに追従させる。
  • PR送る前にローカルでtestを走らせる
  • upstream/masterをmergeしちゃだめ絶対。rebaseする。

あとはプロジェクトごとに CONTRIBUTING.md とか CODE_OF_CONDUCT.md があるので必ず目を通しましょう。

Elixirはコード読んでる途中でtypo見つけて直したので晴れてContributorになりました。EilxirのContributorです、どうも。

github.com

結構いろんなOSSのコード読みつつFix typo系のPR送って生活してるんですが、Phoenixtypoは見つけられませんでした。Contribution guideもしっかりしてるのでコードレビューきっちりやってる印象です。