きゃまなかのブログ

新卒6年目の WEB エンジニアです。 直近3年間は大規模システムの運用を担当していました。 今期から開発チームに移動になって、Java を勉強しています。 好きな言語が Ruby なので、Ruby 系の記事が多いです。

【Ruby on Rails】rubocop と pre-commit を利用して git commit 時にコーディングチェックを行う

概要

Rails アプリケーションに rubocop と pre-commit という gem をインストールして、git commit 時にコーディングチェックを行うようにします。

もし、コーディング規約に準拠しないコードがあった場合はコミットが中断されます。

rubocop  とは

rubocop とは Ruby で書かれたソースコードを解析して、コーディング規約に準拠していない部分を教えてくれるツールです。

Ruby 初心者でも rubocop を利用すれば綺麗なコードに早変わりします。

github.com

使い方

まずは rubocop をインストールします。(又は Gimfile に記載してください)

$ gem install rubocop

rubocop コマンドの引数にコーディングチェックしたい .rb ファイルを指定して実行します。

すると、以下の様にコーディング規約に違反している部分を教えてくれます。

$ rubocop test.rb
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected

今回は3箇所コーディング規約に違反している部分が見つかりました。

 

実は rubocop には便利なオプションがあり、-a オプションを付けると、コーディング規約に違反したコードを自動で修正してくれます。

# 修正前
$ cat test.rb
array = ['aaa', 'bbb', 'ccc']

array.each { |i|
  if i =~ /^a/
    puts i
  end
}
# -a オプションで自動修正
$ rubocop -a test.rb
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: [Corrected] Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: [Corrected] Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: [Corrected] Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected, 3 offenses corrected
# 修正後
$ cat test.rb
array = %w[aaa bbb ccc]

array.each do |i|
  puts i if i =~ /^a/
end

rubocop さんがソースコードを良い感じに直してくれたのが分かります。

注意点

しかし rubocop のコーディング規約には少々使いにくい部分もあります。

例えは、日本語のコメントを書いたり、一行に 80 文字以上書いたりすると注意を受けます。

日本人なので日本語のコメントの方が分かりやすいと思いますし、一行にかけるコードも 80 文字制限は少々厳し気がします。

そういう場合は .rubocop.yml を用意します。

このファイルを用意すると、rubocop コマンドを実行した場合に .rubocop.yml に書かれたルールがデフォルトのルールを上書きする形で適応されるます。

ルールが適応されるのは、.rubocop.yml が置かれたディレクトリよりも下の階層になります。

$ vim .rubocop.yml
# 日本語のコメントを OK とする
Style/AsciiComments:
  Enabled: false

# 一行に 120 文字まで書いても OK とする Metrics/LineLength: Max: 120

コーディング規約とは自分たちが開発するプロジェクトにおいて、誰が開発してもプログラミング品質が均一になるために守るべきルールです。

デフォルトのルールに従えば世界共通の認識になるかもしれませんが、ある程度は自分たちのプロジェクトが開発しやすいようにカスタマイズしても問題ないと思います。

pre-commit とは

pre-commit とはコミット時に実行して欲しい処理を記載したものです。

実行後の終了ステータスが 0 (成功) でないと、コミットが中断するようになっています。

参考:Git - Git フック

github.com

使い方

まずは pre-commit をインストールします。(又は Gemfile に記載してください)

$ gem install pre-commit

以下のコマンドを実行すると対象レポジトリに .git/hooks/pre-commit というファイルが作成されます。

$ pre-commit install
Installed /home/ec2-user/.rbenv/versions/2.4.0/lib/ruby/gems/2.4.0/gems/pre-commit-0.38.1/templates/hooks/automatic to .git/hooks/pre-commit

# ファイルを確認
$ ls -la .git/hooks/
total 56
drwxrwxr-x 2 ec2-user ec2-user 4096 Jun 19 20:42 .
drwxrwxr-x 7 ec2-user ec2-user 4096 Jun 19 20:43 ..
-rwxrwxr-x 1 ec2-user ec2-user  478 Jun 19 20:40 applypatch-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user  896 Jun 19 20:40 commit-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user  189 Jun 19 20:40 post-update.sample
-rwxrwxr-x 1 ec2-user ec2-user  424 Jun 19 20:40 pre-applypatch.sample
-rwxr-xr-x 1 ec2-user ec2-user 1104 Jun 19 20:42 pre-commit # 新しく作成された
-rwxrwxr-x 1 ec2-user ec2-user 1642 Jun 19 20:40 pre-commit.sample
-rwxrwxr-x 1 ec2-user ec2-user 1348 Jun 19 20:40 pre-push.sample
-rwxrwxr-x 1 ec2-user ec2-user 4898 Jun 19 20:40 pre-rebase.sample
-rwxrwxr-x 1 ec2-user ec2-user  544 Jun 19 20:40 pre-receive.sample
-rwxrwxr-x 1 ec2-user ec2-user 1239 Jun 19 20:40 prepare-commit-msg.sample
-rwxrwxr-x 1 ec2-user ec2-user 3610 Jun 19 20:40 update.sample

 

しかし、これだけではまだ git commit 時に rubocop が呼び出される様な設定になっていません。

$ pre-commit list
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : common rails # 何も設定されていない
Enabled   checks   : common rails # 何も設定されていない
Evaluated checks   : tabs nb_space whitespace merge_conflict debugger pry local jshint console_log migration
Default   warnings :
Enabled   warnings :
Evaluated warnings :

# 以下のコマンドで git commit 時に rubocop が呼ばれる様にします
$ git config pre-commit.checks rubocop

$ pre-commit list
Available providers: default(0) git(10) git_old(11) yaml(20) env(30)
Available checks   : before_all ci coffeelint common console_log csslint debugger gemfile_path go go_build go_fmt jshint jslint json local merge_conflict migration nb_space pry rails rspec_focus rubocop ruby ruby_symbol_hashrockets scss_lint tabs whitespace yaml
Default   checks   : rubocop # rubocop が実行される様になった
Enabled   checks   : rubocop # rubocop が実行される様になった
Evaluated checks   : rubocop # rubocop が実行される様になった
Default   warnings :
Enabled   warnings :
Evaluated warnings :

 

実際に設定が反映されているかコミットを試して見ます。

$ git commit
pre-commit: Stopping commit because of errors.
Inspecting 1 file
C

Offenses:

test.rb:1:9: C: Style/WordArray: Use %w or %W for an array of words.
array = ['aaa', 'bbb', 'ccc']
        ^^^^^^^^^^^^^^^^^^^^^
