Hatena::ブログ(Diary)

(ひ)メモ このページをアンテナに追加 RSSフィード

2009-07-24 (Fri)

InnoDBテーブルでunique keyがある場合、それをprimary keyにするべきかどうか

InnoDBなテーブルのお話です。

とあるテーブルで、いくつかあるカラムのうち、ただひとつのカラムがunique keyになり得るものだとします。

こういうときは、そのunique keyは無条件でprimary keyにしたほうがイイ!と思ってたんですが、調べてみると必ずしもそうでないようなので、今のところのモヤモヤをまとめてみます。

教えて! 偉い人!!

予備知識

High Performance MySQL

High Performance MySQL

からの超抜粋。しかしこの本は神本ですね。


  • clustered indexとprimary keyの関係
    • InnoDBの場合、primary keyはclustered indexとなります。
    • primary keyが明示されていない場合、uniqueかつnot nullな制約がついているカラムがclustered indexとなります。
    • uniqueかつnot null制約なカラムがない場合は、MySQLが隠しprimary keyを作ってくれます。
  • clustered indexの特徴
    • B-Tree
    • 末端のリーフノードのページには、(データが格納されているページへのポインタではなく)データそのもの(インデックスカラム以外のカラムの値)が格納されています。
  • clustered indexのメリット
    • インデックスをひけばそこにはもうデータが!
  • clustered indexのデメリット
    • レコードの挿入やprimary keyの更新が重い。clustered indexの順に次々にinsertしていくのならば末尾に追加していけばいいが、そうではなくランダム性のある値(例:MD5とかSHA1ハッシュ値)をinsertする場合、ページのフラッシュや移動が発生するため。
    • secondary key (primaryではないただのインデックス) のリーフにはclustered indexの値が格納されるので、clustered indexが大きいと secondary key が占有する領域も付随して大きくなる。
    • secondary key を引く場合、データに辿り着くには 2 回インデックスを引かなければならない。(1) secondary keyを使って、値(= primary keyの値)を得て、(2)primary keyの値とclustered indexを使って、欲しい値を得る。

ぼくの考え

あんまし自信ないので、教えてください!偉い人!!

  • unique keyをprimary keyにした方がいい場合
    • 当該カラムの値が小さい
    • 当該カラムの値が大きめ(でかめの型とか複合キーとか)だけど、ほかのキー(secondary key) がない
    • 今後 insert していく当該カラム値がソートされている
  • unique keyをprimary keyにしない方がいい場合
    • 当該カラムの値が大きくて、secondary keyもいくつかある
    • 今後 insert していく当該カラム値がばらばら(ハッシュ値とか)である。

他、よくわからない点

unique not nullがない場合に MySQL が勝手に作る隠し primary keyは、行番号みたいな単調増加していくような値なのだろうか?

件の書籍では、隠し primary key を作るのではなく auto_increment なカラムを使う例が紹介されているが、auto_increment は発番時にテーブルロックがかかるので並列性を損なう恐れがありあまり使いたくない。

なので、もし隠し primary key が単調増加値 (= insertの処理の負荷が小さくて済む) ならば、auto_increment ではなく隠し primary key を使った方が効率的な気がするがどうなのだろうか?

2009-07-22 (Wed)

keepalivedのnb_get_retryの動きが不思議な#2

の続き。

今回は keepalived 1.1.17 で試してみたす。

ヘルスチェック (HTTP_GETの場合) のループ間隔に関係ありそうな設定項目はこの3つ。

  • virtual_server group
    • delay_loop (delay timer for service polling)
  • real_server HTTP_GET
    • nb_get_retry (number of get retry)
    • delay_before_retry (delay before retry)

コードレベルじゃなくて実験観察結果ですけど、まとめるとこんな感じ:

┌─────────┐
│                  ↓
│              delay_loop
│                  ↓
│                CHECK (最大connect_timeout)
│      Enable      ↓      Disable
│      <成功>───┴───<失敗>
│        ↓                  │
│ delay_before_retry         │
│        ↓                  ↓
└────┴─────────┘

文章で書くと:

  • 当該リアルサーバがアップしているときのループ間隔は delay_loop + delay_before_retry 秒になる
  • 当該リアルサーバがダウンしているときのループ間隔は delay_loop 秒になる
    • ただし、リアルサーバが即座にエラーを返すのではなく、ブロックして応答を返さない場合は、ループ間隔は connect_timeout + delay_loop 秒になる
  • ヘルスチェックに成功/失敗したら即座に IPVS の割り当てに復帰/除外される
  • よって、ヘルスチェックが失敗してから IPVS の割り当てから外れるまでには最大で connect_timeout + delay_before_retry + delay_loop 秒かかる
    • ヘルスチェック用URLが非200を返すようにして、IPVSの割り当てから外してから安全に httpd の restart を行うような運用の場合、非200を即座に返すならば connect_timeout は勘定せずに、非200を返すように指示してから delay_before_retry + delay_loop 秒待てば IPVS の割り当てから外されていることが期待できる、ということになる。

