Tricore 최초 게시: 2017년 8월 2일
MongoDB는 시작하기 쉬운 데이터베이스이지만, 실제 애플리케이션을 구축하다 보면 생각보다 복잡한 문제에 직면하게 됩니다. 다음과 같은 고민을 해본 적이 있으신가요?
- 레플리카 셋(replica set)에서 멤버를 어떻게 재동기화할 수 있을까?
- 크래시가 발생한 후 MongoDB를 어떻게 복구해야 할까?
- 파일을 저장하고 조회할 때 GridFS 사양은 언제 사용해야 할까?
- 손상된 데이터는 어떻게 수정해야 할까?
이 글에서는 MongoDB를 사용할 때 이러한 상황에 대처하는 데 도움이 되는 실전 팁 여섯 가지를 소개합니다.
팁 1: repair 명령어에 의존해 데이터를 복구하지 마세요
데이터베이스가 크래시되었는데 --journal 플래그 없이 서버를 실행하고 있었다면, 해당 서버의 데이터를 그대로 사용해서는 안 됩니다.
MongoDB의 repair 명령어는 찾을 수 있는 모든 문서를 훑으며 깨끗한 사본을 만들어 줍니다. 하지만 이 과정은 시간이 오래 걸리고, 현재 사용 중인 것과 맞먹는 디스크 공간을 추가로 필요로 하며, 손상된 레코드는 그냥 건너뛰어 버린다는 점을 기억해야 합니다. MongoDB의 복제(replication) 과정으로는 손상된 데이터를 복구할 수 없기 때문에, 재동기화를 진행하기 전에 반드시 손상 가능성이 있는 데이터를 먼저 삭제해야 합니다.
팁 2: 레플리카 셋 멤버 재동기화하기
레플리카 셋의 멤버를 재동기화하려면 먼저 최소 하나의 세컨더리(secondary) 멤버와 하나의 프라이머리(primary) 멤버가 정상적으로 실행 중인지 확인합니다. 그다음 Oracle 사용자로 로그인하여 MongoDB 서비스를 중지합니다.
이후 MongoDB 사용자로 로그인해 모든 데이터 파일을 백업 폴더로 옮깁니다. 문제가 발생했을 때 복원할 수 있도록 하기 위함입니다. 백업 폴더에 예전 파일이 남아 있다면 삭제해도 됩니다. 데이터 파일 위치를 모르겠다면 /etc/mongod.conf 파일을 확인하세요. 그다음 Oracle 사용자로 MongoDB 서비스를 다시 시작합니다.
데이터베이스에 접속해 상태를 검증합니다. 해당 멤버가 레플리카 셋에 동기화되기 전까지는 인증 없이도 데이터베이스에 접근할 수 있습니다.
복제 과정이 완료되면 상태가 STARTUP2에서 SECONDARY로 변경됩니다.
팁 3: 작은 바이너리 데이터에는 GridFS를 사용하지 마세요
MongoDB는 대용량 파일을 저장하고 조회하기 위해 GridFS 사양을 사용합니다. 요약하자면, GridFS는 큰 바이너리 객체를 잘게 나눈 뒤 데이터베이스에 저장하는 방식입니다. GridFS는 두 번의 쿼리가 필요합니다. 하나는 파일의 메타데이터를 가져오는 쿼리이고, 다른 하나는 실제 내용을 가져오는 쿼리입니다. 따라서 작은 파일을 GridFS로 저장하면 애플리케이션이 수행해야 하는 쿼리 수가 두 배로 늘어납니다.

출처: https://www.slideshare.net
GridFS는 말 그대로 '빅 데이터', 즉 단일 문서에 담기에는 너무 큰 데이터를 저장하도록 설계되었습니다. 경험상 클라이언트에서 한 번에 로드하기 어려운 데이터라면 서버에도 한꺼번에 올리지 않는 것이 좋습니다. 이때의 대안은 스트리밍입니다. 클라이언트로 스트리밍할 계획인 데이터라면 GridFS의 좋은 후보라고 할 수 있습니다.
팁 4: 디스크 접근을 최소화하세요
개발자라면 RAM에서 데이터를 읽는 것은 빠르고 디스크에서 읽는 것은 느리다는 사실을 잘 알고 있습니다.
디스크 접근 횟수를 줄이는 것이 훌륭한 최적화 기법이라는 것은 알아도, 실제로 어떻게 해야 하는지는 모르는 경우가 많습니다.
첫 번째 방법은 SSD(솔리드 스테이트 드라이브)를 사용하는 것입니다. SSD는 전통적인 HDD(하드 디스크 드라이브)보다 많은 작업을 훨씬 빠르게 처리하며, MongoDB와도 매우 잘 어울립니다. 다만 용량이 작고 가격이 비싼 편이라는 단점이 있습니다.
다음 이미지는 SSD와 HDD를 비교한 것입니다.

