PHP と MySQL で
  カジュアルに
MapReduce する

   @yuya_takeyama
アジェンダ

•MapReduce とは

• 自作フレームワーク
MyMR の紹介
お断り (1)

ビッグデータの話は
  ありません
お断り (2)

  業務では
まだやってません
お断り (3)

 Hadoop
 未経験です
   ※MongoDB での
MapReduce ならやりました
MapReduce とは
固有名詞として

•
Google の大規模データ処理フ
レームワーク

•
検索インデックスの作成とかに
使われている
普通名詞として
•Map/Reduce 関数でデータを
 処理するプログラミングモデル

•   マシンを増やしただけスケール

•Hadoop, MongoDB,
 CouchDB などが主な実装
入力
処理の流れ      ↓
         Map
           ↓
        Reduce
           ↓
          出力
やや厳密な          入力
 処理の流れ          ↓
              Map
                ↓
             Shuffle
                ↓
             Reduce
より厳密には          ↓
もっと複雑らしいです
               出力
Map


•入力データを受け取り
• 複数の Key/Value ペアを出力
Shuffle


•Map による Key/Value を
• Key ごとにまとめて出力
Reduce


•Shuffle による中間データを
• 集約して答えを出力
複数の関数の
入出力を経て
最終的な答えを出力
文章中の
単語の数を数える例
 (word count)
入力


•to be or not to be
Map   •<"to", 1>
      •<"be", 1>
      •<"or", 1>
      •<"not", 1>
      •<"to", 1>
      •<"be", 1>
Shuffle
   •<"be", [1, 1]>
   •<"not", [1]>
   •<"or", [1]>
   •<"to", [1, 1]>
Reduce
  • <"be", 2>
  • <"not", 1>
  • <"or", 1>
  • <"to", 2>
MapReduce の利点
•Map も Reduce も並列化
すればスケールする

•関数型っぽい考え方が活きる
※ただし, Hadoop や MongoDB の MapReduce の Map と Reduce は
 関数型言語のそれとはやや異なる (参照透過でなかったり)



•   パターンとして共有しやすい
※手続き型のバッチ処理と比較して
MongoDB について

•
通常は MapReduce を
並列に実行することができない

•   それでも MapReduce は便利

•何故か?
スケーラビリティだけじゃない

•プログラミングモデルとしての
 MapReduce にも価値がある

•   MongoDB で処理が完結

•JS で関数ふたつ書くだけ
MySQL でも
MapReduce
  したい!!!
というわけで作りました
MyMR
   https://github.com/yuya-takeyama/mymr


•MySQL を入出力とする

• PHP で Map/Reduce を書く

• コマンドラインで実行
MyMR による処理の流れ
•テーブルからレコードを読む
•1 行 1 行に Map (PHP) を適用して
  中間テーブルへ

•MySQL による Shuffle
•その結果に Reduce (PHP) を適用して
  出力テーブルへ
に よ る
 yM R
M
    文章中の
単語の数を数える例
 (word count)
use MyMRBuilder;
                      Map/Reduce の定義
$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;
use MyMRBuilder;
                      Map/Reduce の定義
$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;
use MyMRBuilder;
                      Map/Reduce の定義
$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);
    }                                    この辺が Map
});

$builder->setReducer(function ($key, $values) {
    $sum = 0;
    foreach ($values as $count) {
        $sum += $count;
    }
    return array('count' => $sum);
});

return $builder;
use MyMRBuilder;
                      Map/Reduce の定義
$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);      この辺が Reduce
});

return $builder;
入力


•to be or not to be
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);
    }
}
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
Reduce
function ($key, $values) {
    $sum = 0;
    foreach ($values as $count) {
        $sum += $count;
    }
    return array('count' => $sum);
}
Reduce
                 Key      Value の配列

function ($key, $values) {
    $sum = 0;
    foreach ($values as $count) {
        $sum += $count;
    }
    return array('count' => $sum);
}
Reduce
                         Value を全て足す
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);
}
                     返り値の連想配列を
                    レコードとして INSERT
+----+--------------------+

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
 value には JSON で入れるので         | 1     |
               | 5 | to
     構造化データも使用可能              | 1     |
               | 6 | be       | 1     |
               +----+---------+-------+
+----+---------+-------+
                            | id | key     | value |

Shuffle                      +----+---------+-------+
                            | 1 | to
                            | 2 | be
                                           | 1
                                           | 1
                                                   |
                                                   |
                            | 3 | or       | 1     |
                            | 4 | not      | 1     |
                            | 5 | to       | 1     |
                            | 6 | be       | 1     |
                            +----+---------+-------+

                          ↓ キーで GROUP BY して ↓