落とし穴:

  • delay_before_retryは名前と違って失敗時のretryじゃなくて成功時のループ間隔時間に影響する
  • nb_get_retry はどんな値を設定しても影響しないぽい?

2009-07-13 (Mon)

keepalived と 当ててるパッチたち

今使ってるのは 1.1.17 、その前に使ってたのは 1.1.15 す。

keepalived 1.1.19

1.1.19ではpidfileとnopreemptパッチは取り込まれているので、

だけ当てています。

keepalived 1.1.17

  • keepalived-1.1.13-extcheck-1.0.patch
    • DSASさんところのヘルスチェック拡張パッチです。ふふ。DNS_CHECKとSSL_HELLOを使ってます。
    • ちなみに、このパッチを当てないで DNS_CHECK とか SSL_HELLO を使うと、keepalived.confにreal_serverを複数書いているにも関わらず、ipvs的には1つしか有効にならないという不思議状態になりますので、バージョンアップのときとかにはパッチ当て忘れないように気をつけてください。
  • keepalived-1.1.17_pidfile.patch
    • 1.1.16以降で、IPVS(--check)とVRRP(--vrrp)とが別プロセスで起動できなくなったのでその修正パッチです。
  • nopreempt from FAULT state not working

ちなみに、keepalivedをIPVS(--check)とVRRP(--vrrp)とでプロセスを分けて起動している理由はこうです。

IPVS的な変更(リアルサーバの追加とかバーチャルサーバの追加)は割と頻繁にあるオペレーションなのですが、その反映にはkeepalivedプロセスの再起動 (それか自分は使ってませんがSIGHUPによるconfの再読み込み) が必要になります。

このとき、もし1つのプロセスで運用していると、無関係なVRRPまで再起動がかかってしまいます。

おとなしく動いている子にはさわらない、というのが運用の定石です。しかもVRRPはフェイルオーバのトリガになるのでなおさらさわりたくありません。

ですので、IPVS (--check オプション) と VRRP (--vrrp オプション) とを別プロセスで起動して、それぞれ独立に再起動できるようにしているわけです。

前述の通り、IPVS は割と頻繁に再起動しますが、VRRP の方は keepalived のバージョンアップのときぐらいでほとんど再起動せず動きっぱなしです。

ちなみにdaemontools配下でkeepalived -n --check, keepalived -n --vrrp, なプロセスを動かしています。

……とDSAS勉強会のときに聞いたのでぼくもそうしてます^^


keepalived 1.1.15

合わせて読んでください手前味噌

*><*

2009-07-09 (Thu)

ブラウザのリクエストに応じて、予め圧縮しておいたのか生のか選んでjsやcssを配信する方法 〜Apache 2.2編〜

ググるといろいろできますけど、若干おハマりしたので。

こんな設定で:

# Expires*は圧縮には関係ないけど
ExpiresActive On
ExpiresByType application/javascript  "access plus  1 years"
ExpiresByType text/css                "access plus  1 years"

# こっちが本丸
<Location ~ "\.(js|css)(\.(js|css|gz))?$">
  Options     +MultiViews
  RemoveType  .gz
  AddEncoding x-gzip .gz
</Location>

こんなファイルを置いておくと:

$ ls -1 js/
jj.js.gz
jj.js.js

こんな感じのレスポンスになります。

$ curl -i -H 'Accept-Encoding: gzip'  http://goa/js/jj.js?801|egrep -a ^Content
Content-Location: jj.js.gz
Content-Length: 36
Content-Type: application/javascript
Content-Encoding: gzip
  ↑圧縮されたデータが返ってきてる

$ curl -i -H 'Accept-Encoding: curry' http://goa/js/jj.js?801|egrep -a ^Content
Content-Location: jj.js.js
Content-Length: 11
Content-Type: application/javascript
  ↑こっちは生データ

今回のおはまりポイント:

  • うっかり「AddType application/x-gzip .gz」とかしてると、レスポンスのContent-Typeがapplication/x-gzipになっちゃう。なので RemoveType してる。
  • /js/jj.js のようにURLに拡張子を入れたかったら、生の方のファイル名は jj.js じゃなくて jj.js.js にしないといけない。cssだったら foo.css.css に。
  • MultiviewsMatchとかrewriteとかは使わないでもできた

