メルカリのデータベース戦略 / phpとmysqlの怖い話 myna会2015年8月

51
メルカリのデータベース戦略 PHPMySQLの怖い話 MyNA(日本MySQLユーザ会)20158Masahiro Nagano @kazeburo

Upload: masahiro-nagano

Post on 16-Apr-2017

21.040 views

Category:

Technology


1 download

TRANSCRIPT

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

メルカリのデータベース戦略PHPとMySQLの怖い話

MyNA(日本MySQLユーザ会)会会会会会会会会会会 2015年8月Masahiro Nagano @kazeburo

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

@kamipo

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

@kamipoOracle ACE おめでとうございます!!

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

Me•長野雅広(Masahiro Nagano)

•@kazeburo

•Mercari, Inc.

•Operations Engineer, Site Reliability

• ISUCON芸人

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

メルカリのデータベース戦略

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

WEB+DB PRESS vol.88

メルカリのデータベースについて書きました

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

主要KPI

ダウンロード数

購入金額

出品数

2000万DL(JP+US)

月間数十億円

1日数十万品以上

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

Webアプリケーションのスケール戦略

期 スケール戦略 ハードウェア/その他

Blog スケールアウト 32bit CPUSCSI または ATA HDD

SNSスケールアウトしたサーバをスケールアップし台数を抑える

64bit CPUSAS または SATA HDD

KVS の活用

ソーシャルゲーム スケールアップSSD、PCIE接続のフラッシュデバイス

スマートフォンアドテク

スケールアップしたサーバをスケールアウト

SSD、PCIE接続のフラッシュデバイスNoSQL、クラウド

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

2000万DLを支えるインフラ

• JP: クラウド + 専用サーバ 専用Private LANで接続

• US: AWS

クラウドとオンプレの環境の両方を利用

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

2000万DLを支えるMySQL

•MySQL 5.6系を利用

• JP: 専用サーバ + ioMemory

• US: RDS

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

急増するデータへの対策

•データベースをテーブル毎に分割•MySQL以外のデータベース・ミドルウェアの利用

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

データベース分割• 対象となるテーブルをそのまま別サーバに移動

• テーブル内のデータを複数台のサーバに分散する Sharding はまだしていない

• 移動するテーブル

• データサイズの大きなテーブル

• 商品の購入など、トランザクションに含まれないテーブル

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

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

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

接続先の管理$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..');

テーブル、機能ごとに接続先を管理

さらなる分割も視野にいれた仕組み

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

MySQL以外のデータストア/処理ミドルウェア・サービス

• データの一時的キャッシュ

• memcached

• 新着商品リスト

• Redis

• ログデータ分析

• Treasure Data

• BigQuery

• Norikra

• KPI

• MySQL 5.7

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

Treasure Data

BigQuery

ログデータ分析基盤

クラウドで爆発的に増えるデータを処理する超大規模でもない限り、分析基盤を自前で構築するメリットは薄い

App

App

App

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

ログのStream処理

App

App

App SQLを投入

Norikraを使い、ログをリアルタイムに集計してMackerelで可視化、Slackに通知

Mackerel

Slack

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

Norikra SQLSELECT 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_2xxFROM access_log.win:time_batch(1 min)WHERE ua NOT LIKE '%some_bot%'

1分間のtime window毎に集計

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

Mackerel

グラフによる可視化に加えアラートの設定ができる

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

Slackへの通知

エラーログをNorikraで集計してSlack通知

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

分析でのMySQLの利用

• KPI集計

•アドホックな分析、調査✓ 3つのクラスタを統合して使いやすく

✓ 個人情報の取り扱い

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

MySQL 5.7

•Multi-Source Replication

• Trigger で書き換え

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

Master

Slave Backup

Master Backup Master Backup

Main Cluster Sub Cluster Sub2 Clustertable A,B,C table D table E,F,G,H

Multi-Source Replication

analyze-dbtable A,B,C,D,E,F,G,H...

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

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 をつけるだけ。問題なく動作している

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

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に変更ユニーク性は確保

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

前半終了

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

CM

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

ISUCON52015/9/26-27 予選2015/10/31 本選

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

/CM

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

PHPとMySQLの怖い話2つほど..

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

PHPはじめましたこの6ヶ月の間にハマったことを紹介します

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

1. commit() が例外を出さないあるいはPHPの例外とエラーについて

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

<?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!!";

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

[kazeburo@kazeburomba2-2 /tmp]% php -v PHP 5.6.5 (cli) (built: Jan 28 2015 16:00:57)

$ php hoge.phpPHP Warning: PDO::commit(): MySQL server has gone away in /private/tmp/hoge.php on line 14PHP Warning: PDO::commit(): Error reading result set's header in /private/tmp/hoge.php on line 14Hello!!$

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

commit() はエラーになっても例外を出さないエラーを別途補足して例外に変換

http://php.net/manual/ja/pdo.commit.phpには例外に関することが書かれてない

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

<?phpset_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!!";

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

$ php hoge.phpPHP Fatal error: Uncaught exception 'PDOException' with message 'There is no active transaction' in /private/tmp/hoge.php:17Stack trace:#0 /private/tmp/hoge.php(17): PDO->rollBack()#1 {main} thrown in /private/tmp/hoge.php on line 17$

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

ただし

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

$ rpm -qa|grep phpphp-5.3.3-27.el6_5.x86_64$ php -iPDO Driver for MySQL => enabledClient API version => 5.1.70

$ php hoge.phphello!!$

アイエエエエ!ナンデ!ウゴクナンデ!

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

• PHPのアップデート

•mysqlndの利用(PHP5.3でも問題なし)

• commit() の前に query(”SELECT 1”)

• PDOに ping() が欲しい

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

2. Empty row packet body

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

<?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”;

十分に大きいテーブル

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

$ rpm -qa|grep phpphp-5.3.3-27.el6_5.x86_64$ php -iPDO Driver for MySQL => enabledClient API version => 5.1.70

$ php fuga.phphello!$

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

$ php -vPHP 5.6.5 (cli) (built: Jan 28 2015 16:00:57)

$ php fuga.phpPHP 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$

アイエエエエ!ナンデ!エラーナンデ!

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

• unbuffered queryを使わない

• net_write_timeout を伸ばす

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

<?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);}

再現コード置いておきます

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

おしまい