일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
- Server
- 공룡책
- Java
- OS
- softeer
- Test code
- ExceptionResolver
- Coputer Science
- proxyFactory
- 자바
- RequestBody
- db
- MySQL
- Test Doulbe
- Test
- ObjectMapper
- FCM
- mapstruct
- modelmapper
- backend
- JPQL
- 일상
- 소프티어
- Spring
- enumSet
- Service 계층 테스트
- JPA
- Junit 5
- 인프콘2023
- 테크쇼
- Today
- Total
공부내용공유
MongoDB 정규화, 반정규화 본문
서론
MySQL에서도 고려할만한 주제이지만 특히 MongoDB를 사용할 때 많이 고민하게 되는 부분은 정규화, 반정규화 이다.
결국 상황에 맞게 알잘딱깔센으로 하는게 대부분 자료들의 결론이지만 자료들(몽고DB 완벽 가이드, Real MongoDB)을 찾고 보면서 간략하게 내용을 정리해 보았다.
본론
정규화 vs 비정규화
먼저 정규화와 비정규화의 차이를 학생과 과목을 예시로 살펴보자
- students (학생별 도큐먼트)
- classes(과목별 도큐먼트)
- studentClasses(학생과 강의를 참조하는 도큐먼트)
학교 수업 관리자가 학생들이 듣는 과목 리스트, 상세 정보들을 조회하고 싶다면 어떻게 해야할까?
일반적인 잘 정규화된 테이블에서 SQL의 방식은 Student 아이디로 StudentClasses를 조회 후 그 안에 과목들을 조회한다. (케이스 마다 다르겠지만)
db.studentClasses.find()
{
id:objectId("sdkjbsfd9sfs12431") // 학생강의 id
studnetId: objectId("14kjbaksjbfdafb") //학생 id
classes:[ //수강중인 과목 id
objectId("14kjbaksjbfdafb")
,objectId("14kjbaksjbfdafb")
,objectId("14kjbaksjbfdafb")
]
}
이렇게 되면
- 학생 정보를 위해 stduents에서 조회
- 특정 학생의 sudentClass를 조회
- 강의 정보를 위해 studentClass에서 얻은 강의 id로 classes에서 조회
총 3번의 쿼리를 날려야 한다. (걍 그렇다고 하자)
이때 약간의 반정규화로 Student 도큐먼트에 class 정보를 넣게 되면
{
id:objectId("sdkjbsfd9sfs12431") //학생 id
name: "alice"
classes:[ //수강중인 과목
{class : "이산수학" 학점: "3", room : "301"}
,{class: "알고리즘", 학점:"4" , room : "302"}
,{class : "데이터베이스", 학점: "3" , room:"303"}
]
}
학생 조회 쿼리 1번으로 원하는 정보를 모두 가져올 수 있다.
하지만 그에따른 단점들이 있다.
- 도큐먼트의 크기가 커져 공간을 많이 차지한다.
- 도큐먼트의 최대 크기를 고려해야 한다.
- 컬렉션의 책임이 너무 커지면 index의 성능에 문제가 생긴다.
- 특정 과목의 학점이 변경된다 하면 그 과목을 수강하는 학생들의 모든 도큐먼트들을 변경해야 한다.
- 아래에서 일관성 문제에 대해 알아보자
반정규화의 정도도 어느정도 조절할 수 있다.
{
id:objectId("sdkjbsfd9sfs12431") //학생 id
name: "alice"
classes:[ //수강중인 과목
{objectId("14kjbaksjbfdafb"),class : "이산수학" }
,{objectId("kjbfakf7wad1d"), class: "알고리즘"}
,{objectId("skdfnsdknfs09"), class : "데이터베이스"}
]
}
이렇게 자주 사용하고 잘 변하지 않는 정보만 반정규화를 하고 그 외 정보는 실제 도큐먼트를 참조하는 방식이다.
이렇게 했을 때 장점은
- 몇몇 정보들은 참조를 하므로 수정시에 큰 비용이 들지 않는다.
- 자주 조회되거나 업데이트가 별로 없는 정보들은 조회 비용을 낮출 수 있다.
- 도큐먼트의 크기가 위의 예시에 비해 줄일 수 있다.
위 세가지 예시를 통해 알 수 있는 결론은 '상황에 맞게 사용해야 한다'이다.
상황별 추천
내장 방식 추천 (반정규화) 참조 방식 추천(정규화)
작은 서브 도큐먼트 | 큰 서브 도큐먼트 |
잘 변하지 않는 데이터 | 자주 변하는 데이터 |
결과적인 일관성이 허용될 때 | 즉각적인 일관성을 요구할 때 |
증가량이 적은 도큐먼트 | 증가량이 많은 도큐먼트 |
2번째 쿼리를 수행하는데 자주 필요한 데이터 | 결과에서 자주 제외되는 데이터 |
빠른 읽기 | 빠른 쓰기 |
이렇게 데이터의 성격, 활용 상황에 따라
- 조회 빈도
- 수정 빈도
- 데이터의 양
- 일관성의 엄격함 정도
등을 고려하여 스키마를 설계하면 된다.
비정규화시 데이터 일관성 문제
만약 비정규화를 했고 데이터 일관성을 빠르게 보장해야 한다면 결국 다른 도큐먼트에 있는 데이터들도 업데이트를 하여 일관성을 보장해줘야 한다.
이때 어떤 방식으로 업데이트를 할 수 있을까?
1. stream change
MongoDB 3.6부터 사용이 가능하고 replica나 sharded cluster 구성일때만 사용이 가능하다.
MongoDB 가 publisher가 되고 어플리케이션이 subscriber가 된다.
DB에 변경이 일어나면 어플리케이션이 알게되고 알림, 일관성 유지를 위한 조치 등등의 작업을 할 수 있다.
간단하게 적용해봤는데,
이런식으로 사용을 했고 filter 등을 사용해서 특정 update에만(특정 filed가 업데이트 되었을 때) 반응하게 조정이 가능하다.
이런식으로 수정이 일어났을 때 change stream을 사용하여 넓은 범위에서 데이터 sync가 가능하다.
다만 listening을 계속해야 하기 때문에 일단은 executorservice를 사용하여 구현하였고 작동도 정상적으로 되었으나 구현 방법에 있어서 고려가 더 필요할 것 같다.
change stream은 보통 reactive로 사용하는 것 같다. (https://docs.spring.io/spring-data/mongodb/reference/mongodb/change-streams.html)
webflux, reactiveTemplate 을 사용한 change stream 코드 예시
Flux<ChangeStreamEvent<User>> flux = reactiveTemplate.changeStream(User.class)
.watchCollection("people")
.filter(where("age").gte(38))
.listen();
2. Change Data Capture (CDC)
change stream과 마찬가지로 데이터베이스 내 변경을 식별하여 자동으로 후속 처리를 해주는 기술이다.
CDC 오픈 소스중 Debezium 이 kafka connector와 가장 많이 사용된다고 한다.
https://engineering.linecorp.com/ko/blog/line-shopping-platform-kafka-mongodb-kubernetes
위 글에서도 대량으로 발생하는 자료 수정을 위해 mongoDB, Mysql의 cdc와 카프카를 사용한 사례를 설명하고있다. (예시 코드는 추후 해당 방식을 사용하게 된다면 추가할 예정이다.)
3. 백그라운드, 스케줄러
일관성 보장이 즉각적으로 필요하지 않을 때 데이터들을 모았다가 어플리케이션 단에서 수정하는 등의 방식으로 풀어나갈 수 있다.
결론
정규화와 반정규화는 결국 도메인 지식과 경험을 기반으로 잘 설계해나가야 하는 문제인 것 같다, 프로젝트를 진행하면서 이러한 것에 대한 경험을 많이 쌓아야겠다.
또 일관성 처리 작업들도 여러가지 방법들이 있겠지만 규모에 따라 적절한 방식을 취해주면 될거 같고 가능하다면 적절한 정규화와 반정규화를 통해 데이터 sync 작업을 최소화 하는 것이 좋을 것 같다.
'ComputerScience > DataBase' 카테고리의 다른 글
Redis 알아보기 (feat: redis는 single thread?) (0) | 2024.08.18 |
---|---|
MySQL GroupBy 사실과 오해 (feat: only_full_group_by, window function) (0) | 2024.05.01 |
MongoDB Bulk Ops (feat: insertMany, Bulk Write) 알아보기 (0) | 2024.04.21 |
Mongo DB Sharding (feat: replication, partitioning) (2) | 2024.04.05 |
MySQL Bulk Update (feat: temporal table) (0) | 2024.03.21 |