mongoDB 특징
- SELECT를 수행할 때는 MySQL보다 느리지만, INSERT나 DELETE 그리고 UPDATE를 수행할 때는 훨씬 빠르다.
mongoDB 사용사례
1. 모바일 게임
- 개발 효율 향상: 모바일 게임은 단기간에 개발해야 하는데, 게임은 특성상 변경이 자주 발생한다. 따라서, 스키마레이스인 mongoDB는 데이터 구조 변경에 유연하게 대응할 수 있어 효율이 좋다.
- 신기능, 변경 릴리스 대응: 모바일 게임은 주 3,4회 릴리즈 하는 경우가 대다수인데, 컬럼 추가나 인덱스 추가를 온라인 상태에서 할 수 없어서 점검이 필요하게 된다. 이때 mongoDB를 사용하면 점검하지 않고 추가할 수 있다.
- 유연한 쿼리와 인덱스: 계층 구조화한 문서 내부에서도 인덱스를 확장할 수 있고, 유연한 쿼리 검색을 할 수 있다.
- 초기 비용이 작고, 확장성을 보장: ReplicaSets(비동기 레플리케이션)과 Sharding(자동 데이터 분산)을 제공한다.
2. 카카오 택시
- 공간 인덱스를 활용해, 사용자 위치 기반 검색을 효율적으로 할 수 있게 함
- 전국의 택시와 사용자 정보를 매칭시키기 위해 복합인덱스(일반 인덱스 + 공간 인덱스)를 사용해 빠른 쿼리가 가능하도록 함.
집계
- 집계란, 이미 저장되어 있는 정보에서 다른 정보를 합해 출력하거나, 그룹화를 통해 다른형태로 정보를 보여주는 것을 의미한다.
도큐먼트를 '집계'하는 방법은 크게 3가지가 있다.
- 데이터베이스의 모든 정보를 불러와 애플리케이션 단계에서 집계하는 방법
- MongoDB의 맵-리듀스 기능을 이용하는 방법
- MongoDB의 집계 파이프라인 기능을 이용하는 방법
이를 정리한 표는 아래와 같다.
Application | Map-Reduce | Pipeline | |
자유도 | 좋다 | 좋다 | 나쁘다 |
처리 속도 | 가장 나쁘다 | 보통 | 가장 좋다 |
램 사용량 | 매우 높다 | 높다 | 낮다 |
처리 위치 | app 내부 | 자바스크립트 엔진 | MongoDB 내부 |
- 대부분의 상황에서는 파이프라인을 사용하는 것이 좋고, 파이프라인에서 처리할 수 없는 처리가 있다면 맵-리듀스 방식을 고려해보고 그 다음으로 어플리케이션 단의 처리를 고려해야 한다.
왜 Pipeline에서 성능이 가장 좋은건데?
'맛있는 MongoDB'라는 교재에서 이런 비유를 든다.
부산에서 수입한 오렌지를 이용해 오렌지 주스를 만들어 서울에서 판다고 가정해보자.
오렌지를 부산에서 서울까지 운반한 후에 오렌지 주스를 만드는 것이 좋을까, 아니면 오렌지 주스를 부산에서 만들고 완성된 제품을 서울로 운반하는 것이 좋을까?
당연히, 운반하는 무게와 부피가 줄어들기 때문에 후자가 더 좋은 선택이다.
그러니까, app에서 처리하는 건 전자고 pipeline에서 처리하는 건 후자라는 뜻이다.
집계 명령은 수많은 데이터를 처리해 작은 양의 정보를 애플리케이션에 전달하는 특징이 있어, 정보를 최대한 작게 만든 후에 애플리케이션으로 작아진 정보를 전송하는 것이 더 효율적이라는 것이다.
물론, 항상 Pipeline에서 처리하는 게 좋은건 아니다. 부산에서 만드는 것보다 서울에서 만드는 게 더 효율적인 경우도 존재하니 말이다.
Map-Reduce란?
맵-리듀스는 도큐먼트를 그룹으로 묶고 난 후(Mapping), 묶인 데이터에 대해서 처리(Reducing)하는 것을 기반으로 한다. 그래서 <map>과 <reduce>에 대한 정의를 꼭 해주어야 한다.
db.collection.mapReduce(
<map>,
<reduce>,
{
out: <string or document>,
query: <document>,
sort: <document>,
limit: <number>,
finalize: <function>,
scope: <document>,
jsMode: <boolean>,
verbose: <boolean>
}
)
파라미터 | 타입 | 설명 |
map | function | 어떤 정보들끼리 서로 묶을 수 있는지 정하는 함수 |
reduce | function | 묶인 정보들끼리 연산을 하는 함수 |
out | string 또는 document | 출력된 정보를 데이터베이스 내 기록으로 남게 설정 |
query | document | map 과정을 실행하기 전, 먼저 필요한 정보를 쿼리로 걸러냄 |
sort | document | map 과정 실행 전, 어떤 값을 기준으로 정렬할지 결정 |
limit | number | map 과정 실행 전, 함수에 넣을 도큐먼트의 수를 제한 |
finalize | function | reduce 과정 끝난 후 실행할 함수 설정 |
scope | document | map, reduce, finalize 함수에서 사용 가능한 전역변수 설정 |
jsMode | boolean | map, reduce 사이의 정보를 자바스크립트 형태로 남길 지 설정, 기본값은 false |
verbose | boolean | 연산 처리에 걸린 시간을 보여줄 지 설정, 기본값 false |

