IT/NOSQL

[MongoDB] MongoDB Performance를 향상시키는 전략

JeongHyeongKim 2021. 9. 22. 03:19

 

0. Table Of Contents

 

 

1. 많은 인덱스를 생성하지 않는다.

다음 사진과 같이 인덱스를 하나의 사전이라고 생각해보자.

위 같은 구조에서 collection에 name이라는 property가 “AB“라는 document를 추가한다고 가정하자. 현재 collection에 등록된 document의 name property에 의한 순서는 “A-B-C-D”로 되어있지만, 추가가 되게 되면 “A-AB-B-C-D“로 변경이 되어야 한다.

먄약에 위처럼 데이터가 엄청 많이 추가되는 상황이라면 위 사전의 목차는 계속해서 업데이트 되어야 할 것이다. 즉, collection의 업데이트가 많은 구조의 index 또한 업데이트가 많이 일어날 것이며 index가 많을 수록 이러한 현상이 많아질 가능성이 높아진다.

또한 인덱스는 시스템 메모리에 상주하고 있는데, 인덱스가 많아져서 메모 디스크 공간을 메모리 처럼 사용하기 위해 가상메모리를 형성하게 된다. 이러한 상태가 지속되게되면 mongodb의 전체적인 퍼포먼스에 부정적인 영향을 줄 수 있다.

이러한 이유로 index를 많이 만드는 것을 지양하는 것이 좋다.

 

 

2. Index Prefix를 적극적으로 이용하자.

MongoDB를 이용해본 개발자라면 compound index의 개념을 알 것이다. 모르는 분은 여기(MongoDB Official Document)에서 개념을 참고한다. 아래와 같은 compound index를 예를 들어보자.

 

db.students.createIndex({name:1, grade:1, createdAt:1})

 

 

위같은 compound index를 해석하면 다음과 같다.

“name으로 asc 정렬한 다음 grade를 asc정렬하고, 마지막으로 createdAt으로 asc 정렬한 index“

 

위 처럼 해석된 compound index가 존재하려면 다음과 같은 조건이 필요하다.

“name으로 asc 정렬한 다음 grade를 asc 정렬한 index”

 

또한, 위처럼 해석된 compound index가 존재하려면 다음과 같은 조건이 필요하다.

“name으로 asc 정렬한 index“

즉, compound index의 경우에 순서를 지킨 (compound)index가 자동으로 적용된다는 것을 알 수 있으며, 공식문서에도 이에 대한 내용을 볼 수 있다. 정리하면, 위 index를 생성함으로써 부가적으로 사용가능해진 index는 다음과 같다.

 

db.students.createIndex({name:1, grade:1, createdAt:1})
db.students.createIndex({name:1, grade:1})
db.students.createIndex({name:1})

 

 

 

 

3. Multi Sorting의 경우 sort 방향 신경써서 index를 설계하자

single index의 경우에는 sort 방향이 필요 없다. 위에서 언급한 사전을 예로 들면, 단순히 사전의 목차를 처음부터 보느냐 마지막꺼부터 역방향으로 보느냐 차이기 때문이다. 그러나 compound index의 경우, 정렬된 것을 다른 것을 기준으로 정렬하기 때문에 sorting 방향을 엄격하게 지킬 필요가 있다. 이를 지키지 않을 시, index를 찾는 것이 아닌 collection 상에서 full scan이 일어나게 된다.

 

 

아래 처럼 index를 만들었다고 가정하자.

 

db.students.createIndex({name:1, grade:1})
db.students.createIndex({name:1, grade:-1})
db.students.createIndex({grade:1, name:1})
db.students.createIndex({grade:1, name:-1})

 


첫 번째 라인의 index를 이용하면 아래와 같은 index들을 사용할 수 있다.

 

db.students.createIndex({name:1, grade:1})    -> original index
db.students.createIndex({name:1})             -> can be used by index prefix
db.students.createIndex({name:-1})            -> can be used inverse of index prefix
db.students.createIndex({name:-1, grade:-1}). -> can be used by inverse of original index

 

 

 

나머지 index들도 위 케이스와 같이 동일하게 적용하면 아래와 같은 find method에 대해 full scan을 할 필요 없이 index만으로 sorting 처리할 수 있게 된다.

 

db.find().sort({name:1, grade:1})
db.find().sort({name:1, grade:-1})
db.find().sort({name:-1, grade:1})
db.find().sort({name:-1, grade:-1})
db.find().sort({grade:1, name:1})
db.find().sort({grade:1, name:-1})
db.find().sort({grade:-1, name:1})
db.find().sort({grade:-1, name:-1})
db.find().sort({grade:1})
db.find().sort({grade:-1})
db.find().sort({name:1})
db.find().sort({name:-1})

 

 

이 외의 sort를 이용하기 위해서는 추가적인 index를 설계해야할 것이며, 현재 가지고 있는 index가 얼마만큼의 sorting을 cover할 수 있는지 잘 판단하여야 한다.

 

 

4. 하나의 collection을 여러개의 collection으로 분리하자

하나의 collection 내부에 많은 document를 가지게 되면 아래와 같은 현상을 자연스럽게 수반하게 된다.

  • index의 size가 증가한다.
  • index의 카디널리티가 증가한다

위 상황은 아래와 같은 상황에서 인덱스를 이용하여 query result를 return 할 때, query processor가 불필요한 index 를 참조하기 때문에 퍼포먼스가 낮아질 수 있다. 따라서, 여러 collection으로 분리하는 전략이 하나의 선택이 될 수 있다.

 

 

5. MongoDB를 4.0이상 버전으로 유지하자.

 

5.1. Non blocking Secondary Read

4.0 이전 버전에는 write가 primary에 완전히 commit 된 데이터들을 secondary에 전달완료 하기 전까지는 read operation을 block한다.

이를 mongodb는 WiredTiger timestamp를 이용하여 update에 대한 order를 보장하고, consistency snapshot를 이용하여 secondary 복제가 일어나는 순간에는 secondary가 아닌 snapshot를 읽게 함으로써 non blocking secondary read를 구현하였다.

 

 

5.2. Multi Transaction

RDB의 경우, 서로 관계가 있는 데이터의 경우는 정규화를 해서 데이터를 insert하지만, mongodb의 경우 이를 정규화 하지 않고 하나의 document에 저장하는 성질을 가지고 있다. 이 이론에 기반하여 MongoDB에서 제시하는 이상적인 collection 설계에 따르면 document 하나로 데이터의 무결성이 보장이 된다. 그러나 모든것이 이상적일 수가 없기 때문에 이를 개발자들은 관계를 가진 여러개의 document에 대해 무결성을 보장하기 위해 pending, rollback을 모두 직접 구현한 2-Phase-Commit 패턴으로 해결하였다. 그러나 4.0 이상의 버전에는 multi trransaction을 지원하므로 이를 사용하는 것을 권장한다.

 

 

 

6. Reference