メルカリのデータベース戦略 / PHPとMySQLの怖い話 MyNA会2015年8月

18,655 views

Published on

メルカリのデータベース戦略 / PHPとMySQLの怖い話 MyNA会2015年8月

Published in: Technology
1 Comment
20 Likes
Statistics
Notes
  • どこでもスピーカーになる人になったんだねぇ
       Reply 
    Are you sure you want to  Yes  No
    Your message goes here
No Downloads
Views
Total views
18,655
On SlideShare
0
From Embeds
0
Number of Embeds
12,073
Actions
Shares
0
Downloads
12
Comments
1
Likes
20
Embeds 0
No embeds

No notes for slide

メルカリのデータベース戦略 / PHPとMySQLの怖い話 MyNA会2015年8月

  1. 1. メルカリのデータベース戦略 PHPとMySQLの怖い話 MyNA(日本MySQLユーザ会)会会会会会会会会会会 2015年8月 Masahiro Nagano @kazeburo
  2. 2. @kamipo
  3. 3. @kamipo Oracle ACE おめでとうございます!!
  4. 4. Me •長野雅広(Masahiro Nagano) •@kazeburo •Mercari, Inc. •Operations Engineer, Site Reliability •ISUCON芸人
  5. 5. メルカリの データベース戦略
  6. 6. WEB+DB PRESS vol.88 メルカリのデータベース について書きました
  7. 7. 主要KPI ダウンロード数 購入金額 出品数 2000万DL(JP+US) 月間数十億円 1日数十万品以上
  8. 8. Webアプリケーションの スケール戦略 期 スケール戦略 ハードウェア/その他 Blog スケールアウト 32bit CPU SCSI または ATA HDD SNS スケールアウトしたサーバを スケールアップし台数を抑える 64bit CPU SAS または SATA HDD KVS の活用 ソーシャルゲーム スケールアップ SSD、PCIE接続の フラッシュデバイス スマートフォン アドテク スケールアップしたサーバを スケールアウト SSD、PCIE接続の フラッシュデバイス NoSQL、クラウド
  9. 9. 2000万DLを支えるインフラ •JP: クラウド + 専用サーバ 専用Private LANで接続 •US: AWS クラウドとオンプレの環境の両方を利用
  10. 10. 2000万DLを支えるMySQL •MySQL 5.6系を利用 •JP: 専用サーバ + ioMemory •US: RDS
  11. 11. 急増するデータへの対策 •データベースをテーブル毎に分割 •MySQL以外のデータベース・ミドルウ ェアの利用
  12. 12. データベース分割 • 対象となるテーブルをそのまま別サーバに移動 • テーブル内のデータを複数台のサーバに分散する Sharding はまだしていない • 移動するテーブル • データサイズの大きなテーブル • 商品の購入など、トランザクションに含まれないテー ブル
  13. 13. Master Backup Master Slave Backup Master Backup Master Backup Main Cluster Sub Cluster Sub2 Cluster 分割 スケールアップとスケールアウトを組み合わせた構成 table A,B,C table D table E,F,G,H
  14. 14. 接続先の管理 $cluster1 = array('dsn' => 'mysql:host=db10;dbname=mercari'); $cluster2 = array('dsn' => 'mysql:host=db12;dbname=mercari'); $db_config = array(); $db_config['main'] = $dsn; $db_config['todo_master'] = $cluster1 $db_config['comments_master'] = $cluster2 $db_config['likes_master'] = $cluster2 public static function conn($key = 'main') { new PDO($db_config[$key],$user,$pass); } $pdo = MyDB::conn('todo_master'); $pdo->prepare('SELECT * FROM todo WHERE..'); テーブル、機能ごとに 接続先を管理 さらなる分割も視野にいれた仕組み
  15. 15. MySQL以外の データストア/処理ミドルウェア・サービス • データの一時的キャッシュ • memcached • 新着商品リスト • Redis • ログデータ分析 • Treasure Data • BigQuery • Norikra • KPI • MySQL 5.7
  16. 16. Treasure Data BigQuery ログデータ分析基盤 クラウドで爆発的に増えるデータを処理する 超大規模でもない限り、分析基盤を自前で構築するメリットは薄い App App App
  17. 17. ログのStream処理 App App App SQLを投入 Norikraを使い、ログをリアルタイムに集計して Mackerelで可視化、Slackに通知 Mackerel Slack
  18. 18. Norikra SQL SELECT COUNT(1, status like "5%")/COUNT(1)*100 AS rate_5xx, COUNT(1, status like "4%")/COUNT(1)*100 AS rate_4xx, COUNT(1, status like "3%")/COUNT(1)*100 AS rate_3xx, COUNT(1, status like "2%")/COUNT(1)*100 AS rate_2xx FROM access_log.win:time_batch(1 min) WHERE ua NOT LIKE '%some_bot%' 1分間のtime window毎に集計
  19. 19. Mackerel グラフによる可視化に加え アラートの設定ができる
  20. 20. Slackへの通知 エラーログをNorikraで集計してSlack通知
  21. 21. 分析でのMySQLの利用 •KPI集計 •アドホックな分析、調査 ✓ 3つのクラスタを統合して使いやすく ✓ 個人情報の取り扱い
  22. 22. MySQL 5.7 •Multi-Source Replication •Trigger で書き換え
  23. 23. Master Slave Backup Master Backup Master Backup Main Cluster Sub Cluster Sub2 Cluster table A,B,C table D table E,F,G,H Multi-Source Replication analyze-db table A,B,C,D,E,F,G,H...
  24. 24. Multi-Source Replicationの 使い方 CHANGE MASTER TO MASTER_HOST='db1',.. FOR CHANNEL 'db1'; START SLAVE FOR CHANNEL ‘db1’; STOP SLAVE FOR CHANNEL ‘db1’; SHOW SLAVE STATUS FOR CHANNEL ‘db1’G FOR CHANNEL をつけるだけ。問題なく動作している
  25. 25. Triggerで書き換え CREATE TRIGGER insert_user_address BEFORE INSERT ON user_address FOR EACH ROW BEGIN SET NEW.family_name = MD5(concat(NEW.family_name,'secret_key')); SET NEW.first_name = MD5(concat(NEW.first_name,'secret_key')); END; CREATE TRIGGER update_user_address BEFORE UPDATE ON user_address FOR EACH ROW BEGIN SET NEW.family_name = MD5(concat(NEW.family_name,'secret_key')); SET NEW.first_name = MD5(concat(NEW.first_name,'secret_key')); END; MD5でhashに変更 ユニーク性は確保
  26. 26. 前半終了
  27. 27. CM
  28. 28. ISUCON5 2015/9/26-27 予選 2015/10/31 本選
  29. 29. /CM
  30. 30. PHPとMySQLの怖い話 2つほど..
  31. 31. PHPはじめました この6ヶ月の間にハマったことを紹介します
  32. 32. 1. commit() が例外を出さない あるいはPHPの例外とエラーについて
  33. 33. <?php $pdo = new PDO('mysql:dbname=test;host=127.0.0.1'); $pdo->query('SET SESSION wait_timeout=1'); $pdo->beginTransaction(); try { sleep(2); $pdo->commit(); # ここでエラー } catch( Exception $e) { $pdo->rollBack(); throw $e; } echo "Hello!!";
  34. 34. [kazeburo@kazeburomba2-2 /tmp]% php -v PHP 5.6.5 (cli) (built: Jan 28 2015 16:00:57) $ php hoge.php PHP Warning: PDO::commit(): MySQL server has gone away in /private/tmp/hoge.php on line 14 PHP Warning: PDO::commit(): Error reading result set's header in /private/tmp/hoge.php on line 14 Hello!! $
  35. 35. commit() はエラーになっても例外を出さない エラーを別途補足して例外に変換 http://php.net/manual/ja/pdo.commit.php には例外に関することが書かれてない
  36. 36. <?php set_error_handler(function ($severity, $message, $file, $line) { throw new ErrorException($message, 0, $severity, $file, $line); }); $pdo = new PDO('mysql:dbname=test;host=127.0.0.1', 'root', ‘’); $pdo->query('SET SESSION wait_timeout=1'); $pdo->beginTransaction(); try { sleep(2); $pdo->commit(); } catch( Exception $e) { $pdo->rollBack(); throw $e; } echo "Hello!!";
  37. 37. $ php hoge.php PHP Fatal error: Uncaught exception 'PDOException' with message 'There is no active transaction' in / private/tmp/hoge.php:17 Stack trace: #0 /private/tmp/hoge.php(17): PDO->rollBack() #1 {main} thrown in /private/tmp/hoge.php on line 17 $
  38. 38. ただし
  39. 39. $ rpm -qa|grep php php-5.3.3-27.el6_5.x86_64 $ php -i PDO Driver for MySQL => enabled Client API version => 5.1.70 $ php hoge.php hello!! $ アイエエエエ!ナンデ!ウゴクナンデ!
  40. 40. •PHPのアップデート •mysqlndの利用(PHP5.3でも問題なし) •commit() の前に query(”SELECT 1”) •PDOに ping() が欲しい
  41. 41. 2. Empty row packet body
  42. 42. <?php $pdo = new PDO('mysql:dbname=test;host=127.0.0.1', 'root', ''); $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); $sth = $pdo->prepare('SELECT * FROM buffer'); $sth->execute(); while ($rows = $sth->fetch(PDO::FETCH_ASSOC)) { #job($rows) } echo “hello!n”; 十分に大きい テーブル
  43. 43. $ rpm -qa|grep php php-5.3.3-27.el6_5.x86_64 $ php -i PDO Driver for MySQL => enabled Client API version => 5.1.70 $ php fuga.php hello! $
  44. 44. $ php -v PHP 5.6.5 (cli) (built: Jan 28 2015 16:00:57) $ php fuga.php PHP Warning: Empty row packet body in /private/tmp/ fuga.php on line 23 Warning: Empty row packet body in /private/tmp/fuga.php on line 23 $ アイエエエエ!ナンデ!エラーナンデ!
  45. 45. •unbuffered queryを使わない •net_write_timeout を伸ばす
  46. 46. <?php $pdo = new PDO('mysql:dbname=test;host=127.0.0.1', 'root', ''); $pdo->exec("CREATE TABLE IF NOT EXISTS buffer ( buf varchar(256) )"); $data = array(); for ($i = 0; $i < 100; $i++) $data[] = str_pad('', 256); for ($k=0; $k < 500; $k++ ) { $sql = "INSERT INTO buffer VALUES " .implode(",", array_fill(0, count($data), "(?)")) . ""; $stmt = $pdo->prepare($sql); $stmt->execute($data); } $pdo = new PDO('mysql:dbname=test;host=127.0.0.1', 'root', ''); $pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false); $pdo->query('SET SESSION net_write_timeout=1'); $sth = $pdo->prepare('SELECT * FROM buffer'); $sth->execute(); while ($rows = $sth->fetch(PDO::FETCH_ASSOC)) { usleep(1000); } 再現コード置いておきます
  47. 47. PHP怖くない(´・ω・`)
  48. 48. おしまい

×