SELECT                    ↓ 値は GROUP_CONCAT ↓
  `key`,                      +---------+--------+
  GROUP_CONCAT(`value`)       | key     | values |
FROM                          +---------+--------+
  `中間テーブル`                    | be      | 1,1    |
                              | not     | 1      |
GROUP BY                      | or      | 1      |
  `key`                       | to      | 1,1    |
                              +---------+--------+
+---------+--------+
            | key     | values |
Reduce      +---------+--------+
            | be      | 1,1    |
            | not     | 1      |
            | or      | 1      |
            | to      | 1,1    |
            +---------+--------+
         ↓ キーと値の配列を Reduce へ ↓
           +----+---------+-------+
           | id | key     | count |
           +----+---------+-------+
           | 1 | be       |     2 |
           | 2 | not      |     1 |
           | 3 | or       |     1 |
           | 4 | to       |     2 |
           +----+---------+-------+
+---------+--------+
                 | key     | values |
Reduce           +---------+--------+
                 | be      | 1,1    |
                 | not     | 1      |
                 | or      | 1      |
 実際にはデリミタとして改行を使用| to      | 1,1    |
                 +---------+--------+
   改行区切りの JSON になる

             ↓ キーと値の配列を Reduce へ ↓
                +----+---------+-------+
                | id | key     | count |
                +----+---------+-------+
                | 1 | be       |     2 |
                | 2 | not      |     1 |
                | 3 | or       |     1 |
                | 4 | to       |     2 |
                +----+---------+-------+
モチベーション

•
プログラミングモデルとしての
MapReduce を使いたい

•   MySQL を入出力にしたい

•LL でサクッとやりたい
モチベーション

•
プログラミングモデルとしての
MapReduce を使いたい

•   MySQL を入出力にしたい

•LL でサクッとやりたい
       PHP である必要はあまり無い
今後の目標


•非同期 INSERT による並列化
• Hadoop へのシームレスな
移行方法の提供
まとめ

•
ビッグデータは無くとも
MapReduce は有効

•   MySQL でできたら便利なはず

•PHP で書けたら楽しいはず
リンク

