본문 바로가기
코딩과 알고리즘

몽고DB(mongodb) PHP 에서의 맵리듀스(mapReduce) 사용하기

몽고DB PHP 에서의 맵리듀스(mapReduce) 사용하기

지난 시간에 이어 이번에는 콘솔창이 아닌 PHP 에서의 맵리듀스를 사용하는 방법을 알아보겠습니다.
우선 지난번의 샘플 데이터 입력과 share.php 소스가 먼저 생성된 단계에서 시작하겠습니다.

itadventure.tistory.com/384

 

몽고DB 콘솔에서 맵리듀스(MapReduce) 기술

지난 챕터에서는 몽고DB의 가장 기본 중의 기본, CRUD 에 대해 다루어 보았는데요. itadventure.tistory.com/383 PHP + 몽고DB 크루드! ( CRUD ) '크루드'하면 웬지 "딴-딴-딴딴 따라다~' 배경음악이 등장하는 미.

itadventure.tistory.com

PHP 컴포저 라이브러리를 사용하는 예제는 어느정도 있지만, 컴포저 없이 사용하는 예제는 없어서
관련 부분 사용법에 대해 디깅(diging:발굴)을 좀 했지요 :)

PHP mongo.so 에서 몽고DB 관련 통계를 산출하는 명령은 대부분은 executeCommand 명령입니다.
보통 아래와 같이 사용하는데요.

$cursor = $manager->executeCommand('DB명', $command);
$cursorArr = $cursor->toArray();

mapReduce 든, 중복을 제거하는 distinct 든, 그룹을 지어주는 group 든 다양한 통계 기능이 이 함수를 거쳐서 사용됩니다.

$command 파라미터에는 MongoDB\Driver\Command 클래스로 정의된 오브젝트가 입력이 되는데요..
맵 리듀스를 실행하고자할 경우 먼저 이렇게 커맨드를 만들어 준 다음, 앞의 코드를 실행해주면 되는 것이지요.

$command = new MongoDB\Driver\Command([
    'mapReduce' => '맵리듀스를 수행할 컬렉션',
    'map'=>'맵 자바스크립트 함수',
    'reduce'=>'리듀스 자바스크립트 함수',
    'out' => '맵리듀스 결과를 저장할 컬렉션'
]);

여기서 'map' 과 'reduce' 자바스크립트 함수는 직접 함수전체를 문자열로 대입해도 되고,
변수로 정의해서 변수를 대입해도 되는데요.

다음과 같이 코드를 정의하면, 지난번과 동일한 결과를 만들어냅니다.

<?php
// 공유파일.
include_once(dirname(__FILE__)."/share.php");

$map="
function(){
  for(var i=0;i<this.classInfo.length;++i)
  {  
    var key=this.code;
    var value={ 'classCount':1, 'totalStudent': this.classInfo[i].nowstudent };
    emit(key, value);
  }
};";

$reduce="
function(key, valueArray){
  var result={
    'classCount':0,
    'totalStudent':0
  };
  for(var i=0;i<valueArray.length;++i){
    result.classCount+=valueArray[i].classCount;
    result.totalStudent+=valueArray[i].totalStudent;
  }
  return result;  
};
";

$command = new MongoDB\Driver\Command([
    'mapReduce' => 'department',
    'map'=>$map,
    'reduce'=>$reduce,
    'out' => 'department_out'
]);
$manager->executeCommand('crayUniversity', $command);

몽고콘솔창과 좀 다른 점은 $map 에 입력될 맵 함수와 $reduce 에 입력될 리듀스 함수를 정의할 때,
앞에 "var map =" 또는 "var reduct = " 과 같은 부분을 정의할 필요가 없다는 것입니다.

그냥 이 변수를 바로 커맨드에 넣어주면 되니, 이름을 짓기 위해 고민할 필요는 없는 것이지요.

그리고 정의된 $map, $reduce 함수를 파라미터로 실행할 맵 리듀스 규칙을 아래와 같이 정의해 준 다음에,

$command = new MongoDB\Driver\Command([
    'mapReduce' => 'department',
    'map'=>$map,
    'reduce'=>$reduce,
    'out' => 'department_out'
]);

crayUniversity 데이터베이스에 해당 규칙을 커맨드로 실행해주면 맵리듀스가 실행됩니다.

$manager->executeCommand('crayUniversity', $command);

그리고 규칙에 out 파라미터에 정의한 대로 department_out 컬렉션에 결과가 저장되는 것이지요.
이 컬렉션을 읽어서 그 결과를 화면에 보여주면 되는 것인데요.

여기서 PHP를 사용하는 이점을 하나 더 살려봅시다.
소스 코드를 아래와 같이 변경해 봅시다.

<?php
// PHP 에서 몽고DB MAPREDUCE 사용 샘플

// 공유파일.
include_once(dirname(__FILE__)."/share.php");

echo "PHP MAPREDUCE 샘플입니다.<br/>";

$map="
function(){
  for(var i=0;i<this.classInfo.length;++i)
  {  
    var key=this.code;
    var value={ 'classCount':1, 'totalStudent': this.classInfo[i].nowstudent };
    emit(key, value);
  }
};";

$reduce="
function(key, valueArray){
  var result={
    'classCount':0,
    'totalStudent':0
  };
  for(var i=0;i<valueArray.length;++i){
    result.classCount+=valueArray[i].classCount;
    result.totalStudent+=valueArray[i].totalStudent;
  }
  return result;  
};
";

$command = new MongoDB\Driver\Command([
    'mapReduce' => 'department',
    'map'=>$map,
    'reduce'=>$reduce,
    'out' => ['inline' => 1]
]);
$cursor = $manager->executeCommand('crayUniversity', $command);
$cursorArr = $cursor->toArray()[0];
dump($cursorArr);