test.rb:3:12: C: Style/BlockDelimiters: Avoid using {...} for multi-line blocks.
array.each { |i|
           ^
test.rb:4:3: C: Style/IfUnlessModifier: Favor modifier if usage when having a single-line body. Another good alternative is the usage of control flow &&/||.
  if i =~ /^a/
  ^^

1 file inspected, 3 offenses detected

pre-commit: You can bypass this check using `git commit -n`

すると、先ほどの rubocop のエラーが表示され、commit が中断された事が分かります。

Stopping commit because of errors. と書いてありますね。

設定を解除したい場合

設定を解除したい場合、.git/hooks/pre-commit ファイルを削除するかリネームしてください。

$ rm .git/hooks/pre-commit # 削除
# 又は
$ mv .git/hooks/pre-commit .git/hooks/pre-commit.ruby.sample # リネーム

まとめ

rubocop と pre-commit の gem を導入して、git commit 時にコーディングチェックを行うようにしました。

自分は開発環境で複数のアプリケーションを開発したり、一次的に git clone して修正したりしたい人なので、Gemfile に記載して特定の Rails アプリケーションだけ rubocop と pre-commit を導入せずに gem install でサーバに導入してしまいました。

最低でも rubocop は導入して自分の書いたコードをリファクタリングする癖をつけると良いと思います。

忘れっぽい人は pre-commit も導入して強制化するのも手だと思います。

 

【Ruby on Rails】オートコンプリート機能付き検索フォームを実装する

概要

Ruby on Rails でオートコンプリート機能付きの検索フォームを実装してみます。

gem の紹介

今回はこちらの gem を利用します。

rails4-autocomplete という名前ですが、rails5 でも利用できます。

github.com

対応手順

1. Gemfile の修正

初めに Gemfile に rails4-autocomplete を追加します。

Gemfile に記載したら bundle install でパッケージをインストールします。

$ vim Gemfile
gem 'rails4-autocomplete' # 追記

# Gemfile に書かれた gem パッケージと、その依存パッケージをインストールします
$ bundle install --path vendor/bundle

2. application.js の修正

次に app/assets/javascripts/application.js に autocomplete-rails.js を利用する旨の記述をします。

これで rails4-autocomplete に含まれている  autocomplete-rails.js を利用できるようになります。

//= require jquery
//= require jquery_ujs
//= require autocomplete-rails # 追加
//= require turbolinks
//= require_tree .

3. Controller の修正

ここまで完了したら、オートコンプリート機能を利用したいクラスの Controller に以下のアクションを追加します。

引数には取得したい model 名と column 名をセットで記載します。

他のクラス又は全てのクラスで利用できるように application_controller.rb に書いてしまうのも良いと思います。

今回、自分はそうしてます。

class ApplicationController < ActionController::Base

  protect_from_forgery with: :exception
# 第 1 引数 => model名 :user (必須)
# 第 2 引数 => column名 :name (必須)
# 第 3 引数 => オプション full: true (任意) autocomplete :user, :name, full: true # 追加 end
full オプション

デフォルトは前方一致検索のみです。全検索(部分一致検索・後方一致検索)したい場合には設定を true にします。

例えば、ruby on rails と言うワードを検索したい場合、デフォルトでは ruby で検索した場合しかヒットしません。

このオプションを true にすると on や rails で検索した場合もヒットするようになります。

他にもオプションがあるので確認してください。

https://github.com/peterwillcn/rails4-autocomplete#options

4. ルーティングの設定

先ほど作成したアクション名を使って、ルーティングの設定を追加します。

$ vim config/routes.rb

Rails.application.routes.draw do
  resources :users do
    get :autocomplete_user_name, on: :collection # 追加
  end
end

 

これで /users/autocomplete_user_name と言うパス (URL) が作成されます。

curl コマンドを叩いて動作確認をしてみます。

すると以下のようにユーザー名一覧の配列が取得できます。

※ この時点で既に users テーブルにデータを格納しておく必要があります。

オートコンプリート機能は Ajax 処理で、検索フォームに何かワードを入れるとバックエンドでこの URL を叩いて情報を取得してフロントエンドに表示しています。

$ curl 'http://localhost/users/autocomplete_user_name'

["aaaaaaaaaa","bbbbbbbbbb","cccccccccc","dddddddddd","aaaaabbbbb","cccccddddd","aaaaaccccc","bbbbbddddd"]               

5. View の修正

最後にオートコンプリート機能付きの検索フォームを表示したい箇所に以下の View ヘルパーを設置します。

<%= autocomplete_field_tag 'user[name]', nil,  autocomplete_user_name_users_path %>

 

検索フォームが表示されると思いますので、何かワードを入力してオートコンプリート機能が備わっているか確認してください。

f:id:kyamanak83:20180527221847p:plain

ただし、この時点ではただの検索ボックスみたいなもので Controller 側に入力されたデータを渡す機能がありません。

6. form の実装

検索フォームで入力された値を Controller に渡せるようにします。

今回は users_path('/users')に入力データを渡します。

Rails は URL にアクセスする HTTPメソッドによって挙動が変わります。

'/users' に POST でデータを渡すと新規作成になってしまうので、GET で渡すようにしてください。

また Rails の form_tag はデフォルトではパラメーターに utf8=✓ が付与されます。

こちらも不要だと思ったので enforce_utf8: false で除外しました。

<%= form_tag(users_path, method: :get, enforce_utf8: false) do %>
  <%= autocomplete_field_tag 'user[name]', nil,  autocomplete_user_name_users_path %>
<% end %>

7. 検索機能の実装

検索フォームで入力されたデータを Controller で受け取ります。

params[:user][:name] に入力されたデータが入っています。

Controller の処理は以下のようにしました。

  • 検索クエリがあれば該当するユーザーを返します。
  • 検索クエリがなければ全ユーザーを返します。
# GET /users
# GET /users.json
def index
# 検索クエリ: params[:user][:name] if params[:user] && params[:user][:name] user_name = params[:user][:name] @users = User.where("name LIKE '%#{user_name}%'") else @users = User.all end end

 

オートコンプリート機能が付いていれば、検索フォームから入力されたデータは補完された上で Controller 側に渡されるので、完全一致でも良いかもしれません。

※ User.where(name: user_name) でも良いと言う事です。

まとめ

オートコンプリート機能の付きの検索フォームを実装しました。

gem を利用すればフロントエンド側は autocomplete_field_tag を記載するだけで簡単に実装する事ができます。

バックエンド側は検索クエリに一致するデータを DB から取得してフロントエンド側に返す処理が必要です。

検索ワードがある程度限定された検索フォームを作成する場合は、是非導入してみてください。

【Ruby on Rails】Mysql2::Error: Data too long for column 'xxxxx' at row 1:

概要

自分が担当しているシステムでユーザーからデータの保存ができないとお問い合わせを受けたことがあります。

システムが高負荷になっていた訳でもなく、そのユーザー以外は普通に利用できていたので、最初は原因が分からなかったのですが、ログを調べて見たらあるエラー吐かれていました。

今回、そのエラーについて調べたときの事をまとめました。

エラー文言

以下のエラーが発生して DB へのデータ挿入に失敗していました。

ActiveRecord::ValueTooLong(Mysql2::Error: Data too long for column 'xxxxx' at row 1: INSERT INTO 'table' ('column1', 'column2', 'column3'....))

原因

対象カラムのデータ型が varchar (255) となっており、ユーザーが挿入しようとしたデータが 255 バイト以上だったため、カラムに格納できる最大バイト数を超えてエラーとなっていました。

解決策

解決策としては 2 つの方法を考えました。

  • フロントエンド側で入力フォームの文字数制限を入れる
  • 対象カラムのデータ型を変更する

最終的にはどちらも実装した方が良いのですが、今回はユーザーがデータを保存できないエラーを解決したかったので、取り急ぎ対象カラムのデータ型を変更する方法をとりました。

余力があれば、フロントエンド側にテキストボックスの文字数制限を入れようと思います。

また今回、例外処理が入っていなかったので、例外処理の記述も追加したいと思います。

何のデータ型にすれば良いのか?

そもそも varchar (255) にした理由は rails generate scaffold でカラムを生成した際に、string 型で作ろうとしたらデフォルトで設定されたデータ型だったからです。

参考:【Rails・MySQL】MySQLのデータ型とRailsのマイグレーションファイルのデータ定義の対応まとめ

 

まず初めに変更を考えたデータ型は text 型です。

text 型で格納できるバイト数は 2 の 16 乗なので 65536 バイトとなります。

参考:MySQL :: MySQL 5.6 リファレンスマニュアル :: 11.7 データ型のストレージ要件

 

しかし、今まで最大 255 バイトで十分だったカラムにいきなり 65536  バイトも設定する必要があるでしょうか?

そこで varchar の最大バイト数を変更する方法を取りました。

具体的には varchar (255) を varchar (500) としました。

mysql> alter table 'table名' modify 'column名' varchar(500); 

データ型変更は既存のデータに影響を与える処理なので、本来であれば慎重に行わないといけません。

しかし、今回の変更は最大長を 255 バイトから 500 バイトに変更しただけなので問題が発生する可能性は極めて少ないと思われます。

日本語文字って何バイトか?

ちなみに日本語って何バイトなんだろうと疑問に思いました。

文字コードによって違うようで、全角 1 文字 2 バイトなのは EUC-JP や Shift_JIS を使ったときのようです。

現在主流の UTF-8 を使うと 2 ~ 4 バイトで表されるようです。

参考:マルチバイト文字を扱う際に気をつけること

例外処理を入れる

最大長を 500 バイトに更新しましたが、それ以上のデータを挿入された場合の為に例外処理を書いておきます。

begin
# ** DB にデータを挿入する処理 **
# 例えば
# # @user.create
# # @user.save
# # など rescue ActiveRecord::ValueTooLong => e  # ** 例外が発生した場合の処理 **
# 例えば
# # エラーページにリダイレクト
# # return redirect_to '/500.html'
# # エラーログを出力する
# # log.error(e.message)
# # インスタンス変数を使って View 側にメッセージを表示
# # @is_toolong_value = true
# # など end

まとめ

rails generate scaffold で strign 型のカラムを作成するとデフォルトでは varchar (255) となります。

ユーザー入力では開発者が想定外のものを入力されることが稀にありますので、フロントエンド側でしっかり制御したり、格納できるデータは余裕をもって大きい値にしておく事をお勧めします。

【Apache】502 Bad Gateway proxy: error reading status line from remote server

概要

内部ネットワークからインターネットへのアクセスをプロキシしているサーバで稀に 502 Bad Gateway のエラーを返していることがありました。

リクエスト元のサーバではリトライ処理を入れていたので問題は無かったのですが、通常時は 200 で成功しているリクエストが、突然 502 で失敗してしまうのは謎でした。

今回、その時に調査したことをまとめました。

エラー文言

リモートサーバのステータスラインの読み込みに失敗しました。

proxy: error reading status line from remote server...

ステータスラインとは HTTP/1.1 200 OK のことです。

HTTP の Version と HTTPステータスコードとメッセージが一行に記載されています。

おそらくリモートサーバにプロキシ出来ていないんだと思います。

原因

これは Apache 2.1 以降の機能で ProxyPass を利用する際に、コネクションプールを使うようになったのが原因です。

コネクションプールとは、リクエストの度にコネクションを生成するのではなく、コネクションを事前に用意しておいて再利用しようと言う考え方(仕組み)のことです。

コネクションを再利用することで、コネクションを生成する時にかかる(接続に必要とされる)オーバーヘットをカットして双方の負荷を軽減してくれます。

このエラーは事前に用意されたコネクションを使って接続しようとしたら、接続中にコネクションを切られてしまい接続できなかった時に発生するエラーだと思われます。

参考:mod_proxy - Apache HTTP サーバ バージョン 2.4

解決策

コネクションプールを使わない設定を追加します。

SetEnv proxy-initial-not-pooled 1

Apache の公式ドキュメントにも proxy: error reading status line from remote server のエラーが起きたらこれを設定してと書いてあります。

ただし、注意点としてコネクションプールを使わない場合、リクエストの度にコネクションを生成するので、パフォーマンスが劣化するらしいです。

参考:mod_proxy_http - Apache HTTP Server Version 2.4

パフォーマンス確認

パフォーマンスが劣化すると書いてあったので、proxy-initial-not-pooled の設定を追加したサーバでしばらく様子を見ました。

サーバの CPU 使用率やメモリ使用率は設定を追加した前後で変化はありませんでした。

Apache のレスポンスタイムも特に遅くなったりせず、設定を追加したことによって他のエラーが発生するなどの事象も起きませんでした。

おそらく、影響度はサーバの負荷(リクエスト数)によって違うんだと思います。

ちなみに、今回対応したサーバの秒間リクエスト数は 50 以下でした。

パフォーマンスが劣化した場合は、プロキシサーバを増設して負荷を分散できればパフォーマンスの劣化も防げるかも知れませんね。

 

【Apache】http 通信を https 通信に変換してプロキシする

概要

内部ネットワークのサーバがインターネット上のサーバにアクセスする場合、セキュリティ上の理由でプロキシサーバを経由してアクセスする場合が多いと思います。

参考:プロキシサーバーを使うと、なぜセキュリティが向上するのか?

tech.nikkeibp.co.jp

 

最近では Google が推奨していることもあってか、常時 SSL/TLS のサイトが増えています。

常時 SSL/TLS とは、個人情報を扱うページだけではなくサイト全体を SSL/TLS 化しましょうと言う取り組みです。

www.websecurity.symantec.com

そのため、インターネット上で公開している API のサイトなども https に切り替わってきています。

今回、今まで http でアクセスしていたサイトが https に切り替わるため急遽対応した時のことをまとめました。

本来であれば、リクエストしている箇所のソースを修正するのが正しいやり方だと思うのですが、時間がなく修正したパッケージをリリースするサーバ台数も多かったので、プロキシサーバの設定を更新する方法を取りました。

必要なパッケージ

Apache24 で SSL/TLS Proxy を利用するには mod24_ssl と言うパッケージが必要となります。

$ sudo yum install httpd24 mod24_ssl

mod24_ssl のパッケージがインストールされていない環境で https のサイトにプロキシしようとすると以下のエラーが発生します。

Invalid command 'SSLProxyEngine', perhaps misspelled or defined by a module not included in the server configuration

対応方法

SSL/TLS Proxy を実現するには、設定ファイルの中で SSLProxyEngine on を記載します。

参考:mod_ssl - Apache HTTP Server Version 2.4

$ sudo vim /etc/httpd/conf/httpd.conf
== 追加 ==
SSLProxyEngine on
ProxyPass /sha2 https://sha256.badssl.com/
ProxyPassReverse /sha2 https://sha256.badssl.com/

上記の httpd.conf の設定は /sha2 配下に来たアクセスを、sha256.badssl.com のサイトにプロキシします。 

 

もしも SSLProxyEngine on の設定なしで、SSL/TLS Proxy しようとすると以下のエラーが発生して Proxy には失敗します。

$ tail /etc/httpd/logs/error_log
[Sun Apr 29 11:00:41.577274 2018] [ssl:error] [pid 3512] [remote XXX.XXX.XX.XXX:443] AH01961: SSL Proxy requested for ip-XXX-XX-XX-XXX.ap-northeast-1.compute.internal:80 but not enabled [Hint: SSLProxyEngine] [Sun Apr 29 11:00:41.577315 2018] [proxy:error] [pid 3512] AH00961: HTTPS: failed to enable ssl support for XXX.XXX.XX.XXX:443 (sha256.badssl.com)

余談

SSLProxyEngine on が記載された設定ファイルに http の proxy と https の proxy が混ざり合っていても問題ありません。

SSLProxyEngine on
# https の proxy
ProxyPass /hoge https://hogehoge.com/
ProxyPassReverse /hoge https://hogehoge.com/
# http の proxy
ProxyPass /fuga http://fugafuga.com/
ProxyPassReverse /fuga http://fugafuga.com/

こんな記載方法でも問題なく Proxy できます。

 

【備忘録】HBase とは?HBase の構成とデータ構造について!

概要

新しく HBase を扱うサーバの担当になりました。

IT 業界ではデータ利活用の分野が活発になっており、ビッグデータを安全かつ高速に扱える HBase のようなプラットフォームは今後ますます注目されると思います。

しかし、自分は HBase について全く知識が無いので、今回 HBase について調べて、重要だと思う点を忘れないようにまとめました。

HBase とは?

HBase とは Google のほとんどのサービスを支える基盤である BigTable を元に設計されたオープンソースソフトウェア (OSS) です。

NoSQL の一つであり、Hadoop の分散ファイルシステムである HDFS 上で動作する列指向型の分散データベースです。 

低レイテンシーでデータの読み書きを行うことに優れており、強い一貫性を保証してくれます。

ネットワーク障害が発生した場合でも、通信可能なサーバだけでシステムを稼働し続けることができる耐性を持っています。(ただし処理性能が落ちる可能性あり)

構成

HBase は Master サーバと Region サーバの 2 種類のサーバで構成されており、Region サーバを増設することで簡単にスケールアウト出来る仕組みとなっています。

HBase のデータはリージョンと呼ばれる単位で分割されており、リージョンの扱い方でサーバの種類が分けられます。

Master サーバ

HBase の全データ(リージョン)がどの Region サーバに保存されているかメタ情報を管理しているサーバです。

Region サーバへのリージョンの割り当てや、Region サーバの障害検知も行います。

ちなみにこれらの仕事は Master サーバ内で動いている ZooKeeper と言うソフトウェアが担当しています。

ZooKeeper は HBase 以外にも多くの分散アプリケーションで利用されているソフトウェアです。

Region サーバ

Master サーバから割り当てられたリージョンを保存しているサーバです。

クライアントはまず Master サーバ(正確には ZooKeeper)に問い合わせをして、対象のデータ(リージョン)を保存している Region サーバの位置を教えてもらいます。

その後、その Region サーバがクライアントからのリクエストを担当します。

リージョンは必ず単一の Region サーバで管理されており、読み書きを行うためデータの一貫性が保たれます。

Region サーバがダウンした場合は、割り当てられていたリージョンは他の Region サーバにフェールオーバーされます。(この作業は Master サーバが行う)

ただし、フェールオーバーが完了するまで、クライアントは対象リージョンへの読み書きを行うことができません。

データ構造

HBase のテーブルには型がなく、バイト配列(byte[ ])で格納されています。

HBase の行は一意な行キーで昇順にソートされており、テーブルの値を読み書きする際にはこの行キーを介して行います。

前述しましたが、HBase で管理するデータは一定範囲毎にリージョンと言う単位で分割されており、テーブルには複数のリージョンが存在します。

また、HBase の列も列ファミリと言う単位でグループ化されています。

テーブルのデータはリージョン毎に分割されて、更に列ファミリ毎に分割されてファイルに吐き出されます。

ファイルは別でも同じリージョンであれば、同じ Region サーバに保存されます。

f:id:kyamanak83:20180324162702p:plain

HBase のデータ(セルの値)にはそれぞれタイムスタンプが付与されており、バージョンを管理しています。

ファイルには以下のようなフォーマットで保存されます。

行(行キー):列ファミリ:列:タイムスタンプ:値

まとめ

HBase 初心者の自分が、HBase の概要や構成など基本的な部分を調べて、重要だと思う点をまとめました。

今後、導入や運用を開始して思うことがあれば追記したいと思います。

自分と同じ HBase 初心者がこの記事を読んで、なんとなく HBase について理解して頂けたら幸いです。

 

【Ruby on Rails】whenever を使って定期的にバッチ処理を行う

概要

Ruby on Rails で定期的にバッチ処理を実行する方法をまとめます。

定期的に処理を実行したい場合、クーロンを利用するのが定石かと思います。

クーロンを実行するには、crontab に設定を記載する必要があります。

Ruby on Rails の場合、lib ディレクトリ内に置かれたモジュールや Rakefile に書かれたタスクを定期的に実行したいと思う事が多いと思います。

しかし、これらを処理を crontab に記載するのは慣れてないと少々難しいです。

何故なら、Rakefile のタスクだったり、モジュールの実行は、Rails アプリケーションの ROOT ディレクトリで実行する必要があるからです。

更に Rails の環境ごとに実行だったり、ログ出力だったりを考えると、どんどん複雑になってきます。

そこで、これらの設定を簡単に crontab に自動生成してくれる whenever と言う便利な gem があるので、それの使い方をまとめました。

導入方法

gem インストール

github.com

Gemfile に whenever を追記してインストールしてください。

$ vim Gemfile
gem 'whenever', require: false

# インストール
$ bundle install --path vendor/bundle

設定ファイル生成

Rails アプリケーションの ROOT ディレクトリで以下のコマンドを実行して下さい。

そうすると config ディレクトリ内に schedule.rb と言うファイルが生成されます。

この schedule.rb がクーロンの設定を記載するファイルです。

$ bundle exec wheneverize .
[add] writing './config/schedule.rb' # schedule.rb ファイルが生成
[done] wheneverized!

定期的に実行したい処理を記載

例として 2 つのバッチ処理を用意しました。

  • 毎週月曜日 AM 4:30 と PM 6:00 に Rakefile に記載された hoge タスクを実行する
  • 4 日おきに Hoge モジュールの hoge メソッドを実行する

crontab の書式と違って、schedule.rb の書式の方が書きやすいし、読みやすいと思います。

また、ruby ファイルなのでメソッドを使えたり、変数を使えたりするのが便利です。

$ vim config/schedule.rb
# Use this file to easily define all of your cron jobs.
#
# It's helpful, but not entirely necessary to understand cron before proceeding.
# http://en.wikipedia.org/wiki/Cron

require File.expand_path(File.dirname(__FILE__) + "/environment")

rails_env = Rails.env.to_sym
rails_root = Rails.root.to_s

# environment は設定しないと production になってしまう set :environment, rails_env set :output, "#{rails_root}/log/cron.log"
# 毎週月曜日 AM 4:30 と PM 6:00 に rake タスクを実行する every :monday, at: ['4:30 am', '6:00 pm'] do rake 'hoge' end # 4 日おきに Hoge モジュールの hoge メソッドを実行する every 4.days do runner 'Hoge.hoge' end

詳しい書き方は README.md を参考にして下さい。

whenever/README.md at master · javan/whenever · GitHub

設定の反映・削除など

設定ファイルが出来上がったら whenever のコマンドを実行して、crontab を生成します。

設定の確認
# 生成される crontab が問題ないか確認
$ bundle exec whenever
30 4 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 18 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 0 1,5,9,13,17,21,25,29 * * /bin/bash -l -c 'cd /var/workspace/rails_app && bundle exec bin/rails runner -e development '\''Hoge.hoge'\'' >> /var/workspace/rails_app/log/cron.log 2>&1'
設定の反映
$ bundle exec whenever --update-crontab
[write] crontab file updated

# crontab に設定が反映されたことを確認
$ crontab -l
# Begin Whenever generated tasks for: /var/workspace/rails_app/config/schedule.rb at: 2018-02-18 16:52:03 +0900
30 4 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 18 * * 1 /bin/bash -l -c 'cd /var/workspace/rails_app && RAILS_ENV=development bundle exec rake hoge --silent >> /var/workspace/rails_app/log/cron.log 2>&1'

0 0 1,5,9,13,17,21,25,29 * * /bin/bash -l -c 'cd /var/workspace/rails_app && bundle exec bin/rails runner -e development '\''Hoge.hoge'\'' >> /var/workspace/rails_app/log/cron.log 2>&1'

# End Whenever generated tasks for: /var/workspace/rails_app/config/schedule.rb at: 2018-02-18 16:52:03 +0900
設定の削除
$ bundle exec whenever --clear-crontab
[write] crontab file

# crontab から設定が削除されたことを確認
$ crontab -l
# 何も出てこなければOK

 

生成された crontab を見ると以下の処理をワンライナーで実行してくれているのが分かります。

  1. Rails アプリケーションの ROOT ディレクトリに移動
  2. 環境変数をセット (rake の場合) or 環境を指定 (rails runner の場合)
  3. コマンドを実行
  4. 標準エラーを標準出力にマージしてログ出力

まとめ

whenever を利用する事で Rakefile に記載されたタスクや、lib ディレクトリ内で管理されたモジュールを簡単に呼び出して crontab に記載する事ができました。

複雑なワンライナーを crontab に自動生成してくれる点以外にも、以下のメリットがあると思います。

  • schedule.rb の方が書式が見やすい・書きやすい
  • ruby ファイルなので凝った事ができる
  • 設定ファイルを config ディレクトリ内で一元管理できる

「設定ファイルを config ディレクトリ内で一元管理できる」メリットとは、crontab.conf などを別途作って GitHub で管理する場合、何処のディレクトリに配置すればいいのか悩みます。

schedule.rb ならば Rails アプリケーションの config ディレクトリ内に配置できるので、設定ファイルを一元管理する事もできて分かりやすいと思いました。

whenever では他にもメールを飛ばしたりなど、色んな機能があるので、是非 README.md を読んでみてください。

【Linux】xargs コマンドの使い方がよく分からない

概要

結構複雑な処理をサーバーサイドでやりたいなと思って検索すると xargs コマンドを利用するケースが多々見受けられます。

ただし、使う機会は多くないので、検索して見つけたコマンドをコピペして使っていたり、xargs コマンドが何者なのかよく理解していませんでした。

折角の機会なので xargs コマンドの使い方をまとめました。

xargs とは

前のコマンド (command1) の実行結果を標準入力 (stdin) から受け取って、次のコマンドの引き数に渡す (stdout) 仲介役をしてくれるコマンドです。

f:id:kyamanak83:20180129224458p:plain

参考:

www.atmarkit.co.jp

よく見る利用法

よく使われるのは、find コマンドと組み合わせて、名前検索でヒットしたファイルを標準入力から受け取って、削除する方法かな思います。

$ find . -name "*.log" | xargs rm -fv
'./access.log' を削除しました
'./error.log' を削除しました

自分はこのワンライナーで初めて xargs コマンドに触れました。

そして、これなら rm *.log だけで良くない?xargs コマンドを使うメリットってどこにあるの?って混乱したのを覚えています

確かに、このワンライナーと実行結果を見ると rm コマンド単体で問題ありません。

しかし、find コマンドは名前検索以外にも様々な条件でファイルを検索することが出来るので、xargs コマンドと組み合わせると作業効率の幅が広がります。

  • タイムスタンプで検索できる
  • パーミッションで検索できる
  • ファイルサイズで検索できる(空ファイルも検索できる)
  • 検索する階層を指定できる

ネットで検索した xargs コマンドのサンプルには、このようにメリットが感じられないサンプルも多々あります。

それを見て、こうやって使うのか!って鵜呑みにしたことが当時間違えでした。

高速に並列処理できる

find コマンドには exec オプションと言うものがあり、検索でヒットしたファイルを、exec オプション以下のコマンドに渡して実行することができます。

$ find . -name "*.log" -exec rm -fv {} \;
'./access.log' を削除しました
'./error.log' を削除しました

一見、xargs コマンドと同じですが、内部的な処理が違います。

exec オプションを使った場合
$ rm -fv access.log
$ rm -fv error.log

rm コマンドを 2 回叩いたのと同様です

xargs コマンドを使った場合
$ rm -fv access.log error.log

rm コマンドの引数に 2 つのファイルを指定したのと同様です。

この 2 つのコマンドは大量のファイル数を削除する場合に明確な差が出ます。

xargs コマンドを使った方が、複数のファイルを一度に削除してくれるので高速です。

参考:

qiita.com

Argument list too long の制限を受けない

大量のファイルを rm *.log で削除しようとすると、Argument list too long と怒られることがあります。

これはコマンドの引数の文字列が長すぎるせいです。

rm *.log を実行した場合、内部的には rm aaa.log bbb.log ccc.log ddd.log .... を実行しています。ファイル数が多い場合、この文字列はどうしても長くなります。

OS 毎に引数に指定できる文字数は決まっており、その文字数を超えるとエラーとなります。

以下のコマンドでその文字数を確認できます。

$ getconf ARG_MAX

xargs コマンドは、この ARG_MAX の値を見て良い感じに処理を分割して実行してくれるので文字数の制限に引っかからないらしいです。

参考:

qiita.com

ドライランオプション

xargs コマンドには ドライランオプションがあります。

前のコマンドの出力結果が不確かな場合や、自分が意図した処理を実行してくれるが心配な場合、p オプションを付けることで生成されるコマンドを確認することができます。

$ find . -name "*.log" | xargs -p rm -fv
rm -fv ./error.log ./access.log?... # 上のコマンドはこのコマンドを叩くのと同様

p オプションは生成されるコマンドを確認するだけで、実際には実行されません。

確認して問題なければ p オプションを外して実行してください。

引数の場所を指定する

rm コマンドを実行する場合、引数が 1 つなので問題ありませんが、cp コマンドや mv コマンドを実行する場合は引数が 2 つ必要になります。

そのような場合、i オプションを使って、xargs コマンドで受け取った値を引数に指定する位置を決める必要があります。{} に受け取った値が入ります。

$ find . -name "*.log" | xargs -i cp {} /tmp/.

これは find コマンドで受け取ったファイルを /tmp/ 以下にコピーする例となります。

便利なワンライナー

個人的に良く使うワンライナーなので紹介します。

Ruby on Rails の様なフレームワークを好んで使う人には使い道があると思います。

/var/rails_app/ 配下で hogehoge という文字列を全て fugafuga に置換してくれます。

$ grep -rl 'hogehoge' /var/rails_app/ | xargs perl -i -pe "s/hogehoge/fugafuga/g" 

例えば、1 つのファイルで共通メソッドを管理していたとします。

※ Ruby on Rails の場合は app/controllers/application.rb を想像して下さい。

もし、共通メソッドの名前を変更した場合、そのメソッドを呼び出している箇所を全て探して修正する必要があります。

何処で呼び出しているか分からない場合、まずは grep コマンドで検索しますよね?

その後、検索でヒットしたファイルをいちいち vim とかで開いて修正するのは面倒臭いです。

この一連の作業を一発でやってくれるのが、このワンライナーです。

最後に

今回は削除(rm コマンド)をベースに話しましたが、他のコマンドの引数に渡すことも良くやると思います。

xargs コマンドを使えるようになって、最近はログ調査で、その便利さをしみじみ感じています。

サーバーサイドのエンジニアは作業効率の幅が広がるので、良く分からないと思っている人も使い始めてみることをオススメします。

【Linux】コマンドの標準出力を色付けして運用を楽しくする

概要

単色の標準出力だと見づらいですし、運用していて楽しくないので、コマンドの標準出力を色付けしてくれる拡張パッケージをまとめました。

導入方法

拡張パッケージをインストールして、その後  bashrc か bash_profile に標準コマンドの alius を設定してサーバに反映させようと思います。

拡張パッケージ

exa

Rust で書かれた ls コマンドの出力を色付けしてくれるパッケージです。

github.com

はじめに Rust 系製品のパッケージマネージャーである cargo をインストールします。

$ curl https://sh.rustup.rs -sSf | sh
1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
1 # 1 を入力して Enter

# インストール確認
$ rustc --version
rustc 1.23.0 (766bd11c8 2018-01-01)

 参考:rustup のインストール方法

※ 環境によって cmake など必要なパッケージがない場合には別途インストールして下さい。exa インストール時に足りないパッケージがエラー出力されると思います。

# cargo 経由でインストール
$ cargo install exa
# インストール確認 $ exa --version exa v0.8.0

 

ls コマンドと exa コマンドの見易さを比較します。

ls コマンド

exa コマンド

f:id:kyamanak83:20180114164714p:plain

f:id:kyamanak83:20180114164728p:plain

 

-T オプションをつけると tree コマンドと同じ標準出力になります。

tree コマンド

exa コマンド ( -T オプション)

f:id:kyamanak83:20180114165738p:plain

f:id:kyamanak83:20180114165259p:plain

colordiff

diff の標準出力を色付けしてくれるパッケージです。そのままのネーミングですね。

https://www.colordiff.org/

$ sudo yum --enablerepo=epel install colordiff

 

colordiff コマンドの場合、追加された行が 、削除された行が で色付けされます。

f:id:kyamanak83:20180114173310p:plain

grc

ifconfig, dig, netstat, ping, ps, traceroute, df, free など運用に欠かせないコマンドの色付けを手助けしてくれます。 

github.com

色付けしてくれるコマンドは grc.bashrc ⬇︎ に記載されているコマンド全部です。

https://github.com/garabik/grc/blob/master/grc.bashrc

※ 環境によって python34 など必要なパッケージがない場合には別途インストールして下さい。grc コマンド実行時にエラー出力されると思います。

$ git clone git@github.com:garabik/grc.git
$ cd grc/
$ sudo ./install.sh

 

一部のコマンドの色付け結果を見てみます。

■ ps コマンド

f:id:kyamanak83:20180114180150p:plain

■ dig コマンド

f:id:kyamanak83:20180114180211p:plain

ccze

tail などと併用してログを色付けして出力してくれるパッケージです。

github.com

$ sudo yum --enablerepo=epel install ccze

ccze コマンドは tail コマンドの結果をパイプで受けて色付けします。

f:id:kyamanak83:20180114181610p:plain

source-highlight

less の結果を色付けしてくれるパッケージです。

GNU Source-highlight - GNU Project - Free Software Foundation (FSF)

$ sudo yum install source-highlight

 

はじめに bashrc か bash_profile に以下の内容を記載することをオススメします。

export LESS='-R'
export LESSOPEN='| /usr/bin/src-hilite-lesspipe.sh %s'

その後、いつも通り less でファイルを開くと色付きで見ることができます。

f:id:kyamanak83:20180114184534p:plain

標準コマンドのエイリアスを設定

毎回 grc だったり、ccze だったりを付けるのは面倒くさいので、標準コマンドのエイリアスにしてしまします。

exa コマンドも馴染みがないので ls コマンドのエイリアスにしてしまった方がいいと思います。

.bashrc か .bash_profile に記載して下さい。

/etc/bashrc に記載して全ユーザに適用するのもありだと思います!

# grc を clone した時に入っていた bashrc を読み込む
. ~/grc/grc.bashrc 
# alias dig='grc dig', alias ps='grc ps' などを全てやってくれる

export PATH="$HOME/.cargo/bin:$PATH"
export LESS='-R'
export LESSOPEN='| /usr/bin/src-hilite-lesspipe.sh %s'
tailc () {
    tail $@ | ccze -A
}
alias tail='tailc'
alias ls='exa'

最後に

単色の標準出力で長時間作業しているとストレスが溜まってしまうと思うので、是非導入できるサーバには導入して運用を楽にして下さい。

なによりも色付きの標準出力の方が作業していて楽しいと思います!

 

【Apache】graceful 再起動でホットデプロイを行う

概要

自分のお仕事は運用メインなので、リリース作業を行う機会が多いです。

正直、数年間同じ仕事をしているので、リリース作業は慣れましたし飽きました(苦笑)

そこで、少しでも今のリリース作業が簡略化できる方法が無いか調べてみました。

そこで見つけたのがホットデプロイです。

現在のリリース手順とは、ロードバランサから数台ずつサーバーをサービスアウトしてリリースして戻す(サービスイン)と言うものです。

リリース時にサーバーの再起動(詳しくは Apache の再起動)を行うため、ユーザー影響がないようにサービスアウトしています。

ホットデプロイを導入すると、アプリケーションの修正を行っても、サーバーの再起動を行わずに本番環境に反映させることが出来ます。

と言うことは、ロードバランサから数台ずつサービスアウトせずに、一気に全台リリース出来るようになりますよね??

実現すれば、かなり作業が簡略化されます。

自分が運用しているシステムは LAMP 環境が多いので、LAMP 環境でホットデプロイが出来るか調べてみました。

はじめに

LAMP 環境とは?

Web サービスを開発する際の環境の組み合わせで、それぞれの頭文字も取ったものです。

OS  Linux
Webサーバ  Apache
DB  MySQL
プログラミング言語  PHP ( Perl, Python )

参考:LAMP (ソフトウェアバンドル) - Wikipedia

多くの企業で導入実績があり、沢山のモジュールやプラグインが配布されているため、拡張性に優れている気がします。

全てオープンソースソフトウェア(無償)なので個人でも簡単に環境構築できます。

ホットデプロイとは?

アプリケーションに変更を加えた際に、サーバーのサービスアウトと再起動を行わずに運用環境に反映させる仕組みのことです。

詳しくは定義されていないので、サービスアウトと再起動なしで、アプリケーションの更新ができれば、なんでもホットデプロイと呼ばれる気がします。

参考:ホットデプロイとは - IT用語辞典 Weblio辞書

ホットデプロイ導入

最初は Ruby on Rails で良く使われている Unicorn みたいに Apache も出来るんじゃないかと思っていました。

誰かしら便利なモジュールを作っていると思ってました。けど無かったです。

調べてみたら、Apache でサービス停止をせずにプロセスを新しく立ち上げ直す方法があったのでそれを使おうと思います。

graceful で再起動

サービスの停止をせずに、アプリケーションの変更を取り入れて、プロセスを新しく立ち上げ直してくれます。

親プロセスは、子プロセスに現在のリクエスト処理が完了したら終了するように命令します。

親プロセスは設定ファイルを再読み込みして、ログファイルを開き直します。

restart と違って親プロセスは終了しないので PID はそのままです。

子プロセスが徐々に無くなるにつれて、新しい設定を反映した子プロセスが立ち上がりリクエストに応答します。

graceful では再起動する前に、設定ファイルの構文チェックをしてくれます。

誤りがあった場合は、エラーメッセージを出力し、サーバーの再起動は行いません。

コマンド
$ sudo apachectl -k graceful

又は

$ sudo /etc/init.d/httpd graceful
Gracefully restarting httpd:

参考:Stopping and Restarting Apache HTTP Server - Apache HTTP Server Version 2.4

注意点

SSL 証明書の更新や、モジュールの追加を行った場合は、graceful では反映されないケースがある様なので注意してください。

owen11.hateblo.jp

まとめ

Apache を利用しているサーバーでは graceful を利用することでホットデプロイ出来ることが分かりました。

今までアプリケーションを修正した際には、何でもかんでも(念のため) restart していましたが、 今後は graceful や reload など使い分けたいと思います。

SSL 証明書の更新や、モジュールの追加を伴うリリースは頻度が少ないので、基本は graceful を使おうと思います。

まだ実運用はしていませんが、これで通常のリリース手順を簡略化できたと思います。

ただし、全台一気に行うリリース作業は時間が短縮されましたが、事故リスクは高まりました。

development 環境で開発して、いきなり production 環境へのリリースではなく、staging 環境に一旦リリースして確認することが大事だと思います。

 

【Ruby on Rails】ログ出力とログローテートの設定まとめ【Logger】

概要

Rails では ActiveSupport::Logger クラスを利用してログ出力ができます。

railsguides.jp

今回、ログの設定変更やログローテート方法、古いログの削除、オススメのプラグインなどをまとめました。

ログの設定

基本的には app/config/environments/*.rb を変更します。

環境毎に設定を分けたくない場合は app/config/application.rb に記載しても OK です。

ログレベルの変更

Logger クラスで指定可能なログレベルは全部で 6 つあります。

レベル 概要 説明
debug デバッグ用の情報 システムの動作状況に関する詳細な情報
info 情報 実行時の何らかの注目すべき事象(開始や終了など)
warn 警告 エラーに近い事象など、実行時に生じた異常とは言い切れないが正常とも異なる何らかの予期しない問題
error エラー 予期しないその他の実行時エラー
fatal 致命的なエラー プログラムの異常終了を伴うようなもの
unknown 不明なエラー ログレベルの把握できない原因不明のエラー

参考:log4j - Wikipedia

ログレベルは debug < info < warn < error < fatal < unknown の順に高くなっています。

ログに出力されるメッセージのログレベルが、設定済みのログレベル以上になった場合に、対応するログファイルにそのメッセージが出力されます。

デフォルトログレベルは全環境で debug です。

デフォルトのログレベルを変更するには以下のような設定を追加します。

config.log_level = :ログレベル

ログファイルの変更

デフォルトのログの保存場所は app/log 配下となります。

ログのファイル名には、アプリケーションが実行されるときの環境名が利用されます。

環境 ログファイル PATH
 development  app/log/development.log
 test  app/log/test.log
 production  app/log/production.log

 

デフォルトのログファイルを変更するには以下のような設定を追加します。

config.logger = Logger.new('ログファイル PATH')

ログフォーマットの変更

ログを出力する call メソッドは 4 つの引数 (severity, time, program name, message) を受け取ります。

call メソッドの返り値は文字列にしてください。

参考:class Logger (Ruby 2.4.0)

config.logger.formatter = proc { |severity, datetime, progname, message|
  # ここでログフォーマットを自由にカスタマイズする
  "[#{datetime}] [#{severity}] -- #{message}\n"
}

ログ出力

アプリケーション内でログ出力したい場合は、それぞれのログレベルに対応したメソッドを利用します。

設定したログレベル以下のメソッドは、使用しても出力されませんので注意して下さい。

logger.debug('Hello')
logger.info('Hello')
logger.warn('Hello')
logger.error('Hello')
logger.fatal('Hello')
logger.unknown('Hello')

ログローテート

アプリケーションログをいつまでも同一のファイルに出力していると、ログファイルが肥大化して、サーバ容量を圧迫してしまいます。

定期的にログローテートして、古いログファイルは削除することをお勧めします。

ログローテートは「日付」か「ファイルサイズ」のどちらかで行うことができます。

日付でログローテート

「日付」でのログローテートは、何か問題が発生した場合に、発生日時からログを検索しやすいのが利点だと思います。

ログローテートは daily、weekly、monthly から選びます。

ただし、1 日 3 回朝昼晩などの設定ができないので、アクセス量の多いアプリケーションには不向きかもしれません。

config.logger = Logger.new('log/trace.log', 'daily')
config.logger = Logger.new('log/trace.log', 'weekly')
config.logger = Logger.new('log/trace.log', 'monthly')

ファイルサイズでログローテート

「ファイルサイズ」でのログローテートは、サーバリソースを上手く使えるのが利点だと思います。

アクセス量が少ないアプリケーションでは、毎日ログローテートする必要も無いですし、逆にアクセス量の多いアプリケーションでは1日に数回ログローテートした方がいい場合があります。

また「ファイルサイズ」でログローテートした場合、一定期間ログファイルを保存して、期間を過ぎた古いログファイルを削除することができます。

以下の設定ではファイルサイズが 10 M となったらログローテートして、14日以上経過した古いログファイルは削除してくれます。

config.logger = Logger.new('log/trace.log', 14, 10 * 1024 * 1024)

これは Logger クラスの第二引数がログファイルを保持する数か、ログファイルを切り替える頻度 (daily, weekly, monthly) となっているためです。

そのため「日付」でログローテートする場合、古いログファイルの削除ができません。

参考:class Logger (Ruby 2.4.0)

その他

Rails のアプリケーションログをカラーリングして見やすくしてくれるプラグインです。

ハッシュ形式でログ出力する場合にも、見やすく整形してくれるので便利です。

in.fablic.co.jp

ログローテートは本格的にやるのであれば、Logger クラスのオプションでは少ないと思うので、logrotate.conf を作成して、定期的にlogrotate コマンドを叩くのがいいとお思います。

qiita.com

まとめ

今回の設定を app/config/environments/development.rb に反映させました。

※ ログ出力周りの設定だけを抜粋しています。

Rails.application.configure do
# ログレベルの設定 config.log_level = :info
# ログ出力ファイルの設定、ログローテートの設定 config.logger = Logger.new('/var/log/rails_app.log', 14, 10 * 1024 * 1024)
# ログフォーマットの設定 config.logger.formatter = proc { |severity, datetime, _progname, message| "[#{datetime}] [#{severity}] -- #{message}\n" }
end

ログを確認すると、指定されたログファイルに指定されたフォーマットで出力されるようになりました。

$ tail -f /var/log/rails_app.log
[2017-12-25 14:11:37] [INFO] -- Started GET "/v1/users/9" for 127.0.0.1 at 2017-12-25 14:11:37 +0900
[2017-12-25 14:11:37] [INFO] -- Processing by UsersController#show as */*
[2017-12-25 14:11:37] [INFO] --   Parameters: {"id"=>"9"}
[2017-12-25 14:11:37] [INFO] -- Hello
[2017-12-25 14:11:37] [INFO] -- Completed 200 OK in 24ms (Views: 0.1ms | ActiveRecord: 12.0ms)

 

【Ruby on Rails】API のレスポンスを生成するメソッドを紹介

概要

Ruby on Rails 5 からは API モードが導入され、API 専用のアプリケーションを簡単に作成できるようになりました。

railsguides.jp

 

Ruby on Rails では CRUD と呼ばれる HTTP メソッドを利用して、RESTful なアプリケーション(API)を作成できるように設計されています。

CRUD 名 操作 HTTP メソッド Ruby on Rails のデフォルトアクション
CREATE 作成 POST create
READ 読み込み GET index, show, new, edit
UPDATE 更新 PUT/PATCH update
DELETE 削除 DELETE destroy

そのため、リクエストを受ける API のエンドポイント(URL)は比較的悩むことなく、スムーズに作成できるかと思います。

 

しかし、API のレスポンスは、自分で考えて作成しないといけません。

自分なりのルールがあれば良いのですが、無い場合は設計に悩みます。

今回、自分がレスポンスを作成する際に、過去のソースコードから毎回コピペして使っているメソッドがあるので、それを紹介します。 

紹介する前に

GET を利用したリクエストは、『読み込み(取得)』なので、リクエストされたデータをそのままレスポンスで返せば良いと思います。

ユーザ ID 1 番のデータに GET リクエストされた場合は、ユーザ ID 1番のデータを返すのが普通ですよね?

リクエストを投げた人は、そのユーザの名前だったり、性別だったり、年齢だったりを取得したいと思ったはずです。

では POST、PUT/PATCH、DELETE を利用したリクエストを受けた場合は、どうでしょうか?

多分なんですけど ...

一番知りたい情報は、作成・更新・削除 が成功したかどうかだと思います。

なので最低限、成功・失敗のステータスを返せればレスポンスとして成り立ちます。

失敗した場合は、失敗した理由(エラーメッセージ)も返せれば尚良いと思います。

しっかりやりたい人は作成・更新・削除したデータもレスポンスに含めるかもしれません。

コピペして使っているメソッド

GET 以外のリクエストを受けた際に利用します。

各 Controller でレスポンスを返す度に、render を記載するとコードが汚くなるので、共通メソッドとしてまとめました。

app/controllers/application_controller.rb などに記載して下さい。

# 200 Success
def response_success(class_name, action_name)
  render status: 200, json: { status: 200, message: "Success #{class_name.capitalize} #{action_name.capitalize}" }
end

# 400 Bad Request
def response_bad_request
  render status: 400, json: { status: 400, message: 'Bad Request' }
end

# 401 Unauthorized
def response_unauthorized
  render status: 401, json: { status: 401, message: 'Unauthorized' }
end

# 404 Not Found
def response_not_found(class_name = 'page')
  render status: 404, json: { status: 404, message: "#{class_name.capitalize} Not Found" }
end

# 409 Conflict
def response_conflict(class_name)
  render status: 409, json: { status: 409, message: "#{class_name.capitalize} Conflict" }
end

# 500 Internal Server Error
def response_internal_server_error
  render status: 500, json: { status: 500, message: 'Internal Server Error' }
end

レスポンスには HTTP ステータスコードとメッセージしか記載していません。

HTTPステータスコード - Wikipedia

HTTP ステータスは沢山ありますが、実際に API で使うのは限られます。

ケース・バイ・ケースで増やしたり減らしたりして下さい。

ステータスコード メッセージ 使う場面(例)
200 Success リクエストに成功した場合
400 Bad Request リクエストに必要なバラメータが欠けていた場合
401 Unauthorized トークンの認証が不正だった場合
404 Not Found 取得しようとしたデータが削除されていた場合
409 Conflict DB に重複するデータを格納しようとした場合
500 Internal Server Error DB や NW で障害が発生した場合

使い方

実際に利用しているソースコードを用意しました。

response_success などは何のアクションが成功したのか分かるように、引数にクラス名とアクション名を入れます。

  • response_success(:user, :create) でユーザの作成に成功した
  • response_success(:shop, :update) でお店の更新に成功した
  • response_success(:image, :delete) で画像の削除に成功した

などの情報がレスポンスで返ります。

class UsersController < ApplicationController
  before_action :set_user, only: :show

  # GET /users/1
  def show
    render json: response_fields(@user.to_json)
  end

  # POST /users
  def create
    @user = User.new(user_params)
    if @user.name.blank?
      # 必須パラメータが欠けている場合
      response_bad_request
    else
      if User.exists?(email: @user.email)
        # 既に登録済みのメールアドレスで登録しようとした場合
        response_conflict(:user)
      else
        if @user.save
          # ユーザ登録成功
          response_success(:user, :create)
        else
          # 何らかの理由で失敗
          response_internal_server_error
        end
      end
    end
  end

  private

  def set_user
    @user = User.find_by(id: params[:id])
    # 取得しようとしたユーザが存在しない
    response_not_found(:user) if @user.blank?
  end

  def user_params
    params.require(:user).permit(:name, :email)
  end
  
  # 他のコントローラでも除外したいフィールドが同じであれば、共通メソッドとして扱っても良い
  def response_fields(user_json)
    user_parse = JSON.parse(user_json)
    # レスポンスから除外したいパラメータ
    response = user_parse.except('created_at', 'updated_at', 'deleted_at')
    # JSON を見やすく整形して返す
    JSON.pretty_generate(response)
  end
end

共通メソッドにはしていませんが、最後の response_fields メソッドも結構コピペで使い回します。

created_at, updated_at, deleted_at は運用の観点で DB に必要なカラムなので、レスポンスで返す必要はないと思います。

動作確認

API にリクエストを投げて、返ってきたレスポンスの中身を確認します。

GET リクエストを受けた際には、pretty_generate  と言う JSON モジュールを使って、見やすく整形する事をオススメします。

module function JSON.#pretty_generate (Ruby 2.4.0)

GET リクエストに失敗した場合や、GET 以外のリクエストを受けた際のレスポンスは、HTTP ステータスコードとメッセージだけとなっています。

# ユーザの取得に成功
$ curl -X GET "http://localhost/v1/users/1"
{
  "id": 1,
  "name": "aaa",
  "email": "bbb"
}

# 取得しようとしたユーザが存在しない
$ curl -X GET "http://localhost/v1/users/9"
{"status":404,"message":"User Not Found"}

# 既に登録済みのメールアドレスで登録しようとした場合
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"ccc","email":"bbb"}}'
{"status":409,"message":"User Conflict"}

