2014年10月

gem install で C拡張をビルドする流れを追ってみた

bin/gems に pry を仕込んでステップ実行。コマンドは以下のように実行してみた。

LDFLAGS="`mysql_config --libs` -lstdc++" bin/gem install mysql2 -- --with-mysql-lib=/usr/lib64/mysql
  1 #!/usr/bin/env ruby
  2 #--
  3 # Copyright 2006 by Chad Fowler, Rich Kilmer, Jim Weirich and others.
  4 # All rights reserved.
  5 # See LICENSE.txt for permissions.
  6 #++
  7
  8 require 'rubygems'
  9 require 'rubygems/gem_runner'
 10 require 'rubygems/exceptions'
 11
 12 required_version = Gem::Requirement.new ">= 1.8.7"
 13
 14 unless required_version.satisfied_by? Gem.ruby_version then
 15   abort "Expected Ruby Version #{required_version}, is #{Gem.ruby_version}"
 16 end
 17
 18 args = ARGV.clone
 19
 20 begin
=> 21   binding,pry
 22   Gem::GemRunner.new.run args
 23 rescue Gem::SystemExitException => e
 24   exit e.exit_code
 25 end
 26

中略

この辺りからC拡張の処理っぽいところに入ってきた

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/installer.rb @ line 230 Gem::Installer#install:

    225:
    226:     if @options[:install_as_default]
    227:       extract_bin
    228:       write_default_spec
    229:     else
    230:       extract_files
    231:
 => 232:       build_extensions
    233:       write_build_info_file
    234:       run_post_build_hooks
    235:
From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/installer.rb @ line 675 Gem::Installer#build_extensions:

    674: def build_extensions
 => 675:   builder = Gem::Ext::Builder.new spec, @build_args
    676:
    677:   builder.build_extensions
    678: end

ここで ruby ext/mysql2/extconf.rb を実行して、Makefile を生成しているようだ。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 198 Gem::Ext::Builder#build_extensions:

    179: def build_extensions
    180:   return if @spec.extensions.empty?
    181:
    182:   if @build_args.empty?
    183:     say "Building native extensions.  This could take a while..."
    184:   else
    185:     say "Building native extensions with: '#{@build_args.join ' '}'"
    186:     say "This could take a while..."
    187:   end
    188:
    189:   dest_path = @spec.extension_dir
    190:
    191:   FileUtils.rm_f @spec.gem_build_complete_path
    192:
    193:   @ran_rake = false # only run rake once
    194:
    195:   @spec.extensions.each do |extension|
    196:     break if @ran_rake
    197:
 => 198:     build_extension extension, dest_path
    199:   end
    200:
    201:   FileUtils.touch @spec.gem_build_complete_path
    202: end