뭔가 어마무시한 걸 쓴거 같은데, Map 함수 - Reducing 함수 - Out 만 잘 정의하면 된다는 걸 생각해보자.
그러면 아래와 같은 간단한 맵-리듀스 명령어를 볼 수 있다.
// mapping function 정의
var mapper = function(){
emit(this."그룹핑의 기준", this."다음 단계에 넘겨줄 값")
}
// reducing function 정의, 배열의 길이를 return
// key: grouping된 기준값
// values: 배열에 grouping에 따른 각각의 값이 담긴 형태
var reducer = function(key, values){
return values.length
}
// 콘솔창에 출력만 하라고 out를 설정
db.rating.mapReduce(mapper, reducer, {out: {inline: 1}})
여기에 원한다면 '선택 단계'에 원하는 내용을 알맞게 집어넣으면 된다.
선택 단계는 검색하는게 더 빠를듯?
집계 파이프라인 스테이지
앞에서 더 좋다고 한 pipeline을 나가보자. 더 효율적으로 집계 연산을 처리할 수 있게 도와주긴 하나 복잡하다는 단점이 있으니 잘 정리해야지...
- 파이프라인이란 한 처리 단계의 출력이 다음 단계의 입력으로 이어지는 구조
- 집계 파이프라인은 데이터를 받아 각각의 공정을 거쳐 원하는 결과물이 출력되는 구조로 이루어짐
스테이지 | 설명 | 형식 |
$project | 어떤 필드를 숨기고, 어떤 필드를 새로 만들지 정함 | {$project: {<field>:<boolean>}} {$project: {<field>:<expression>}} |
$group | _id값으로 지정된 내용이 같은 도큐먼트끼리 그룹화 | {$group: {_id: <expression>, <field1>: {<accumlator1> : <expression1>}, ... }} |
$match | 도큐먼트를 필터링해서 반환한다. find문과 비슷한 역할 | {$match: {<query>}} |
$unwind | 입력 도큐먼트에서 배열 필드를 분해해 각 요소에 대한 도큐먼트로 분리해 출력 | {$unwind: <field path>} {$unwind:{ path: <field path>, includeArrayIndex: <string>, preserveNullAndEmptyArrays: <boolean> }} |
$out | 파이프라인의 결과를 컬렉션에 기록 | {$out: "<collection name>"} |
예시를 들면서, 어떤 코드인지 설명해보자.
1. $project 스테이지
db.rating.aggregate([
{
$project: {_id:0, multiply: {$multiply: ["$_id", "$user_id"]}}
}
])
우선, '_id:0'은 _id 필드를 숨긴다는 것이고,
multiply는 뒤에 나올 $multiply라는 함수 결과를 나타내는 '변수명'의 개념이다.
$multiply: ["a", "b"]에 의해 '_id'값과 'user_id'값을 곱한다는 것을 알 수 있다. 저걸 왜 곱하는지는 묻지 말자, 이유 없다.
2. $group 스테이지
db.rating.aggregate([
{$group:{_id:"$rating", count: {$sum: 1}}}
])
우선, _id에 있는 '$rating'은 그룹화의 기준이 되는 값을 의미한다.
이 그룹화를 진행하면서, 값을 더하거나 배열로 값을 저장하는 등의 특별한 연산이 필요할 수도 있는데, 그게 바로 "count: {$sum: 1}"이다.
group 스테이지와 함께 쓰이는 연산자에는 first, last, max, min, avg, sum, push, addToSet같은게 있다. 대충 이런게 있구나 하고 넘어가자.
연산자명 | 설명 |
$first | 첫 번째 값을 반환, $sort가 있어야 의미가 있다. |
$last | 마지막 값을 반환, $sort가 있어야 의미가 있다. |
$max | 해당 필드의 최댓값을 반환 |
$min | 해당 필드의 최솟값을 반환 |
$avg | 해당 필드의 평균값을 반환 |
$sum | 해당 필드의 합산값을 반환 |
$push | 해당 필드의 모든 값을 배열에 넣어 반환(중복 제거 X) |
$addToSet | $push와 같은데, 여기선 중복을 제거한다 |
3. $match 스테이지
db.rating.aggregate([
{$match: {rating: {$gte: 4}}},
{$group:{_id:"$rating", count: {$sum: 1}}}
])
그냥 원하는 쿼리를 넣으면 된다.
여기서 match는 rating이 4 이상인 경우에 대한 쿼리를 의미한다.
4. $unwind 스테이지
이게 좀 복잡하다. 이건 하나의 도큐먼트에 들어있는 배열 요소들을 각각의 도큐먼트에 하나의 값으로 갖도록 만드는 작업을 해준다. 그러니까, unwind 단독으로는 못쓴다는 소리라 예시를 들어야 한다.
어떤 pipeline의 결과가 아래와 같다고 하자.
{"_id": 5, "user_ids": [11,12,10,9]}
{"_id": 4, "user_ids": [8,4]}
여기에 아래의 unwind를 집어넣으면
{$unwind: "$user_ids"}
아래와 같은 결과가 나온다는 뜻이다.
{"_id": 5, "user_ids": 9}
{"_id": 5, "user_ids": 10}
{"_id": 5, "user_ids": 11}
{"_id": 5, "user_ids": 12}
{"_id": 4, "user_ids": 4}
{"_id": 4, "user_ids": 8}
여기에 'includeArrayIndex'를 적용시키면?
{$unwind: {path: "$user_ids", includeArrayIndex: "index"}}
아래와 같은 결과가 나오게 된다.
{"_id": 5, "user_ids": 9, "index": NumberLong(0)}
{"_id": 5, "user_ids": 10, "index": NumberLong(1)}
{"_id": 5, "user_ids": 11, "index": NumberLong(2)}
{"_id": 5, "user_ids": 12, "index": NumberLong(3)}
{"_id": 4, "user_ids": 4, "index": NumberLong(0)}
{"_id": 4, "user_ids": 8, "index": NumberLong(1)}
5. $out 스테이지
주어진 이름의 컬렉션에 저장하는 역할인데, 쉽게 표현하면 아래와 같다.
{$out: "user_ids_by_rating"}
파이프라인의 위의 $out 스테이지를 추가하면, 아래처럼 이 컬렉션을 사용할 수 있게 된다.
db.user_ids_by_rating.find()
일단 배운 내용은 여기까지.
확실히 안쓰던거라 정리 안하면 빨리 잊어버릴거 같긴 하다.
정리 끝!
'개인 공부용' 카테고리의 다른 글
MSA Design Pattern (0) | 2022.10.29 |
---|---|
hadoop 맵리듀스 실습 기록용 (0) | 2022.10.21 |
hadoop 실습 기록 (0) | 2022.10.20 |
hadoop 설치(기록용) (0) | 2022.10.20 |
nosql, mongodb 정리 (0) | 2022.10.17 |