reizist's blog

ウェブ

apache drillが凄い

apache drillというツールが凄いので雑に使ってみました。

一言でいうと Treat your data like a table even when it's not なんですけど、もっというと jsonに対してsql実行できたりします。

公式のインストール方法の通りにできますが、

reizist@reizist ~/apache-drill-1.5.0 $ brew search apache-drill                                                                                                              [2.3.0]
apache-drill

の通りbrew経由でも落とせるみたいですね。

ちょっとサーバーのアクセスログの解析が必要だったんですけど、 気軽にやる方法ないかなーと思って(もっぱら最近はfluentd + kibanaとかBigQueryとかそこらへんが主流ですが)検討した結果、 「ltsv出力してあるnginxログをjsonに変換してapache drillに流す」ことをしてみようと思いました。 awkとか使いこなせる人にはあまり要らないのかな。

まず

gem install ltsv2json
ltsv2json production_access_log.log > production_access_log.json

などとしjsonを生成したあとおもむろにapache drillを起動します

reizist@reizist ~/apache-drill-1.5.0 $ bin/drill-embedded                        [2.3.0]
Java HotSpot(TM) 64-Bit Server VM warning: ignoring option MaxPermSize=512M; support was removed in 8.0
3 16, 2016 8:36:58 午後 org.glassfish.jersey.server.ApplicationHandler initialize
情報: Initiating Jersey application, version Jersey: 2.8 2014-04-29 01:25:26...
apache drill 1.5.0
"a drill is a terrible thing to waste"
0: jdbc:drill:zk=local> select * from dfs.`/tmp/production_lb_access.json` where uri='/';
+-----------------------------+-----------------+---------------+-------+---------+------+---------+-------+--------------------------+----------------------------------------------------------------------------------------------------------------+----------+--------+-----------+----------+--------------------+
|            time             |      host       | forwardedfor  | user  | method  | uri  | status  | size  |         referer          |                                                       ua                                                       | reqtime  | cache  |  runtime  | apptime  |       vhost        |
+-----------------------------+-----------------+---------------+-------+---------+------+---------+-------+--------------------------+----------------------------------------------------------------------------------------------------------------+----------+--------+-----------+----------+--------------------+
| 14/Mar/2016:05:42:24 +0900  | 111.11.11.111  | -             | -     | GET     | /    | 302     | 96    | -                        | Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/48.0.2564.116 Safari/537.36  | 0.110    | -      | 0.107515  | 0.110    |  [censored]  |
| 14/Mar/2016:06:02:17 +0900  | 111.11.11.111   | -             | -     | GET     | /    | 302     | 96    | -                        | Mozilla/5.0 (compatible; bingbot/2.0; +http://www.bing.com/bingbot.htm)                                        | 0.011    | -      | 0.008270  | 0.011    | [censored]  |
| 14/Mar/2016:06:47:02 +0900  | 111.11.11.111  | -             | -     | GET     | /    | 302     | 194   | -                        | Mozilla/5.0 (iPhone; CPU iPhone OS 8_3 like Mac OS X) AppleWebKit/600.1.4 (KHTML, like Gecko) Mobile/12F70     | 0.055    | -      | 0.051819  | 0.055    | [censored]    |
+-----------------------------+-----------------+---------------+-------+---------+------+---------+-------+--------------------------+----------------------------------------------------------------------------------------------------------------+----------+--------+-----------+----------+--------------------+

おぉ凄い便利。

例えば実アクセスログから処理時間がかかっているリクエストを調べたい場合は以下のようにできますね。

0: jdbc:drill:zk=local> select * from dfs.`/tmp/production_lb_access.json` order by runtime desc limit 10;
+----------+--------+---------------+------------------+---------+----------+----------+-----------+-------+---------+-----------------------------+----------------------------------------------------------------------------------------------------------------+------------------------+-------+------------------+
| apptime  | cache  | forwardedfor  |       host       | method  | referer  | reqtime  |  runtime  | size  | status  |            time             |                                                       ua                                                       |          uri           | user  |      vhost       |
+----------+--------+---------------+------------------+---------+----------+----------+-----------+-------+---------+-----------------------------+----------------------------------------------------------------------------------------------------------------+------------------------+-------+------------------+
| 9.935    | -      | -             | 111.111.11.11   | POST    | -        | 9.979    | 9.932773  | 5666  | 200     | 14/Mar/2016:05:12:42 +0900  | Dalvik/1.6.0 (Linux; U; Android 4.4.4; 305SH Build/S0114)                                                      | /path/action           | -     | [censored]  |
| 9.914    | -      | -             | 111.111.11.11      | POST    | -        | 9.963    | 9.911458  | 9185  | 200     | 14/Mar/2016:12:34:58 +0900  | Dalvik/1.6.0 (Linux; U; Android 4.4.2; F-01F Build/V10R22A)                                                    | /path/action           | -     | [censored]  |
| 10.472   | -      | -             | 111.111.11.11   | GET     | -        | 10.472   | 9.910428  | 3611  | 200     | 14/Mar/2016:14:14:02 +0900  | Mozilla/5.0 (iPhone; CPU iPhone OS 9_2_1 like Mac OS X) AppleWebKit/601.1.46 (KHTML, like Gecko) Mobile/13D15  | /path/action     | -     | [censored]  |
| 9.901    | -      | -             | 111.111.11.11    | POST    | -        | 9.901    | 9.897920  | 8154  | 200     | 14/Mar/2016:14:13:03 +0900  | Dalvik/2.1.0 (Linux; U; Android 5.0.2; SC-05G Build/LRX22G)                                                    | /path/action           | -     | [censored]  |
| 14.933   | -      | -             | 111.111.11.11    | POST    | -        | 14.956   | 9.888321  | 3377  | 200     | 14/Mar/2016:14:39:21 +0900  | Dalvik/1.6.0 (Linux; U; Android 4.4.2; LGV31 Build/KVT49L.LGV3110f)                                            | /path/action           | -     | [censored]  |
| 9.884    | -      | -             | 111.111.11.11  | POST    | -        | 9.884    | 9.881206  | 5225  | 200     | 14/Mar/2016:13:19:43 +0900  | hoge/1.0.10 CFNetwork/758.2.8 Darwin/15.0.0                                                                | /path/action           | -     | [censored]  |
| 9.910    | -      | -             | 111.111.11.11   | POST    | -        | 9.910    | 9.869544  | 4398  | 200     | 14/Mar/2016:03:35:00 +0900  | Dalvik/1.6.0 (Linux; U; Android 4.1.2; SBM203SH Build/S0026)                                                   | /path/action        | -     | [censored]  |
| 10.186   | -      | -             | 111.111.11.11    | POST    | -        | 10.215   | 9.869021  | 4168  | 200     | 14/Mar/2016:03:40:30 +0900  | hoge/1.0.10 CFNetwork/758.2.8 Darwin/15.0.0                                                                | /path/action        | -     | [censored]  |
| 9.862    | -      | -             | 111.111.11.11   | POST    | -        | 9.862    | 9.859476  | 2938  | 200     | 14/Mar/2016:12:30:27 +0900  | Dalvik/2.1.0 (Linux; U; Android 5.1; KYV35 Build/103.0.2e00)                                                   | /path/action  | -     | [censored]  |
| 9.851    | -      | -             | 111.111.11.11      | POST    | -        | 9.871    | 9.847916  | 8592  | 200     | 14/Mar/2016:05:11:59 +0900  | Dalvik/2.1.0 (Linux; U; Android 5.1; KYT31 Build/100.0.1c10)                                                   | /path/action           | -     | [censored]  |
+----------+--------+---------------+------------------+---------+----------+----------+-----------+-------+---------+-----------------------------+----------------------------------------------------------------------------------------------------------------+------------------------+-------+------------------+

ちなみに上記クエリは3GBのjsonファイルに対して実行しかかった時間は 74.361 seconds でした。

上記程度であれば例えばNewRelicのようなものがあればこんなことをせずにすみますが、使い慣れたSQLでさらさらと手元で調べられるのは小回りがきいていいですね。

rails+unicornで特定のサーバーだけ異なる設定でunicornを起動する

production環境においてappサーバーと管理画面を同居させていた場合、unicornやnginxの設定を同一のものにしなくてはいけない制限が生まれてしまう。

今携わっているプロジェクトで、通常のappサーバーでタイムアウトを30秒に設定しているため管理画面から行う特別重い処理がタイムアウトが発生して満足に使えない状況が発生した。

このため管理画面を別サーバーに切り出す対応を行ったが、unicornの設定に少しだけ手こずったのでメモ。

task :set_unicorn_config_path do
  roles(:admin) do |host|
    set :unicorn_config_path, -> { File.join(current_path, "config", "unicorn", "admin.rb") }'
  end
end

before 'unicorn:start', :set_unicorn_config_path

対応内容は上記のみなのだけど、 capistrano3_unicornunicorn.rake における unicorn:start にフックしてproductionのみ set_unicorn_config_path タスクを実行するようにした。 role :admin に指定したインスタンスは、別のunicornの設定ファイルをつかうことができる。

unicorn:restartからもinvokeされるので、結果 deploy:restart からでも実行され、 cap deploy でも cap unicorn:restart でも上記タスクは実行される。助かる。

The theory of everythingを観た

www.amazon.co.jp

職場の人にオススメを聞いてこの映画を観た。 残念ながら日本語に疎いので的確な表現が思いつかずにいる。

とりわけ評価するべき点としては、 主演のエディ・レッドメインフェリシティ・ジョーンズの演技が映画の世界観に入り込むのに最大限の手助けをしてくれた。

個人的にはもう少し宇宙理論の話を掘り下げて欲しかった。原題の「The theory of everything」が「博士と彼女のセオリー」と訳される程度にはヒューマンドラマ、とりわけ「愛」にフォーカスした映画だ、という事前情報を得てから観ればそうは思わなかったのかもしれない。 演出も綺麗で、名作映画を見終わった後特有の余韻感を感じることができたので、ともすれば自分の中では「名作映画」のリストに入るのだろうな。

migration comment gemを使っていたらtableのROW_FORMATがCompactになって困った

何があった

  • my.cnf にて innodb_large_prefix = ON, innodb_file_format = Barracuda であるのに varchar(255) カラムに対する unique index付与時 Specified key was too long; max key length is 767 bytes で怒られる
  • innodb_large_prefix = ON なのに large_prefix が有効にならないということは、 ROW_FORMAT=DYNAMIC でないとアタリをつけ検証したところ、確かに DYNAMIC でなく Compact になっていることが判明
  • 同migrationファイルを用いてstaging環境で実行すると、意図通りmigrationは実行されindexも付与される。

詳細

絵文字対応を行うためutf8でなく1文字4byteのutf8mb4エンコードでDB運用を行う場合、767byteまでしかindex column lengthを許容していないmysqlの仕様上varchar(255)のカラムに普通にindexを貼ることは出来ない。これに対応するためには、

  • mysqlの設定で innodb_large_prefix を有効に
  • tableの設定で ROW_FORMAT=DYNAMIC or COMPRESSED

する必要がある。 参考

このうち後者に関しては

ActiveSupport.on_load :active_record do
  module ActiveRecord::ConnectionAdapters

    class AbstractMysqlAdapter
      # utf8mb4 を使用するため index のサイズを拡大したい。
      # そのために必要な設定です。
      # @see config/database.yml
      def create_table_with_innodb_row_format(table_name, options = {})
        table_options = options.reverse_merge(options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
        create_table_without_innodb_row_format(table_name, table_options) do |td|
          yield td if block_given?
        end
      end
      alias_method_chain :create_table, :innodb_row_format
    end
  end
end

のようにモンキーパッチを当てて対応を行っている。

だが、ローカル環境においてのみ上記のモンキーパッチによって付与されるはずの ROW_FORMAT=DYNAMIC が正しく有効にならず、作られるテーブルは Compact になっていることを発見した。 デバッグしたところ、何故かローカルではモンキーパッチ部分の引数にデフォルト値のようなものが渡されてしまい、意図せぬ挙動になってしまっていた。 development環境とstaging環境において挙動が変わる→development環境にのみ使用しているgemが怪しいとアタリをつけ調査を開始した。

調査

staging環境におけるデバッグ

From: ***/server/config/initializers/model_ext.rb @ line 193 ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#create_table_with_innodb_row_format:

    191: def create_table_with_innodb_row_format(table_name, options = {})
    192:   binding.pry
 => 193:   table_options = options.reverse_merge(options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
    194:   create_table_without_innodb_row_format(table_name, table_options) do |td|
    195:     yield td if block_given?
    196:   end
    197: end

2.1.5 (#<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0x007fe2a9ae94d8>):0 > show-stack
when_started hook failed: NoMethodError: private method `eval' called for nil:NilClass
***/server/vendor/bundle/ruby/2.1.0/gems/pry-stack_explorer-0.4.9.1/lib/pry-stack_explorer.rb:109:in `bindings_equal?'
(see _pry_.hooks.errors to debug)

Showing all accessible frames in stack (34 in total):
--
=> #0  create_table_with_innodb_row_format <ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#create_table_with_innodb_row_format(table_name, options=?)>
   #1 [block]   block in run <Byebug::PryProcessor#run(&_block)>
   #2 [method]  run <Byebug::PryProcessor#run(&_block)>
   #3 [method]  resume_pry <Byebug::PryProcessor#resume_pry()>
   #4 [method]  at_line <Byebug::PryProcessor#at_line()>
   #5 [method]  at_line <Byebug::Context#at_line()>
   #6 [method]  create_table_with_innodb_row_format <ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#create_table_with_innodb_row_format(table_name, options=?)>
   #7 [method]  create_table <ActiveRecord::SchemaMigration.create_table(limit=?)>
   #8 [method]  initialize_schema_migrations_table <ActiveRecord::ConnectionAdapters::Mysql2Adapter#initialize_schema_migrations_table()>
   #9 [method]  initialize <ActiveRecord::Migrator#initialize(direction, migrations, target_version=?)>
   #10 [method]  up <ActiveRecord::Migrator.up(migrations_paths, target_version=?)>
   #11 [method]  migrate <ActiveRecord::Migrator.migrate(migrations_paths, target_version=?, &block)>
  ....

development環境におけるデバッグ

2.1.5 (#<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0x007fee429f8cc8>):0 > show-stack
when_started hook failed: NoMethodError: private method `eval' called for nil:NilClass
***/server/vendor/bundle/ruby/2.1.0/gems/pry-stack_explorer-0.4.9.1/lib/pry-stack_explorer.rb:109:in `bindings_equal?'
(see _pry_.hooks.errors to debug)

Showing all accessible frames in stack (36 in total):
--
=> #0  create_table_with_innodb_row_format <ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#create_table_with_innodb_row_format(table_name, options=?)>
   #1 [block]   block in run <Byebug::PryProcessor#run(&_block)>
   #2 [method]  run <Byebug::PryProcessor#run(&_block)>
   #3 [method]  resume_pry <Byebug::PryProcessor#resume_pry()>
   #4 [method]  at_line <Byebug::PryProcessor#at_line()>
   #5 [method]  at_line <Byebug::Context#at_line()>
   #6 [method]  create_table_with_innodb_row_format <ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter#create_table_with_innodb_row_format(table_name, options=?)>
   #7 [method]  create_table <ActiveRecord::ConnectionAdapters::Mysql2Adapter#create_table_without_migration_comments(table_name, options=?)>
   #8 [method]  create_table_with_migration_comments <MigrationComments::ActiveRecord::ConnectionAdapters::MysqlAdapter#create_table_with_migration_comments(table_name, options=?)>
   #9 [method]  create_table <ActiveRecord::SchemaMigration.create_table(limit=?)>

#7 においてstgには無かった処理が介入しているのを発見。

2.1.5 (#<ActiveRecord::ConnectionAdapters::Mysql2Adapter:0x007fc938689b70>):0 > frame 7

Frame number: 7/35
Frame type: method

From: ***/server/vendor/bundle/ruby/2.1.0/gems/activerecord-4.0.8/lib/active_record/connection_adapters/abstract_mysql_adapter.rb @ line 471 ActiveRecord::ConnectionAdapters::Mysql2Adapter#create_table_without_migration_comments:

    470: def create_table(table_name, options = {}) #:nodoc:
 => 471:   super(table_name, options.reverse_merge(:options => "ENGINE=InnoDB"))
    472: end

migration comment gem をロードした結果、ActiveRecord::ConnectionAdapters::Mysql2Adapter::AbstractMysqlAdapter#create_table により下記メソッドの引数 options に常時 { options: 'ENGINE=InnoDB' } という値がわたってしまったことが原因。

def create_table_with_innodb_row_format(table_name, options = {})
  table_options = options.reverse_merge(options: 'ENGINE=InnoDB ROW_FORMAT=DYNAMIC')
  create_table_without_innodb_row_format(table_name, table_options) do |td|
    yield td if block_given?
  end
end

hash_a.reverse_merge(hash_b)hash_a の値を優先するため ROW_FORMAT=DYNAMIC が反映されなかった。

とはいえ、幸いなことに varchar(192) 以上必要なカラムは現プロジェクトに存在しないので、 migration commentのgemを使いindex使用時は limit: 191 をつける対応で乗りきれるので、問題は無かった。

atomのセットアップした

「何か作業をしようと思うと途端に部屋の掃除がしたくなる」あるあるネタatomのセットアップした。

以前vimに出会う前、sublime -> atom の流れで触ったことはあった。 とはいえ完全にmarkdown専用エディタとなっていたatomをもう少し使いたいなと。

動機は2つ。

IDE揃えるコストお金かかる問題

普段ruby/railsを書いている時には会社が買ってくれたRubyMineを使用できるが、例えばjavascriptを書きたい場合WebStorm別途買うの?IDE派じゃないの?vimなの?

vimの環境構築コストかかる問題

vimのように調べれば調べるほど味が出るというか、好き勝手環境構築して自分好みにカスタマイズ!できるエディタを使い倒せばそれでいいのだけど、vim弱者の自分としてはそこまで使い倒せない+そこに苦労するより、簡単な方法あればそれでいいじゃんというのを前から思ってた。

要はvim使いこなせないので楽したいですという開き直り発言。

やったこと

とはいえvim modeは必須。以前vimのようにatomを使うチャレンジをしたとき、vimでできることが全然atomで出来なくて泣いた。 最近 ベターvimとしてAtomを使うを見て「vimいけるじゃん」と思いまんま書いてある通りにパッケージをインストール、そしてストレス無く使えるレベルになった。

入れたpackage listはこちら。

reizist@reizist ~/environments $ apm list -bi
atom-beautify@0.28.22
autocomplete-paths@1.0.2
autocomplete-ruby@0.1.0
color-picker@2.1.1
editor-background@1.2.17
es6-javascript@0.5.0
ex-mode@0.8.0
git-plus@5.13.0
highlight-column@0.5.1
highlight-selected@0.11.2
linter@1.11.3
linter-ruby@1.2.1
merge-conflicts@1.3.7
minimap@4.19.0
monokai@0.7.0
pigments@0.24.1
project-manager@2.7.6
script@3.5.2
sort-lines@0.14.0
term3@0.21.2
vim-mode@0.64.0
vim-mode-visual-block@0.2.15

ちょっと雑にいじった感じ、パッと気軽に何かいじる程度だったら少なくとも問題無さそう。 ただ、仕事のプロジェクトでいじるような巨大なファイルとか、tsvの編集などはむずそう。

特に巨大ファイルの一斉置換とか、sublime textなどの他のエディタの軽量さには勝てない。 sublime textを今のatomと同じようなカスタマイズしようと思ったら色々とハマったのでまた困ったらsublime頑張ってみる。

飽きたらテーマ切り替えてみたりしよう。

「Web制作者のためのCSS設計の教科書」読んだ

www.amazon.co.jp

正確には「読んでた」。 関わっているプロジェクトの都合上、フロントに触る機会はほぼ無く、cssも我流で雑な実装しかしたことがなかったので 頭にindexを作る目的で読んだ。

cssの設計について、モダンな設計手法をいくつか示した上で筆者独自の設計方法も紹介する本であった。 betterな考え方を踏まえて、プロジェクトに沿ってジャストフィットする手法を模索すべき、という感じの結論の無い本で、実際、BEMやOOCSS、SMACSSという考え方はあくまで考え方であり、ベストプラクティスではない。 しかしながら、何故CSSが壊れやすいか、再利用するとはどういうことなのか、堅牢とは何かを言語化しておくことで確かに良い設計に近づけるはずだ。 識者のCSSに対する考えを聞くという意味で読んでおいて損はないと思う。

ただし、昨今はモダンで素晴らしいcssフレームワークがたくさんあるので、自分でゼロからcss設計する機会があるかというと首を傾げるレベル。 「このフレームワークはこういう考え方に則って設計されているんだな」と理解するために読む感じになりそう。

「アルゴリズムが世界を支配する」を読んだ

www.amazon.co.jp

kindleで500円程度で買えるので読み物として読んだ。

アルゴリズムがカバーする領域は、日々拡大している。様々な業界において、人間がやっていた仕事をアルゴリズムが取って代わるようになった。」

アルゴリズムがどのような貢献をしているか、近世の動向を多くの例と共に示している。 「アルゴリズム」という言葉を既に知っている人間なら、「ふーん、そうか」というの感想で終始するのでは?というのが私の感想であった。

始めの例が「ウォール・ストリートにおける金融市場取引のためのアルゴリズムの発展、及び株式取引手段の変遷」なのだが、「フラッシュ・クラッシュ」という単語に興味があった関係上、最も面白い章だった。 暴論ではあるが、その後の章、例えば医療業界におけるアルゴリズムの利用など、「アルゴリズムが世界を支配する」という主張を支持するため無理矢理に作ったような薄い話だなと感じた。 実際にはもちろんアルゴリズムが貢献していることに間違いはないだろうが、自分には無関係の業界における小さな発展についてあまり興味が湧かなかったことに起因するのかもしれない。

筆者の主張どおり、今後のさらなるアルゴリズムの発展によって、間違いなく人間の生き方は変わるだろう。 この主張に「なんで?」「ほんとに?」と思う人がいれば、高いものでもないので読んでおくと少し考え方が変わりそうだ。