reizist's blog

ウェブ

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円程度で買えるので読み物として読んだ。

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

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

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

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

ローカルからパフォーマンスチェック時fdの制限値に引っかかる

厳密にネットワーク越しにパフォーマンスチェックを行う場合、それなりのスペックマシンを別途用意するなど何かしらの対応をするとは思うが、 最低限の温度感を把握するだけの場合などローカルからベンチマークを実行することがあるときの話。

尚例では boomを用いているが当然 apache benchなどでも同じ。

$ boom -n 3000 -c 3000 -m GET http://example.com/bench_path

として大量のアクセスを想定するテストを行う場合に

Too many open files

と怒られる場合がある。

これはローカルのデフォルトでのファイルディスクリプタの制限値が低いから。

$ ulimit -n

などとして値を確認し、必要に応じて調整する。

$ ulimit -n 1024

middleman v4をインストールするとinit後server起動できない

middleman init project
cd project
middleman server

と実行すると、

/path/to/project not found 

というようにインストール先のプロジェクトがnot foundとなるエラーが発生。

https://github.com/middleman/middleman/issues/1723でissueがあがっていて、

bundle exec middleman server

と実行すればひとまず動作する。

「スイッチ!」読んだ

www.amazon.co.jp

新年を迎えてから通勤時間と寝る前の一時を利用して読み進めていて、今日読み終えた。

本書は「何か変えたくても変えられない」人に対して、様々な成功事例・実験・歴史を例に交えつつ実践的なコツを紹介している。 「英語を勉強したいのに仕事で疲れて帰宅後すぐ寝てしまう」、「最近なんとなく仕事がうまくいってないように感じる」、などの日常抱える問題に対して、どのように考えればうまく変化を起こす(=スイッチする)ことができるのか。 本書はスイッチのための基本的なフレームワークを提唱する。

人間は誰しも2つの側面を持つ。理性的な側面、感情的な側面のことだ。 変化を起こす場合、この両面に訴えかけることが必要不可欠である。 これらを象使い(理性的な側面)・象(感情的な側面)に例え、以下の3つのシンプルな条件で誰もが思うよりも簡単にスイッチできると繰り返し述べている。

  • 象使いに方向を教える
  • 象にやる気を与える
  • 道筋を定める

この3つの要素に対し更に具体的なファクターを提示している。

1. 象使いに方向を教える

  • ブライトスポットを見つける。
    • スイッチできない原因を探るのではなく、うまくいったときのことを考察し、取り入れる。
  • 大事な一歩の台本を書く。
    • 大きな目標を掲げるのではなく、まず何が必要か、最小の目標を具体的に建てる。
  • 目的地を指し示す。
    • 目的地を掲示し、メリットを理解しておくことで変化のハードルを下げる。

2. 象にやる気を与える

  • 感情を芽生えさせる。
    • 理論的に変化が必要だと分かっても、変化には不十分である。感情的にも変化を起こすきっかけを与える。
  • 変化を細かくする。
    • 変化が大きいと、拒否反応は大きくなる。禁煙したいなら、まず「今日タバコ吸わない」ことから始める。
  • 人を育てる。
    • 「しなやかマインドセット」を育む。柔軟に思考を変化させることのできる人間の方が変化に対する拒否反応は少ない。

道筋を定める

  • 環境を変える。
    • 行動は環境に起因するところが大きい。麻薬依存から抜け出したいと思った時、真っ先に麻薬を常飲するグループから離れることが必要。
  • 習慣を生み出す。
    • 「変化」には思考のコストがかかる。「思考」は負担である。「右足を前に出す」「左足を前に出す」と考えながら歩く人はいない。当然できる段階に至れば実行は容易い。
  • 仲間を集める。
    • 行動は伝染する。「環境に起因する」ことと関連するが、周りがやっていることに対して比較的同調して行う傾向にある。

何故このような主張を行うか、繰り返し複数の事例を織り交ぜてわかりやすく簡潔に述べており腑に落ちる。 スイッチの必要性は、日々の生活に多く潜んでいる。個人の変化、組織の変化、社会の変化...。 自分の身の回りのことに関して改善/改革を望む人にとっては、本書は大変参考になるのではないだろうか。

自分は昨年は日々の業務を行うことに終始し、新たな価値や成長といった所にははっきり言って進捗があったと胸を張れない状況であった。本書のフレームワークに沿って、以下の様な思考を辿った。


ここから自分のための思考の軌跡


問題点

更に成長するためには?

私は、日々の業務に追われ、能力の上昇や改善ができているかと不安に思っている。 多くの著名プログラマーがそうであるように、仕事をしっかりとこなした上で外部に成果発表できるようなエンジニアになりたいと考えている。 業務を蔑ろにせずにいて、なおかつ今後身になりそうな勉強をするにはどうすればよいか?

変えるべきポイントは?

日々の業務の傾向として、1つ1つのタスクは小さいが、会議・割り込みが発生したり、単純にタスクの量が多いことからコンテキストスイッチが多発し、1つのタスクに集中する時間がとれずに効率が悪いことに気づいた。 全力で仕事をしているつもりなのだが、思ったように進捗も出ずに精神的には疲弊しているので業務終了後に趣味で開発という気分にもならず、スマホを触って寝るという悪い習慣が根付いてしまっていた。 少しでも趣味の開発の時間を十分に確保するために、悪い習慣を撲滅する必要がある。

変化を起こすには?
象使いに方向を教える。
  1. ブライトスポットを見つける。 趣味でプログラムを触ったり調べ物をしているときはどんな時か。何よりもまず「楽しい」と思った時であると思う。 プログラマーを目指したきっかけを振り返っても、作ることに幸せを感じていたことにほかならない。 作る楽しみよりも着手するときのだるさが勝るといけない。まずはハードルを下げて自分が楽しいと感じたことを行う。

  2. 大事な一歩の台本を書く。 楽しいと感じて、(真に成長に繋がるかはおいておいて)趣味の開発を再スタートできるような計画を建てる。 本書を読みながらここまでの思考に至った結果、「まずは直近で自分があったら嬉しいなと思うプログラム/サービスを作る」ことに思い至った。実は先日の記事はその成果である。 引き続きいくつかの目標があるので着実に進めていきたい。

象にやる気を与える。
  1. 感情を芽生えさせる。何故成長したいのか。成長しなければどうなるのか?改めて、自己の方向性を考えなおし、未来の理想の自分を想像する。いまいまでの小さな変化が未来の自分を作るんだとコブする。

  2. 変化を細かくする。前述の通り、とにかくハードルを下げ何か少しでも生み出すことにつながっていることを日々実行する。

道筋を定める。
  1. 環境を変える。実際今は環境への変化は望めない。ただし、今年中に引っ越しを検討。 今はまともに机や椅子、モニタが無いので、いざ家で作業をしようと思っても不満を感じることが多い。

  2. 習慣を築く。ひとまずgithubに草を生やすことを目標にする。

  3. 仲間を集める。これは知り合いや同僚何人かに声をかけプライベートでslackのチャットルームを作ったが、今のところあまり考えていた通りには進んでいない。またきっかけがあれば別途考える。

以上の戦略により年始以降は好調なスタートを切れていると考えているので、引き続き進捗を出していきたい。