MongoDB 주요 특징
Secondary Index
▪ 다른 NOSQL 보다 secondary index 기능이 발달되어 있음
샤드키 지정
▪ _id : 키 필드
▪ Shard Key <> _id
- 대부분의 NOSQL은 Row Key = Shard Key 임
Document 기반
▪ JSON Style의 Document : BSON(Binary JSON)
Modelling 주요 적용 모델링 기법
▪ 비정규화(Denormalization)
▪ 집합(Aggregation)
기타 mongoDB에 대한 정보 → https://blog.voidmainvoid.net/239
MongoDB document 패턴
1:1 패턴
{
"emp_id" : 1001,
"emp_name" : "홍길동",
"reg_number" : "111111-1111111"
}
Aggregation --> Embedded Document
1:N 패턴
■ Linked Document는 기존 관계형 데이터베이스 처럼 비정규화하지 않고, 두개의 테이블로 분리시키는 방법이다. 제약조건이 없다는 점을 제외하면 관계형 데이터베이스와 동일하다고 볼 수 있다.
방법 1) embedded : 자식 객체가 단독으로 사용되지 않고 부모객체 내에서만 사용될 때 사용.
- 예) 주문 정보와 주문 세부 항목 정보
- 한번의 Read로 필요한 정보 모두를 읽어옴 → 읽기 성능 향상
- Strong Association
방법 2) linked : 자식객체가 부모객체와는 별개로 단독으로 사용될 때 적용
- 예) 상품 분류 정보와 상품 정보
-상품분류별 상품 정보들을 조회하려면 여러번 Read를 해야 함. → 읽기 성능 저하
-데이터 일관성이 상대적으로 중요할 때 사용
-Weak Association
Embed vs link 선택
Embedded document의 경우
{
"_id" : ObjectId("506ebba74c935cb364575e95"),
"name" : "이몽룡",
"age" : 24,
"phones" : [ "010-513-2372", "031-748-2461" ],
"addresses" : [
{ "city" : "경기", "zip" : 122333, "street" : "용인" },
{ "city" : "서울", "zip" : 354351, "street" : "노원" }
]
}
Linked document의 경우
{ "_id" : 1, "name" : "음료", "desc" : "콜라, 사이다 등" }
{ "_id" : 2, "name" : "한식", "desc" : "불고기, 식혜 등" }
{ "_id" : 3, "name" : "분식", "desc" : "라면, 김밥 등" }
{ "_id" : 1001, "productname" : "콜라", "unitprice" : 1000, "categoryid" : 1 }
{ "_id" : 1002, "productname" : "김밥", "unitprice" : 2000, "categoryid" : 1 }
{ "_id" : 1003, "productname" : "김치전", "unitprice" : 4000, "categoryid" : 1 }
{ "_id" : 1004, "productname" : "떡볶이", "unitprice" : 2500, "categoryid" : 1 }
■ 참조하는 테이블이 여러개일 수 있다면, DBRef라는 MongoDB 내부의 표준적인 참조 기법을 이용할 수 있다.
■ DBRef는 참조하는 컬렉션과 키 값을 함께 명시하는 참조방법. 이로써 하나의 Document가 여러개 컬렉션의 Document를 참조할 수 있게 된다.
N:M 패턴
▪ 관계형 데이터베이스에서는 주로 관계 테이블을 정의하여 조인하지만, MongoDB에서는 배열 키를 이용할 수 있다.
- 배열 필드의 각 값에도 인덱싱이 가능하다.
▪ 예) 한 분류 코드에 여러 상품이 포함될 수 있으며, 동시에 한 상품이 여러 분류 코드에 포함될 수 있는 경우
▪ 참조하는 측의 컬렉션에서 배열키 필드 사용하여 인덱싱
방법 1) 단방향 참조
[Categories 컬렉션]
{ "_id" : 1, "name" : "한식", "desc" : "불고기, 식혜 등" }
{ "_id" : 2, "name" : "분식", "desc" : "라면, 김밥 등" }
{ "_id" : 3, "name" : "음료", "desc" : "식혜,콜라,사이다 등" }
[Products 컬렉션]
{
"_id" : 1001, "productname" : "김밥", "unitprice" : 2000,
"categoryid" : [ 1, 2 ]
}
{
"_id" : 1002, "productname" : "식혜", "unitprice" : 3000,
"categoryid" : [ 1, 3 ]
}
→ 단방향 참조
방법 2) 양방향 참조
[Categories 컬렉션]
{ "_id" : 1, "name" : "한식", "desc" : "불고기, 식혜 등", productid : [ 1001, 1002 ] }
......
[ Products 컬렉션 ]
{ "_id" : 1001, "productname" : "김밥", "unitprice" : 2000, "categoryid" : [ 1, 2 ] }
{ "_id" : 1002, "productname" : "식혜", "unitprice" : 3000, "categoryid" : [ 1, 3 ] }
→ 부모컬랙션에서 자식 컬렉션을 참조하는 방법
※ 어느 방향으로 query가 빈번히 일어나는지 확인하여 모델링한다. ☞ 읽기성능 향상
Tree 패턴
아래와 같은 hihierarchy일 경우 어떤 tree로 표현할 수 있을까?
a
|–b
|–c
|-d
|- e
|- f
▪ Embedded Tree
{
_id : tree,
name : "a",
childs : [
{
name : "b",
childs ": [
{ name : "c" },
{ name : "d" }
]
}, {
name : "e",
childs : [
{ name : "f" }
]
}
]
}
- 전체 트리를 하나의 Document안에 포함시켜 작성하는 방법
- 트리 구조 전체를 액세스할 경우가 빈번한 경우에 유용함
- 지나치게 복잡해질 우려가 있음
- Document 크기는 16MB로 제한
▪ Linked Document
{ _id: "a" }
{ _id: "b", ancestors: [ "a" ], parent: "a" }
{ _id: "c", ancestors: [ "a", "b" ], parent: "b" }
{ _id: "d", ancestors: [ "a", "b" ], parent: "b" }
{ _id: "e", ancestors: [ "a" ], parent: "a" }
{ _id: "f", ancestors: [ "a", "e" ], parent: "e" }
- 트리 구조를 분리된 Document에 포함시키는 방법
- 특정 노드를 검색(Query)할 일이 빈번한 경우에 유용
- ancestors 정보와 parent 정보를 함께 저장(부모와 조상까지 참조하는 모델)하여 검색을 편리하게 함.
- node가 이동하게 되면 변경해야할 요소가 많다. ex) 부모 node가 타 child node로 이동할 경우 재참조 필요
Dynamic Field 패턴
일반적으로 POJO를 지원하는 document를 적용할 경우
// Object Mapper 사용가능
{
_id: "S001",
name : "홍길동",
courses: [
{ coursename : "국어", score : 90, instructor:"김샘" },
{ coursename : "수학", score : 80, instructor:"박샘" },
...
]
}
Dynamic Field를 사용하여 field명에 데이터를 저장하는 방식으로 사용
// Object Mapper 사용 불가
{
_id: "S001",
name : "홍길동",
courses: {
"국어" : { score: 90, instructor:"김샘" },
"수학" : { score: 80, instructor:"박샘" },
...
},
clist : ["국어", "수학" ]
}
■ 장점
▪ 데이터 사이즈를 줄일 수 있음.
▪ Application 측에서 값을 액세스할 때 더욱 간단히 접근이 가능함. Dynamic Type 을 지원하는 언어인 경우 유리(예:Javascript, Python)
– doc.courses[1].score --> 90
– doc.courses["국어"].score --> 90
■ 단점
▪ Java와 같은 언어에서 POJO 객체를 사용할 수 없음. (필드명에 값을 저장하기 때문에)
▪ Secondary Index 를 사용하지 못하므로 검색시 쿼리 조건으로 사용할 경우 문제가 있음.
– secondary index 문제는 별도의 배열필드를 이용해 검색하도록 할 수 있다. 배열인덱스를 사용하면 됨.
MongoDB 모델링 시 고려사항 정리
Data와 Biz 중심의 설계
▪ Application의 쿼리 중심 설계를 의미함. ☞ 정규화 적인 고민도 필요.
▪ Biz 요구사항에 맞춰서 비정규화, 데이터 중복을 허용
Embedded VS Linked
▪ Embedded
- 16MB 제한
- 빈번한 업데이트, 크기가 증가하는 업데이트일 때는 권장하지 않음 --> 단편화
- 읽기 속도 향상 : 한번의 쿼리로 조회
▪ Linked
- 더 복잡하지만 유연한 데이터 구조
- 데이터 크기 제한 없음
- 상대적으로 강한 일관성 제공 가능
되도록 단일 쿼리로 조회할 수 있도록 할 것
▪ 여러번 쿼리 하는 것보다 읽기 속도가 향상 --> Embedded 무엇이 우선인가?
▪ 뭣이 중헌디 : 높은 일관성? 읽기 성능? 쓰기 성능?
데이터 액세스 패턴
▪ 읽기/쓰기 비율
▪ 쿼리/업데이트의 타입
▪ 데이터의 Life Cycle
▪ 크기가 증가하는 업데이트를 수행하는가?
▪ 분석적 작업을 수행하는가? (Map/Reduce, Aggregation)
MongoDB 모델링 예제 - 도서관 관리 애플리케이션
1단계 : 요구사항
▪ Entity
- 출판사(publishers) - 도서(books)
- 멤버(members)
▪ 관계
- 출판사는 여러개의 도서를 출판한다.
- 한 멤버는 여러개의 도서를 대여한다.
- 한 도서는 여러 멤버에게 대여될 수 있다.
- 멤버와 도서는 N:M 관계
2단계 : 쿼리 결과 디자인 및 고려사항
▪ 고려사항
- 읽기 성능이 우선이며, 쓰기 성능은 중요하지 않다.
- 고도의 일관성이 요구되지 않는다.
▪ 조회 화면
- 도서 정보 조회 화면
• 도서 정보 조회시 출판사이름과 현재 대여 중인 멤버의 이름도 함께 조회된다.
• 도서 대여 정보조회시 도서 정보와 멤버의 정보가 한번에 조회된다.
• 이 화면의 읽기 비율이 높다.
- 대여 내역 목록 화면
• 대여 내역만을 별도로 조회할 수 있어야 한다.
• 도서에 대한 과거의 대여 내역을 알 수 있어야 한다.
- 출판사 정보 조회 화면
- 멤버 정보 조회 화면
• 현재 멤버의 도서 대여 내역을 조회 할 수 있어야 한다.
▪ 쓰기 화면
- 출판사 등록 화면, 도서 등록 화면, 멤버 등록 화면, 도서 대여 화면, 도서 반납 화면
관계형 데이터베이스에서는 join 쿼리를 사용하지만, MongoDB는 Join하지 않는다는 점에 주목
- 자주 조회되는 화면은 한번의 쿼리로 데이터를 가져올 수 있도록 한다.
- 조회빈도가 낮은 화면이거나 데이터량이 큰 경우는 2번의 쿼리로 나누어서 가져올 수 있도록 한다.
3단계 : 컬렉션 추출
publishers collection
{
"_id" : 124522,
"pub_name" : "구글출판사",
"pub_address" : "서울시 강남구 역삼동"
}
members collection
{
"_id" : "gdhong",
"memb_name" : "홍길동",
"memb_type" : "모범",
"address" : "경기도 성남시 분당구 이매동"
}
books collection
// 대여 가능 상태일 경우
{
"_id" : 27462,
"author" : "성춘향",
"title" : "nosql 데이터 모델링",
"price" : 20000,
"pub_id" : 124522,
"pub_name" : "구글출판사",
"available" : 1
}
// 대여 상태일 경우
{
"_id" : 27462,
"author" : "성춘향",
"title" : "nosql 데이터 모델링",
"price" : 20000,
"pub_id" : 124522,
"pub_name" : "구글출판사",
"available" : 0,
"lended" : {
"memb_id" : 124522,
"memb_name" : "홍길동",
"issuedate" : "2015-05-02",
"duedate" : "2015-05-12"
}
}
lending collection
{
"_id" : ObjectId("....."),
"memb_id" : " gdhong",
"memb_name" : "홍길동",
"book_id" : 27462,
"title" : "nosql 데이터 모델링",
"pub_name" : "구글출판사",
"issuedate" : "2015-05-02",
"duedate" : "2015-05-12",
"returndate" : "2015-05-11" // returndate 필드가 존재하지 않으면 대여중인 상태
}
옵션 : 출판사, 도서를 더 빠르게 조회하는 방법
방법 1 : embedded 로 내용 추가
{
"_id" : 124522,
"pub_name" : "구글출판사",
"pub_address" : "서울시 강남구 역삼동",
"books" : [
{ "book_id" : 211, "title" : "hadoop Essential", "price" : 25000 },
{ "book_id" : 224, "title" : "mongodb 완벽 가이드", "price" : 27000 },
......
]
}
→ 출판사의 도서가 너무 많아지면 document 최대 크기(16MB)가 넘을 수도 있고 혹은 단편화(Fragmentation)이 발생되어 쓰기 성능을 떨어트림.
방법 2 : 도서 ID를 배열값으로 참조
{
"_id" : 124522,
"pub_name" : "구글출판사",
"pub_address" : "서울시 강남구 역삼동",
"book_ids" : [ 211, 224 , ... ]
}
추가적으로 고려할만한 사항
인덱스
▪ MongoDB는 _id 필드뿐만 아니라 Array, Embedded Document 내의 필드에도 인덱스 설정이 가능하다.
▪ 쿼리시에 조건절에서 사용되거나 정렬의 기준이 되는 필드에 대해 인덱스를 설정한다.
- db.lendings.ensureIndex({ book_id : 1 })
- db.lendings.ensureIndex({ memb_id : 1 })
큰 사이즈의 인덱스
▪ 너무 많은 필드가 인덱싱되는 경우 큰 사이즈의 인덱스가 생성되고, 이로 인해 오히려 성능이 저하될 수 있음.
▪ 해결책 :
- 항상 검색할 때 사용하는 필드를 concat한 값에 대해 해시값을 생성한 후 _id 필드값으로 사용한다.
- SHA-1 알고리즘의 경우 경우의 수가 2^160 이므로 충돌회피가 가능하다.
※ 많은 필드가 인덱싱되면 인덱스 사이즈가 지나치게 커지므로 이로 인해 성능저하가 발생
▪ MongoDB는 다른 nosql에 비해 Index가 강점이다. 관계형 데이터베이스 수준의 인덱스 기능을 제공. 해결을 위해 해쉬 사용 가능
▪ 조회시에 항상 조회 조건으로 사용하는 필드값들을 concat한 값으로 해시를 생성하여 그 값을 _id 필드 값으로 설정한다.
▪ 조회하는 기능의 애플리케이션에서 해시를 생성하여 _id필드로 조회한다. 몇십억건 수준내에서는 충돌회피가 가능하다.
- MD5 : 128bit
- SHA-1 : 160bit
- SHA-256 : 256bit
Example) a, b, c field 조합으로 검색을 하고 싶을때 a+b+c field의 hash(MD5 등)값을 _id 필드로 지정 및 key로 지정 ☞ 속도향상 기대 가능
샤딩 환경 고려
▪ 데이터량이 아주 많은 컬렉션인 경우 샤딩을 고려할 수 있다.
※ 기본적으로 node가 4개 이상일때 성능 향상을 기대할 수 있음
▪ 샤딩은 샤드키 설정이 중요하다.
- 증가하는 샤드키는 대량의 쓰기시에 핫스팟을 유발시킴. : ex) Timstamp, ObjectId
- 랜덤한 샤드키는 Range Query시에 여러 서버에 쿼리를 요청함. 쓰기 부하는 골고루 분산됨 ex) Hash값을 이용한 샤딩
- 카디널리티가 중요함 : 골고루 분산되게끔...
- 샤드키를 이용한 쿼리가 가능하도록 설계함.
- 샤드키는 미리 인덱싱되어 있어야 함.
▪ 예제) lendings collection(대여정보 저장)
- _id 필드 : 골고루 분산되긴 하지만 샤드키를 사용한 쿼리가 이루어지지 않음.
- 도서 대역 목록을 조회할 때, issuedate와 사용자명으로 쿼리할 일이 많다면?
- issuedate + memb_id : 복합 샤드키를 고려할 수 있음.(복합샤드키는 순서가 중요)
'빅데이터 > nosql' 카테고리의 다른 글
mongodb shell에서 array로 정의한 multi db 검색하기 (0) | 2019.09.02 |
---|---|
mongodb shell에서 서로다른 database의 데이터 비교하기 (0) | 2019.08.22 |
mongodb shell에서 db 이름 명시하여 데이터 조회하기 (423) | 2019.08.22 |
NoSQL강의) mongoDB 개요 및 설명 한페이지에 끝내기(mapReduce, aggregate 예제 포함) (407) | 2019.07.23 |
NoSQL강의) Document Database 개요 및 설명 (375) | 2019.07.23 |
NoSQL강의) DynamoDB 개요, 특징 및 설명 (392) | 2019.07.23 |