CubicLouve

Spring_MTの技術ブログ

サーバーの状態やMySQLの状態の指標のまとめ

指標に関していつもググってばっかりいたので、まとめてみました。

ツッコミ大歓迎。

CPU usage

name detail
User ユーザ空間(アプリケーション)でCPUが使われた時間の割合
Nice 優先度を変更された(nice値が変更された)プロセスにより、ユーザ空間でCPUが使われた時間の割合
System カーネル空間でCPUが使われた時間の割合
Idle CPUが何も処理をせずに待機していたCPUの時間の割合(ディスクI/O待ち以外)
Wait(iowait) CPUがディスクI/O、またはネットワークI/Oの結果を待っていた時間の割合(I/O処理中で、その終了を待機している時間)
Intr 割り込み
SoftIRQ ソフト割り込み
Steal 仮想サーバがCPUを使って待たされていた時間の割合

http://blog.suusuke.info/2011/10/24/365/

hiboma/Linuxカーネル解読室-3-1.md at master · hiboma/hiboma · GitHub

yakst.com

O'Reilly Japan - 詳解 システム・パフォーマンス6.3.6 6.6.3 も合わせて参照

Memory Usage

name detail
used 使用している物理メモリ量
buffer ファイルなどのメタデータをキャッシュしている物理メモリ量
cached ページキャッシュに使用されているメモリ量
avail real 利用可能な物理メモリの量
total real 総物理メモリの量
used swap Swap領域で使用している量

cached も buffers も空きメモリの一部 状況に応じてflushされる

Nginx

name detail
Reading nginxはリクエストヘッダーを読み込んでいる数
Writing nginxはリクエストボディーを読み込んでいる、リクエストを処理中、またはクライアントへ返信している数。upstreamから結果を受け取ってクライアントに返し中なのか、upstreamからの回答待ちであるかが区別されてないっぽいのが困る。
Waiting リクエスト処理を待っているクライアントのコネクション数 (idle client connections waiting for a request.) keep-aliveの接続数

http://wiki.nginx.org/HttpStubStatusModule#stub_status

MySQL

変数の参照はここ

MySQL :: MySQL 5.6 リファレンスマニュアル :: 5.1.6 サーバーステータス変数

MySQL Threads

name detail
Cached キャッシュされているスレッド数(スレッドは使いまわされる)
Connected 現在の接続数
Running スリープ状態になっていないスレッドの数

Threads_created 接続を処理するために生成されたスレッド数(この値が増えまくるなら、cached が足りていない)

Threads_cached + Threads_connected が thread_cache_sizeの値より小さければ想定内。

MySQLのスレッドとか接続数とか - @bayashi Wiki

MySQL Processlist

name detail
State Closing Tables 変更されたテーブルデータをディスクにフラッシュし、使用されたテーブルを閉じているスレッド数
State Copying To Tmp Tables モリー内の一時テーブルにコピーしているスレッド数
State End ALTER TABLE、CREATE VIEW、DELETE、INSERT、SELECT、または UPDATE ステートメントの最後、ただしクリーンアップの前に発生しまる状態のスレッド数
State Freeing Items コマンドの実行を完了したスレッド数、通常、この状態のあとは cleaning up になります
State Init ALTER TABLE、DELETE、INSERT、SELECT、または UPDATE ステートメントの初期化の前に発生する状態のスレッド数
State Locked 別のクエリーによってロックされているスレッド数
State Login クライアントが正常に認証されるまでの初期状態のスレッド数
State Reading From Net ネットワークからパケットを読み込んでるスレッド数
State Sending Data SELECT ステートメントのために行を作成し、また、クライアントにデータを送っているスレッド数
State Sorting Result
State Statistics クエリー実行計画を開発する統計を計算しているスレッド数
State Updating 更新する行を探していて、それらを更新しているスレッド数
State Writing To Net ネットワークにパケットを書き込んでいるスレッド数
State None Stateのないスレッド数 例えば SLEEP中のスレッド
State Other

http://mysql.stu.edu.tw/doc/refman/5.1-olh/ja/general-thread-states.html

MyISM Indexes

name detail
Key Read Requests
Key Reads
Key Writes Requests
Key Writes

MySQL Handlers

クエリの I/O 動作