# 必須パラメータが欠けている場合
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"","email":""}}'
{"status":400,"message":"Bad Request"}

# ユーザ登録成功
$ curl -X POST "http://localhost/v1/users" -H "Content-Type: application/json" -d '{"user":{"name":"xxx","email":"yyy"}}'
{"status":200,"message":"Success User Create"}

おまけ

ページが存在しません

response_not_found は config/routes.rb に記載する事で、存在しないページにアクセスされた時のレスポンスにも利用できます。

get '*path', controller: 'application', action: 'response_not_found'

最後に

API のレスポンスを生成するメソッドを紹介しました。

GET リクエストでは、リクエストされたデータをそのまま返せば良いと思いますが、GET 以外のリクエストはどこまで情報を返せば良いか悩みます。

自分は HTTP ステータスコードとメッセージを返しています。

レスポンスでどこまでの情報を返すかは、開発者の優しさだと思います。

情報が多ければ多いほど、一緒に開発しているメンバーは API 周りの開発工数やテストの工数を削減できるかもしれません。

また、公開 API であれば利用者のトラブルが少なくなるかもしれません。

必要であれば、今回のメソッドを拡張して使って下さい。

今回、紹介していませんが、サーバ運用の観点からレスポンスの結果をログに吐き出したりするのも良いと思います。

