Upgrade to Pro — share decks privately, control downloads, hide ads and more …

成長サービスのDB負荷問題を解決する

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 成長サービスのDB負荷問題を解決する

Avatar for Kanazawa Yuki

Kanazawa Yuki

October 12, 2019
Tweet

More Decks by Kanazawa Yuki

Other Decks in Technology

Transcript

  1. 1)1ΧϯϑΝϨϯεԭೄ ランサーズSREチームの業務  サーバー運⽤ 安定化 レスポンス改善 負荷改善 冗⻑化 セキュリティ対策 最新バージョン追従

    リソース管理 新サービスローンチ⽀援 キャパシティプランニング ドメイン、SSL証明書、リポジトリ管理 サーバー費⽤削減 旧サービス、旧機能の削除 効率化 サーバーのコード化 定型作業の⾃動化 開発⽀援 開発環境構築 リリースシステム構築 旧ソースの削除 社内インフラ、情シス系 社内サーバー 社内LAN PC周り 定型作業の委譲 他
  2. 1)1ΧϯϑΝϨϯεԭೄ ランサーズSREチームの業務  サーバー運⽤ 安定化 レスポンス改善 負荷改善 冗⻑化 セキュリティ対策 最新バージョン追従

    リソース管理 新サービスローンチ⽀援 キャパシティプランニング ドメイン、SSL証明書、リポジトリ管理 サーバー費⽤削減 旧サービス、旧機能の削除 効率化 サーバーのコード化 定型作業の⾃動化 開発⽀援 開発環境構築 リリースシステム構築 旧ソースの削除 社内インフラ、情シス系 社内サーバー 社内LAN PC周り 定型作業の委譲 他 •SREとして認知されている業務
  3. 1)1ΧϯϑΝϨϯεԭೄ ランサーズSREチームの業務  サーバー運⽤ 安定化 レスポンス改善 負荷改善 冗⻑化 セキュリティ対策 最新バージョン追従

    リソース管理 新サービスローンチ⽀援 キャパシティプランニング ドメイン、SSL証明書、リポジトリ管理 サーバー費⽤削減 旧サービス、旧機能の削除 効率化 サーバーのコード化 定型作業の⾃動化 開発⽀援 開発環境構築 リリースシステム構築 旧ソースの削除 社内インフラ、情シス系 社内サーバー 社内LAN PC周り 定型作業の委譲 他 •ランサーズのSREチームが重視している業務 SREチームを介さずに 完結できる仕組みの構築
  4. 1)1ΧϯϑΝϨϯεԭೄ ランサーズSREチームの業務  サーバー運⽤ 安定化 レスポンス改善 負荷改善 冗⻑化 セキュリティ対策 最新バージョン追従

    リソース管理 新サービスローンチ⽀援 キャパシティプランニング ドメイン、SSL証明書、リポジトリ管理 サーバー費⽤削減 旧サービス、旧機能の削除 効率化 サーバーのコード化 定型作業の⾃動化 開発⽀援 開発環境構築 リリースシステム構築 旧ソースの削除 社内インフラ、情シス系 社内サーバー 社内LAN PC周り 定型作業の委譲 他 •スタートアップで必要とされる業務 組織が拡⼤すれば 管理部に委譲できる クラウド化も選択肢
  5. 1)1ΧϯϑΝϨϯεԭೄ 今回お話するテーマ  サーバー運⽤ 安定化 レスポンス改善 負荷改善 冗⻑化 セキュリティ対策 最新バージョン追従

    リソース管理 新サービスローンチ⽀援 キャパシティプランニング ドメイン、SSL証明書、リポジトリ管理 サーバー費⽤削減 旧サービス、旧機能の削除 効率化 サーバーのコード化 定型作業の⾃動化 開発⽀援 開発環境構築 リリースシステム構築 旧ソースの削除 社内インフラ、情シス系 社内サーバー 社内LAN PC周り 定型作業の委譲 他
  6. 1)1ΧϯϑΝϨϯεԭೄ  ランサーズのサーバー構成 EC2 instance CloudSearch CloudFront Route 53 CloudFront

    ALB ALB API Gateway Lambda Auto Scaling App S3 Aurora Reader Aurora Reader Aurora Writer Api ElastiCache Redis AI系API サムネイル表⽰ 仕事検索 ランサー検索 ランサーズ Batch PHP7 CakePHP2.10 Python3 MySQL5.7
  7. 1)1ΧϯϑΝϨϯεԭೄ  前提 •CakePHP2 ◦全てのテーブルに以下のカラムがある ▪id ▪created ▪modified •MySQL5.7 ◦(原則)1テーブルにつき1インデックスしか使えない

    ◦B-Treeインデックス ▪インデックスを利⽤しない検索(スキャン) •O(n) ▪インデックスを利⽤する検索 •O(logn) •AWS ◦RDS Aurora ◦ElastiCache
  8. 1)1ΧϯϑΝϨϯεԭೄ  カーディナリティの低いインデックス •カーディナリティの低いとインデックスの効果が出にくくなる ◦⼩数派の検索が多いか⾒極める必要がある •カーディナリティ2の例 SELECT * FROM proposals

    WHERE deleted = 0 SELECT * FROM proposals WHERE win = 1 インデックス効果 ほとんどなし インデックス効果 あり ◦削除フラグ(deleted) ▪deleted = 0のレコードが⼤多数 ▪deleted = 0の検索が⼤半 ◦当選フラグ(win) ▪win = 0のレコードが⼤多数 ▪win = 1の検索が⼤半
  9. 1)1ΧϯϑΝϨϯεԭೄ  複合インデックスによる改善例 SELECT * FROM payments WHERE created BETWEEN

    '2019-01-01 23:59:59' AND '2019-06-30 23:59:59' AND operation_type IN ('reward', 'external_reward') AND user_id = 122864 ORDER BY id DESC •本番環境で25秒かかるクエリ
  10. 1)1ΧϯϑΝϨϯεԭೄ  スロークエリ改善のステップ •インデックスの確認 •EXPLAINで調査 ◦user_idのインデックスが適⽤されてもrowsが12万超 mysql> EXPLAIN SELECT ->

    * -> FROM -> payments -> WHERE -> created BETWEEN '2019-01-01 23:59:59' AND '2019-06-30 23:59:59' -> AND -> operation_type IN ('reward', 'external_reward') -> AND -> user_id = 122864 -> ORDER BY -> id DESC; +----+-------------+---------+------------+------+--------------------------------+---------+---------+-------+--------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+--------------------------------+---------+---------+-------+--------+----------+-------------+ | 1 | SIMPLE | Payment | NULL | ref | user_id,operation_type_created | user_id | 4 | const | 120624 | 6.50 | Using where | +----+-------------+---------+------------+------+--------------------------------+---------+---------+-------+--------+----------+-------------+ 1 row in set, 1 warning (0.02 sec) mysql> SHOW INDEX FROM payments; +----------+------------+-------------------------+--------------+----------------+-------------+----------+--------+------+------------+ | Table | Non_unique | Key_name | Seq_in_index | Column_name | Cardinality | Sub_part | Packed | Null | Index_type | +----------+------------+-------------------------+--------------+----------------+-------------+----------+--------+------+------------+ | payments | 0 | PRIMARY | 1 | id | 40525056 | NULL | NULL | | BTREE | | payments | 1 | user_id | 1 | user_id | 424991 | NULL | NULL | | BTREE | | payments | 1 | belong_id | 1 | belong_id | 1872714 | NULL | NULL | YES | BTREE | | payments | 1 | operation_type_created | 1 | operation_type | 21 | NULL | NULL | | BTREE | | payments | 1 | operation_type_created | 2 | created | 10692329 | NULL | NULL | YES | BTREE | | payments | 1 | coupon_id | 1 | coupon_id | 381 | NULL | NULL | YES | BTREE | | payments | 1 | modified | 1 | modified | 5868035 | NULL | NULL | YES | BTREE | +----------+------------+-------------------------+--------------+----------------+-------------+----------+--------+------+------------+ 7 rows in set (0.00 sec)
  11. 1)1ΧϯϑΝϨϯεԭೄ  スロークエリ改善のステップ •user_id, createdの複合インデックスを作成 •user_idの単⼀インデックスを削除 ◦↑の複合インデックスで代替できる •EXPLAINを再確認 ◦rowsが120624→5274に減少 •実⾏時間

    ◦25秒→1秒に改善 +----+-------------+---------+------------+------+------------------------------------------+---------+-------+------+----------+-------------+ | id | select_type | table | partitions | type | possible_keys | key | key_len | ref | rows | filtered | Extra | +----+-------------+---------+------------+------+------------------------------------------+---------+-------+------+----------+-------------+ | 1 | SIMPLE | Payment | NULL | ref | user_id_created,oper.. | user_id_created | 4 | const | 5274 | 50.00 | Using where | +----+-------------+---------+------------+------+------------------------------------------+---------+-------+------+----------+-------------+ 1 row in set, 1 warning (0.02 sec) mysql> ALTER TABLE payments ADD INDEX user_id_created(user_id, created) mysql> ALTER TABLE payments DROP INDEX user_id;
  12. 1)1ΧϯϑΝϨϯεԭೄ  (緊急⼿段)FORCE INDEX(USE INDEX)を使う •CakePHP2の場合 ◦FROMの直後にUSE INDEXを付与することが可能 ▪公式にサポートされている⽅法ではないので注意 •注意点

    ◦user_id_createdのインデックスを消すとエラーになる ▪PRIMARYなら⼤丈夫 ◦MySQLの統計情報に関わらず同じインデックスを使い続ける ▪特にFORCE INDEXの場合 •なるべくUSE INDEXにしておいた⽅が良い ▪定期的に観測し、問題なくなったら削除する $this->Payment->find('first', [ 'conditions' => [ 'user_id' => $id, 'created >' => date('Y-m-d', strtotime($format)), ], 'joins' => ['USE INDEX(user_id_created)'], SELECT FROM payments USE INDEX (use_id_created) WHERE user_id = $id AND created > '2019-09-16' ORDER BY id LIMIT 1
  13. 1)1ΧϯϑΝϨϯεԭೄ  インデックスが効かなくなる例(UNIONクエリ) •※status, createdにそれぞれ単⼀インデックスが付与されている場合 ◦2つに分割してそれぞれインデックスを効かせる SELECT id FROM results

    WHERE work_id = 1 AND status IN ('worked', 'working') UNION SELECT id FROM results WHERE work_id = 1 AND created > '2019-10-01' SELECT id FROM results WHERE work_id = 1 AND status IN ('worked', 'working') SELECT id FROM results AS Result WHERE work_id = 1 AND created > '2019-10-01'
  14. 1)1ΧϯϑΝϨϯεԭೄ  インデックスが効かなくなる例(SQLで条件分岐) •IF、CASEで条件を分岐しているパターン ◦条件の処理はPHP側で⾏う SELECT * FROM payments WHERE

    user_id = 1136075 AND CASE WHEN belong_to IN ('TimechargeInvoice', 'Milestone') THEN operation_type IN ('withholding_client') ELSE operation_type IN ('withholding_lancer') END; SELECT * FROM payments WHERE user_id = 1136075 AND operation_type IN ('withholding_client') SELECT * FROM payments WHERE user_id = 1136075 AND operation_type IN ('withholding_lancer')
  15. 1)1ΧϯϑΝϨϯεԭೄ  参照SQLをRead Replicaに分散する •読み込みの負荷が⾼いサービスの場合 ◦Read Replicaに分散させることで回避可能 ◦Auroraなら15台まで増やせる ▪RDS for

    MySQLは5台まで •CakePHPはRead Replicaに切り替える機能はサポートされていない ◦独⾃で実装する必要がある •CakePHP2のプラグインが提供されている ◦https://packagist.org/packages/connehito/cakephp2-master-replica EC2 Aurora Reader Aurora Reader Aurora Writer
  16. 1)1ΧϯϑΝϨϯεԭೄ  MySQLの全⽂検索 SELECT * FROM works WHERE MATCH (title)

    AGAINST ('PHP') OR MATCH (description) AGAINST ('PHP') MySQL 5.6 MySQL 5.7 EC2 on MySQL スペース区切り ※MroongaはN-Gram、Mecabをサポート スペース区切り N-Gram 形態素解析(Mecab) RDS for MySQL スペース区切り スペース区切り N-Gram Aurora スペース区切り スペース区切り •MATCH 〜 AGAINST構⽂でサポートされている全⽂検索機能 ◦※インデックスが1つしか使えない点は考慮が必要 ▪複雑な条件で検索し始めると⾏き詰まる