mongodump
mongodump --host <hostname> --port 27017 --db <database> -- out <backup_directory>
mongorestore
mongorestore --host <hostname> --port 27017 --db <database> <backup_directory>
MongoDB가 데이터를 효율적으로 검색할 수 있도록 쿼리 구조를 개선하고 적절한 인덱스를 사용하는 과정
이게 뭘까 한참 생각해봤는데... RDBMS에서도 쿼리 최적화를 하기 위해 정규화/비정규화를 하는 것과 비슷한 개념이 아닐까 했다. (인덱스를 사용하기도 하니까) 당연히 접근법이나 구현은 다르겠지만! 목적 자체는 같다는 사실을 염두에 두면 공부가 조금 쉬울까 ^^... 하여 생각해봤다. 여전히 NoSQL은 처음 접하는 개념이다보니 어려운 것 같다.
* 쿼리 플래너라는 것도 ^^... 아 이게 뭔데~!!! 싶었으나, 쿼리 최적화를 목적으로 한다는 점에서 RDBMS의 옵티마이저 같은 것이 아닐까? 물론 옵티마이저는 SQLP 시험 범위라서 모름 ㅋ 들어만 봄. 둘 다 쿼리를 실행하기 위한 최적의 경로를 결정하는 실행 계획을 생성한다고 한다. 쿼리 성능을 향상시키기 위한 비슷한 원리, 과정을 따르지만 각각 데이터베이스 특성에 맞는 최적화 작업을 수행한다.
인덱스(Index)를 적절히 사용해 쿼리 성능 크게 향상
👉 다양한 인덱스 유형을 지원 (복합인덱스, 텍스트인덱스, 해시인덱스 등)
집계 프레임워크(Aggregation Framework)를 통해 복잡한 데이터 변환 및 집계 작업을 효율적으로 처리
통계 기반 → 컬렉션의 통계(Statistics Collection)를 바탕으로 쿼리 최적화 수행
힌트(Hints) → 쿼리 실행 계획을 직접 지정할 수 있도록 힌트 제공
MongoDB에서 최적의 쿼리 실행 계획을 선택하는 핵심 구성 요소
인덱스와 데이터를 기반으로 여러 가지 실행 계획을 평가한 후 가장 효율적인 방법을 선택함
explain()
메소드 → 쿼리가 어떻게 동작하며 최적화될 수 있는지에 대한 정보 제공
executionStats 모드
winningPlan
👉 MongoDB가 선택한 최적의 실행 계획totalKeyExamined
👉 인덱스에서 조회된 키의 수totalDocsExamined
👉 쿼리에서 조회된 문서의 수executionTimeMills
👉 쿼리 실행에 소요된 시간(밀리초 단위)
아...! explain() 메소드를 executionStats 모드로 사용해서 위의 요소들을 통해 실행 계획 혹은 통계를 확인할 수 있다는 의미인듯. 이걸 왜 알려주나 했네... 이게 뭔지도 이해가 안 갔다.
예시로 확인을 해보도록 하겠다!
테스트 데이터베이스를 만들고 데이터를 넣어주었다.
-- 인덱스 생성
db.users.createIndex({ name: 1 });
-- 복합 인덱스 생성
db.users.createIndex({ name: 1, age: -1 })
이때 데이터를 효율적으로 검색하는 방법 ! 쿼리 최적화를 하는 것이다...
-- 인덱스를 활용하여 최적화 된 쿼리 찾기
db.users.find({name:"Alice"}).sort({age:-1});
-- 힌트를 사용한 쿼리
db.user.find({name:"Alice"}).hint({name:1, age:-1});\
이렇다고 한다.
쿼리 실행계획을 확인해보자!
-- 쿼리 실행 계획 확인
db.users.find({name:"Alice"}).explain("executionStats");
db.users.find({name:"Alice"}).sort({age:-1}).explain("executionStats");
db.user.find({name:"Alice"}).hint({name:1, age:-1}).explain("executionStats");
db.users.find({name:"Alice"}).explain("executionStats");
실행 결과{
explainVersion: '1',
queryPlanner: {
namespace: 'queryPlannerDB.users',
indexFilterSet: false,
parsedQuery: {
name: {
'$eq': 'Alice'
}
},
...
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: {
name: 1
},
indexName: 'name_1',
...
}
}
},
...
executionStats: {
executionSuccess: true,
nReturned: 0,
executionTimeMillis: 0,
totalKeysExamined: 0,
totalDocsExamined: 0,
executionStages: {
stage: 'FETCH',
nReturned: 0,
executionTimeMillisEstimate: 0,
works: 2,
...
}
}
},
...
}
db.users.find({name:"Alice"}).sort({age:-1}).explain("executionStats");
실행 결과{
explainVersion: '1',
queryPlanner: {
namespace: 'queryPlannerDB.users',
indexFilterSet: false,
parsedQuery: {
name: {
'$eq': 'Alice'
}
},
...
winningPlan: {
stage: 'FETCH',
inputStage: {
stage: 'IXSCAN',
keyPattern: {
name: 1,
age: -1
},
indexName: 'name_1_age_-1',
...
}
}
},
...
executionStats: {
executionSuccess: true,
nReturned: 0,
executionTimeMillis: 0,
totalKeysExamined: 0,
totalDocsExamined: 0,
executionStages: {
stage: 'FETCH',
nReturned: 0,
executionTimeMillisEstimate: 0,
works: 2,
...
}
}
},
...
}
db.user.find({name:"Alice"}).hint({name:1, age:-1}).explain("executionStats");
실행 결과{
explainVersion: '1',
queryPlanner: {
namespace: 'queryPlannerDB.user',
indexFilterSet: false,
parsedQuery: {
name: {
'$eq': 'Alice'
}
},
...
winningPlan: {
stage: 'EOF'
},
rejectedPlans: []
},
executionStats: {
executionSuccess: true,
nReturned: 0,
executionTimeMillis: 0,
totalKeysExamined: 0,
totalDocsExamined: 0,
executionStages: {
stage: 'EOF',
nReturned: 0,
executionTimeMillisEstimate: 0,
works: 1,
...
}
},
...
}
오... 1과 2는 별 차이가 없다. 그래봐야 winningPlan
에 indexName
정도만 다르다. 사용된 인덱스가 다르기 때문이다.
하지만 .hint
를 사용한 결과는 확실히 다르다! winningPlan
에 stage
항목이 다르다. 인덱스를 사용한 결과에서는 'IXSCAN'
이라는 결과가 나왔지만 여기에서는 'EOF'
라는 결과가 나오고 위와는 다르게 결과 항목이 많지 않다.
stage 항목이 다르게 나타나는 이유는 인덱스 사용 여부에 따라 쿼리의 처리 방식이 달라지기 때문이다.
IXSCAN
(Index Scan)이는 인덱스를 사용해서 데이터를 검색하는 단계이다. 쿼리에 적합한 인덱스가 있다면, MongoDB가 해당 인덱스를 스캔해서 필요한 문서를 찾는다. 일반적으로 더 빠르고, 데이터가 많을 때도 효율이 높다.
EOF
(End of File)약자를 풀이한 그대로 쿼리 실행이 끝났음을 나타내는 단계다. 그래서 인덱스로 검색했던 방식과 다르게 뭐 다른 항목이 나오지 않았던 것! hint를 사용하여 특정 인덱스를 강제로 지정했을 때, 해당 인덱스가 쿼리에 적합하지 않거나 결과가 없을 경우 EOF로 표시될 수 있다. 인덱스를 사용한 후 더 이상 반환할 결과가 없다는 것을 의미한다.
인덱스가 쿼리 성능에 이런 영향을 미칠 수도 있다는 것...을 보여주는 사례라고 이해하면 될 것 같다. 위의 방법이 훨씬 효율적이라는 것!
하 처음에 공부할 때는 실습 활동이 대체 무엇을 의미하는지 이해가 안 갔는데... 인덱스를 생성해서 쿼리를 실행한 후 최적화 방식을 보는 것과, 힌트를 사용해 인덱스를 강제 적용해서 쿼리를 최적화하는 방식을 비교해서 보라는 것이었다! 이제 좀 이해가 가는군...
여전히 외계어처럼 보이긴 함 ㅎㅎ
queryPlanner.winningPlan.queryPlan.stage
(스테이지)👉 쿼리 실행 과정의 각 단계를 나타낸다
COLLSCAN
전체 문서를 하나씩 확인하기 때문에 당연히 효율성이 떨어진다.
IXSCAN
FETCH
SORT
executionStats.nReturned
executionStats.totalKeysExamined
executionStats.totalDocsExamined
데이터베이스의 성능을 최적화하는 데 중요한 역할을 한다!
위에서 대강 확인 함... 하긴 함 작동방식이 잘 이해는 안 가지만
DB 검색을 빠르게 하기 위해 데이터의 순서를 미리 정리해두는 과정
데이터 검색 성능을 향상시키는 중요한 도구이나... 많이 쓰면 성능 저하 ? (서버를 잡아먹음)
하나의 필드에 대해 생성된 인덱스, 가장 기본이며 자주 사용
db.collection.createIndex({"fieldName": 1})
두 개 이상의 필드에 대해 생성된 인덱스, 복잡한 쿼리 성능 향상
db.collection.createIndex({"field1": 1, "field1": -1})
부분 인덱스도 지원한다고 한다 ! 그냥 일단 알아만 두자...
문자열 데이터를 기반으로 텍스트 검색을 최적화
db.collection.createIndex({"fieldName": "text"})
예를 들어서 우리가 text 인덱스를 지정한 다음 텍스트 인덱스가 적용된 쿼리와, 정규표현식을 사용하여 검색하는 쿼리를 비교한다면 전자가 훨씬 더 성능이 좋은 것이다.
예를 들어 db.article.find({body:/database/i})
이런 정규 표현식을 사용한 검색은 기본적으로 전체 컬렉션을 스캔해야 할 수 있다. (cf. /.../i
와 같은 형태는 대소문자를 구분하지 않기 때문에 인덱스를 사용할 수 없는 경우가 많아서 성능 저하의 위험이 있다. 정규표현식이 특정 패턴으로 시작하는 경우에는 인덱스를 사용할 수 있지만, 그렇지 않다면 전체 스캔을 해야 할 수도 있다.)
db.article.createIndex({body:"text"});
db.article.find({ $text: { $search: "database" } });
👉 body 필드에 대해 텍스트 인덱스를 생성해 준 후
👉 $text
연산자를 사용하고 검색할 단어를 $search
키워드로 지정하여 텍스트 인덱스를 사용하여 검색한다.
이렇게 바꾸면 "database"라는 단어가 포함된 문서를 더 효율적으로 검색할 수 있다. 일반적으로 정규 표현식 검색보다 성능이 좋다.
지리적 데이터를 기반으로 하는 쿼리 성능 최적화
db.collection.createIndex({"location": "2dsphere"})
이거 인덱스 안 걸어주면 지리 검색 안된대용 참고참고
너무너무너무너무너무너무 어려웠다... 쿼리 최적화 부분은 진짜 개대박 어려웠음. 사실 당장 프로젝트에 이것들이 필요하지는 않겠다 싶었지만 궁금하기도 했고... 나중에 SQLP 딸 건데 거기서 옵티마이저라는 개념을 잘 모르지만 엄청 어렵다는 건 알고 있음 ㅎㅎ 그래서 미리... 찍먹 느낌 ㅎ 뭐 엄청 다르겠지만요... ㅠㅠ 인덱스도 솔직히 알겠는데 그래서 뭐가 젤 효율일까는 잘 모르겠다. 당연함. 방금 배웠고, 고민도 많이 안 해봄. 성능 차이도 아직 잘 모른다. 걍 ... 열심히 하자 ... 흑흑 고민하면서 공부를 해야겠다.
아니 근데 이렇게 디비가 끝이라니. 에바임,,, 어려워 ㅠ
본 포스팅은 글로벌소프트웨어캠퍼스와 교보DTS가 함께 진행하는 챌린지입니다