メルカリのデータベース戦略
PHPとMySQLの怖い話
MyNA(日本MySQLユーザ会)会会会会会会会会会会 2015年8月
Masahiro Nagano @kazeburo
@kamipo
@kamipo
Oracle ACE おめでとうございます!!
Me
•長野雅広(Masahiro Nagano)
•@kazeburo
•Mercari, Inc.
•Operations Engineer, Site Reliability
•ISUCON芸人
メルカリの
データベース戦略
WEB+DB PRESS vol.88
メルカリのデータベース
について書きました
主要KPI
ダウンロード数
購入金額
出品数
2000万DL(JP+US)
月間数十億円
1日数十万品以上
Webアプリケーションの
スケール戦略
期 スケール戦略 ハードウェア/その他
Blog スケールアウト
32bit CPU
SCSI または ATA HDD
SNS
スケールアウトしたサーバを
スケールアップし台数を抑える
64bit CPU...
2000万DLを支えるインフラ
•JP: クラウド + 専用サーバ
専用Private LANで接続
•US: AWS
クラウドとオンプレの環境の両方を利用
2000万DLを支えるMySQL
•MySQL 5.6系を利用
•JP: 専用サーバ + ioMemory
•US: RDS
急増するデータへの対策
•データベースをテーブル毎に分割
•MySQL以外のデータベース・ミドルウ
ェアの利用
データベース分割
• 対象となるテーブルをそのまま別サーバに移動
• テーブル内のデータを複数台のサーバに分散する
Sharding はまだしていない
• 移動するテーブル
• データサイズの大きなテーブル
• 商品の購入など、トランザクション...
Master Backup
Master
Slave Backup
Master Backup Master Backup
Main Cluster Sub Cluster Sub2 Cluster
分割
スケールアップとスケールアウトを組み合...
接続先の管理
$cluster1 = array('dsn' => 'mysql:host=db10;dbname=mercari');
$cluster2 = array('dsn' => 'mysql:host=db12;dbname=me...
MySQL以外の
データストア/処理ミドルウェア・サービス
• データの一時的キャッシュ
• memcached
• 新着商品リスト
• Redis
• ログデータ分析
• Treasure Data
• BigQuery
• Norikra
...
Treasure Data
BigQuery
ログデータ分析基盤
クラウドで爆発的に増えるデータを処理する
超大規模でもない限り、分析基盤を自前で構築するメリットは薄い
App
App
App
ログのStream処理
App
App
App SQLを投入
Norikraを使い、ログをリアルタイムに集計して
Mackerelで可視化、Slackに通知
Mackerel
Slack
Norikra SQL
SELECT
COUNT(1, status like "5%")/COUNT(1)*100 AS rate_5xx,
COUNT(1, status like "4%")/COUNT(1)*100 AS rate_4x...
Mackerel
グラフによる可視化に加え
アラートの設定ができる
Slackへの通知
エラーログをNorikraで集計してSlack通知
分析でのMySQLの利用
•KPI集計
•アドホックな分析、調査
✓ 3つのクラスタを統合して使いやすく
✓ 個人情報の取り扱い
MySQL 5.7
•Multi-Source Replication
•Trigger で書き換え
Master
Slave Backup
Master Backup Master Backup
Main Cluster Sub Cluster Sub2 Cluster
table A,B,C table D table E,F,G,H
Mu...
Multi-Source Replicationの
使い方
CHANGE MASTER TO MASTER_HOST='db1',.. FOR CHANNEL 'db1';
START SLAVE FOR CHANNEL ‘db1’;
STOP...
Triggerで書き換え
CREATE TRIGGER insert_user_address
BEFORE INSERT ON user_address
FOR EACH ROW
BEGIN
SET NEW.family_name = MD5...
前半終了
CM
ISUCON5
2015/9/26-27 予選
2015/10/31 本選
/CM
PHPとMySQLの怖い話
2つほど..
PHPはじめました
この6ヶ月の間にハマったことを紹介します
1. commit() が例外を出さない
あるいはPHPの例外とエラーについて
<?php
$pdo = new PDO('mysql:dbname=test;host=127.0.0.1');
$pdo->query('SET SESSION wait_timeout=1');
$pdo->beginTransactio...
[kazeburo@kazeburomba2-2 /tmp]% php -v
PHP 5.6.5 (cli) (built: Jan 28 2015 16:00:57)
$ php hoge.php
PHP Warning: PDO::comm...
commit() はエラーになっても例外を出さない
エラーを別途補足して例外に変換
http://php.net/manual/ja/pdo.commit.php
には例外に関することが書かれてない
<?php
set_error_handler(function ($severity, $message, $file, $line) {
throw new ErrorException($message, 0, $severity, $f...
$ php hoge.php
PHP Fatal error: Uncaught exception 'PDOException'
with message 'There is no active transaction' in /
priva...
ただし
$ 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 h...
•PHPのアップデート
•mysqlndの利用(PHP5.3でも問題なし)
•commit() の前に query(”SELECT 1”)
•PDOに ping() が欲しい
2. Empty row packet body
<?php
$pdo = new PDO('mysql:dbname=test;host=127.0.0.1', 'root', '');
$pdo->setAttribute(PDO::MYSQL_ATTR_USE_BUFFERED_QUER...
$ 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 f...
$ 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/
...
•unbuffered queryを使わない
•net_write_timeout を伸ばす
<?php
$pdo = new PDO('mysql:dbname=test;host=127.0.0.1', 'root', '');
$pdo->exec("CREATE TABLE IF NOT EXISTS buffer (
buf ...
PHP怖くない(´・ω・`)
おしまい
メルカリのデータベース戦略 / PHPとMySQLの怖い話 MyNA会2015年8月
メルカリのデータベース戦略 / PHPとMySQLの怖い話 MyNA会2015年8月
メルカリのデータベース戦略 / PHPとMySQLの怖い話 MyNA会2015年8月
Upcoming SlideShare
Loading in...5
×

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

953

Published on

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

Published in: Technology
0 Comments
3 Likes
Statistics
Notes
  • Be the first to comment

No Downloads
Views
Total Views
953
On Slideshare
0
From Embeds
0
Number of Embeds
0
Actions
Shares
0
Downloads
0
Comments
0
Likes
3
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. おしまい
  1. A particular slide catching your eye?

    Clipping is a handy way to collect important slides you want to go back to later.

×