Aggregation

count, max, min 과 같이 컬럼들을 같은 key 값으로 묶은 그룹에 대한 값을 집계하는 것을 의미한다.

phones collection test data 생성

Enterprise test> populatePhones=function(area,start,stop){
  for(var i= start;i<stop;i++){
    var country=1+((Math.random() *8) <<0);
    var num=(country *1e10) + (area*1e7)+i;
    db.phones.insertOne(
      {_id:num,components:
        {
          country:country,
          area:area,
          prefix:(i*1e4)<<0,
          number:i
        },
        display:"+"+country+" "+area+"-"+i
      }
    );
  }
}

populatePhones(800,5550000,5650000)
populatePhones(855,5550000,5650000)

MongoDB Aggregation

RDBMS와 달리 집계함수를 제한적으로 제공하기 때문에, 필요한 집계 기능에 대해서는 MapReduce을 통해 구현할 수 있다.

Count

collection 내 document의 갯수를 조회하는 기능

Enterprise test> db.actors.find()
[
  {
    _id: ObjectId("63b3ac0bc7f7ae56b4b2a8af"),
    actor: 'Richard Gere',
    movies: [ 'Pretty Woman', 'Runaway Bride', 'Chicago' ]
  },
  {
    _id: ObjectId("63b3ac0bc7f7ae56b4b2a8b0"),
    actor: 'Julia Roberts',
    movies: [ 'Pretty Woman', 'Runaway Bride', 'Erin Brokovich' ]
  }
]

//특정 컬렉션에 있는 문서의 갯수
Enterprise test> db.actors.countDocuments()
2

//특정 조건에 부합하는 문서의 갯수
Enterprise test> db.actors.find({actor: "Julia Roberts"}).count()
1

distinct

지정된 키에 대한 중복 제거

//person collection에 대해서 age field에 대한 중복 제거를 수행한다.
db.runCommand({"distinct": "person", "key":"age"})

//phone collection에 대해 components.number field에 대해 중복을 제거하는데, 이때 components.number의 값이 5550005보다 작은 값을 가지는 document들을 제거한다.
db.phones.distinct("components.number", {"components.number":{$lt :5550005}})

group

지정된 키에 대한 그룹핑을 수행한다. RDBMS의 group by와 유사한 기능을 수행하는데, 속도가 느리므로 제한적으로 사용해야하며, 샤드 클러스터 환경에서는 동작하지 않는다.

db.phones.group(
  {
    //count 값을 0으로 초기화
    initial:{count:0},
    //aggregation 수행시 result의 count 값을 1씩 누적함
    reduce:function(phone,output){output.count++;},
    //grouping 과정에서 WHERE 조건
    cond:{"components.number":{"gt":5599999}},
    //grouping 할 key값을 지정
    key:{"components.area":true}
    }
);

MongoDB의 group 함수는 3.4 버전 이후로 deprecated 되었기 때문에 아래에 나오는 aggregation을 활용하여 group을 대신한다.

db.phones.aggregate(
  {
    "$match":{"components.number":{"$gt":5599999}}
  },
  {
    "$group":{"_id":"$components.area","count":{"$sum":1}}
  },
  {
    "$project":{"components.area":1,"count":1}
  },
  {
    "$sort":{"count":-1}
  },
  {
    "$limit":5
  }
);

[ { _id: 800, count: 50000 }, { _id: 855, count: 50000 } ]

Aggregation Framework

MongoDB 2.1 버전 이후 부터는 집계 프레임워크를 제공하는데, 내부적으로 MapReduce 기반을 활용하여 빠른 성능 기반의 집계 기능을 제공한다. 여과, 선출, 묶음, 등 다양한 파이프라인 기반의 연산을 수행하는 것이 가능하다.

따라서 대용량 기반의 데이터를 처리해야하는 경우 기본적인 내장 집계함수 대신에 집계 프레임워크 또는 Map/Reduce를 직접적으로 활용하여 빠른 연산 처리를 수행한다.

pipelines

Unix의 pipe과 같은 역할로 각 document들을 stream으로 만들어서 stream 기반의 연산을 처리한다. map-reducing과 같은 원리이다.

$project, $match, $sort, $group …

expressions

input document을 수행한 계산값을 바탕으로 output document을 생성하며, $addToSet, $first, $last, 등의 명령어를 지원한다.

input -> ouput으로 묶어내는 연산

Example

잡지 기사 컬렉션에서 가장 많은 기사를 쓴 기자를 찾는다? 라는 과제가 주어졌을 때 아래의 단계를 생각해볼 수 있다.

  1. 각 기사에 대한 기자를 선출한다.
  2. 이름으로 기자를 묶고 그룹 내에서 나타난 횟수를 센다.
  3. 나타난 횟수를 기준으로 내림차순 정렬한다.
  4. 처음 다섯개의 결과를 반환한다.
db.articles.aggregate(
  //SELECT 절:각 문서에 대한 필드를 선출(key 값 지정) 
  {
    "$project":{"author":1}
  },
  //GROUP BY 절: 각각의 문서에 대해 key값으로 묶고, key 값이 나타나는 횟수만큼 count를 증가시킨다.
  {
    "$group":{"_id":"$author","count":{"$sum":1}}
  },
  //ORDER BY 절: 집계 결과를 count를 기준으로 내림차순 정렬한다.
  {
    "$sort":{"count":-1}
  },
  //ROWNUM,LIMIT 절: 처음 다섯개의 document를 출력한다.
  {
    "$limit":5
  }
);

eval

로컬 환경에서 MongoDB에 대한 연산을 처리하게 되면 원격 서버에서 있는 데이터를 로컬 환경으로 모두 복사 한 후, 이를 처리한 다음에 다시 원격지에 저장하는 현상이 일어난다. 이를 방지하기 위해 eval 명령어를 활용하면 로컬 환경이 아닌 원격환경에서 바로 명령어를 실행하게 되어 성능 낭비를 방지할 수 있다.

db.eval(update_area);
db.eval("distinctDigits(db.phones.findOne({"components.number":5551213"}))");

References

댓글남기기