out 파라미터가 ['inline'=>1] 로 변경되었는데요.
이 파라미터는 컬렉션을 생성하지 않고 실행결과를 바로 리턴해줍니다.
그리고 여기서는 그 결과는 $cursor 변수에 보관됩니다.
이렇게 할 경우 불필요한 컬렉션이 생성되지 않고 결과를 바로 PHP에서 받아 처리할 수 있어 한결 수월합니다.
당연히 속도도 더 빠르겠지요? :)

$cursor = $manager->executeCommand('crayUniversity', $command);

이어서 커서의 결과를 PHP에서 사용하기 용이한 배열로 변환해 줍니다.

$cursorArr = $cursor->toArray()[0];

$cursorArr 변수를 dump로 확인해본 결과는 아래와 같습니다.

object(stdClass)#12 (2) {
  results=>{
    [0]=>object(stdClass)#7 (2) {
      _id=>"10010"
      value=>object(stdClass)#6 (2) {
        classCount=>float(3)
        totalStudent=>float(68)
      }
    }
    [1]=>object(stdClass)#9 (2) {
      _id=>"10012"
      value=>object(stdClass)#8 (2) {
        classCount=>float(3)
        totalStudent=>float(51)
      }
    }
    [2]=>object(stdClass)#11 (2) {
      _id=>"10011"
      value=>object(stdClass)#10 (2) {
        classCount=>float(3)
        totalStudent=>float(68)
      }
    }
  }
  ok=>float(1)
}

결과는 트리구조로 구성되어 있는데 아래와 같이 해석하시면 됩니다.
최상위 오브젝트 object(stdClass) 하위에 results, ok 라는 항목이 포함습니다.

object(stdClass)#12 (2) {
  results=>{
    :
  }
  ok=>float(1)
}

오브젝트이기 때문에 각각의 요소에 접근하시려면 다음과 같이 '->' 기호를 이용하여야 합니다.
마치 C++ 에서의 포인터 같기도 하지요.

$cursorArr->results, $cursorArr->ok

results 는 여러개의 배열 요소로 구성되어 있는데요.

  results=>{
    [0]=>object(stdClass)#7 (2) ...
    [1]=>object(stdClass)#9 (2) ...
    [2]=>object(stdClass)#11 (2) ..
  }

이 경우 PHP 에서는 [첨자]로 접근하여야 합니다.

아래와 같이 접근해도 되고,

$cursorArr->results[0]
$cursorArr->results[1]
$cursorArr->results[2]

아니면 foreach 문으로 반복문마다 각각의 요소를 하나의 변수에 받아서 처리할 수도 있지요.
여기서는 후자를 사용해보겠습니다.

foreach($cursorArr->results as $one)
{
    :
}

각 배열요소는 PHP 오브젝트인데 아래와 같이 구성됩니다.

    [0]=>object(stdClass)#7 (2) {
      _id=>"10010"
      value=>object(stdClass)#6 (2) {
        classCount=>float(3)
        totalStudent=>float(68)
      }
    }

먼저 _id 라는 키값과 value라는 값이 속성으로 제공되고
value 는 또 다시 classCount 라는 학급수합계값과 totalStudent 라는 학생수 합계값이 제공됩니다.

value 또한 오브젝트이기 때문에 $one 변수기준으로 아래와 같이 심도있게 변수 접근이 가능합니다.

$one->_id
$one->value->classCount
$one->value->totalStudent

만일 $one 이 아닌 $cursorArr 기준이라면 아래와 같습니다.

$cursorArr->results[순번]->_id
$cursorArr->results[순번]->value->classCount
$cursorArr->results[순번]->value->totalStudent

PHP 에서는 이렇게 dump()를 찍어보면서 결과를 찾는게 편리한데요.
실제 자료의 JSON 트리 구조에 대한 이해를 고심할 일이 줄어들기 때문입니다.

최종소스는 아래와 같습니다. 적당한 파일명으로 share.php 와 동일한 폴더에 저장 후 실행해 주세요,

<?php
// PHP 에서 몽고DB MAPREDUCE 사용 샘플

// 공유파일.
include_once(dirname(__FILE__)."/share.php");

echo "PHP MAPREDUCE 샘플입니다.<br/>";

$map="
function(){
  for(var i=0;i<this.classInfo.length;++i)
  {  
    var key=this.code;
    var value={ 'classCount':1, 'totalStudent': this.classInfo[i].nowstudent };
    emit(key, value);
  }
};";

$reduce="
function(key, valueArray){
  var result={
    'classCount':0,
    'totalStudent':0
  };
  for(var i=0;i<valueArray.length;++i){
    result.classCount+=valueArray[i].classCount;
    result.totalStudent+=valueArray[i].totalStudent;
  }
  return result;  
};
";

$command = new MongoDB\Driver\Command([
    'mapReduce' => 'department',
		'map'=>$map,
		'reduce'=>$reduce,
    'out' => ['inline' => 1]
]);
$cursor = $manager->executeCommand('crayUniversity', $command);
$cursorArr = $cursor->toArray()[0];

foreach($cursorArr->results as $one)
{
	echo "학과코드 : ".$one->_id.", ";
	echo "학급수 : ".$one->value->classCount.", ";
	echo "총학생수 : ".$one->value->totalStudent."<br/>";
}

출력결과

PHP MAPREDUCE 샘플입니다.
학과코드 : 10010, 학급수 : 3, 총학생수 : 68
학과코드 : 10012, 학급수 : 3, 총학생수 : 51
학과코드 : 10011, 학급수 : 3, 총학생수 : 68