코딩과 알고리즘

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

Cray Fall 2020. 12. 13. 18:51

몽고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