name detail
Handler Write INSERTの回数
Handler Update UPDATEの回数
Handler Delete DELETEの回数
Handler Read First テーブルやインデックスの全件検索(インデックスフルスキャン)の際にまず最初に先頭レコードの取得するが、その回数。フルスキャンが多いとこの回数が増える。(要チューニング)
Handler Read Key インデックスに基づく読み込み回数。これが多い場合は適切にインデックスが貼られている。
Handler Read Next インデックスに基づいて行を特定した後、後続の行を読んだ回数。範囲指定のインデックススキャンの場合に増えます。
Handler Read Prev インデックスに基づいて行を特定した後、その前の行を読んだ回数。範囲指定のインデックススキャンの場合に増えます。
Handler Read Rnd 固定位置に基づくレコード読んだの回数(handler::rnd_pos()が呼ばれた回数)。固定位置に基づくレコードの読み込みとは、Random Readのこと。結果のソートを必要とするクエリを多く実行すると、この値が大きくなる。(要チューニング)
Handler Read Rnd Next データファイルでの次のレコードを読み取った回数。 テーブルスキャンが多く実行されると、この値が大きくなる。(要チューニング)

www.percona.com

MySQL Select Types

name detail
Select Full Join インデックスのないカラムで JOINした回数
Select Full Range Join 範囲指定の効果はあるがインデックスは使わず JOINした回数
Select Range WHERE などの指定によって範囲が限定された探索を行った回数
Select Range Check インデクッスなしのJOIN数
Select Scan テーブル(またはインデックスでも)の先頭行から全件検索(スキャン)をした回数

JOINに関してはEXPLAINしてtypeがeq_refになるようにする。

MySQL Sorts

name detail
Sort Rows ソートしたレコード数
Sort Range 範囲検索ソートの回数
Sort Merge Passes ソートで必要としたマージパスの回数
Sort Scan テーブルスキャンでソートした回数

MySQL Temporary Objects

name detail
Created Tmp Tables 作成した一時テーブルの数
Created Tmp Disk Tables ディスク上に作成した一時テーブルの数
Created Tmp Files 作成した一時ファイルの数

sort_buffer_sizeを超える大きなORDER BYなどで作成される。

漢(オトコ)のコンピュータ道: Using filesort

MySQL Transaction Handle

name detail
Handler Commit コミットの要求数
Handler Rollback ロールバックの要求数
Handler Savepoint セーブポイントの要求数
Handler Savepoint Rollback セーブポイントロールバックの要求数

Cache hit rate

name detail
key cache
query cache
table lock immediate
thread cache
tmp table on memory

Dirty page rate

buffer pool の page のうち disk に flush されてない page の比率

ここが増加していると、diskへの書込みが追いついていない

https://dev.mysql.com/doc/refman/5.6/ja/glossary.html#glos_dirty_page

Buffer Pool Activity

name detail
Page Created 作成されたページの数
Page Read 読み込まれたページの数
Page Written 書き込まれたページの数

Checkpoint Age

Current Lock Wait

トランザクションのロック開放待ち時間の合計秒数

InnoDB I/O

InnoDB I/O Pending

Lock Structures

開放まちLock structの数

InnoDB Log

name detail
Innodb Log Buffer Size ログバッファのサイズ
Log Byte Written ログに書き込まれたデータ量
Log Byte Flushed ロクから書き出されたデータ量
Unflushed Log ログから書き出されていないデータ量

Row Lock Time

行ロック獲得のための所用総時間(msec)

Row Lock Waits

行ロック獲得待機回数

InnoDB Tables In Use

name detail
InnnoDB Tables In Use 実行中のトランザクションが利用しているテーブル数の合計数
InnnoDB Locked Tables 実行中のトランザクションがロックしているテーブル数の合計数

InnoDB Transactions

name detail
InnnoDB Transactions 生成されたトランザクション
History List undo領域にある未破棄のトランザクション

参照

mpstat

CPUごとの使用状況 -P ALLですべてのコアの情報を表示する

vmstat

unix domain socketのstatus

cat /proc/net/unixの中身

項目 説明
Num カーネルのテーブルスロット ffff8800798ec0c0
RefCount ソケットを使用しているユーザー数 00000002
Protocol いまのところいつも 0
Flags ソケット の状態を保持しているカーネル内部のフラグ
Type always '1' とかいてあるが2もある。。。 0001
St ソケットの内部状態 01 02 03 https://github.com/torvalds/linux/blob/master/include/uapi/linux/net.h#L47
Inode
Path (もしあれば) ソケットのパス名

sinatraで設定したパス一覧を取得する

メモ

こんな感じでスクリプトを書いた。

require 'rack'
require 'sinatra'

class Sinatra::Base
  private
  class << self
    attr_reader :original_routes
    # なぜかsuperが呼べない...
    def route(verb, path, options = {}, &block)
      @original_routes ||= {}
      (@original_routes[verb] ||= []) << path
      host_name(options.delete(:host)) if options.key?(:host)
      enable :empty_path_info if path == "" and empty_path_info.nil?
      signature = compile!(verb, path, block, options)
      (@routes[verb] ||= []) << signature
      invoke_hook(:route_added, verb, path, block)
      signature
    end
  end
end

