php と mysql で 1 カチャカチャカチャ...ッターン! mapreduce (@ニコニコ超会議)
DESCRIPTION
TRANSCRIPT
PHP と MySQL で1-Click
MapReduce(@ニコニコ超会議)
@yuya_takeyama
はじめに
1-Click無理でした!!!
PHP と MySQL で1-Click
1カチャカチャカチャ...ッターン!MapReduce(@ニコニコ超会議)
@yuya_takeyama
N番煎じでお送りします• MyMR on GitHubhttps://github.com/yuya-takeyama/mymr
• PHP と MySQL でカジュアルに MapReduce するhttp://blog.yuyat.jp/archives/1706
• もっとカジュアルに PHP と MySQL で MapReduce するhttp://blog.yuyat.jp/archives/1853
• PHP と MySQL でカジュアルに MapReduce する(スライド・Long Version)http://www.slideshare.net/taketyan/php-mysql-mapreduce
• PHP と MySQL でカジュアルに MapReduce する(スライド・Short Version)http://www.slideshare.net/taketyan/php-mysql-mapreduce-short-version
MapReduce とは(word count による例)
MapReduce とは
データ処理のためのプログラミングモデル
処理の流れ入力↓Map↓
Shuffle↓
Reduce↓出力
Map
•入力データを受け取り•複数の Key/Value ペアを出力
•to be or not to be
•<"to", 1>
•<"be", 1>
•<"or", 1>
•<"not", 1>
•<"to", 1>
•<"be", 1>
Shuffle
•Map による Key/Value を•Key ごとにまとめて出力
•<"to", 1>
•<"be", 1>
•<"or", 1>
•<"not", 1>
•<"to", 1>
•<"be", 1>
•<"be", [1, 1]>
•<"not", [1]>
•<"or", [1]>
•<"to", [1, 1]>
Reduce
•Shuffle による中間データを•集約して答えを出力
•<"be", [1, 1]>
•<"not", [1]>
•<"or", [1]>
•<"to", [1, 1]>
•<"be", 2>
•<"not", 1>
•<"or", 1>
•<"to", 2>
複数の関数の入出力を経て最終的な答えを出力
ところで
MySQL でMapReduce したい!!!
MySQL でMapReduce したい!!!
それも1カチャカチャ...っターン!で
モチベーション•プログラミングモデルとしてのMapReduce を使いたい
•GROUP BY では難しい集計•MySQL を入出力にしたい•LL でサクッとやりたい
モチベーション•プログラミングモデルとしてのMapReduce を使いたい
•GROUP BY では難しい集計•MySQL を入出力にしたい•LL でサクッとやりたい
PHP である必要はあまり無い
モチベーション•プログラミングモデルとしてのMapReduce を使いたい
•GROUP BY では難しい集計•MySQL を入出力にしたい•LL でサクッとやりたい
PHP である必要はあまり無い
もちろん1カチャカチャ...ッターン!で
というわけで作りました
MyMR
•MySQL を入出力とする•PHP で Map/Reduce を書く•カチャカチャ...ッターン!で実行
https://github.com/yuya-takeyama/mymr
実行までの流れ
•入出力に使う MySQL テーブルの用意•PHP で Map 関数を書く•PHP で Reduce 関数を書く•mymr コマンドを実行する
実行までの流れ
•入出力に使う MySQL テーブルの用意•PHP で Map 関数を書く•PHP で Reduce 関数を書く•mymr コマンドを実行するこの辺がカチャカチャカチャ...
実行までの流れ
•入出力に使う MySQL テーブルの用意•PHP で Map 関数を書く•PHP で Reduce 関数を書く•mymr コマンドを実行する
この辺が...ッターン!
文章中の単語の数を数える例(word count)
MyMR による
use \MyMR\Builder;
$builder = new Builder;
$builder->setInputTable('root@localhost/db/texts');$builder->setOutputTable('root@localhost/db/word_counts');
$builder->setMapper(function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }});
$builder->setReducer(function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);});
return $builder;
Map/Reduce の定義
use \MyMR\Builder;
$builder = new Builder;
$builder->setInputTable('root@localhost/db/texts');$builder->setOutputTable('root@localhost/db/word_counts');
$builder->setMapper(function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }});
$builder->setReducer(function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);});
return $builder;
Map/Reduce の定義
入出力テーブルの指定
use \MyMR\Builder;
$builder = new Builder;
$builder->setInputTable('root@localhost/db/texts');$builder->setOutputTable('root@localhost/db/word_counts');
$builder->setMapper(function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }});
$builder->setReducer(function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);});
return $builder;
Map/Reduce の定義
この辺が Map
use \MyMR\Builder;
$builder = new Builder;
$builder->setInputTable('root@localhost/db/texts');$builder->setOutputTable('root@localhost/db/word_counts');
$builder->setMapper(function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }});
$builder->setReducer(function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);});
return $builder;
Map/Reduce の定義
この辺が Reduce
入力
•to be or not to be
function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }}
Map
function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }}
レコードを連想配列として受け取る
Map
function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }} text カラム内の
文字列をスペースで分割
Map
function ($record, $emitter) { $words = preg_split('/\s+/u', $record['text']); foreach ($words as $word) { $emitter->emit($word, 1); }}
Key/Value のペアとして中間テーブルに INSERT
Map
Map+----+--------------------+| id | text |+----+--------------------+| 1 | to be or not to be |+----+--------------------+
↓ レコードを連想配列として Map へ ↓+----+---------+-------+ | id | key | value | +----+---------+-------+ | 1 | to | 1 | | 2 | be | 1 | | 3 | or | 1 | | 4 | not | 1 | | 5 | to | 1 | | 6 | be | 1 | +----+---------+-------+
Map+----+--------------------+| id | text |+----+--------------------+| 1 | to be or not to be |+----+--------------------+
↓ レコードを連想配列として Map へ ↓+----+---------+-------+ | id | key | value | +----+---------+-------+ | 1 | to | 1 | | 2 | be | 1 | | 3 | or | 1 | | 4 | not | 1 | | 5 | to | 1 | | 6 | be | 1 | +----+---------+-------+
value には JSON で入れるので構造化データも使用可能
Shuffle
↓ キーで GROUP BY して ↓↓ 値は GROUP_CONCAT ↓
+---------+--------+| key | values |+---------+--------+| be | 1,1 || not | 1 || or | 1 || to | 1,1 |+---------+--------+
+----+---------+-------+ | id | key | value | +----+---------+-------+ | 1 | to | 1 | | 2 | be | 1 | | 3 | or | 1 | | 4 | not | 1 | | 5 | to | 1 | | 6 | be | 1 | +----+---------+-------+
SELECT `key`, GROUP_CONCAT(`value`)FROM `中間テーブル`
GROUP BY `key`
function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);}
Reduce
function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);}
ReduceKey Value の配列
function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);}
ReduceValue を全て足す
function ($key, $values) { $sum = 0; foreach ($values as $count) { $sum += $count; } return array('count' => $sum);}
Reduce
返り値の連想配列をレコードとして INSERT
Reduce
↓ キーと値の配列を Reduce へ ↓
+---------+--------+| key | values |+---------+--------+| be | 1,1 || not | 1 || or | 1 || to | 1,1 |+---------+--------+
+----+---------+-------+| id | key | count |+----+---------+-------+| 1 | be | 2 || 2 | not | 1 || 3 | or | 1 || 4 | to | 2 |+----+---------+-------+
Reduce
↓ キーと値の配列を Reduce へ ↓
+---------+--------+| key | values |+---------+--------+| be | 1,1 || not | 1 || or | 1 || to | 1,1 |+---------+--------+
+----+---------+-------+| id | key | count |+----+---------+-------+| 1 | be | 2 || 2 | not | 1 || 3 | or | 1 || 4 | to | 2 |+----+---------+-------+
実際にはデリミタとして改行を使用改行区切りの JSON になる
今後の目標
•非同期 INSERT による並列化•Hadoop へのシームレスな移行方法の提供
今後の野望
•V8 エンジンとかで•ストレージエンジン API を•カジュアルに叩いて•MapReduce したい
今後の野望
•V8 エンジンとかで•ストレージエンジン API を•カジュアルに叩いて•MapReduce したい
もちろん1カチャカチャ...ッターン!で
ご清聴ありがとうございました