【備忘録】RAID とは? RAID の種類と仕組みまとめ

概要

最近、仮想サーバばかり扱っていて、実機サーバを自分で購入する機会が無かったので、RAID の種類と仕組みを忘れてしまいました。

忘れないように RAID について調べたことを簡単にまとめました。

RAID とは

複数の HDD を組み合わせて、1 つの論理的な記録領域として管理する仕組みです。

HDD が 1 本(又は複数)壊れたとしても、他の HDD でシステムを停止せずに稼働し続けれるように HDD の冗長化をとった仕組みです。

主にシステムの可用性を向上する役割があります。

RAID の種類によっては HDD が故障しても、データを復元できるものもあります。

RAID の種類

  • RAID 0:ストライピング
  • RAID 1:ミラーリング
  • RAID 5:分散パリティ
  • RAID 0+1:ストライピングしたデータをミラーリングする
  • RAID 1+0:ミラーリングしたデータをストライピングする

実際に使用される可能性が高いものは、上の種類ぐらいかなと思ったので、これらの役割だけをまとめました。

RAID 0(ストライピング)とは

f:id:kyamanak83:20171119205018p:plain

複数の HDD に対して分散してデータの書き込みを行います。

読み込む際も、複数の HDD から並列に読み込めるので、処理の高速化が図れます。

