2009-09-21 21:55:24 +02:00
|
|
|
// mr.cpp
|
|
|
|
|
|
|
|
/**
|
2009-09-24 16:51:27 +02:00
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License, version 3,
|
|
|
|
* as published by the Free Software Foundation.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
2009-09-21 21:55:24 +02:00
|
|
|
|
|
|
|
#include "../stdafx.h"
|
|
|
|
#include "db.h"
|
|
|
|
#include "instance.h"
|
|
|
|
#include "commands.h"
|
|
|
|
#include "../scripting/engine.h"
|
|
|
|
|
|
|
|
namespace mongo {
|
2009-09-24 16:51:27 +02:00
|
|
|
|
|
|
|
namespace mr {
|
|
|
|
|
2009-10-01 01:07:01 +02:00
|
|
|
typedef pair<BSONObj,BSONObj> Data;
|
|
|
|
typedef list< Data > InMemory;
|
|
|
|
|
|
|
|
class MyCmp {
|
|
|
|
public:
|
|
|
|
MyCmp(){}
|
|
|
|
bool operator()( const Data &l, const Data &r ) const {
|
|
|
|
return l.first.woCompare( r.first ) < 0;
|
|
|
|
}
|
|
|
|
};
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
BSONObj reduceValues( list<BSONObj>& values , Scope * s , ScriptingFunction reduce ){
|
|
|
|
uassert( "need values" , values.size() );
|
2009-09-21 21:55:24 +02:00
|
|
|
|
|
|
|
BSONObj key;
|
|
|
|
|
|
|
|
BSONObjBuilder reduceArgs;
|
2009-09-24 16:51:27 +02:00
|
|
|
|
2009-09-21 21:55:24 +02:00
|
|
|
BSONObjBuilder valueBuilder;
|
|
|
|
int n = 0;
|
|
|
|
for ( list<BSONObj>::iterator i=values.begin(); i!=values.end(); i++){
|
|
|
|
BSONObj o = *i;
|
|
|
|
if ( n == 0 ){
|
2009-10-02 21:43:30 +02:00
|
|
|
reduceArgs.append( o["_id"] );
|
2009-09-21 21:55:24 +02:00
|
|
|
BSONObjBuilder temp;
|
2009-10-02 21:43:30 +02:00
|
|
|
temp.append( o["_id"] );
|
2009-09-21 21:55:24 +02:00
|
|
|
key = temp.obj();
|
|
|
|
}
|
|
|
|
valueBuilder.appendAs( o["value"] , BSONObjBuilder::numStr( n++ ).c_str() );
|
|
|
|
}
|
2009-09-24 16:51:27 +02:00
|
|
|
|
2009-09-21 21:55:24 +02:00
|
|
|
reduceArgs.appendArray( "values" , valueBuilder.obj() );
|
|
|
|
BSONObj args = reduceArgs.obj();
|
2009-09-24 16:51:27 +02:00
|
|
|
|
2009-09-21 21:55:24 +02:00
|
|
|
s->invokeSafe( reduce , args );
|
|
|
|
if ( s->type( "return" ) == Array ){
|
|
|
|
uassert("reduce -> multiple not supported yet",0);
|
2009-09-24 16:51:27 +02:00
|
|
|
return BSONObj();
|
2009-09-21 21:55:24 +02:00
|
|
|
}
|
2009-09-24 16:51:27 +02:00
|
|
|
BSONObjBuilder b;
|
2009-10-02 21:43:30 +02:00
|
|
|
b.append( key["_id"] );
|
2009-09-24 16:51:27 +02:00
|
|
|
s->append( b , "value" , "return" );
|
|
|
|
return b.obj();
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
class MRTL {
|
|
|
|
public:
|
|
|
|
MRTL( DBDirectClient * db , string coll , Scope * s , ScriptingFunction reduce ) :
|
|
|
|
_db( db ) , _coll( coll ) , _scope( s ) , _reduce( reduce ) , _size(0){
|
2009-10-01 01:07:01 +02:00
|
|
|
_temp = new InMemory();
|
2009-09-24 16:51:27 +02:00
|
|
|
}
|
|
|
|
~MRTL(){
|
|
|
|
delete _temp;
|
|
|
|
}
|
|
|
|
|
|
|
|
void reduceInMemory(){
|
|
|
|
BSONObj prevKey;
|
|
|
|
list<BSONObj> all;
|
|
|
|
|
2009-10-01 01:07:01 +02:00
|
|
|
_temp->sort( MyCmp() );
|
|
|
|
|
|
|
|
InMemory * old = _temp;
|
|
|
|
InMemory * n = new InMemory();
|
2009-09-24 16:51:27 +02:00
|
|
|
_temp = n;
|
|
|
|
_size = 0;
|
|
|
|
|
2009-10-01 01:07:01 +02:00
|
|
|
for ( InMemory::iterator i=old->begin(); i!=old->end(); i++ ){
|
2009-09-24 16:51:27 +02:00
|
|
|
BSONObj key = i->first;
|
|
|
|
BSONObj value = i->second;
|
|
|
|
|
|
|
|
if ( key.woCompare( prevKey ) == 0 ){
|
|
|
|
all.push_back( value );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( all.size() == 1 ){
|
|
|
|
insert( prevKey , *(all.begin()) );
|
|
|
|
all.clear();
|
|
|
|
}
|
|
|
|
else if ( all.size() > 1 ){
|
|
|
|
BSONObj res = reduceValues( all , _scope , _reduce );
|
|
|
|
insert( prevKey , res );
|
|
|
|
all.clear();
|
|
|
|
}
|
|
|
|
prevKey = key.getOwned();
|
|
|
|
all.push_back( value );
|
|
|
|
}
|
|
|
|
|
|
|
|
if ( all.size() == 1 ){
|
|
|
|
insert( prevKey , *(all.begin()) );
|
|
|
|
}
|
|
|
|
else if ( all.size() > 1 ){
|
|
|
|
BSONObj res = reduceValues( all , _scope , _reduce );
|
|
|
|
insert( prevKey , res );
|
|
|
|
}
|
|
|
|
|
|
|
|
delete( old );
|
|
|
|
}
|
|
|
|
|
|
|
|
void dump(){
|
2009-10-01 01:07:01 +02:00
|
|
|
for ( InMemory::iterator i=_temp->begin(); i!=_temp->end(); i++ ){
|
2009-09-24 16:51:27 +02:00
|
|
|
BSONObj key = i->first;
|
|
|
|
BSONObj value = i->second;
|
|
|
|
BSONObjBuilder b;
|
|
|
|
b.appendElements( value );
|
|
|
|
|
|
|
|
BSONObj o = b.obj();
|
|
|
|
_db->insert( _coll , o );
|
|
|
|
}
|
|
|
|
_temp->clear();
|
|
|
|
_size = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
void insert( const BSONObj& key , const BSONObj& value ){
|
2009-10-01 01:07:01 +02:00
|
|
|
_temp->push_back( pair<BSONObj,BSONObj>( key , value ) );
|
2009-09-24 16:51:27 +02:00
|
|
|
_size += key.objsize() + value.objsize() + 32;
|
|
|
|
}
|
|
|
|
|
|
|
|
void checkSize(){
|
2009-10-01 01:07:01 +02:00
|
|
|
if ( _size < 1024 * 10 )
|
2009-09-24 16:51:27 +02:00
|
|
|
return;
|
|
|
|
|
|
|
|
long before = _size;
|
|
|
|
reduceInMemory();
|
|
|
|
log(1) << " mr: did reduceInMemory " << before << " -->> " << _size << endl;
|
|
|
|
|
|
|
|
if ( _size < 1024 * 15 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
dump();
|
|
|
|
log(1) << " mr: dumping to db" << endl;
|
2009-09-21 21:55:24 +02:00
|
|
|
}
|
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
private:
|
|
|
|
DBDirectClient * _db;
|
|
|
|
string _coll;
|
|
|
|
Scope * _scope;
|
|
|
|
ScriptingFunction _reduce;
|
|
|
|
|
2009-10-01 01:07:01 +02:00
|
|
|
InMemory * _temp;
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
long _size;
|
|
|
|
};
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
boost::thread_specific_ptr<MRTL> _tlmr;
|
|
|
|
|
|
|
|
BSONObj fast_emit( const BSONObj& args ){
|
|
|
|
BSONObj key,value;
|
|
|
|
|
|
|
|
BSONObjIterator i( args );
|
|
|
|
|
|
|
|
{
|
|
|
|
assert( i.more() );
|
|
|
|
BSONObjBuilder b;
|
2009-10-02 21:43:30 +02:00
|
|
|
b.appendAs( i.next() , "_id" );
|
2009-09-24 16:51:27 +02:00
|
|
|
key = b.obj();
|
|
|
|
}
|
|
|
|
|
|
|
|
{
|
|
|
|
assert( i.more() );
|
|
|
|
BSONObjBuilder b;
|
|
|
|
b.append( key.firstElement() );
|
|
|
|
b.appendAs( i.next() , "value" );
|
|
|
|
value = b.obj();
|
|
|
|
}
|
|
|
|
assert( ! i.more() );
|
|
|
|
_tlmr->insert( key , value );
|
|
|
|
return BSON( "x" << 1 );
|
2009-09-21 21:55:24 +02:00
|
|
|
}
|
2009-09-24 16:51:27 +02:00
|
|
|
|
|
|
|
class MapReduceCommand : public Command {
|
|
|
|
public:
|
|
|
|
MapReduceCommand() : Command("mapreduce"){}
|
|
|
|
virtual bool slaveOk() { return true; }
|
|
|
|
|
|
|
|
virtual void help( stringstream &help ) const {
|
|
|
|
help << "see http://www.mongodb.org/display/DOCS/MapReduce";
|
|
|
|
}
|
|
|
|
|
|
|
|
string tempCollectionName( string coll ){
|
|
|
|
static int inc = 1;
|
|
|
|
stringstream ss;
|
|
|
|
ss << database->name << ".mr." << coll << "." << time(0) << "." << inc++;
|
|
|
|
return ss.str();
|
|
|
|
}
|
|
|
|
|
|
|
|
void doReduce( const string& resultColl , list<BSONObj>& values , Scope * s , ScriptingFunction reduce ){
|
|
|
|
if ( values.size() == 0 )
|
|
|
|
return;
|
|
|
|
|
|
|
|
BSONObj res = reduceValues( values , s , reduce );
|
|
|
|
db.insert( resultColl , res );
|
|
|
|
}
|
|
|
|
|
|
|
|
void finalReduce( const string& resultColl , list<BSONObj>& values , Scope * s , ScriptingFunction reduce ){
|
|
|
|
if ( values.size() == 0 )
|
|
|
|
return;
|
|
|
|
|
2009-10-02 21:43:30 +02:00
|
|
|
BSONObj key = values.begin()->extractFields( BSON( "_id" << 1 ) );
|
2009-09-24 16:51:27 +02:00
|
|
|
|
|
|
|
if ( values.size() == 1 ){
|
|
|
|
assert( db.count( resultColl , key ) == 1 );
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
|
|
|
db.remove( resultColl , key );
|
|
|
|
doReduce( resultColl , values , s , reduce );
|
|
|
|
}
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
bool run(const char *dbname, BSONObj& cmdObj, string& errmsg, BSONObjBuilder& result, bool fromRepl ){
|
|
|
|
Timer t;
|
2009-09-22 05:47:09 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
string ns = database->name + '.' + cmdObj.firstElement().valuestr();
|
|
|
|
log(1) << "mr ns: " << ns << endl;
|
2009-10-01 23:15:44 +02:00
|
|
|
|
|
|
|
if ( ! db.exists( ns ) ){
|
|
|
|
errmsg = "ns doesn't exist";
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
auto_ptr<Scope> s = globalScriptEngine->getPooledScope( ns );
|
|
|
|
s->localConnect( database->name.c_str() );
|
2009-09-30 23:11:56 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
string resultColl = tempCollectionName( cmdObj.firstElement().valuestr() );
|
2009-09-30 23:11:56 +02:00
|
|
|
string finalOutput = resultColl;
|
2009-09-30 05:37:13 +02:00
|
|
|
if ( cmdObj["out"].type() == String )
|
2009-09-30 23:11:56 +02:00
|
|
|
finalOutput = database->name + "." + cmdObj["out"].valuestr();
|
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
string resultCollShort = resultColl.substr( database->name.size() + 1 );
|
2009-09-30 23:11:56 +02:00
|
|
|
string finalOutputShort = finalOutput.substr( database->name.size() + 1 );
|
2009-09-24 16:51:27 +02:00
|
|
|
log(1) << "\t resultColl: " << resultColl << " short: " << resultCollShort << endl;
|
|
|
|
db.dropCollection( resultColl );
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
int num = 0;
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
try {
|
2009-09-30 23:11:56 +02:00
|
|
|
dbtemprelease temprlease;
|
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
s->execSetup( (string)"tempcoll = db[\"" + resultCollShort + "\"];" , "tempcoll1" );
|
|
|
|
if ( s->type( "emit" ) == 6 ){
|
|
|
|
s->injectNative( "emit" , fast_emit );
|
|
|
|
}
|
|
|
|
|
|
|
|
ScriptingFunction mapFunction = s->createFunction( cmdObj["map"].ascode().c_str() );
|
|
|
|
ScriptingFunction reduceFunction = s->createFunction( cmdObj["reduce"].ascode().c_str() );
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
MRTL * mrtl = new MRTL( &db , resultColl , s.get() , reduceFunction );
|
|
|
|
_tlmr.reset( mrtl );
|
|
|
|
|
|
|
|
BSONObj q;
|
2009-09-29 18:59:16 +02:00
|
|
|
if ( cmdObj["query"].type() == Object )
|
|
|
|
q = cmdObj["query"].embeddedObjectUserCheck();
|
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
auto_ptr<DBClientCursor> cursor = db.query( ns , q );
|
|
|
|
while ( cursor->more() ){
|
|
|
|
BSONObj o = cursor->next();
|
|
|
|
s->setThis( &o );
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
if ( s->invoke( mapFunction , BSONObj() , 0 , true ) )
|
|
|
|
throw UserException( (string)"map invoke failed: " + s->getError() );
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
num++;
|
|
|
|
if ( num % 100 == 0 ){
|
|
|
|
mrtl->checkSize();
|
|
|
|
}
|
2009-09-22 05:47:09 +02:00
|
|
|
}
|
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
|
|
|
|
result.append( "timeMillis.emit" , t.millis() );
|
2009-09-22 05:47:09 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
// final reduce
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
mrtl->reduceInMemory();
|
|
|
|
mrtl->dump();
|
|
|
|
|
|
|
|
BSONObj prev;
|
|
|
|
list<BSONObj> all;
|
2009-10-02 21:43:30 +02:00
|
|
|
BSONObj sortKey = BSON( "_id" << 1 );
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-10-02 21:43:30 +02:00
|
|
|
cursor = db.query( resultColl, Query().sort( sortKey ) );
|
2009-09-24 16:51:27 +02:00
|
|
|
while ( cursor->more() ){
|
|
|
|
BSONObj o = cursor->next().getOwned();
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
if ( o.woSortOrder( prev , sortKey ) == 0 ){
|
|
|
|
all.push_back( o );
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
finalReduce( resultColl , all , s.get() , reduceFunction );
|
|
|
|
|
|
|
|
all.clear();
|
|
|
|
prev = o;
|
2009-09-21 21:55:24 +02:00
|
|
|
all.push_back( o );
|
|
|
|
}
|
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
finalReduce( resultColl , all , s.get() , reduceFunction );
|
|
|
|
}
|
|
|
|
catch ( ... ){
|
|
|
|
log() << "mr failed, removing collection" << endl;
|
|
|
|
db.dropCollection( resultColl );
|
|
|
|
throw;
|
2009-09-21 21:55:24 +02:00
|
|
|
}
|
2009-09-30 23:11:56 +02:00
|
|
|
|
|
|
|
|
|
|
|
if ( finalOutput != resultColl ){
|
|
|
|
// need to do this with the full dblock, that's why its after the try/catch
|
|
|
|
db.dropCollection( finalOutput );
|
|
|
|
BSONObj info;
|
|
|
|
uassert( "rename failed" , db.runCommand( "admin" , BSON( "renameCollection" << resultColl << "to" << finalOutput ) , info ) );
|
|
|
|
}
|
|
|
|
|
|
|
|
result.append( "result" , finalOutputShort );
|
2009-09-24 16:51:27 +02:00
|
|
|
result.append( "numObjects" , num );
|
|
|
|
result.append( "timeMillis" , t.millis() );
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-10-01 23:15:44 +02:00
|
|
|
return true;
|
2009-09-24 16:51:27 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
private:
|
|
|
|
DBDirectClient db;
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
} mapReduceCommand;
|
2009-09-21 21:55:24 +02:00
|
|
|
|
2009-09-24 16:51:27 +02:00
|
|
|
}
|
2009-09-21 21:55:24 +02:00
|
|
|
|
|
|
|
}
|
|
|
|
|