def run(*args)
  args.first.instance_variable_get(:@mapping).each do |mapping|
    # 二番目の要素がmappingのbase path
    base_path = mapping[1]
    # 三番目の要素がcontroller class
    mapping[3].original_routes.each do |method, paths|
      paths.each { |path| puts "#{method}\t#{base_path}#{path}" }
    end
  end
end

Sinatra::Base.set :running_server, true
load './config.ru'

railsで特定のモデルに対してbelongs_toで参照先を設定しているモデルを取り出す

まあ、いろいろ調べる必要がありまして。。。

ざっくりとこんな感じでとれた。

# production環境では要らないが、development環境では必要かと
Dir.glob(File.expand_path('./app/models/*.rb', Rails.root)).each do |f|
  require f
end

# 定数を全部取得
constants = Object.constants.map do |name|
  Object.const_get(name)
end

# 定数一覧からモデルを絞り込む
all_models = constants.select do |c|
  c.class == Class && c < ActiveRecord::Base && !c.abstract_class?
end
                                                                                                                          
target = {}
all_models.each do |model|
  model.reflect_on_all_associations(:belongs_to).each do |association|
    if association.name == :hoge || %w(Hoge).include?(association.options[:class_name])
      target[association.active_record.name] = { model: association.active_record, foreign_key: association.foreign_key }
    end
  end
end
target

これで対象モデルが取得できたのであとはよしなに使う。 例えば、一つも参照がされていないデータを取ってくるとか。

target_ids = Hoge.pluck(:id)

target.each do |name, modle|
  used_ids = model[:model].where(model[:foreign_key] => target_ids).pluck(:"#{model[:foreign_key]}")
  target_ids -= used_ids
end
target_ids

とか

rubyで局所的にパッチを当てる方法を模索中

下記のようなコードで、Cクラスの中だけで何とかAクラスに対するパッチを当てたい。

class A
  def self.foo
    p 'foo'
  end
  def bar
    p 'bar'
  end
end

module ExtendFoo
  refine A.singleton_class do
    def foo
      p 'extend foo'
    end
  end
  refine A do
    def bar
      p 'extend bar'
    end
  end
end

class B
  def initialize
    A.foo
    A.new.bar
  end
end

class C
  using ExtendFoo
  def initialize
    B.new
  end
end

C.new

これを実行すると、

"foo"
"bar"

になるのだが、

"extend foo"
"extend bar"

にする方法がないものか。。。

railsアプリでstackprofを使ってボトルネックを探す + JSON::Schema(2.2.1)の高速化

railsアプリが遅いって言われたので、久しぶりにrubyでisuconしてみました。

railsアプリでstackprofを使ったプロファイリング

まず、自分がいつもやってる方法なのですが、config.ruにstackprofの設定を仕込みます。

stackprofはrackミドルウェアとして差し込めるようになっています。

下記設定はrailsだけでなく、sinatraでももちろん動きます。(これをいつも仕込んでおいてあります。)

Gemfileにgem 'stackprof'を書いてconfig.ruに下記のように仕込んでいます。

is_stackprof         =  ENV['ENABLE_STACKPROF'].to_i.nonzero?
stackprof_mode       = (ENV['STACKPROF_MODE']       || :cpu).to_sym
stackprof_interval   = (ENV['STACKPROF_INTERVAL']   || 1000).to_i
stackprof_save_every = (ENV['STACKPROF_SAVE_EVERY'] || 100 ).to_i
stackprof_path       =  ENV['STACKPROF_PATH']       || 'tmp'
use StackProf::Middleware, enabled:    is_stackprof,
                           mode:       stackprof_mode,
                           raw:        true,
                           interval:   stackprof_interval,
                           save_every: stackprof_save_every,
                           path:       stackprof_path

run Rails.application

環境変数でstackprofの設定を渡しています。 stackprofを仕込んでrailsを起動するときはこんな感じです。

ENABLE_STACKPROF=1 bundle exec rails s 

で、今回とったプロファイリング結果がこちら。 (まあ、色々削った結果を載せています)

==================================
  Mode: cpu(1000)
  Samples: 7758 (7.75% miss rate)
  GC: 1720 (22.17%)
==================================
     TOTAL    (pct)     SAMPLES    (pct)     FRAME
      5799  (74.7%)        2987  (38.5%)     JSON::Schema.add_indifferent_access
       858  (11.1%)         638   (8.2%)     JSON#generate
       439   (5.7%)         439   (5.7%)     block in JSON::Schema::TypeV4Attribute.data_valid_for_type?
       866  (11.2%)         359   (4.6%)     ActiveSupport::JSON::Encoding::JSONGemEncoder#jsonify
       279   (3.6%)         279   (3.6%)     block in JSON::Schema.add_indifferent_access
       174   (2.2%)         174   (2.2%)     String#to_json_with_active_support_encoder
       296   (3.8%)         118   (1.5%)     block in Array#as_json
       111   (1.4%)          98   (1.3%)     block in Hash#as_json
      2205  (28.4%)          96   (1.2%)     block in JSON::Schema::PropertiesV4Attribute.validate