ただし、冗長化をしていないため、HDD が 1 本でも故障すると全てのデータを失います。

RAID 1(ミラーリング)とは

f:id:kyamanak83:20171119205036p:plain

同一のデータを複数(2本)の HDD に対して書き込みを行います。

一方の HDD が故障しても、他方の HDD にデータが残っているため、処理を継続して行うことができます。

ただし、全く同じデータを複数(2本)の HDD に対して書き込むため、書き込み速度は低下します。また、利用できるディスク容量も半分になります。

RAID 5(分散パリティ)とは

f:id:kyamanak83:20171119205049p:plain

パリティと呼ばれる誤り訂正コードを付与して、複数(3本以上)の HDD に書き込みを行います。

HDD が 1 本故障した場合でも、パリティを元にデータを復元できます。

RAID 0+1 とは

f:id:kyamanak83:20171119211042p:plain

RAID 0 のストライピンググループを、RAID 1 でミラーリングした構成です。

最低でも HDD が 4 本必要となります。

ストライピングのグループが 2 つあった場合、グループ 1 で HDD 故障が発生しても、グループ 2 だけでシステムを稼働し続けることができます。

ただし、グループ 1 とグループ 2 の両方で HDD 故障が発生すると RAID 全体が死亡します。

RAID 1+0 とは

