my sqlで2億件のシリアルデータと格闘した話
TRANSCRIPT
MySQL で 2 億件のシリアルデータと格闘したチューニングの話
YAPC::Asia Tokyo 2015Kenji Saito / @saiken3110
自己紹介
・斉藤 健二 Kenji Saito・ @saiken3110・ Java PHP Perl・ Oracle MySQL
今日の内容
MySQL で2 億件のシリアルデータを
つかった話
主にチューニングの話です。Perl も最後にちょっとでてきます。。
Topic・システムの概要・負荷テストした話・ 2 億件登録したときの話・ update ではまった話・今後の課題 ・まとめ
システムの概要(フロント)
商品購入
シリアル入力
入力
・商品 A・サイズ
商品サイズ選択
登録
XXL ▼
商品シリアル紐付 利用履歴
システムの概要(バックエンド)
シリアルマスタ
商品名
シリアル数
商品・シリアル紐付け
紐付け
▼
商品シリアル紐付
2 億件!! 1億件!!
・ Red Hat 5.6・ Perl 5.20・ MySQL 5.6
DB サーバのインフラ事情①
・ CPU 2 コア・メモリ 8 G・ディスク 450G・ /tmp 2G
DB サーバのインフラ事情②
→ 2 コア、 8G 。。。 2 億件。。。
Topic・システムの概要
・負荷テストした話・ 2 億件登録したときの話・ update ではまった話・今後の課題 ・まとめ
□ 対象テーブル ・シリアルマスタ(2億件) ・商品シリアル紐付(1億件)
□ 内容 ・インデックスを使った検索 ・プライマリーキーでの検索 ・フルスキャンでの検索 ・フルスキャンでの count ・フルスキャンでの order by ・ insert ・ alter table (カラム追加)
負荷テスト
いろいろと問題が・・・
○count に 84 秒、 CPU を 80 ~ 95 % 使用 → アプリケーションからは 実行しないため、対応不要とした
○order by や alter で /tmp が枯渇
→ tmp_dir を物理ディスクに変更 → エラーがでなくなったが、 50G くらい一時領域を使ってた。。 → 運用で回避することにした
負荷テスト 結果①
ERROR 3 (HY000) at line 1: Error writing file '/tmp/MY3p25s8' (Errcode: 28 - No space left on device)
○ 商品シリアル紐付のデータファイルが 28G に。。 → パフォーマンスのために、 各レコードで商品情報を持っていたのをやめた → 管理用の TBL を作り、 正規化、 15G に縮小!
○insert が意外と早い 「 LOAD DATA 」で 3 億件の登録が 70 分 → 後でいろいろ問題が発生。。
負荷テスト 結果②
Topic・システムの概要・負荷テストした話
・ 2 億件登録したときの話・ update ではまった話・今後の課題 ・まとめ
2 億件登録したときの話①
シリアルマスタ
商品名
シリアル数
商品・シリアル紐付け
紐付け
▼
商品シリアル紐付
2 億件!! 1億件!!
ココ
負荷テストのときに、3億件が 70 分で登録できた
実際のデータで2億件登録しようとしたら、、
75時間かかった。。
2 億件登録したときの話②
□ 実行時間の推移
2 億件登録したときの話③
※500 万件ずつ「 LODA DATA 」で登録
8000万件から急激に遅延
□ 負荷テストのときより遅くなった原因 → 負荷テストのときはシリアルが連番 実際は、ランダムな数字 → insert 時に index の再作成が発生!
□8000 万件くらいから急激に遅くなった原因 → innodb_buffer_pool が枯渇 → 検証してみた
2 億件登録したときの話④
innodb_buffer_pool と insert①
さすがに 75 時間を何度もは無理なので、
innodb_buffer_pool_size を 10 分の1くらいで
試してみた
40 時間くらいかけて。。
innodb_buffer_pool と insert②
innodb_buffer_pool_size を 1G 、 500M 、 250M で10 万件の insert × 100 回にかかる時間を計測
250M で実行した場合、340万件から遅延が発生
500M で実行した場合、700万件から遅延が発生
innodb_buffer_pool と insert③
データ量(登録件数)が増加し、innodb_buffer_pool が枯渇すると、insert が大幅に遅延し始める。
→ メモリからあふれて、 ディスク I/O が発生しているため。
→ メモリを増やすことで、 データ量が多くても遅延が発生しない
ついでに、、
主キーがシーケンスならインデックスの再構築が
早いんじゃないかと思った
試してみた
再びの 40 時間。。
主キーと insert①
主キーをシリアル(ランダムな数字)からシーケンスに変更して、 2 億件の登録にかかる時間を比較
○ 変更前 ・主キー:シリアル ・ユニークキー:シーケンス
○ 変更後 ・主キー:シーケンス ・ユニークキー:シリアル
主キーと insert②
○ 2億件の登録にかかった時間 75時間 → 30時間に改善! → 主キーが連番になったことで、 主キーのインデックス構築にかかる時間が減少
変更前は2億件の登録に約 75時間
変更後は2億件の登録が約 30時間で完了
Topic・システムの概要・負荷テストした話・ 2 億件登録したときの話
・ update ではまった話・今後の課題 ・その他 Perl のチューニング・まとめ
update ではまった話①
シリアルマスタ
商品名
シリアル数
商品・シリアル紐付け
紐付け
▼
商品シリアル紐付
2 億件!! 1億件!!
ココ
負荷テストのときは、100 万件の紐付が74秒だった
お客さんの前でデモしたら、、
1時間かかった
update ではまった話②
update ではまった話③
□ アプリケーションの動き ※1回の max が 100 万件で1万件ごとにコミット
1)シリアルマスタからステータスが 「未使用」のものを取得
2)商品シリアル紐付へ、 入力した商品の情報をつけてシリアルを insert
3)シリアルマスタのステータスを「使用済み」に update → 1回の update で 35 秒かかっていた → 100 万件の場合、 35 秒 ×100 回!
update ではまった話④
□update が遅かった原因 → update 時に index の再構築が発生 → シリアルがランダムなため、 紐付く主キーの読込みが重い。。
read_buffer_size ( 16k)ずつ読み込まれる紐付く主キーがバラバラなため、読込が毎回発生
update ではまった話⑤
□ 対応 ステータス管理をやめ、 管理テーブルを作成し、 update 自体を不要にした
→ 60分が45秒に改善! → インデックスの再構築怖い。。。
そんなこんなで、なんとか無事リリースできました
よかった
Topic・システムの概要・負荷テストした話・ 2 億件登録したときの話・ update ではまった話
・今後の課題 ・その他 Perl のチューニング・まとめ
今後の課題①
□ 増分バックアップ 現状mysqldump でフルバックアップ → 今後さらにデータが増えてくると、 バイナリログを使った 増分バックアップの方がよさそう
今後の課題②
□ データ退避とかパーティショニング → 古いシリアルの退避とか、 できれば「 xxxx_2015 」とか作りたくないので、 うまくパーティションとか使えると。。
Topic・システムの概要・負荷テストした話・ 2 億件登録したときの話・ update ではまった話・今後の課題
・その他 Perl のチューニング・まとめ
その他 Perl のチューニング①
□ コネクションプーリングっぽいこと
コネクションオブジェクトを シングルトンにし、 1worker 1 コネクションを共有
→ コネクション生成のコストをなくした
その他 Perl のチューニング②
□ マスタデータをキャッシュ
PSGI サーバ起動時に、メモリに DB のマスタデータを保持 → worker 内でメモリは共有されるため、 DB アクセスが不要になる
※ただし、「 Copy-On-Write 」なので、 書き換えようとすると、メモリリークの原因になる
※ただし、マスタデータを変更する場合には、 再起動が必要になるので、 本当は memcached とかを使った方がよさそう
まとめ①
□innodb_buffer_pool_size は大きい方がいい
□ 主キーはシーケンスにした方が insert が早い
□ インデックスの再構築は意外とばかにならない
まとめ②
□ 負荷テスト大事 実際のデータに近いデータでやる
□ 結局計測してみないとわからないことは多い → 効果測定にすごく時間がかかるので、 負荷テストとかは、早めにやる
ありがとうございました!