・
・
      7150  (92.2%)          32   (0.4%)     JSON::Schema::Validator#validate
・
・
      3361  (43.3%)          11   (0.1%)     JSON::Schema#initialize

JSON::Schema.add_indifferent_accessが悪さしているぽい。

ボトルネックの洗い出しと解消

で、ここからは、最近のコード変更点とJSON::Schemaに関わる部分を洗い出して、悪さしているコードを洗い出して、最小の再現コード書いてみたのがこちら。

require 'json'
require 'json-schema'
require 'benchmark'

class BenchmarkJSONSchmea
  def initialize
    @json_data = []
    70_000.times do |i|
      @json_data << {id: i, name: "てすとてすと", point: 12345, description: "テストてすと"}
    end

    @json_schema = <<-JSON
    {
      "type": "array",
      "items": {
        "type": "object", "required": ["id"],
        "properties": {
          "id": {"type": "integer"},
          "name": {"type": "string", "minLength": 2, "maxLength": 15},
          "point": {"type": "integer"},
          "description": {"type": "string", "minLength": 2, "maxLength": 15}
        }
      }
    }
    JSON
  end

  def run
    # 試行回数は1回
    Benchmark.bm(7, ">total:", ">ave:") do |x|
      x.report("raw: ") { JSON::Validator.validate!(JSON.parse(@json_schema), JSON.parse(@json_data.to_json)) }
    end
  end
end

BenchmarkJSONSchmea.new.run

で、結果がこちら

% bundle exec ruby bench.rb
              user     system      total        real
raw:      6.310000   0.110000   6.420000 (  7.567306)

遅い。。。

stackprofの結果を鑑みて

stackprofではJSON::Schema.add_indifferent_accessが遅いって言われているので、ここを見てみてパッチを当ててみた。

require 'json'
require 'json-schema'
require 'benchmark'

class JSON::Schema
  def self.add_indifferent_access(schema)
    if schema.is_a?(Hash)
      schema.default_proc = proc do |hash,key|
        if hash.has_key?(key)
          hash[key]
        else
          hash.has_key?(key) ? hash[key] : nil
        end
      end
      schema.keys.each do |key|
        add_indifferent_access(schema[key])
      end
    end
  end
end

class BenchmarkJSONSchmea
  def initialize
    @json_data = []
    10_000.times do |i|
      @json_data << {"id" => i, "name" => "てすとてすと", "point" => 12345, "description" => "テストてすと"}
    end

    @json_schema = <<-JSON
    {
      "type": "array",
      "items": {
        "type": "object", "required": ["id"],
        "properties": {
          "id": {"type": "integer"},
          "name": {"type": "string", "minLength": 2, "maxLength": 15},
          "point": {"type": "integer"},
          "description": {"type": "string", "minLength": 2, "maxLength": 15}
        }
      }
    }
    JSON
  end

  def run
    # 試行回数は1回
    Benchmark.bm(7, ">total:", ">ave:") do |x|
      x.report("patch: ") do
        JSON::Validator.validate!(JSON.parse(@json_schema), JSON.parse(@json_data.to_json))
      end
    end
  end
end

BenchmarkJSONSchmea.new.run

結果がこちら

% bundle exec ruby patch_bench.rb
              user     system      total        real
patch:    0.890000   0.010000   0.900000 (  0.993027)

これで5倍くらいは速くなってます。

あとはrefimentsでパッチ当てて、こんな感じで局所化したかったけどなぜかうまく動作しない。。。

module JSONSchemaExtension
  refine JSON::Schema.singleton_class do
    def add_indifferent_access(schema)
      if schema.is_a?(Hash)
        schema.default_proc = proc do |hash,key|
          if hash.has_key?(key)
            hash[key]
          else
            hash.has_key?(key) ? hash[key] : nil
          end
        end
        schema.keys.each do |key|
          add_indifferent_access(schema[key])
        end
      end
    end
  end
end

class BenchmarkJSONSchmea
  using JSONSchemaExtension
end

Webサイトに必要なfaviconが21個になっていたらしいのでrack-favicon_allっての書いた

Webサイトに必要なfaviconが21個になっていた - IT探検記

どうもWebサイトに必要なfaviconが21個もあるらしい。

@bayashi

増殖中の favicon を Plack::Middleware ひとつで配信する - @bayashi Diary

のような記事を書いていたから、rackミドルウェア版作ってみた。

SpringMT/rack-favicon_all · GitHub

rack-favicon_all | RubyGems.org | your community gem host

誰か作ってるかもしれないけれど、後悔はしていない。