f:id:kyamanak83:20171119211857p:plain

RAID 1 のミラーリンググループを、RAID 0 でストライピングした構成です。

最低でも HDD が 4 本必要となります。

ミラーリングのグループが 2 つあった場合、グループ 1 の HDD がどれか 1 本故障しても、同じグループ内のもう一方でシステムを稼働し続けることができます。

ただし、グループ内で同じデータを格納している HDD が故障すると RAID 全体が死亡します。

おまけ

RAID 0+1 と RAID 1+0 の比較

  • RAID 0+1 では各グループで HDD 故障が発生してしまうと RAID 全体が死亡します。
  • RAID 1+0 ではグループ内の同じデータを格納している HDD が故障してしまうと RAID 全体が死亡します。

発生頻度で考えると、グループ内の同じデータを格納している HDD が故障する確率の方が低いですから、RAID 1+0 の方が可用性が高いと思います。

参考:

itpro.nikkeibp.co.jp

最後に

RAID の種類と仕組みを簡単にまとめました。

詳しく知りたい方はこちらのサイトがいいと思います。

参考:https://note.cman.jp/server/raid/

【AWS】OpsWorks を使って Chef サーバの構築とノード追加

概要

Chef サーバを構築してサーバの構成管理を行いたいと思っています。

AWS には OpsWorks と言うサービスがあって、簡単に Chef サーバを構築できるようです。