[1] pry(#<Gem::Ext::Builder>)> extension
=> "ext/mysql2/extconf.rb"
[2] pry(#<Gem::Ext::Builder>)> dest_path
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/extensions/x86-linux/2.1.0-static/mysql2-0.3.16"

gem install mysql -- --with-mysql-lib=/usr/lib64/mysql のように渡したオプションは@build_args に渡っている。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 161 Gem::Ext::Builder#build_extension:

    146: def build_extension extension, dest_path # :nodoc:
    147:   results = []
    148:
    149:   extension ||= '' # I wish I knew why this line existed
    150:   extension_dir =
    151:     File.expand_path File.join @gem_dir, File.dirname(extension)
    152:   lib_dir = File.join @spec.full_gem_path, @spec.raw_require_paths.first
    153:
    154:   builder = builder_for extension
    155:
    156:   begin
    157:     FileUtils.mkdir_p dest_path
    158:
    159:     CHDIR_MUTEX.synchronize do
    160:       Dir.chdir extension_dir do
 => 161:         results = builder.build(extension, @gem_dir, dest_path,
    162:                                 results, @build_args, lib_dir)
    163:
    164:         say results.join("\n") if Gem.configuration.really_verbose
    165:       end
    166:     end
    167:
    168:     write_gem_make_out results.join "\n"
    169:   rescue => e
    170:     results << e.message
    171:     build_error extension_dir, results.join("\n"), $@
    172:   end
    173: end

[4] pry(#<Gem::Ext::Builder>)> extension
=> "ext/mysql2/extconf.rb"
[5] pry(#<Gem::Ext::Builder>)> @gem_dir
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16"
[6] pry(#<Gem::Ext::Builder>)> dest_path
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/extensions/x86-linux/2.1.0-static/mysql2-0.3.16"
[7] pry(#<Gem::Ext::Builder>)> results
=> []
[8] pry(#<Gem::Ext::Builder>)> @build_args
=> ["--with-mysql-lib=/usr/lib64/mysql"]
[9] pry(#<Gem::Ext::Builder>)> lib_dir
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib"

結果、最終的に ruby extconf.rb --with-mysql-lib=/usr/lib64/mysql と実行されて、Makefile が生成されている。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/ext_conf_builder.rb @ line 39 Gem::Ext::ExtConfBuilder.build:

    34:       begin
    35:         ENV["RUBYOPT"] = ["-r#{siteconf_path}", rubyopt].compact.join(' ')
    36:         cmd = [Gem.ruby, File.basename(extension), *args].join ' '
    37:
    38:         begin
 => 39:           run cmd, results
    40:         ensure
    41:           FileUtils.mv 'mkmf.log', dest_path if File.exist? 'mkmf.log'
    42:         end
    43:
    44:         ENV["DESTDIR"] = nil

[1] pry(Gem::Ext::ExtConfBuilder)> cmd
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/bin/ruby extconf.rb --with-mysql-lib=/usr/lib64/mysql"
From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 72 Gem::Ext::Builder.run:

    67:       rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil
    68:       if verbose
    69:         puts(command)
    70:         system(command)
    71:       else
 => 72:         results << command
    73:         results << `#{command} #{redirector}`
    74:       end
    75:     ensure
    76:       ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps
    77:     end

[1] pry(Gem::Ext::ExtConfBuilder)> command
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/bin/ruby extconf.rb --with-mysql-lib=/usr/lib64/mysql"
[2] pry(Gem::Ext::ExtConfBuilder)> redirector
=> "2>&1"

make の箇所

その後は make コマンドの実行を行っているようだ。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/ext_conf_builder.rb @ line 48 Gem::Ext::ExtConfBuilder.build:

    43:
    44:         ENV["DESTDIR"] = nil
    45:         ENV["RUBYOPT"] = rubyopt
    46:         siteconf.unlink
    47:
 => 48:         make dest_path, results
    49:
    50:         if tmp_dest
    51:           # TODO remove in RubyGems 3
    52:           if Gem.install_extension_in_lib and lib_dir then
    53:             FileUtils.mkdir_p lib_dir

[1] pry(Gem::Ext::ExtConfBuilder)> dest_path
=> "/home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/gems/2.1.0/extensions/x86-linux/2.1.0-static/mysql2-0.3.16"

あまり明確に意識していなかったが、make clean してから、make して、make install していた。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 51 Gem::Ext::Builder.make:

    29: def self.make(dest_path, results)
    30:   unless File.exist? 'Makefile' then
    31:     raise Gem::InstallError, 'Makefile not found'
    32:   end
    33:
    34:   # try to find make program from Ruby configure arguments first
    35:   RbConfig::CONFIG['configure_args'] =~ /with-make-prog\=(\w+)/
    36:   make_program = ENV['MAKE'] || ENV['make'] || $1
    37:   unless make_program then
    38:     make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
    39:   end
    40:
    41:   destdir = '"DESTDIR=%s"' % ENV['DESTDIR'] if RUBY_VERSION > '2.0'
    42:
    43:   ['clean', '', 'install'].each do |target|
    44:     # Pass DESTDIR via command line to override what's in MAKEFLAGS
    45:     cmd = [
    46:       make_program,
    47:       destdir,
    48:       target
    49:     ].join(' ').rstrip
    50:     begin
 => 51:       run(cmd, results, "make #{target}".rstrip)
    52:     rescue Gem::InstallError
    53:       raise unless target == 'clean' # ignore clean failure
    54:     end
    55:   end
    56: end

extconf.rb 実行時と同じ run メソッドに入って、make コマンドが実行されている。

From: /home/seo.naotoshi/.rbenv/versions/2.1.2/lib/ruby/2.1.0/rubygems/ext/builder.rb @ line 73 Gem::Ext::Builder.run:

    68:       rubygems_gemdeps, ENV['RUBYGEMS_GEMDEPS'] = ENV['RUBYGEMS_GEMDEPS'], nil
    69:       if verbose
    70:         puts(command)
    71:         system(command)
    72:       else
 => 73:         results << command
    74:         results << `#{command} #{redirector}`
    75:       end
    76:     ensure
    77:       ENV['RUBYGEMS_GEMDEPS'] = rubygems_gemdeps
    78:     end

[1] pry(Gem::Ext::ExtConfBuilder)> command
=> "make \"DESTDIR=\""

make にオプションを渡す方法

今回、make 時に -lstdc++ を指定したかったので、LDFLAGS 環境変数を指定してみたがうまくいかなかった。

調査してみると、ruby ext/mysql2/extconf.rb で生成した Makefile 内ではそもそも LDFLAGS という値を使っていなかった。(ldflags ならあった。うーん、LDFLAGS のほうが一般的だと思うが ...)

# Makefile

LOCAL_LIBS =
LIBS =  -L/usr/lib64 -lmysqlclient -lpthread -lm -lrt -ldl  -lpthread -lrt -ldl -lcrypt -lm   -lc

...

$(DLLIB): $(OBJS) Makefile
        $(ECHO) linking shared-object mysql2/$(DLLIB)
        -$(Q)$(RM) $(@)
        $(Q) $(LDSHARED) -o $@ $(OBJS) $(LIBPATH) $(DLDFLAGS) $(LOCAL_LIBS) $(LIBS)

代わりに LOCAL_LIBS というよさげなやつを見つけたので、

$ LOCAL_LIBS="-lstdc++" make

とやってみたが、これはダメだった。これは make の話だけども、どうやら環境変数で指定する場合は make -e オプションを使わないと Makefile 内の設定で上書きされてしまうらしい。もしくは、

$ make LOCAL_LIBS="-lstdc++"

のように書くと、make の機能として変数置換をしてくれるようだ。make -e は環境変数全てを 変数設定してしまうため、こちらの方式のほうが良さそうだ。

さて、コードリーディングの結果 make コマンドは、このような定義になっていて、

make_program = ENV['MAKE'] || ENV['make'] || $1
unless make_program then
  make_program = (/mswin/ =~ RUBY_PLATFORM) ? 'nmake' : 'make'
end

MAKE 環境変数で制御できるようだ。そこで、以下のように MAKE 環境変数に引数を指定してみたところ、-lstdc++ へのリンクも追加してビルドできた。

MAKE="make LOCAL_LIBS='-lstdc++'" gem install mysql2 -- --with-mysql-lib=/usr/lib64/mysql

というわけで、MAKE 環境変数に指定することで、make にオプションを渡せる。

補足:とはいえ、bundle install 時に環境変数で渡すとなると全 gem に影響が出てしまうので、あまり使い勝手はよくなさそうだ。やはり、mysql2 gem に関しては mysql2 gem を mysql5.6 の libmysqlclient.a と static link したい話 に書いたように /usr/lib/mysql_config をいじるやり方のほうが筋がよさそう。

mysql2 gem を mysql5.6 の libmysqlclient.a と static link したい話

mysql2 gem を mysql5.6 の libmysqlclient.a と static link させたくて奮闘してた。

なぜ、そんなことをしたいのかというと、capistrano-bundle_rsync なるものを使って、デプロイサーバで mysql2 gem をビルドして、それを production サーバに rsync して撒くという運用をしているためである。

静的リンクできるようになれば、production サーバの OS の違いとか、入っている rpm のバージョン違いとか気にしなくてよくなるのでうれしい。

使った rpm は Oracle が配布しているオフィシャルなやつ で、こいつがおかしい。

$ sudo rpm -qa  | grep -i mysql
MySQL-client-5.6.21-1.el6.x86_64
MySQL-server-5.6.21-1.el6.x86_64
MySQL-devel-5.6.21-1.el6.x86_64
MySQL-shared-5.6.21-1.el6.x86_64

問題1. mysql_config --libs が示すパスがおかしい

$ ls /usr/lib64/libmysqlclient.so
/usr/lib64/libmysqlclient.so

$ ls /usr/lib64/mysql/libmysqlclient.a
/usr/lib64/mysql/libmysqlclient.a

$ mysql_config --libs
-L/usr/lib64 -lmysqlclient -lpthread -lm -lrt -ldl

mysql_config --libs の結果に -L/usr/lib64/mysql が含まれておらず、おかしい。これでは static link されない。 こちらにバグ報告がある => http://bugs.mysql.com/bug.php?id=67851

実際、mysql2 gem は mysql_config --libs の出力結果 を元に extconf.rb で Makefile 中の LIBS を生成しているが、-L/usr/lib64/mysql が含まれていないので、gem install mysql2 で静的リンクしてくれない。

対応方法として二つ考えられたので2番をやってみた。

  1. /usr/bin/mysql_config をいじる
  2. mysql2 gem ビルド時に --with-mysql-lib オプションを使って /usr/lib64/mysql を指定する。

補足: --with-mysql-lib オプションは mysql2 の README には書かれていないやつなので要チェックな。

$ gem install mysql2 -- --with-mysql-lib=/usr/lib64/mysql

# bundler を使いたい場合は以下のようにする。
# $ bundle config build.mysql2 --with-mysql-lib=/usr/lib64/mysql
# $ bundle

$ find ~/.rbenv/versions/2.1.3 -name 'mysql2.so'
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/ext/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so

$ ldd /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
    linux-vdso.so.1 =>  (0x00007fff5bbe8000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fdacb621000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fdacb39c000)
    librt.so.1 => /lib64/librt.so.1 (0x00007fdacb194000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fdacaf90000)
    libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fdacad58000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fdaca9c4000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fdaca7ae000)
    /lib64/ld-linux-x86-64.so.2 (0x00000035d6200000)
    libfreebl3.so => /usr/lib64/libfreebl3.so (0x00007fdaca536000)

ちなみに、静的リンクできていないときはこうなる

$ gem install mysql2

$ find ~/.rbenv/versions/2.1.3 -name 'mysql2.so'
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/ext/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
/home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so

$ ldd /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/gems/mysql2-0.3.16/lib/mysql2/mysql2.so
    linux-vdso.so.1 =>  (0x00007fff38bf2000)
    ### libmysqlclient.so への依存が出ていて静的リンクされていないことがわかる
    libmysqlclient.so.18 => /usr/lib64/libmysqlclient.so.18 (0x00007fc893f82000)
    libpthread.so.0 => /lib64/libpthread.so.0 (0x00007fc893d5b000)
    libm.so.6 => /lib64/libm.so.6 (0x00007fc893ad7000)
    librt.so.1 => /lib64/librt.so.1 (0x00007fc8938cf000)
    libdl.so.2 => /lib64/libdl.so.2 (0x00007fc8936ca000)
    libcrypt.so.1 => /lib64/libcrypt.so.1 (0x00007fc893493000)
    libc.so.6 => /lib64/libc.so.6 (0x00007fc8930ff000)
    libstdc++.so.6 => /usr/lib64/libstdc++.so.6 (0x00007fc892df8000)
    libgcc_s.so.1 => /lib64/libgcc_s.so.1 (0x00007fc892be2000)
    /lib64/ld-linux-x86-64.so.2 (0x00000035d6200000)
    libfreebl3.so => /usr/lib64/libfreebl3.so (0x00007fc89296b000)

問題2. undefined symbol: __cxa_pure_virtual と出る

静的リンクできた!!と思って irb を起動して require mysql2 とすると、

irb(main):001:0> require 'mysql2'
LoadError: /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so: 
undefined symbol: __cxa_pure_virtual - /home/vagrant/.rbenv/versions/2.1.3/lib/ruby/gems/2.1.0/extensions/x86_64-linux/2.1.0-static/mysql2-0.3.16/mysql2/mysql2.so

のようなエラーが出る。これはどうやら libmysqlclient.a が stdc++ とリンクできていなくておこっているらしい。こちらにバグ報告がある => http://bugs.mysql.com/bug.php?id=51642

また、なにか mysql2 gem にオプションがないかと ext/mysql2/extconf.rb を読んだが、どうも指定の方法がないっぽい(あったら教えてください)

仕方がないので /usr/bin/mysql_config をいじることにした(社内ではいじった状態で rpm を作り直してそれを利用することにした)

diff -u /usr/bin/mysql_config.bak /usr/bin/mysql_config
]--- /usr/bin/mysql_config.bak  2014-10-21 15:52:12.527601338 +0900
+++ /usr/bin/mysql_config       2014-10-21 15:50:56.886736049 +0900
@@ -84,7 +84,7 @@
 bindir='/usr/bin'

 # If installed, search for the compiled in directory first (might be "lib64")
-pkglibdir='/usr/lib64'
+pkglibdir='/usr/lib64/mysql'
 pkglibdir_rel=`echo $pkglibdir | sed -e "s;^$basedir/;;"`
 fix_path pkglibdir $pkglibdir_rel lib64/mysql lib64

@@ -111,7 +111,7 @@

 # Create options 
 # We intentionally add a space to the beginning and end of lib strings, simplifies replace later
-libs=" $ldflags -L$pkglibdir  -lmysqlclient   -lpthread -lm -lrt -ldl"
+libs=" $ldflags -L$pkglibdir  -lmysqlclient   -lpthread -lm -lrt -ldl -lstdc++"
 libs="$libs   "
 libs_r=" $ldflags -L$pkglibdir  -lmysqlclient_r   -lpthread -lm -lrt -ldl   "
 embedded_libs=" $ldflags -L$pkglibdir  -lmysqld    -lpthread -lm -lrt -lcrypt -ldl -laio    "

この状態で、gem uninstall mysql2 して、改めて gem install mysql2 したところ、静的リンクされた壊れていない mysql2 gem のビルドが出来た。

ERROR 1071 (42000): Specified key was too long; max key length is 1000 bytes

ただのメモ。

GrowthForecast のテーブルを作ろうと以下の DDL を打ったら失敗した。# 正確には、GF は自動で作ろうとしてくれるが、その時に失敗していた。

CREATE TABLE IF NOT EXISTS complex_graphs (
    id           INT UNSIGNED NOT NULL AUTO_INCREMENT,
    service_name VARCHAR(255) NOT NULL COLLATE utf8_bin,
    section_name VARCHAR(255) NOT NULL COLLATE utf8_bin,
    graph_name   VARCHAR(255) NOT NULL COLLATE utf8_bin,
    number       float UNSIGNED NOT NULL DEFAULT 0,
    description  VARCHAR(255) NOT NULL DEFAULT '',
    sort         INT UNSIGNED NOT NULL DEFAULT 0,
    meta         TEXT,
    created_at   INT UNSIGNED NOT NULL,
    updated_at   INT UNSIGNED NOT NULL,
    PRIMARY KEY (id),
    UNIQUE  (service_name, section_name, graph_name)
)  ENGINE=InnoDB DEFAULT CHARSET=utf8;

エラーメッセージは次のようなもの

ERROR 1071 (42000): Specified key was too long; max key length is 1000 bytes

あれ、なんで今まで大丈夫だったんだ?と思ったので調べた。1000 bytes 制限が出るのは、MyISAM の場合らしい。

MyISAM の場合、複合インデックスで全てのカラムの byte 数を合計した結果が 1000 bytes を超えるとダメで、(255 + 255 + 255) * 3 (utf8) で 1000 bytes 超えたから出たようだ。

つまり、InnoDB プラグインかなにかがちゃんと設定できていなくて、InnoDB で table 作ったつもりが、MyISAM になってしまっていたということだな。


ちなみに InnoDB の場合は 767 byets 制限のメッセージが出る事がある。

ERROR 1071 (42000): Specified key was too long; max key length is 767 bytes

こちらは、複合インデックスでもカラムそれぞれの制限値[1][2]になっているようだ。今回の場合は 255 * 3 (utf8) = 765 bytes なので、制限を超えずちゃんとテーブルを作れる。

なお、mysql5.6 にして utf8mb4 に使おうとすると、255 * 4 (utf8mb4) = 1020 bytes となり、制限を超えてしまうが、その場合は、innodb_large_prefix というオプションを ON にすると、3072 bytes まで扱える。

[1] http://blog.kamipo.net/entry/2012/11/13/102024 ひとつのカラムのキープレフィックスの最大値が767バイト
[2] http://treeapps.hatenablog.com/entry/20110508/p2 カラム分割も制限をかわす1つのテクニックになりうる

「ISUCON4 予選でアプリケーションを変更せずに予選通過ラインを突破するの術」golang 版

ISUCON4 予選でアプリケーションを変更せずに予選通過ラインを突破するの術 - Hateburo: kazeburo hatenablog が公開されたので、golang でも4万点超えできるかやってみた記録。

同じ設定で 
33,500。届かず。
ただし、golang だと unix domain socket を使おうとするとアプリにそれなりの改変を加えないといけないので、local port で通信するように nginx.conf を変更しているという事情がある。


 GOMAXPROCS=4 を env.sh に書いて 36,279 にアップ。まだ届かず。




kazeburo さんと metane さんに助言をもらって設定変更して 4万点超え!!

最終的な設定は以下

init.sh に以下を追加

$ cat init.sh 
cat <<'EOF' | mysql -h ${myhost} -P ${myport} -u ${myuser} ${mydb}
alter table login_log add index ip (ip), add index user_id (user_id);
EOF

/etc/sysctl.conf に以下を追加。

$ cat /etc/sysctl.conf
net.ipv4.tcp_max_tw_buckets = 2000000
net.ipv4.ip_local_port_range = 10000 65000
net.core.somaxconn = 32768
net.core.netdev_max_backlog = 8192
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_fin_timeout = 10
sudo /sbin/sysctl -p

env.sh に以下を追加

GOMAXPROCS=4
MARTINI_ENV=production

nginx.conf を以下のように変更.

$ cat /etc/nginx/nginx.conf
worker_processes  1;

events {
  worker_connections  10000;
}

http {
  include     mime.types;
  access_log  off;
  sendfile    on;
  tcp_nopush  on;
  tcp_nodelay on;
  etag        off;
  upstream app {
    server 127.0.0.1:8080;
    keepalive 16;
  }

  server {
    location / {
      proxy_http_version 1.1;
      proxy_set_header Connection "";
      proxy_pass http://app;
    }
    location ~ ^/(stylesheets|images)/ {
      open_file_cache max=100;
      root /home/isucon/webapp/public;
    }
  }
}

/etc/my.conf の設定

$ cat /etc/my.cnf
innodb_buffer_pool_size = 1G
innodb_flush_log_at_trx_commit = 0
innodb_flush_method=O_DIRECT

再起動して適用

$ sudo service mysqld restart
$ sudo /usr/bin/supervisorctl reload
$ sudo service nginx restart
なお、ruby の場合、27,327 だった。erb が遅すぎるので sprintf に置き換えればもっと伸びると思うが、それ普通にアプリを改変している。あとは sinatra を生 rack にするとか。後記: sprintf におきかえてみたが 5,000しか伸びなかった。後記: RACK_ENV=production でアプリ改変なしで4万超えた!

cf. チーム「椅子子」でISUCON4予選で2日目5位でした  / の erb を置き換えて1万伸びている?うーん
cf. sprintf_vs_gsub_vs_erb_vs_erubis.rb. sprintf に変えると7倍速くなる可能性。
cf. sinatra_vs_rack  生 rack にすると 1.7 倍速くなる可能性。

削除されたのに開いたままになっているため解放されてないファイルを調べる

$ sudo lsof +L1 | grep deleted
screen 14638 root  cwd    DIR   104,1 0  0    16805 /home/hisamori.tatsuro (deleted)
bash   14639 8032  cwd    DIR   104,1 0  0    16805 /home/hisamori.tatsuro (deleted)
2011 年ものの myfinder 氏の screen プロセスを見つけたw
A Ruby and Fluentd committer working at DeNA. 記事本文および記事中のコード片は引用および特記あるものを除いてすべて修正BSDライセンスとします。 #ruby #fluentd #growthforecast #haikanko #yohoushi #specinfra #serverspec #focuslight
はてぶ人気エントリー