2009-07-03 (Fri)

gitのcommit、push待ちの状態をプロンプトに表示すると結構便利

gitのブランチ名をプロンプトに表示すると結構便利 の続き。

gitでcommitし忘れ、pushし忘れないように、

_color_() {
  color=$1; shift
  echo -e "\e[${color}${@}\e[0m"
}

fg_black()   { _color_ "30m"   $@; }
fg_BLACK()   { _color_ "30;1m" $@; }
fg_red()     { _color_ "31m"   $@; }
fg_RED()     { _color_ "31;1m" $@; }
fg_green()   { _color_ "32m"   $@; }
fg_GREEN()   { _color_ "32;1m" $@; }
fg_yellow()  { _color_ "33m"   $@; }
fg_YELLOW()  { _color_ "33;1m" $@; }
fg_blue()    { _color_ "34m"   $@; }
fg_BLUE()    { _color_ "34;1m" $@; }
fg_magenta() { _color_ "35m"   $@; }
fg_MAGENTA() { _color_ "35;1m" $@; }
fg_cyan()    { _color_ "36m"   $@; }
fg_CYAN()    { _color_ "36;1m" $@; }
fg_white()   { _color_ "37m"   $@; }
fg_WHITE()   { _color_ "37;1m" $@; }
fg_BOLD()    { _color_ "1m" $@; }
fg_inverse() { _color_ "7m" $@; }
fg_INVERSE() { _color_ "1;7m" $@; }

gitmodified() {
  check_remote="n"
  case $1 in
    -[vr])
      check_remote="y"
      ;;
  esac

  fg_INVERSE '= list branches ='
  git branch -a
  fg_INVERSE '= working tree status ='
  git status
  echo 'try git diff'

  if [ "$check_remote" = 'y' ]; then
    if git config -l | grep svn.url; then
      fg_INVERSE '= remote ='
      git-svn dcommit -n
    else
      fg_INVERSE '= remote ='
      if [ -z "$(git log --pretty=oneline  origin..HEAD)" ]; then
        echo "up to date"
      else
        fg_RED "NEED TO PUSH!!"
        git log --name-status --pretty=oneline --abbrev-commit origin..HEAD
        echo; echo "try:"; echo "  git diff origin..HEAD"
      fi
    fi
  fi
}

なのを使って、こんなふうに確認していたのですが、

f:id:hirose31:20090703201238p:image

いちいち打つのがめんどくさくなってきたのでプロンプトに表示するようにしてみました。こんな感じ:

f:id:hirose31:20090703201239p:image

上から順に:

  1. commit待ちもpush待ちもないので最初のプロンプトはいつもと同じ。
  2. ファイルt1を変更したので、commit待ちを示す「*」がプロンプトに表示されている。
  3. commitしたので、「*」が消えて、push待ちを示す「^」が表示されている。
  4. 再びファイルt1を変更したので、「^」に加えてcommit待ちを示す「*」も表示されている。

プロンプト表示する度に git コマンドとかとか叩いてるんでちょっとあれですけど、便利に使ってます。

__git_reminder() {
  [ -d "$PWD/.git" ] || return
  M=
  git status | grep -q '^nothing to commit' 2>/dev/null || M=$M'*'
  [ ! -z "$(git log --pretty=oneline  origin..HEAD 2>/dev/null)" ] && M=$M'^'
  echo -n "$M"
}
#...
  _colesc="\[\e["
  _cse="\]"
  _colreset="${_colesc}0m${_cse}"

  if [ -r "$HOME/.bash_completion.d/git" ]; then
    PS1="${PS1}[\$(__git_ps1 \"${_colesc};1m${_cse}%s${_colesc}31;1m${_cse}\$(__git_reminder)${_colreset})\")\w]\\$ "
  else
    PS1="${PS1}[\w]\\$ "
  fi
#...
2003 | 11 | 12 |
2004 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 10 | 11 | 12 |
2005 | 01 | 02 | 03 | 05 | 08 | 09 | 10 | 11 | 12 |
2006 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2007 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2008 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2009 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2010 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2011 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 12 |
2012 | 01 | 02 | 03 | 06 | 08 | 10 | 11 | 12 |
2013 | 01 | 02 | 03 | 05 | 06 | 07 | 08 | 09 | 10 | 11 | 12 |
2014 | 01 | 02 | 03 | 04 | 05 | 06 | 07 | 08 | 10 |
2015 | 01 | 02 | 07 | 10 |
2016 | 01 | 05 | 10 | 12 |
2017 | 07 |
2018 | 05 | 11 |