• MyMR on GitHub
  https://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 する

  • 1.
    PHP と MySQLで カジュアルに MapReduce する @yuya_takeyama
  • 2.
  • 3.
  • 4.
    お断り (2) 業務では まだやってません
  • 5.
    お断り (3) Hadoop 未経験です ※MongoDB での MapReduce ならやりました
  • 6.
  • 7.
  • 8.
    普通名詞として •Map/Reduce 関数でデータを 処理するプログラミングモデル • マシンを増やしただけスケール •Hadoop, MongoDB, CouchDB などが主な実装
  • 9.
    入力 処理の流れ ↓ Map ↓ Reduce ↓ 出力
  • 10.
    やや厳密な 入力 処理の流れ ↓ Map ↓ Shuffle ↓ Reduce より厳密には ↓ もっと複雑らしいです 出力
  • 11.
  • 12.
    Shuffle •Map による Key/Valueを • Key ごとにまとめて出力
  • 13.
  • 14.
  • 15.
  • 16.
  • 17.
    Map •<"to", 1> •<"be", 1> •<"or", 1> •<"not", 1> •<"to", 1> •<"be", 1>
  • 18.
    Shuffle •<"be", [1, 1]> •<"not", [1]> •<"or", [1]> •<"to", [1, 1]>
  • 19.
    Reduce •<"be", 2> • <"not", 1> • <"or", 1> • <"to", 2>
  • 20.
    MapReduce の利点 •Map もReduce も並列化 すればスケールする •関数型っぽい考え方が活きる ※ただし, Hadoop や MongoDB の MapReduce の Map と Reduce は  関数型言語のそれとはやや異なる (参照透過でなかったり) • パターンとして共有しやすい ※手続き型のバッチ処理と比較して
  • 21.
    MongoDB について • 通常は MapReduceを 並列に実行することができない • それでも MapReduce は便利 •何故か?
  • 22.
  • 23.
  • 24.
  • 25.
    MyMR https://github.com/yuya-takeyama/mymr •MySQL を入出力とする • PHP で Map/Reduce を書く • コマンドラインで実行
  • 26.
    MyMR による処理の流れ •テーブルからレコードを読む •1 行1 行に Map (PHP) を適用して 中間テーブルへ •MySQL による Shuffle •その結果に Reduce (PHP) を適用して 出力テーブルへ
  • 27.
    に よ る yM R M 文章中の 単語の数を数える例 (word count)
  • 28.
    use MyMRBuilder; Map/Reduce の定義 $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;
  • 29.
    use MyMRBuilder; Map/Reduce の定義 $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;
  • 30.
    use MyMRBuilder; Map/Reduce の定義 $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);     } この辺が Map }); $builder->setReducer(function ($key, $values) {     $sum = 0;     foreach ($values as $count) {         $sum += $count;     }     return array('count' => $sum); }); return $builder;
  • 31.
    use MyMRBuilder; Map/Reduce の定義 $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); この辺が Reduce }); return $builder;
  • 32.
  • 33.
    Map function ($record, $emitter){     $words = preg_split('/s+/u', $record['text']);     foreach ($words as $word) {         $emitter->emit($word, 1);     } }
  • 34.
    Map レコードを 連想配列として受け取る function ($record, $emitter) {     $words = preg_split('/s+/u', $record['text']);     foreach ($words as $word) {         $emitter->emit($word, 1);     } }
  • 35.
    Map function ($record, $emitter){     $words = preg_split('/s+/u', $record['text']);     foreach ($words as $word) {         $emitter->emit($word, 1);     } } text カラム内の 文字列をスペースで分割
  • 36.
    Map function ($record, $emitter){     $words = preg_split('/s+/u', $record['text']);     foreach ($words as $word) {         $emitter->emit($word, 1);     } } Key/Value のペアとして 中間テーブルに INSERT
  • 37.
    Reduce function ($key, $values){     $sum = 0;     foreach ($values as $count) {         $sum += $count;     }     return array('count' => $sum); }
  • 38.
    Reduce Key Value の配列 function ($key, $values) {     $sum = 0;     foreach ($values as $count) {         $sum += $count;     }     return array('count' => $sum); }
  • 39.
    Reduce Value を全て足す function ($key, $values) {     $sum = 0;     foreach ($values as $count) {         $sum += $count;     }     return array('count' => $sum); }
  • 40.
    Reduce function ($key, $values){     $sum = 0;     foreach ($values as $count) {         $sum += $count;     }     return array('count' => $sum); } 返り値の連想配列を レコードとして INSERT
  • 41.
    +----+--------------------+ 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 | +----+---------+-------+
  • 42.
    +----+--------------------+ 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 value には JSON で入れるので | 1 | | 5 | to 構造化データも使用可能 | 1 | | 6 | be | 1 | +----+---------+-------+
  • 43.
    +----+---------+-------+ | id | key | value | Shuffle +----+---------+-------+ | 1 | to | 2 | be | 1 | 1 | | | 3 | or | 1 | | 4 | not | 1 | | 5 | to | 1 | | 6 | be | 1 | +----+---------+-------+ ↓ キーで GROUP BY して ↓ SELECT ↓ 値は GROUP_CONCAT ↓ `key`, +---------+--------+ GROUP_CONCAT(`value`) | key | values | FROM +---------+--------+ `中間テーブル` | be | 1,1 | | not | 1 | GROUP BY | or | 1 | `key` | to | 1,1 | +---------+--------+
  • 44.
    +---------+--------+ | key | values | Reduce +---------+--------+ | be | 1,1 | | not | 1 | | or | 1 | | to | 1,1 | +---------+--------+ ↓ キーと値の配列を Reduce へ ↓ +----+---------+-------+ | id | key | count | +----+---------+-------+ | 1 | be | 2 | | 2 | not | 1 | | 3 | or | 1 | | 4 | to | 2 | +----+---------+-------+
  • 45.
    +---------+--------+ | key | values | Reduce +---------+--------+ | be | 1,1 | | not | 1 | | or | 1 | 実際にはデリミタとして改行を使用| to | 1,1 | +---------+--------+ 改行区切りの JSON になる ↓ キーと値の配列を Reduce へ ↓ +----+---------+-------+ | id | key | count | +----+---------+-------+ | 1 | be | 2 | | 2 | not | 1 | | 3 | or | 1 | | 4 | to | 2 | +----+---------+-------+
  • 46.
  • 47.
    モチベーション • プログラミングモデルとしての MapReduce を使いたい • MySQL を入出力にしたい •LL でサクッとやりたい PHP である必要はあまり無い
  • 48.
    今後の目標 •非同期 INSERT による並列化 •Hadoop へのシームレスな 移行方法の提供
  • 49.
    まとめ • ビッグデータは無くとも MapReduce は有効 • MySQL でできたら便利なはず •PHP で書けたら楽しいはず
  • 50.
    リンク • MyMR onGitHub https://github.com/yuya-takeyama/mymr • PHP と MySQL でカジュアルに MapReduce する http://blog.yuyat.jp/archives/1706 • もっとカジュアルに PHP と MySQL で MapReduce する http://blog.yuyat.jp/archives/1853
  • 51.