출처: https://www.serverintellect.com
두 번째 방법은 RAM을 더 추가하는 것입니다. 하지만 이 방법에는 한계가 있습니다. 결국 데이터 크기를 RAM이 감당하지 못하는 순간이 오기 때문입니다.
핵심 질문은 이것입니다. 테라바이트 또는 페타바이트 규모의 데이터를 디스크에 저장하면서, 자주 요청되는 데이터는 주로 메모리에서 처리하고, 디스크에서 메모리로 데이터를 옮기는 빈도는 최대한 낮추려면 어떻게 설계해야 할까요?
모든 데이터를 실시간으로 무작위 접근한다면 답은 간단합니다. 엄청난 양의 RAM이 필요합니다. 하지만 대부분의 애플리케이션은 그렇게 동작하지 않습니다. 최근 데이터가 오래된 데이터보다 더 자주 조회되고, 특정 사용자가 다른 사용자보다 활발하며, 특정 지역에 고객이 더 많이 분포합니다. 이런 특성을 지닌 애플리케이션이라면 일부 문서를 메모리에 유지하고 디스크 접근을 아주 드물게 발생하도록 설계할 수 있습니다.
팁 5: 데이터베이스 크래시 후 MongoDB를 정상적으로 시작하세요
저널링(journaling)을 활성화한 상태에서 시스템이 복구 가능한 방식으로 크래시되었다면, 데이터베이스를 평소처럼 정상적으로 재시작하면 됩니다. 이때 평소 사용하던 옵션을 모두 그대로 사용해야 하며, 특히 저널 파일을 찾을 수 있도록 --dbpath와 --journal 옵션을 반드시 포함해야 합니다.
MongoDB는 연결 요청을 받기 시작하기 전에 데이터를 자동으로 복구합니다. 대용량 데이터 세트의 경우 이 과정에 몇 분 정도 걸릴 수 있지만, 대규모 데이터에 repair를 실행하는 데 걸리는 시간보다는 훨씬 짧습니다.
저널 파일은 journal 디렉터리에 저장됩니다. 이 파일들은 절대 삭제하지 마세요.
팁 6: repair 명령어로 데이터베이스 압축하기
repair 명령어는 본질적으로 mongodump를 수행한 뒤 mongorestore를 실행하는 것과 같아서, 데이터의 깨끗한 사본을 만들어 줍니다. 이 과정에서 데이터 파일에 생긴 빈 '구멍(hole)'들도 함께 제거됩니다.
repair 명령어는 작업 중 운영을 차단하며, 현재 데이터베이스가 사용 중인 디스크 공간의 두 배가 필요합니다. 하지만 여분의 머신이 있다면 mongodump와 mongorestore를 사용해 동일한 작업을 수동으로 진행할 수 있습니다.
수동으로 진행하는 절차는 다음과 같습니다.
Hyd1 머신을 스텝다운(step down)한 뒤
fsync와lock을 실행합니다:rs.stepDown()
db.runCommand({fsync : 1, lock : 1})파일을 Hyd2로 덤프합니다:
Hyd2$ mongodump --host Hyd1Hyd1의 데이터 파일 사본을 만들어 백업으로 보관합니다. 그런 다음 원본 데이터 파일을 삭제하고, 빈 데이터 상태로 Hyd1을 재시작합니다.
Hyd2에서 복원합니다. 데이터 파일을 복원하려면 다음 명령어를 입력합니다:
Hyd2$ mongorestore --host Hyd1 --port 10000 # 포트가 27017이 아니라면 지정
마치며
이러한 변경 사항들은 MongoDB 성능을 크게 향상시켰습니다. MongoDB를 사용할 계획이라면 이 글을 북마크해 두고, 새 프로젝트를 시작할 때마다 돌아와서 팁을 하나씩 체크해 보시길 권합니다.
2부 시리즈인 다음 글에서는 대규모 기업이 MongoDB의 유용한 기능들을 제대로 설계, 최적화, 구현하는 데 도움이 되는 팁을 소개할 예정입니다.
의견이나 질문이 있다면 피드백 탭을 이용해 주세요.