일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- mapstruct
- RequestBody
- OS
- Test
- backend
- modelmapper
- 인프콘2023
- Spring
- Java
- softeer
- Server
- 소프티어
- Service 계층 테스트
- proxyFactory
- JPA
- JPQL
- Test code
- FCM
- enumSet
- 일상
- Junit 5
- db
- Coputer Science
- ObjectMapper
- 자바
- Test Doulbe
- ExceptionResolver
- MySQL
- 테크쇼
- 공룡책
- Today
- Total
공부내용공유
MongoDB Bulk Ops (feat: insertMany, Bulk Write) 알아보기 본문
MongoDB Bulk Ops (feat: insertMany, Bulk Write) 알아보기
forfun 2024. 4. 21. 16:44서론
프로젝트에서 벌크성 기능을 개발하면서 Mongo DB의 벌크성 쿼리에 대해 조사를 하게 되었다. (어떻게 작동하고, 오류 발생시 어떻게 처리할지)
해당 글에서는 내가 조사한 내용과 테스트 해본 내용을 정리할 예정이다.
본론
1. 쿼리 종류
먼저 Mongo DB에서 지원하는 벌크성 쿼리들을 종류를 나열해보면
- insertMany
- updateMany
- deleteMany
- Bulk Write
정도로 구분할 수 있다.
여기서 조금 헷갈릴만한 부분은 Many 시리즈와 Bulk Write는 무슨 차이가 있을까이다.
Many 시리즈 쿼리들은 말 그대로 여러 documente들에 대해 update
, delete
, insert
와 같은 연산을
진행하는 쿼리이다.
Bulk 쿼리도 위와 같은 방식으로도 여러 document에 대해 한가지 연산을 하는식의 사용이 가능하지만 차이점이라면 여러 종류의 연산을 한번에 실행한다는 점이다.
간단히 예시를 들자면 (Mongo DB docs의 예시 쿼리이다.)
db.pizzas.bulkWrite( [
{ insertOne: { document: { _id: 3, type: "beef", size: "medium", price: 6 } } },
{ insertOne: { document: { _id: 4, type: "sausage", size: "large", price: 10 } } },
{ updateOne: {
filter: { type: "cheese" },
update: { $set: { price: 8 } }
} },
{ deleteOne: { filter: { type: "pepperoni"} } },
{ replaceOne: {
filter: { type: "vegan" },
replacement: { type: "tofu", size: "small", price: 4 }
} }
] )
이런 식으로 사용이 가능하다.
글 뒤에서 각 쿼리에 대해서 좀 더 자세히 알아볼 예정이다.
2. Ordered, Unordered
해당 개념은 inserMany, Bulk Write 쿼리를 사용할 때 option으로 추가할 수 있다. (default는 ordered이다.)
여러 document들이 있거나 bulk Write에 여러 쿼리들이 있을 때 배열에 입력된 순서대로 명령어를 수행할건지 순서에 상관 없이
수행 할건지를 ordered, unordered로 결정할 수 있다.
ordered, unordered 차이점
- ordered의 경우 쿼리 실행 도중에 특정 작업을 실패할 경우 그 이후의 작업을 일절 진행하지 않는다. 하지만 unordered일 경우에는 그 뒤의 작업도 그대로 진행한다.
- ordered에 비해 unordered의 성능이 더 좋다.
위에서도 확인할 수 있듯이 벌크성 쿼리를 처리하다가 작업중 일부가 수행이 되지 않을 수도 있다, 원자성이 보장되어야 하는 작업이라면 transaction을 설정해서 보장해 줄 수도 있으나 그렇게 까지 할 필요없이, 실패한 작업들을 모아놨다가 재시도나 로깅을 하는등의 처리도 가능 할 것이다.
이를 위해 Mongo DB에서는 Exception을 통해 실패한 결과를 알려주는데 이는 글 뒤에서 예시를 알아볼 예정이다.
3. insertMany
max batch size
default batch size는 100,000이라고 한다. 만약 해당 숫자를 넘어가면 예외가 터진다고 하여 20만개를 넣고 쿼리를 실행해 봤다.
굉장히 잘된다. 왜 그럴까?
The driver only divides the group into smaller groups when using the high-level API.
If using db.runCommand() directly (for example, when writing a driver),
MongoDB throws an error when attempting to execute a write batch which exceeds the limit.
db.insertMany는 고수준의 api이고 batch size를 넘어갈 경우 자동으로 나눠서 쿼리를 실행시켜준다. docs에 나와있는db.runCommand로 실행시켜보니
실제로 실패한다. driver 단의 작업으로 직접 고민할 일은 없겠지만 알아두면 좋을 것 같아서 내용에 넣었다.
Exception (feat: ordered, unordered)
위에서 잠깐 언급했던 작업중 일부를 실패했을 경우 BulkWriteError를 반환하여 오류난 document들을 알려준다.
// duplicate key exception을 발생시키기 위해 unique index를 건다.
db.unique_index.createIndex({office_no : 1, user_no : 1}, {unique : true})
//미리 한 데이터를 넣어 놓는다.
db.unique_index.insertOne({office_no : 1, user_no : 1})
//insertMany (ordered true).
db.unique_index.insertMany([
{user_no : 2, office_no :1}
,{user_no : 1, office_no : 1}
,{user_no:3, office_no :1}
], {ordered : true})
//inserMany (ordered false)
db.unique_index.insertMany([
{user_no : 2, office_no :1}
,{user_no : 1, office_no : 1}
,{user_no:3, office_no :1}
], {ordered : false})
각각 bulk write error 메세지에서 성공한 document 수, id를 확인할 수 있다. 그리고 index를 통해 몇 번째 document에서
실패했는지 알 수 있다. (다른 예시도 insert many docs) 에서 확인이 가능하다.
spring 에서도 BulkOperationException
라는 클래스로 지원을 해주니 찾아보고싶다면 해당 클래스를 참고하면 된다.
explain
explain은 사용할 수 없다고 한다.... 다른 벌크성 쿼리도 마찬가지이다.. 왜 안해줄까
transaction
distributed transaction도 사용이 가능하다고 한다, 하지만 트랜잭션 사용에 의존하기 보단 schema modeling을 적절히 하여
문제를 해결할 것을 권장하고 있다.
4. Bulk Write
사용가능한 연산
위에서 얘기했던 것처럼 Bulk Write는 여러 종류의 연산을 bulk 로 처리할 수 있게 해준다. 다만 사용할 수 있는 연산이 제한되어져
있는데
- insertOne
- deleteOne
- updateOne
- deleteMany
- updateMany
- replaceOne
이렇게만 사용이 가능하다.
MaxBatchSize
BulkWrite도 마찬가지로 한 BulkWrite에 100,000 개의 쿼리를 넣을 수 없다.
query result
위 사진에서 볼 수 있듯이 각 연산별로 수행된 document의 수와 id를 알려준다.
만약 작업도중 오류가 발생한다면 insertMany에서 반환하던 오류와 비슷한 형태로 실패한 명령어에 대해 정보를 반환하다.
스프링에서 제공하는 BulkWriteError class는 실패한 명령어의 index정도만 제공하고 자세한 정보는 제공을 안하는듯 하다.
ordered, unordered도 insertMany와 같은 메커니즘으로 작동한다.
결론
예외 처리를 위해 좀 더 자세히 알아야겠다는 생각이 들어서 공부를 하였고 전체적인 구조는 잘 알게되었다!
하지만 실패한 document, 쿼리 처리에 있어서 mongoDB는 다양한 정보를 제공하는 반면, spring에서 제공하는 class는 정보가 제한적이라 예외 처리에 있어서 조금 불편한 감이 있다는 생각이 들었다.
예외 처리가 조금 복잡해질 경우 Bulk Write를 사용하는 것 보다는 Many를 나눠서 사용해야 좀 더 수월하게 할 수 있지 않을까
싶은 생각이다..
'ComputerScience > DataBase' 카테고리의 다른 글
MySQL GroupBy 사실과 오해 (feat: only_full_group_by, window function) (0) | 2024.05.01 |
---|---|
MongoDB 정규화, 반정규화 (0) | 2024.04.28 |
Mongo DB Sharding (feat: replication, partitioning) (2) | 2024.04.05 |
MySQL Bulk Update (feat: temporal table) (0) | 2024.03.21 |
MySQL Order By, Select For Update 최적화 하기 (0) | 2024.03.02 |