生きていればつらいことがある。 しかし、つらいからと言って簡単に投げ出す事は出来ないということも多い。
みなさんもつらまってる時、よくpixivを見ると思う。 当然のごとく僕もそうである。
最近つらい事がよくある。 そんな時のために、pixivのランキングイラストを素早く表示する必要があった。 なので、pixivのランキングイラストをすぐ見れるGoogle Chromeの拡張を作った。
mix3/chrome-pixiv-illust-daily-ranking-redirect - GitHub
「だめだ。もうやってらねー」って時は、空のtabを表示すればすぐpixivのランキングイラストが見れる。最高。自分も絵描きたい。
参考
ことよろ。なんか本当に久しぶりに落書きしたmix3です。
年に1回の年賀絵ぐらい継続して描きたいものですね…
トランザクションってなーに?mix3です
同時更新処理
スレーブ遅延のときにSELECT > UPDATEで古い情報をもとにUPDATEしてしまうと不整合が発生すると書きましたが、
たとえば同じ行を複数で同時にSELECT > UPDATEすると同じように不整合が発生することがあります。
- AがT1からSELECT
- BがT1からSELECT
- AがT1をUPDATE
- BがT1をUPDATE
このような順序で更新がかかると、BがAの更新を上書きしてしまうため、実質Aの更新が無かった事になってしまいます。
これも更新がアトミックであれば発生しないので可能であればアトミックな更新にするのが良いですが、更新が複数ある場合はそういった変更も出来ません。
そんなときこそトランザクション。そんなふうに考えていた時期が僕にもありました。
で、そういうときはトランザクションを使う物だと思っていました。トランザクションは複数の処理を一つの処理としてまとめて成功するか失敗するか、どちらかを保証してくれます。
なので、SELECT > UPDATEをトランザクションでまとめてしまえば大丈夫だよね!と思っていたので、以下のようなコードを書いて確認しました。
scoreが更新回数になるので、'v' => '<更新回数>' と揃えば不整合無く更新出来たことになるのですが、
見ての通りトランザクションを使った方は残念な結果になっています。で、すこしググッた結果トランザクションとロックは別で考えないと行けないことを知りました。
トランザクションを使ったからといって勝手に行読み込みなどをロックして整合性を保証してくれるとかでは全然無いんですね。
当たり前だろダラズ!と言われても仕方ないような勘違いですが、いやトランザクションを使わない世界でしばらく生きていたので…
で、SELECTにテーブルロックや行ロックが掛かってないためにSELECT > UPDATEの不整合が防げていないようなので、SELECT文にFOR UPDATE を付けてそれを防いでみました
結果またしても残念な結果になりました…
ちゃんとストレージエンジンを指定しましょう
結果から言うとストレージエンジンを指定していなかったのでトランザクションの使えないMyISAMでテーブルが作られていたからでした。
こんな感じでInnoDBを指定する事で期待通り、不整合なく更新されるようになりました。めでたしめでたし。
ちなみにSELECTのロックには共有ロックと排他ロックとあり、FOR UPDATEは排他ロックになります。LOCK IN SHARE MODEだと共有ロックになります。
今回の場合にLOCK IN SHARE MODEを使うと10ものプロセスが共有ロックを取り合って凄い勢いでデッドロックしました。
ロックするということは同時にデッドロックする可能性もあるということなので、適切なロックを選択して使い、デッドロックの可能性を減らしながら上手く付き合っていきたいですね。
まとめ
- トランザクションはロックとはまた別問題
- 不整合を防ぎたいならちゃんとロックのことを考えてSQLを書く
- MySQLのデフォルトストレージエンジンはMyISAMだけど、それトランザクション使えないからね?
- ロックにも種類はあり、デッドロックには気をつける
今年のブログはこれが最後な気がするので。
来年はもっとブログをコードを書いてアウトプットを増やして行きたいです。
それでは良いお年を!
DBサーバのslow_queryログがローテートされておらず単一ファイルで100G超えしていて辛いmix3です。
巨大なファイルを rm するのは危険
実際に試していないのでどうなるのか分からないですが、おそらく消し終わるまで間IOが占有されてDBのパフォーマンスに影響が出ちゃうのでしょう。
rm はやらないほうが良いねとなりました。
: > /path/to/file
じゃあどうすればという事で調べていたら
: > /path/to/file
を使ってはどうか?というようなものを見かけました
Is there a way to delete 100GB file on Linux without thrashing IO / load?
容量に余裕のある別サーバで $ dd if=/dev/zero of=tempfile bs=1024000 count=100000 とかしてファイルを作って試したところ、readが発生するもののパフォーマンスにはあまり影響は無い感じで使えそうということに。
ちなみに
: > /path/to/file は空ファイルを作るときの方法のようで、Linuxでファイルを空にする5つの方法 とかで見かけました
他のやり方だとまた違ってくるのでしょうか?
今の案件に触れてからの自分の低能ぶりの凄まじさに自ら恐怖を禁じ得ず、椅子から転げ落ちて失禁しそうなmix3です。
レプリケーション
今の案件ではDBにMySQLを使いレプリケーションを使って負荷分散しております。(レプリケーション必要無いぐらいの負荷だよね、とかインフラの方に言われてまた失禁しそうです)
レプリケーションというのはリアルタイムに複製する技術のこと?上手く説明出来ませんが、まあWebサービスなんかだと書き込みより読み込みの方が圧倒的に割合が大きいらしく、マスターとその複製のスレーブにDBを分けて、書き込みはマスターへ、読み込みは複製のスレーブへ処理を振り分ける事で負荷を分散したりします。
スレーブ遅延
レプリケーションというのはリアルタイムな複製技術なので、基本的にはマスターとスレーブは同期が取れているハズですが、サーバの負荷状況によっては同期がとれていない状態になったりします。スレーブ遅延というものです。
レプリケーション下での更新処理
たとえばインクリメント的なアトミックな更新であればスレーブは関わらないので問題ないと思います。
UPDATE table SET column + 1 WHERE id = ?
そうではなくスレーブから読み込みをし、マスターに書き込みをするような処理をしてしまうと、スレーブが遅延していた場合に古い情報をもとに更新をしてしまい不整合が発生するなどあまりよろしくない状態になります
SELECT hoge FROM table_1 WHERE id = ?
スレーブから取ってきたhogeが古いものだと、その後のUPDATEでそのhogeを使うのは当然よろしくない
×UPDATE table_2 SET column = WHERE id = ?
なので、そういったSELECT>UPDATEのような更新処理の場合は、SELECTもスレーブからではなくマスターからするようにしないといけせん。
ようするにスレーブ遅延による不整合が発生したということ
です。気をつけましょう。
MySQL::Sandbox と Percona Toolkit(旧Maatkit)のpt-slave-delay
こういうスレーブ遅延によるバグは開発環境では気づきにくかったりします。負荷の掛かっていない開発環境ではスレーブ遅延は発生しづらいでしょうし、そもそもDB構成が本番と違っていてレプリケーションされてなかったりすることも多いでしょう。
が、最近は便利になった物で、cpanに上がっているMySQL::SandboxとPercona Toolkit(旧Maatkit)なるツールを使うと割と簡単にレプリケーション環境とスレーブ遅延を再現出来ます。というかMySQL::SandboxもPercona Toolkit(旧Maatkit)結構前からあるので最初から使っておけという感じですね。(正月にデータが吹っ飛ぶ前は実は使ってたりしてたんですけどね...)
MySQL::Sandbox
Install
- cpanm MySQL::Sandbox
How to use
- SANDBOX_AS_ROOT=1 make_replication_sandbox -r master_data --how_many_slaves 1 --sandbox_base_port 11111 download/mysql-5.1.66-linux-x86_64-glibc23.tar.gz
- rootで作業するとSANDBOX_AS_ROOT=1を使わないと怒られるけどそもそもrootで作業するなという話
- sandboxes/master_data/以下にマスター(port: 11111, socket: /tmp/mysql_sandbox11111.sock)スレーブ(port: 11112, socket: /tmp/mysql_sandbox11112.sock)が出来る
- perl -MDBI -e 'DBI->connect("dbi:mysql:test:mysql_socket=/tmp/mysql_sandbox11111.sock", "msandbox", "msandbox")'
- port指定での接続方法は分からなかった。できなくは無いと思うけど
init script
- 以下のような感じで書いてみたがうまくいかん
- 落とし忘れるとsocketファイルが残って次回起動時に残念な事になるので出来ればなんとかしたい
#!/bin/sh
# chkconfig: 2345 65 35
# description: sandbox_action
# Source function library.
. /etc/rc.d/init.d/functions
# Get config.
. /etc/sysconfig/network
# Check that networking is up.
if [ ${NETWORKING} = "no" ]
then
exit 0
fi
prog="sandbox_action"
case "$1" in
start)
echo -n "Starting $prog: "
sandbox_action start
success
echo
;;
stop)
echo -n "Stopping $prog: "
sandbox_action stop
success
echo
;;
status)
sandbox_action status
;;
*)
echo $"Usage: $prog {start|stop|status}"
exit 1
esac
exit 0
Percona Toolkit(旧Maatkit)
Install
- Perconaからtarを落としてきて、perl Makefile.PL, make && make install
- rpmは依存関係がうるさいのでtarからのほうが良いと思う
How to use
- pt-slave-delay -u msandbox -p msandbox --socket /tmp/mysql_sandbox11112.sock
- --daemonize オプションもある