
Tweetと言うわけで、去る2010/11/20(土)にRailsDevCon2010を開催してきました。今回は実行委員と言いつつ、当日まであまり何もできなかったので、会場係として、明い緑のパーカーと言う季節もへったくれもない格好でいろいろ動きまわってました。
実は企画自体はRubyKaigi2009の直後ぐらいから @ysakaki さんといろいろ妄想を語ってたのですが、当時のスタッフの都合などもあり、なかなか開催に踏み切れませんでした。そんなとき、RubyWorldConference 2010での @a_matsuda さんの講演を聞いて、「これはやらんといかんな!」と思い立ち、急遽開催することになりました。少ない準備期間でこれだけの資料を準備していただいたスピーカーの方々、また当日の設営と撤収作業を手伝っていただいた当日スタッフの方々、そして事前の準備に奔走したスタッフの面々、そして隣で仕事と共に頑張ってくれた副実行委員長、本当にありがとうございました!このRailsDevConは定期的に開催できればいいなーと思ってるので、その時はまたみなさんご参加ください。
そして次は TokyuRubyKaigi 03 の司会へと続きます。
Tags: Rails, RubyTweetと言うわけで、Rack化してみました。
Github上のrackブランチがそれにあたります。
以下で実験中です。ちゃんと絵文字と漢字コードが変換されたときはちょっとうれしかったりしました。
特に難しいことはなく、以下のような感じで動作させることができます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 | # -*- coding: utf-8 -*- require 'rubygems' require 'sinatra' $LOAD_PATH << './lib/jpmobile/lib' require 'jpmobile' require 'jpmobile/rack' require 'pp' use Jpmobile::Rack::MobileCarrier use Jpmobile::Rack::ParamsFilter use Jpmobile::Rack::Filter get '/' do erb :index end |
1 2 3 4 5 6 7 8 | <html> <head> <title>そうでもない</title> </head> <body> 言わずもがな </body> </html> |
またパラメータのEncodingも変換しています。Rack::Request#paramsが必ずASCII-8BITになるので、それを無理矢理UTF-8に戻しています。
Rails 3.0 on Ruby 1.9 で動作する jpmobile と言うのが目標です。と言うかRubyKaigi 2010までには何とかしないとダメなので、7月初旬にはリリースできるようにしたいと思っています。Rails 3.0対応のパッチとか送ってもらっているのですが、Rack化でいろいろ変わったのと、見直したいところも多々あるので、しばしお待ちください。すんません…
Tags: jpmobile, Rails, Rails 3.0, SinatraTweet前々回、前回の続きです。次はMySQLドライバを調べてみます。
さてアクティブチェックしては、connection_adaptors/mysql_adaptor.rbのactive?メソッドでした。
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | def active? if @connection.respond_to?(:stat) @connection.stat else @connection.query 'select 1' end # mysql-ruby doesn't raise an exception when stat fails. if @connection.respond_to?(:errno) @connection.errno.zero? else true end rescue Mysql::Error false end |
ここでは、@connection.statか@connection.query 'select 1'が呼び出されます。その前に、まず@connectionとはなにかと言うと、親クラスであるAbstractAdapterの38行目にあるinitializeメソッドで作成されるインスタンス変数です。ファイルはconnection_adaptors/abstract_adaptor.rbです。
38 39 40 41 42 43 | def initialize(connection, logger = nil) #:nodoc: @connection, @logger = connection, logger @runtime = 0 @last_verification = 0 @query_cache_enabled = false end |
このメソッドはconnection_adaptors/mysql_adaptor.rbの200行目で呼び出されるわけです。このクラスは同じファイルの75行目でインスタンスを生成しています。
48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 | # Establishes a connection to the database that's used by all Active Record objects. def self.mysql_connection(config) # :nodoc: config = config.symbolize_keys host = config[:host] port = config[:port] socket = config[:socket] username = config[:username] ? config[:username].to_s : 'root' password = config[:password].to_s database = config[:database] # Require the MySQL driver and define Mysql::Result.all_hashes unless defined? Mysql begin require_library_or_gem('mysql') rescue LoadError $stderr.puts '!!! The bundled mysql.rb driver has been removed from Rails 2.2. Please install the mysql gem and try again: gem install mysql.' raise end end MysqlCompat.define_all_hashes_method! mysql = Mysql.init mysql.ssl_set(config[:sslkey], config[:sslcert], config[:sslca], config[:sslcapath], config[:sslcipher]) if config[:sslca] || config[:sslkey] default_flags = Mysql.const_defined?(:CLIENT_MULTI_RESULTS) ? Mysql::CLIENT_MULTI_RESULTS : 0 options = [host, username, password, database, port, socket, default_flags] ConnectionAdapters::MysqlAdapter.new(mysql, logger, options, config) end end |
この引数mysqlはその前のMySQL.initで生成されています。このMySQLはRubyのMySQLドライバであるMySQL/Rubyのことです。
さてこのMySQL/Rubyですが、拡張ライブラリなのでC言語で書かれています。また内部的にはMySQL APIを呼び出しているので、適宜両方見ていきます。
まず、statメソッドがあればそれが呼ばれるので、そちらを調べると、ext/mysql_api/mysql.cの1978行目にstatメソッドの定義があります。
1 | rb_define_method(cMysql, "stat", my_stat, 0); |
細かいことは省きますが、MySQL#statが呼ばれるとmy_statが実行されます。ではそのmy_statは693行目にありました。
693 694 695 696 697 698 699 700 701 | /* stat() */ static VALUE my_stat(VALUE obj) { MYSQL* m = GetHandler(obj); const char* s = mysql_stat(m); if (s == NULL) mysql_raise(m); return rb_tainted_str_new2(s); } |
ここでGetHandlerはマクロで、構造体の中にある接続ハンドラを取得しています。で、mysql_statという関数を実行して結果を判定しているわけですね。ではこのmysql_statはと言うと、MySQL 5.0のリファレンスマニュアルには「mysqladmin statusと同じ結果を返す」とだけ書かれています。
あまりC/C++をやった経験がないのですが、とりあえず調査続行と言うことでMySQL本体のソースを追ってみます。まずはダウンロードして展開すると、libmysql/libmysql.cと言うファイルが見つかります。前述のは、いわゆる共有ライブラリ経由での接続なので、ここを見ればいいと思われます。このファイルの1407行目にmysql_statがありました。
1407 1408 1409 1410 1411 1412 1413 1414 | const char * STDCALL mysql_stat(MYSQL *mysql) { DBUG_ENTER("mysql_stat"); if (simple_command(mysql,COM_STATISTICS,0,0,0)) DBUG_RETURN(mysql->net.last_error); DBUG_RETURN((*mysql->methods->read_statistics)(mysql)); } |
どうやらsimple_commandを呼び出しているだけのようです。simple_commandはinclude/mysql.hの852行目に定義されているマクロです。
852 853 854 | #define simple_command(mysql, command, arg, length, skip_check) \ (*(mysql)->methods->advanced_command)(mysql, command, 0, \ 0, arg, length, skip_check, NULL) |
mysql経由でadvanced_commandを呼び出しています。mysqlはinclude/mysql.hの258行目で定義されている構造体です。
258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 | typedef struct st_mysql { NET net; /* Communication parameters */ unsigned char *connector_fd; /* ConnectorFd for SSL */ char *host,*user,*passwd,*unix_socket,*server_version,*host_info; char *info, *db; struct charset_info_st *charset; MYSQL_FIELD *fields; MEM_ROOT field_alloc; my_ulonglong affected_rows; my_ulonglong insert_id; /* id if insert on table with NEXTNR */ my_ulonglong extra_info; /* Not used */ unsigned long thread_id; /* Id for connection in server */ unsigned long packet_length; unsigned int port; unsigned long client_flag,server_capabilities; unsigned int protocol_version; unsigned int field_count; unsigned int server_status; unsigned int server_language; unsigned int warning_count; struct st_mysql_options options; enum mysql_status status; my_bool free_me; /* If free in mysql_close */ my_bool reconnect; /* set to 1 if automatic reconnect */ /* session-wide random string */ char scramble[SCRAMBLE_LENGTH+1]; /* Set if this is the original connection, not a master or a slave we have added though mysql_rpl_probe() or mysql_set_master()/ mysql_add_slave() */ my_bool rpl_pivot; /* Pointers to the master, and the next slave connections, points to itself if lone connection. */ struct st_mysql* master, *next_slave; struct st_mysql* last_used_slave; /* needed for round-robin slave pick */ /* needed for send/read/store/use result to work correctly with replication */ struct st_mysql* last_used_con; LIST *stmts; /* list of all statements */ const struct st_mysql_methods *methods; void *thd; /* Points to boolean flag in MYSQL_RES or MYSQL_STMT. We set this flag from mysql_stmt_close if close had to cancel result set of this object. */ my_bool *unbuffered_fetch_owner; /* needed for embedded server - no net buffer to store the 'info' */ char *info_buffer; void *extension; } MYSQL; |
この中のmethodsはst_mysql_methodsと言う構造体で、753行目に定義されています。
753 754 755 756 757 758 759 760 761 762 763 764 765 766 767 768 769 770 771 772 773 774 775 776 777 778 779 780 781 782 | typedef struct st_mysql_methods { my_bool (*read_query_result)(MYSQL *mysql); my_bool (*advanced_command)(MYSQL *mysql, enum enum_server_command command, const unsigned char *header, unsigned long header_length, const unsigned char *arg, unsigned long arg_length, my_bool skip_check, MYSQL_STMT *stmt); MYSQL_DATA *(*read_rows)(MYSQL *mysql,MYSQL_FIELD *mysql_fields, unsigned int fields); MYSQL_RES * (*use_result)(MYSQL *mysql); void (*fetch_lengths)(unsigned long *to, MYSQL_ROW column, unsigned int field_count); void (*flush_use_result)(MYSQL *mysql); #if !defined(MYSQL_SERVER) || defined(EMBEDDED_LIBRARY) MYSQL_FIELD * (*list_fields)(MYSQL *mysql); my_bool (*read_prepare_result)(MYSQL *mysql, MYSQL_STMT *stmt); int (*stmt_execute)(MYSQL_STMT *stmt); int (*read_binary_rows)(MYSQL_STMT *stmt); int (*unbuffered_fetch)(MYSQL *mysql, char **row); void (*free_embedded_thd)(MYSQL *mysql); const char *(*read_statistics)(MYSQL *mysql); my_bool (*next_result)(MYSQL *mysql); int (*read_change_user_result)(MYSQL *mysql, char *buff, const char *passwd); int (*read_rows_from_cursor)(MYSQL_STMT *stmt); #endif } MYSQL_METHODS; |
だんだん長くなってきました。さてこれはMYSQL_METHODS型なのですが、定義しかありません。実体はと言うと、sql-common/client.cの1885行目で代入されている、client_methodsです。
1885 1886 1887 1888 1889 | /* Don't give sigpipe errors if the client doesn't want them */ set_sigpipe(mysql); mysql->methods= &client_methods; net->vio = 0; /* If something goes wrong */ mysql->client_flag=0; /* For handshake */ |
で、このclient_methodsは1763行目で定義されています。
1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 | static MYSQL_METHODS client_methods= { cli_read_query_result, /* read_query_result */ cli_advanced_command, /* advanced_command */ cli_read_rows, /* read_rows */ cli_use_result, /* use_result */ cli_fetch_lengths, /* fetch_lengths */ cli_flush_use_result /* flush_use_result */ #ifndef MYSQL_SERVER ,cli_list_fields, /* list_fields */ cli_read_prepare_result, /* read_prepare_result */ cli_stmt_execute, /* stmt_execute */ cli_read_binary_rows, /* read_binary_rows */ cli_unbuffered_fetch, /* unbuffered_fetch */ NULL, /* free_embedded_thd */ cli_read_statistics, /* read_statistics */ cli_read_query_result, /* next_result */ cli_read_change_user_result, /* read_change_user_result */ cli_read_binary_rows /* read_rows_from_cursor */ #endif }; |
もうちょい続きそうです。で、ここにあるcli_advanced_commandはと言うと、764行目にありました。
1763 1764 1765 1766 1767 1768 1769 1770 1771 1772 1773 1774 1775 1776 1777 1778 1779 1780 1781 1782 1783 1784 1785 1786 1787 1788 1789 1790 1791 1792 1793 1794 1795 1796 1797 1798 1799 1800 1801 1802 1803 1804 1805 1806 1807 1808 1809 1810 1811 1812 1813 1814 1815 1816 1817 1818 1819 1820 1821 1822 1823 1824 1825 1826 1827 1828 1829 | my_bool cli_advanced_command(MYSQL *mysql, enum enum_server_command command, const uchar *header, ulong header_length, const uchar *arg, ulong arg_length, my_bool skip_check, MYSQL_STMT *stmt) { NET *net= &mysql->net; my_bool result= 1; init_sigpipe_variables my_bool stmt_skip= stmt ? stmt->state != MYSQL_STMT_INIT_DONE : FALSE; DBUG_ENTER("cli_advanced_command"); /* Don't give sigpipe errors if the client doesn't want them */ set_sigpipe(mysql); if (mysql->net.vio == 0) { /* Do reconnect if possible */ if (mysql_reconnect(mysql) || stmt_skip) DBUG_RETURN(1); } if (mysql->status != MYSQL_STATUS_READY || mysql->server_status & SERVER_MORE_RESULTS_EXISTS) { DBUG_PRINT("error",("state: %d", mysql->status)); set_mysql_error(mysql, CR_COMMANDS_OUT_OF_SYNC, unknown_sqlstate); DBUG_RETURN(1); } net_clear_error(net); mysql->info=0; mysql->affected_rows= ~(my_ulonglong) 0; /* We don't want to clear the protocol buffer on COM_QUIT, because if the previous command was a shutdown command, we may have the response for the COM_QUIT already in the communication buffer */ net_clear(&mysql->net, (command != COM_QUIT)); if (net_write_command(net,(uchar) command, header, header_length, arg, arg_length)) { DBUG_PRINT("error",("Can't send command to server. Error: %d", socket_errno)); if (net->last_errno == ER_NET_PACKET_TOO_LARGE) { set_mysql_error(mysql, CR_NET_PACKET_TOO_LARGE, unknown_sqlstate); goto end; } end_server(mysql); if (mysql_reconnect(mysql) || stmt_skip) goto end; if (net_write_command(net,(uchar) command, header, header_length, arg, arg_length)) { set_mysql_error(mysql, CR_SERVER_GONE_ERROR, unknown_sqlstate); goto end; } } result=0; if (!skip_check) result= ((mysql->packet_length=cli_safe_read(mysql)) == packet_error ? 1 : 0); end: reset_sigpipe(mysql); DBUG_PRINT("exit",("result: %d", result)); DBUG_RETURN(result); } |
だんだん面倒になってきたので、これぐらいにしておいて上から順に見ていくと、いろいろ初期化して何らかの事情で切断されたことを知っていれば(mysql->net.vio == 0)再接続。続いて準備をしてコマンド(COM_STATISTICS)送信(net_write_command)し、結果を返して終わる。
長くなりましたが、MySQL/RubyのstatメソッドではCOM_STATISTICSを送信していると言うことがわかりました。これは統計情報を取得しようとしていると言うことで、件の負荷上昇に原因となります。ただ、じゃあ外せばいいかというと、Rails + MySQL (+ Mongrel?) でDB接続の通信が無い状態が続くとデッドロックする。 – こせきの技術日記と言うこともあるのでそう簡単ではないようです。今回の場合はRubyではTCP接続をしていないのでMongrelの問題は発生しませんが、負荷軽減を目論んでアクティブチェックを外す場合には、フレームワークやドライバまでちゃんと調べた方が良さそうだと感じました。
Tweet前回の話を受けて、Railsの場合はどうなのかを調べてみたいと思います。ターゲットはRails 2.3.5です。
RailsのO/R MapperはActiveRecordです。今回はActiveRecordでMySQLの場合について調べます。
まずはアクティブチェックしている部分を探します。ざっと見ると、connection_adaptors/mysql_adaptor.rbの275行目にactive?メソッドがありました。
275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 | def active? if @connection.respond_to?(:stat) @connection.stat else @connection.query 'select 1' end # mysql-ruby doesn't raise an exception when stat fails. if @connection.respond_to?(:errno) @connection.errno.zero? else true end rescue Mysql::Error false end |
うぉ、SELECT 1とかそのままじゃないか。とりあえず次はこれを呼び出しているところを探します。するとconnection_adaptors/abstract_adaptor.rbの149行目にverify!(*ignored)メソッドがありました。
147 148 149 150 151 152 | # Checks whether the connection to the database is still active (i.e. not stale). # This is done under the hood by calling <tt>active?</tt>. If the connection # is no longer active, then this method will reconnect to the database. def verify!(*ignored) reconnect! unless active? end |
アクティブでなければ再接続するようです。ではこのメソッドはどこで呼ばれているのかというと、connection_adaptors/abstract/connection_pool.rbの255行目にあるcheckout_and_verifyメソッドです。
255 256 257 258 259 260 | def checkout_and_verify(c) c.verify! c.run_callbacks :checkout @checked_out << c c end |
ここで接続確認して接続を取得、それをインスタンス変数@checked_outに保存しているわけです。まだ続きますが、ではこのメソッドはと言うと、すぐ上にあるchechout_new_connectionとcheckout_existing_connectionメソッドで呼び出されています。
244 245 246 247 248 249 250 251 252 253 | def checkout_new_connection c = new_connection @connections << c checkout_and_verify(c) end def checkout_existing_connection c = (@connections - @checked_out).first checkout_and_verify(c) end |
まだ続きそうです。では、これはと言うと同じファイルの181行目にあるcheckoutメソッドで呼び出されているようです。
181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 | def checkout # Checkout an available connection @connection_mutex.synchronize do loop do conn = if @checked_out.size < @connections.size checkout_existing_connection elsif @connections.size < @size checkout_new_connection end return conn if conn # No connections available; wait for one if @queue.wait(@timeout) next else # try looting dead threads clear_stale_cached_connections! if @size == @checked_out.size raise ConnectionTimeoutError, "could not obtain a database connection#{" within #{@timeout} seconds" if @timeout}. The max pool size is currently #{@size}; consider increasing it." end end end end end |
さてさて、ではこのメソッドはどこから呼ばれるかというと、同じファイルにあるconnectionとwith_connectionメソッドのようです。
89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 | # Retrieve the connection associated with the current thread, or call # #checkout to obtain one if necessary. # # #connection can be called any number of times; the connection is # held in a hash keyed by the thread id. def connection if conn = @reserved_connections[current_connection_id] conn else @reserved_connections[current_connection_id] = checkout end end # Signal that the thread is finished with the current connection. # #release_connection releases the connection-thread association # and returns the connection to the pool. def release_connection conn = @reserved_connections.delete(current_connection_id) checkin conn if conn end # Reserve a connection, and yield it to a block. Ensure the connection is # checked back in when finished. def with_connection conn = checkout yield conn ensure checkin conn end |
とまあだいたいこんな感じで、ActiveRecord::Base.connection.execute("SELECT * FROM users")が実行されるときには、active?メソッドが呼び出されているわけです。実際に、例えばp "Active Check!"なんてのを仕込むと毎回表示されます。これはproduction環境でも変わりませんでした。
さて次回はactive?のところでは実際は何が呼び出されるのかを調べるために、MySQLドライバを見てみたいと思います。
Tweet例によって気になったので意訳。
Checking for a live database connection considered harmful on MySQL Performance Blog
顧客のデータベースでよく見かけて注意するのだが、クエリーを送信する前にデータベース接続がアクティブかどうかをチェックするのは大きなオーバーヘッドになる。これは、次のような擬似コードで書かれるデザインパターンに由来する。
1 2 3 4 5 6 | function query_database(connection, sql) if !connection.is_alive() and !connection.reconnect() then throw exception end return connection.execute(sql) end |
多くの開発環境やフレームワークで、こういうコードになっている。これには、実際には期待したとおりには動かないと言うことと、大きなパフォーマンスのオーバーヘッドがあると言う2点で間違っている。
2つの間違いがあり
このコードはレースコンディション(競合状態)によって動作しない。もしチェックしたときに接続がアクティブだとしても、connection.execute(sql)を実行するときにアクティブだとは保証されない。さらにもし非アクティブで再接続したとしても、同様にアクティブであるとは保証されない。
チェックして実行するのは実用的ではない。代わりに、次のように書き換えるべきだ。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | function query_database(connection, sql, retries=1) while true try result=connection.execute(sql) return result catch InactiveConnectionException e if retries> 0 then retries = retries - 1 connection.reconnect() else throw e end end end end |
is_active()が無くなったのに気付いただろうか。接続がアクティブであればクエリーが実行され、そうでなければ失敗して再接続し、再度実行しようとする。
このコードだと、必要に応じてロック待ちのタイムアウトやデッドロックのときに再試行することができるようになっている。私の経験上、多くのアプリケーションで有効である。ほとんどのアプリケーションでは、こう言うときには単純に再試行するだけで、ちゃんと扱おうとはしていない。
アクティブチェックには大抵の場合、MySQLのプロトコルレベルのコマンドであるpingかstatisticsの呼び出しか、SELECT 1のような自明なクエリーが実行される。前者のコマンドはSHOW GLOBAL STATUSで表示されるCom_admin_commandsをインクリメントし、後者のクエリーは診断を難しくする。これは多くのアプリケーションで非常に高コストとなる。ここには2つのコストがある。1つはネットワーク通信とクエリー実行時間のアプリケーションへのコストで、もう1つはデータベースサーバの負荷上昇。このデータベースサーバへの負荷はかなり大きい。何日か前に、「管理コマンドのstatistics」を使うRuby on Railsアプリケーションを見たが、全クエリー実行時間の40%がこのコマンドだった。この不必要な接続チェックを削除したところ、データベースの負荷が半分程度に削減できた。これは普通じゃない!
アプリケーションのクエリーが長いとき、追加のクエリーはノイズの中で消えてしまう。しかし高トラフィックアプリケーションはクエリー実行時間を短くするのに途方もない努力を費やし、いくつかのチューニングしたアプリケーションでは、クエリーの実行時間がミリ秒より長くならないかドキドキしている。もしデータベースで毎秒20,000クエリー走っているなら、コネクションチェックも毎秒20,000回行われていることになる。これらpingやstatisticsと言うクエリーは、アプリケーションで実行すべきクエリーと同じぐらいコストがかかっているんだ。
これはデータベースサーバへの負荷であった。アプリケーション側では、クエリー実行時間の2倍の遅延があるのがわかるだろう。クエリーを実行するときには、そのアプリケーションフレームワークがチェックのためにネットワーク通信をして、さらに別のネットワーク通信でクエリーを実行する。これもやっぱり問題だ。
問題なのは、さっきの擬似コードがレアケースに気を取られて一般的な場合にペナルティーを課しているところ。普通は接続は生きていて、確認したり再接続したりはしなくてもいい。良い方法は、レースコンディションを解決したコードを使うことだ。もし接続が切れていても、クエリーを実行するときに探せばいいだけだ。そのときまでは、全てがOKで、クエリーを実行できる。
問題となるライブラリのアップストリームメンテナが、この問題を見つけ出して解決することを願っている。アプリケーションが成長するときの大きな手助けになるからだ。ラボではうまくいっていて、現場でもそうであったとしても、パフォーマンスはすぐに問題となる。そしてそれはすごく目立つんだ。
この馬鹿げた結果を見て欲しい。
1 2 3 4 5 6 7 8 9 | # Rank Query ID Response time Calls R/Call Item # ==== ================== ================ ====== ======== =============== # 1 0x5E796D5A4A7D1CA9 10651.0708 73.1% 120487 0.0884 ADMIN STATISTICS # 2 0x85FFF5AA78E5FF6A 1090.0772 7.5% 23621 0.0461 BEGIN # 3 0x6E85B9A9C9FF813E 868.0335 6.0% 6923 0.1254 UPDATE scores # 4 0xA3A0423749EC0E37 851.0152 5.8% 6020 0.1414 UPDATE user_datas # 5 0x813031B8BBC3B329 822.0041 5.6% 23299 0.0353 COMMIT # 6 0xA873BBC4583C4C85 278.4533 1.9% 6985 0.0399 SELECT users user_devices That's right, 73% of the server's load is consumed by checking to see if the connection is still alive |
これが本当だとすると、Rubyで使われているMySQLライブラリや、RailsのMySQLアダプタとかをちゃんと調べないとやばいんじゃないか?と言うわけで調査にはいります。
Tags: MySQL, Rails, Ruby, Ruby on Rails, ネットワークTweet型をt.binaryなどとすると、デフォルトではBLOBとなってしまい、64KiBまでしか保存できなくなってしまいます。そこで:limit => 1.megabyteとオプションで制限を大きめに書いてやると、Railsが勝手に拡張してMEDIUMBLOBにしてくれます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | class CreateImages < ActiveRecord::Migration def self.up create_table :images do |t| t.binary :data, :limit => 1.megabyte t.integer :stat t.timestamps end end def self.down drop_table :images end end |
1 2 3 4 5 6 7 8 9 10 11 | mysql> desc imags; +----------------+------------+------+-----+---------+----------------+ | Field | Type | Null | Key | Default | Extra | +----------------+------------+------+-----+---------+----------------+ | id | int(11) | NO | PRI | NULL | auto_increment | | data | mediumblob | YES | | NULL | | | stat | int(11) | YES | | NULL | | | created_at | datetime | YES | | NULL | | | updated_at | datetime | YES | | NULL | | +----------------+------------+------+-----+---------+----------------+ 5 rows in set (0.00 sec) |