OpsWorks を使って Chef サーバを構築して、ノード追加を行う工程までをまとめました。

参考:AWS OpsWorks for Chef Automate (マネージド型 Chef サーバー) | AWS

Chef サーバを構築

AWS ではコンソール経由で簡単に Chef サーバを構築できます。

1.『Go to OpsWorks for Chef Automate』を使ってみる

f:id:kyamanak83:20171105195143p:plain

 

2.『Create Chef Automate server』で Chef サーバを作成

f:id:kyamanak83:20171105195415p:plain

 

3. 設定の入力

今回、試しに使ってみたかったので、ほとんど設定はいじってないです。

f:id:kyamanak83:20171105195738p:plain

f:id:kyamanak83:20171105195815p:plain

 

f:id:kyamanak83:20171105200223p:plain

 

4. Chef サーバの起動

設定が完了すると、Chef サーバが起動します。起動まで少々時間がかかります。

その間に credential と Starter Kit をダウンロードしてください。

f:id:kyamanak83:20171105200755p:plain

5. Chef サーバにログイン

先ほどダウンロードした『credential』にユーザ名とパスワードが記載されています。

f:id:kyamanak83:20171105200942p:plain

ログインすると Chef Automate サーバのダッシュボードが表示されます。

f:id:kyamanak83:20171105201631p:plain



Chef Automate サーバの構築はこれで完了です。

現段階では、Chef Automate サーバで管理するノードがないので、これからノード追加を行います。

デベロッパーキットのインストール

ノード追加は Workstation と呼ばれる環境から行います。

これから自分に PC に Workstation を構築します。

Chef Workstation は、Chef のインフラストラクチャで重要なもう一つのコンポーネントで、ラップトップやデスクトップ PC など、業務のほとんどを行う作業場に当たります。 たとえば、Chef Workstation を使用して、クックブックとレシピの著作、組織のポリシーの構成、ノードのブートストラップを実行します。

以下の URL から Developer Kit をダウンロードしてください。

https://downloads.chef.io/chefdk

Developer Kit をダウンロードしたら、自分の PC にインストールしてください。

f:id:kyamanak83:20171105201540p:plain

これでローカル(ターミナル)で knife コマンドが実行できるようになります。

$ which knife
/usr/local/bin/knife

ノード追加

先ほど(4. Chef サーバの起動)ダウンロードした Starter Kit を解凍してください。

Starter Kit の中には chef-repo と Chef サーバの秘密鍵、設定ファイル(knife.rb)などが含まれています。

# chef-repo のディレクトリ構成
$ tree -a
.
├── .chef
│   ├── ca_certs
│   │   └── opsworks-cm-ca-2016-root.pem
│   ├── knife.rb # どの Chef サーバと接続するかなど記載された設定ファイル
│   └── private.pem # 秘密鍵
├── Berksfile
├── README.md
├── chefignore
├── cookbooks
│   └── README.md
├── environments
│   └── README.md
├── roles
│   └── README.md
├── userdata.ps1
└── userdata.sh

ノード追加は chef-repo からしか実行できませんので注意してください。

ノード追加は自分の PC 上に構築した Workstation から該当インスタンスに SSH ログインして必要な設定をインストールします。

そのため、ノード追加したいインスタンスのインバウンドルールの編集を先に行って、PC から SSH できるようにしておいて下さい。f:id:kyamanak83:20171112220829p:plain

インバウンドルールの設定が完了したら、bootstrap と言うコマンドを叩いて Chef サーバにノードを追加します。

サーバ OS によって実行ユーザが異なるようなので注意して下さい。

参考:Chef サーバーで管理するノードを追加する - AWS OpsWorks

# ノード追加を行う際は、chef-repo に移動します
$ cd my-chef-automate-3xxxxx3xxxxxxxxx

