2013年7月30日火曜日

MySQLサーバーのディスク容量減少アラートが飛んでくる前に

このビッグウェーブにいつ乗るの! 今でしょ!


元RX-7乗りの適当な日々
Linuxサーバがディスク容量不足になった!何か消さねば!ってなった時にどう対処するか

tagomorisのメモ置き場
Linuxサーバのディスク容量減少アラートが飛んできた!ってときにどう対処するか

iをgに変えるとorangeになることに気づいたoranieの日記
Cassandraサーバのディスク容量減少アラートが飛んできた!ってときにどう対処するか


…と思ってたらMySQLバージョンも既に出た。けどめげない。かぶらないように書きます。というかタイトルはかぶらないように変えました(つд`)

Y-Ken Studio
MySQLサーバのディスク容量減少アラートが飛んできた!ってときにどう対処するか


さてまず、MySQLで勝手に(?)増えるものといえば。

  1. データファイル(.MYD, .MYI, .ibd, ibdata1)
  2. バイナリーログファイル, リレーログファイル
  3. スローログファイル
  4. エラーログファイル
  5. テンポラリーファイル, テンポラリーテーブル
くらいかなあ。少なくとも俺が過去にDISKをあふれさせたことがあるのはこいつらくらいです。


1. データファイル

ウチの場合、取り敢えず一番増えるのは ほげほげ_history とか なんちゃら_logs とかそんな名前のテーブルです。最低限、1行あたりのサイズを押さえておく必要があります。

mysql> SHOW TABLE STATU LIKE 'hogehoge_logs'\G
*************************** 1. row ***************************
           Name: hogehoge_logs
         Engine: InnoDB
        Version: 10
     Row_format: Compressed
           Rows: 18
 Avg_row_length: 455
    Data_length: 8192
Max_data_length: 0
   Index_length: 32768
      Data_free: 0
 Auto_increment: 19
    Create_time: 2013-07-22 23:55:13
    Update_time: NULL
     Check_time: NULL
      Collation: ujis_japanese_ci
       Checksum: NULL
 Create_options:
        Comment:
1 row in set (0.00 sec)

Avg_row_lengthはデータ部分だけのサイズなので、(Data_length + Index_length) / Rowsでインデックス込みの1行あたりの値をとっときます。意外と大きいケースが多かったりするので。するので。

↑のテーブルだと(ちょっとデータ量が少なすぎて全くアテにならないとはいえ)1行当たり2.2KiBくらい、Create_timeから察するに1日の増加量は2.5行、万一(ひどい)サービスがバズってトラフィックが10倍になったとしても1日50KiBくらいですね。


というのを、information_schemaから全テーブル分ばっこ抜きます。

mysql> SELECT
    ->  TABLE_SCHEMA,
    ->  TABLE_NAME,
    ->  ENGINE,
    ->  SUM(((DATA_LENGTH + INDEX_LENGTH) / TABLE_ROWS) / TIMESTAMPDIFF(DAY,  CREATE_TIME, NOW())) AS 1d_size
    -> FROM
    ->  information_schema.tables
    -> WHERE
    ->         ENGINE IN ('InnoDB', 'MyISAM')
    ->  AND TABLE_SCHEMA NOT IN ('information_schema', 'mysql')
    -> GROUP BY
    ->  TABLE_SCHEMA, TABLE_NAME
    -> WITH ROLLUP;
+--------------+-------------------------+--------+----------------+
| TABLE_SCHEMA | TABLE_NAME              | ENGINE | 1d_size        |
+--------------+-------------------------+--------+----------------+
| xxxx         | hogehoge_logs           | InnoDB |   537.18032787 |
..
| xxxx         | NULL                    | InnoDB | 61917.19581370 |
| NULL         | NULL                    | InnoDB | 61917.19581370 |
+--------------+-------------------------+--------+----------------+

SUMもへったくれもない1d_sizeカラムにSUM()をつけているのは、WITH ROLLUPで最後に合計値を出すためです。
便利ですよWITH ROLLUP。滅多に使わないけど。
http://dev.mysql.com/doc/refman/5.6/en/group-by-modifiers.html

というわけで、このMySQL(検証用DBだけど)は1日あたり60KiBちょっと、バズって100倍来ても大丈夫ですね(にっこり)

information_schemaだし、飽くまで参考値にしかなりませんが、定常状態を知っておくのは大事だと思います。


2. バイナリーログファイル, リレーログファイル

データファイルより更に手抜きで引っこ抜きます。
大概の場合、--expire-logs-daysで過去何日分かに絞っていると思うので、それで割り算しちゃいます。

$ ls -l mysql-bin.* | awk '{i+=$5}END{print i}'
356928028

7日間で350MiBくらいなので50MiB/dayくらいですね。100倍バズるとちょっと大変かも。
リレーログも余裕を見てこれと同じだけ見込むことにしてます。
というわけで--log-slave-updatesつきの中間スレーブで100MiB/day。

もしイベントなんかの期間要因がバイナリーログの保存期間の中に含まれていて、そこは除外したいというのがあれば、mysqlbinlogでテキトーに引っこ抜きます。SQLデコードされる分容量が0%(増えない)~100%(2倍)くらいになっちゃうんですが、多少安全側に倒していると思って諦めます。

$ mysqlbinlog mysql-bin.* --start-datetime="2013-07-21 00:00:00" --stop-datetime="2013-07-22 00:00:00" | wc
  12213   59409  543939

540KiB/day。バラつきがあったんだなぁ。。どっちを採用するか却って悩むかも知れません。

【2013/07/30 13:24】
ちなみに、--binlog_format=mixedだと、LOAD DATA INFILEがROWモードで1行ずつロギングされるので--expire-logs-daysに関係なく瞬間的に結構な量になります。↑のばらつきはそうだったっぽいです。


3. スローログファイル

まあ、増える時は増えますよね! たまに気にしてあげてください。
あと、クエリーをまるっと書き出す性質上、DWH的な使い方をしててスローログ出てもいいからサブクエリーやJOINし放題、WHERE .. IN (..)に3000くらい定数が列挙してあるぜはははーなやつがあったりするとあっという間に太ります。

つーか、出ても気にしないんだったら切っとけ。。


4. エラーログファイル

意外と肥大化の原因になってるのが[Warning]で、コイツ、当然ながらエラーは返さないなれどエラーログには記録されるんですよね(--log-warnings=n でレベル指定。暗黙のデフォルトは1)

で、アプリから投げたSQLのWarningって意外と気にしてくれない人が多いので気が付くとぼこぼこぼこぼこぼこぼこぼこぼこ。。

しかも俺が最近よく見るWarningレベルのログって、ご丁寧にワーニングになったクエリーを書き出してくれる親切設計なので、 1 ク エ リ ー 8 K i B も あ る いじめのようなクエリーがごりごり書き出されてあっという間にエラーログが太ったことがありました。

このクエリーはスローログファイルもいじめました。もうやめて! MySQLのライフはゼロよ!


5. テンポラリーファイル, テンポラリーテーブル

これもうちょっと細分化すればよかったかも知れない。
Using filesortの時に使うテンポラリーファイル, Using temporaryの時に使う(かつ、Diskに固定化された)テンポラリーテーブル, 5.6のInnoDB Online Alter Table(--innodb_online_alter_log_max_size=nで制御、暗黙のデフォルトは128M)で使う更新ログっぽいやつは軒並みtmpdirに吐く。

特に前2つはtmpdirが/tmpのままだったりすると意外とあっさり食いきられてError:28(=No space left on device)をエラーログに吐いてSort Abortしたり。
クエリーがAbortされたあとは(一応)自動で消えはするものの、そのクエリーはエラーで返ることになる。で、大概テンポラリーテーブルのサイズがそもそもtmpdirにおさまってないのが問題なので、同じクエリーを投げればまあ再現する羽目に。。

あとはいわゆるフツーのALTER TABLEの時はdatadir(ALTER対象のテーブルのまどなり)にテンポラリーテーブルを作る。インデックスやカラムを追加する時は今のサイズにその新しい容量が乗っかるので、ALTER対象テーブルサイズよりちょっと大きめの量が余計に必要になるので注意。これがあふれてALTERがAbortされると、「俺の3時間を返せええええええ」ってなったりならなかったり。。


なんか終盤ぐだぐだになってますが、体感的にはMySQLのデータを破損させる一番の原因はDisk Fullなので、「そもそもアラートが㌧でこないように! ギリギリの運用イクナイ!」ということを主張するものであります。

おらにえさん、こんな感じでいいですかー?w

0 件のコメント :

コメントを投稿