リレーショナルデータベースには長い歴史がありますが、MongoDBとMapReduceは初めてなので、何か間違ったことをしているに違いありません。質問にすぐジャンプします。長くてすみません。
MySQLに、毎日のメンバープロファイルビューの数を追跡するデータベーステーブルがあります。テスト用に10,000,000行あります。
CREATE TABLE `profile_views` (
`id` int(10) unsigned NOT NULL auto_increment,
`username` varchar(20) NOT NULL,
`day` date NOT NULL,
`views` int(10) unsigned default '0',
PRIMARY KEY (`id`),
UNIQUE KEY `username` (`username`,`day`),
KEY `day` (`day`)
) ENGINE=InnoDB;
一般的なデータは次のようになります。
+--------+----------+------------+------+
| id | username | day | hits |
+--------+----------+------------+------+
| 650001 | Joe | 2010-07-10 | 1 |
| 650002 | Jane | 2010-07-10 | 2 |
| 650003 | Jack | 2010-07-10 | 3 |
| 650004 | Jerry | 2010-07-10 | 4 |
+--------+----------+------------+------+
このクエリを使用して、2010年7月16日以降に最も閲覧された上位5つのプロファイルを取得します。
SELECT username, SUM(hits)
FROM profile_views
WHERE day > '2010-07-16'
GROUP BY username
ORDER BY hits DESC
LIMIT 5\G
このクエリは1分以内に完了します。悪くない!
MongoDBの世界に移ります。 3台のサーバーを使用してシャード環境をセットアップしました。サーバーM、S1、およびS2。次のコマンドを使用してリグを設定しました(注:IPアドレスを隠しました)。
S1 => 127.20.90.1
./mongod --fork --shardsvr --port 10000 --dbpath=/data/db --logpath=/data/log
S2 => 127.20.90.7
./mongod --fork --shardsvr --port 10000 --dbpath=/data/db --logpath=/data/log
M => 127.20.4.1
./mongod --fork --configsvr --dbpath=/data/db --logpath=/data/log
./mongos --fork --configdb 127.20.4.1 --chunkSize 1 --logpath=/data/slog
それらが起動して実行されたら、サーバーMにホップしてmongoを起動しました。次のコマンドを発行しました。
use admin
db.runCommand( { addshard : "127.20.90.1:10000", name: "M1" } );
db.runCommand( { addshard : "127.20.90.7:10000", name: "M2" } );
db.runCommand( { enablesharding : "profiles" } );
db.runCommand( { shardcollection : "profiles.views", key : {day : 1} } );
use profiles
db.views.ensureIndex({ hits: -1 });
次に、MySQLから同じ10,000,000行をインポートし、次のようなドキュメントを取得しました。
{
"_id" : ObjectId("4cb8fc285582125055295600"),
"username" : "Joe",
"day" : "Fri May 21 2010 00:00:00 GMT-0400 (EDT)",
"hits" : 16
}
ここに本物の肉とジャガイモが来ます...私の地図と機能を減らしてください。シェルのサーバーMに戻り、クエリを設定して次のように実行します。
use profiles;
var start = new Date(2010, 7, 16);
var map = function() {
emit(this.username, this.hits);
}
var reduce = function(key, values) {
var sum = 0;
for(var i in values) sum += values[i];
return sum;
}
res = db.views.mapReduce(
map,
reduce,
{
query : { day: { $gt: start }}
}
);
そして、ここで私は問題に遭遇しました。 このクエリは完了するまでに15分以上かかりました! MySQLクエリは1分もかかりませんでした。出力は次のとおりです。
{
"result" : "tmp.mr.mapreduce_1287207199_6",
"shardCounts" : {
"127.20.90.7:10000" : {
"input" : 4917653,
"emit" : 4917653,
"output" : 1105648
},
"127.20.90.1:10000" : {
"input" : 5082347,
"emit" : 5082347,
"output" : 1150547
}
},
"counts" : {
"emit" : NumberLong(10000000),
"input" : NumberLong(10000000),
"output" : NumberLong(2256195)
},
"ok" : 1,
"timeMillis" : 811207,
"timing" : {
"shards" : 651467,
"final" : 159740
},
}
実行に時間がかかるだけでなく、結果も正しくないようです。
db[res.result].find().sort({ hits: -1 }).limit(5);
{ "_id" : "Joe", "value" : 128 }
{ "_id" : "Jane", "value" : 2 }
{ "_id" : "Jerry", "value" : 2 }
{ "_id" : "Jack", "value" : 2 }
{ "_id" : "Jessy", "value" : 3 }
私はそれらの値の数値がはるかに高いはずであることを知っています。
MapReduceパラダイム全体についての私の理解は、このクエリを実行するタスクをすべてのシャードメンバーに分割する必要があることです。これにより、パフォーマンスが向上します。インポート後、Mongoが2つのシャードサーバー間でドキュメントの配布を完了するまで待ちました。このクエリを開始したとき、それぞれにほぼ正確に5,000,000のドキュメントがありました。
だから私は何か間違ったことをしているに違いない。誰かが私に何か指針を与えることはできますか?
編集:IRCの誰かが日のフィールドにインデックスを追加することについて言及しましたが、私の知る限りでは、MongoDBによって自動的に行われました。
o'ReillyのMongoDB Definitive Guideからの抜粋:
MapReduceの使用には料金がかかります。グループは特に高速ではありませんが、MapReduceは低速であり、「リアルタイム」での使用は想定されていません。 MapReduceをバックグラウンドジョブとして実行すると、結果のコレクションが作成され、そのコレクションをリアルタイムでクエリできます。
options for map/reduce:
"keeptemp" : boolean
If the temporary result collection should be saved when the connection is closed.
"output" : string
Name for the output collection. Setting this option implies keeptemp : true.
遅すぎるかもしれませんが...
まず、コレクションをクエリして、インデックスなしでMapReduceに入力します。 「日」にインデックスを作成する必要があります。
MongoDB MapReduceは単一のサーバーでシングルスレッド化されますが、シャードで並列化されます。 mongoシャード内のデータは、シャーディングキーでソートされた隣接するチャンクにまとめて保持されます。
シャーディングキーが「日」であり、それをクエリしているので、おそらく3つのサーバーのうちの1つだけを使用しています。シャーディングキーは、データを分散するためにのみ使用されます。 Map Reduceは、各シャードの「day」インデックスを使用してクエリを実行し、非常に高速になります。
データを分散するために、日キーの前に何かを追加します。ユーザー名は良い選択です。
そうすれば、Map Reduceがすべてのサーバーで起動され、うまくいけば時間を3削減できます。
このようなもの:
use admin
db.runCommand( { addshard : "127.20.90.1:10000", name: "M1" } );
db.runCommand( { addshard : "127.20.90.7:10000", name: "M2" } );
db.runCommand( { enablesharding : "profiles" } );
db.runCommand( { shardcollection : "profiles.views", key : {username : 1,day: 1} } );
use profiles
db.views.ensureIndex({ hits: -1 });
db.views.ensureIndex({ day: -1 });
これらの追加により、MySQLの速度をさらに高速に合わせることができると思います。
また、リアルタイムで使用しないでください。データを「分単位」で正確にする必要がない場合は、マップ削減タスクを時々実行し、結果のコレクションを使用します。
あなたは何も悪いことをしていません。 (コメントですでに気付いたように、間違った値で並べ替える以外に。)
MongoDBのmap/reduceパフォーマンスは、それほど素晴らしいものではありません。これは既知の問題です。たとえば http://jira.mongodb.org/browse/SERVER-1197 を参照してください。単純なアプローチはM/Rより350倍高速です。
ただし、1つの利点は、out
呼び出しのmapReduce
引数を使用して永続的な出力コレクション名を指定できることです。 M/Rが完了すると、一時的なコレクションの名前がアトミックに永続的な名前に変更されます。これにより、統計の更新をスケジュールし、M/R出力コレクションをリアルタイムでクエリできます。
Mongodbのhadoopコネクタを使用してみましたか?
ここのこのリンクを見てください: http://docs.mongodb.org/ecosystem/tutorial/getting-started-with-hadoop/
3つのシャードしか使用していないので、この方法でケースが改善されるかどうかはわかりません。