# ノード追加(bootstrap)実行
$ knife bootstrap <パブリック IP アドレス> -N <インスタンス名(何でもいい)> -x ec2-user --sudo --identity-file <自分のAWS にログインするときの鍵パス>
Creating new client for kyamanak.aws.instance
Creating new node for kyamanak.aws.instance
Connecting to XX.XXX.XXX.XX
XX.XXX.XXX.XX -----> Installing Chef Omnibus (-v 13)
XX.XXX.XXX.XX downloading https://omnitruck-direct.chef.io/chef/install.sh
XX.XXX.XXX.XX   to file /tmp/install.sh.23029/install.sh
XX.XXX.XXX.XX trying wget...
XX.XXX.XXX.XX el 6 x86_64
XX.XXX.XXX.XX Getting information for chef stable 13 for el...
XX.XXX.XXX.XX downloading https://omnitruck-direct.chef.io/stable/chef/metadata?v=13&p=el&pv=6&m=x86_64
XX.XXX.XXX.XX   to file /tmp/install.sh.23034/metadata.txt
XX.XXX.XXX.XX trying wget...
XX.XXX.XXX.XX sha1	2e1390896c8376268f88cc693cca475a76cd1e64
XX.XXX.XXX.XX sha256	ca26f2c9feef419cb3ae0a6ea03843b7c34275b7c0f6a1a18ab56b63383fc341
XX.XXX.XXX.XX url	https://packages.chef.io/files/stable/chef/13.6.0/el/6/chef-13.6.0-1.el6.x86_64.rpm
XX.XXX.XXX.XX version	13.6.0
XX.XXX.XXX.XX downloaded metadata file looks valid...
XX.XXX.XXX.XX downloading https://packages.chef.io/files/stable/chef/13.6.0/el/6/chef-13.6.0-1.el6.x86_64.rpm
XX.XXX.XXX.XX   to file /tmp/install.sh.23034/chef-13.6.0-1.el6.x86_64.rpm
XX.XXX.XXX.XX trying wget...
XX.XXX.XXX.XX Comparing checksum with sha256sum...
XX.XXX.XXX.XX Installing chef 13
XX.XXX.XXX.XX installing with rpm...
XX.XXX.XXX.XX ... 省略 ...
XX.XXX.XXX.XX Running handlers:
XX.XXX.XXX.XX Running handlers complete # 完了 
XX.XXX.XXX.XX Chef Client finished, 10/10 resources updated in 07 seconds

これでノード追加が完了です。

今回、kyamanak.aws.instance と言うインスタンス名で登録しました。

f:id:kyamanak83:20171105203259p:plain

最後に

Chef サーバを構築してノードを追加する工程までをまとめました。

OpsWorks を使うことで、今まで構築に時間をかけていた Chef サーバが数分で構築できるようになりました。

見よう見まねで直ぐに構築できると思うので、是非参考にして下さい。

 

【画像解析 API を使ってみる】食べ物の画像は解析できるのか?

概要

最近、食べ物の写真を撮って SNS にシェアする人が増えています。

自分も SNS に食べ物の写真をアップロードした経験があります。

ラーメン屋に行くと、サラリーマンのおっちゃんまで写真を撮っている時代です。( ˙灬˙ ) 

そんな時代背景もあってか、友達にもっと食べ物を美味しそうに見えるフィルターや、食べ物専用のスタンプが充実したアプリを作りたいと相談されたことがあります。

当時、作りたかったアプリはこれに近いのかなと思います。

linecorp.com

最終的にアプリをリリースするまでに至らなかったのですが、開発中に食べ物の画像を解析したいと思った部分があり、画像解析 API を使ってみたので、その時のことをまとめました。

画像解析 API

当時、使ってみた画像解析 API は以下の 3 つです。

  1. Google Cloud Vision API
  2. IBM Visual Recognition API
  3. Microsoft Computer Vision API

それぞれの API で出来ることを示しておきます。(サイトから引用しました)

Google Cloud Vision API

cloud.google.com

  • ラベル検出
    乗り物や動物など、画像に写っているさまざまなカテゴリの物体を検出できます。
  • 不適切なコンテンツの検出
    アダルト コンテンツや暴力的コンテンツなど、画像に含まれる不適切なコンテンツを検出できます。
  • ロゴ検出
    画像に含まれる一般的な商品ロゴを検出できます。
  • ランドマーク検出
    画像に含まれる一般的な自然のランドマークや人工建造物を検出できます。
  • 光学式文字認識(OCR)
    画像内のテキストを検出、抽出できます。幅広い言語がサポートされており、言語の種類も自動で判別されます。
  • 顔検出
    画像に含まれる複数の人物の顔を検出できます。感情や帽子の着用といった主要な顔の属性についても識別されます。 ただし、個人を特定する顔認識には対応していません。
  • 画像属性
    画像のドミナント カラーや切り抜きのヒントなど、画像の一般的な属性を検出できます。
  • ウェブ検出
    類似の画像をインターネットで検索できます。

IBM Visual Recognition API

www.ibm.com

  • 大量の画像・映像コンテンツに対して、自動的にタグ付けや分類を行う
  • 食事や食品に関する画像を検出し、写っている料理や食べ物を分析する (食べ物に特化した機能あり)
  • 画像に写っている人物の顔を検出し、年齢層・性別や名前をタグ付けする
  • 画像認識により書類やエビデンスを仕分けする
  • 製造ラインにおける画像検査により不良品を検出する
  • ドローンが撮影した画像を使って、家屋や鉄塔の破損箇所を検出する
  • 人工衛星が撮影した画像から、知見を得るための分析・分類を行う
  • 自社サイトに顧客がアップロードした画像から、不適切な画像を検出する
  • SNS等の画像から自社製品を探し出し、コメントをマーケティングに使用する

Microsoft Computer Vision API

azure.microsoft.com

  • 画像内のテキストの読み取り
    光学式文字認識 (OCR) により画像内のテキストを検出し、認識した語句をマシンに抽出して、判読可能な文字ストリームに変換します。画像を分析して埋め込みテキストを検出し、文字ストリームを生成し、検索を有効にします。テキストをコピーする代わりに写真を撮ることで、時間と労力を節約します。

  • 著名人およびランドマークの認識
    ドメイン固有モデルの例として、著名人モデルおよびランドマーク モデルがあります。著名人認識モデルでは、ビジネス、政治、スポーツ、エンターテイメント分野での 200,000 人の著名人を認識できます。ランドマーク認識モデルでは、世界中の 9000 種類の自然物や人工物のランドマークを認識できます。ドメイン固有モデルは Computer Vision API で継続的に進化を遂げている機能です。

  • ほぼリアルタイムでビデオを分析
    ほぼリアルタイムでビデオを分析。ご使用のデバイスでビデオのフレームを抽出し、それらのフレームをお好きな API 呼び出しに送信することで、任意の Computer Vision API をビデオ ファイルに使用できます。ビデオの結果はすぐに返ってきます。

  • サムネイルの生成
    あらゆる入力画像に基づいて、高品質でストレージ効率の高いサムネイルを生成します。サムネイル生成機能を使用して、サイズ、形、スタイルのニーズに最も適したものに画像を変更できます。スマート トリミングを適用すれば、元の画像とは異なる縦横比であるものの、関心領域を維持したサムネイルを生成できます。

API の実行結果

それぞれプロモページでデモが試せるので、『ハンバーグ』の写真を読み込ませて実行結果を確認します。

 Google Cloud Vision API

f:id:kyamanak83:20171104144530p:plain 

IBM Visual Recognition API

f:id:kyamanak83:20171104144601p:plain

Microsoft Computer Vision API

f:id:kyamanak83:20171104145337p:plain

3 つの API 共に食べ物であることは問題なく認識してくれます。

API によっては、実行結果に『Salisbury steak』とあるのでハンバーグであることも認識してくれます。

更に『Garnish』とあるので、ハンバーグがメインで、ポテトやサラダがつけ合わせというところまで認識してくれる API もありました!

当時、ハンバーグの画像を API に読み込ませた時は『カレー』って認識されていたのですが、今回は『カレー』の項目がなくなっていたので、認識精度は日々上がっているんだなと感じました。

食べ物の画像解析だけで言えば、Google と IBM の API が良さそうだと感じました。

まとめ

食べ物の画像を解析することは出来ました!

写真に食べ物が写っているかどうかの判定は高確率で成功すると思います。

写真に写っている食べ物を正確に判定できる確率は 60 % ぐらいかなと感じました。

ただし、認識精度は日々上がっているので、今後もっと良くなることが期待出来ます。

ラーメン・餃子セットのように複数のメニューが並んでいる場合にも対応してくれます。

なので、概要で話したアプリを作る場合、食べ物が写真に写っている場合、特別なフィルターを適用する仕組みなどは実装できそうです。

ただし、暖かい食べ物が写っていたら赤系のフィルターを、冷たい食べ物が写っていたら青系のフィルターを出し分けて適用する仕組みなどはちょっと難しいかなと感じました。

また、写真の何処に食べ物が写っているのかの位置情報は取れないので、食べ物を中心に(焦点を当てて)フィルターを適用する仕組みなども難しいと感じました。

まぁ、ここら辺はユーザーが自分自身で選択するアプリにすれば良いのですが。

最後に

アプリを開発中に有識者に相談したところ、本格的に画像解析をやりたいのであれば OpenCV を使えと言われました。

opencv.jp

ただし、画像解析の精度を上げるにはたくさんのサンプル画像を集める必要があると言われました。

ハンバーグのサンプル画像が 100 枚と 1000 枚では、1000 枚の画像を機会に認識(学習)させた方が解析する時の精度は上がるそうです。

これは個人では時間と労力がかかり過ぎてキツそうなのでやめました。