mongoose를 사용해서 subDocument의 크기가 큰 데이터를 mongodb에 넣을때 너무 느린 현상이 발생했다. 해당 현상에 대한 원인과 해결 방법을 알아보자.
발생상황
약 2MB의 데이터를 mongoose에 넣는데 너무 느린 현상이 발생했다.
해당 document의 구조는 아래 코드와 같았다.
const TestDataSchema = new mongoose.Schema({
// 약 40개의 raw
...
},{
_id: false,
}
);
const testSchema = new mongoose.Schema(
{
testUser: { type: String, required: true, index: true },
testUUID: { type: String, required: true, index: true },
dataList: [
{
type: TestDataSchema,
required: true,
},
],
},
{
timestamps: {
createdAt: true,
updatedAt: true,
},
}
);
const Test = mongoose.model('Test', testSchema);
export { Test };
위 스키마의 구조에서 subDocument의 크기만으로 약 2MB의 크기였고, 해당 데이터를 가공없이 save()를 사용해서 db에 insert할때 2초정도 걸렸다. 원인을 분석하고 해결해보자.
1. 네트워크 속도 문제인가?
해당 서버가 올라가있는 ec2 인스턴스는 좋은 성능은 아니였다. 그래서 첫 번째로 네트워크 자체의 속도 문제를 의심 해 보았다.
가설
네트워크 속도 문제라면 동일한 서버가 서로 다른 리전에 있다고 가정했을 때 monodb에 insert시 속도가 달라야 한다.
검증
EC2 API는 도쿄 리전에 있다고 가정.
EC2 DB는 도쿄 리전에 있다고 가정.
Local API는 서울에 있다고 가정.
1. 같은 VPC와 subnet안에 있는 다른 EC2 인스턴스에서 DB로 insert시 약 2초가 걸렸다. ex) 2100ms
2. Mongodb가 설치되어 있는 ec2 인스턴스 안에서 서버를 켜서 insert 시 약 2초가 걸렸다. ex) 2000ms
3. 내 로컬 PC에서 서버를 실행하여 EC2 DB로 insert시 약 2초가 걸렸다 ex) 2100ms
결론
네트워크 문제가 아니다. 네트워크 문제였다면 위 사진에서 EC2 API와 Local API에서 2초가 나왔더라도 DB가 설치되어 있는 EC2 DB에서 insert 했을 시에는 2초 보다 훨신 빨랐어야 한다.
2. EC2 인스턴스의 성능 문제인가? ( I / O )
가설
mongodb에 mongoImport를 사용해서 insert 해서 똑같이 2초 정도 시간이 걸린다면 I/O 가 느린게 맞다.
검증
EC2 API는 도쿄 리전에 있다고 가정.
EC2 DB는 도쿄 리전에 있다고 가정.
Local API는 서울에 있다고 가정.
mongoImport를 사용해서 위와 동일하게 진행해서 속도를 확인해본다.
1. 같은 VPC와 subnet안에 있는 다른 EC2 인스턴스에서 DB로 insert시 약 0.1초가 걸렸다. ex) 100ms
2. Mongodb가 설치되어 있는 ec2 인스턴스 안에서 서버를 켜서 insert 시 약 0.1초가 걸렸다. ex) 100ms
3. 내 로컬 PC에서 서버를 실행하여 EC2 DB로 insert시 약 0.5~0.6초가 걸렸다 ex) 545ms
결론
I/O 문제도 아니다. I/O 문제였다면 같은 인스턴스에서나 다른 인스턴스에서 mongoImport를 사용했을때 위 속도와 비슷한 2초정도가 나왔어야 한다.
로컬에서 insert시 0.5초 정도 걸렸으니 네트워크 속도가 약 0.3~0.4초라고 예측이 가능하다.
3. mongoose 사용 문제인가?
가설
insert하고자 하는 subDocument의 크기가 큰데 mongoose는 자체 모델로 변환한 후 작업을 한다고 알고 있다. 이 작업 자체가 느린건가?
검증
아래 두 함수의 속도를 비교
insert(dataList: any[]) {
return new Promise((resolve, reject) => {
const test = new Test({
testUser: "testUser1",
testUUID: "testUUID1",
dataList: dataList,
});
test
.save()
.then((result: any) => resolve(result))
.catch((err) => reject(err));
});
}
async insert2(dataList: any[]) {
const test = await Test.collection.insertOne({
testUser: "testUser2",
testUUID: "testUUID2",
dataList: dataList,
});
return test;
}
insert()는 기존 mongoose를 사용해서 insert하는 코드이고
insert2()는 컬렉션에 바로 접근해서 직접 db에 insert하는 코드이다.
둘의 속도를 비교해서 가설이 맞는지 검증해보자.
insert()의 속도는 약 2초정도 걸렸고
그에 반해 insert2의 속도는 0.3초 밖에 걸리지 않았다. <- 위에서 mongoImport를 사용했을때와 비슷한 시간
결론
맞다. subDocument의 크키가 클 때 mongoose를 사용해서 model로 변환하는 과정에서 메모리를 너무 많이 사용해서 느렸었다.
결과
해당 스키마는 이슈가 있어서 이슈가 해결 될 때 까지 collection에 직접 접근해서 insert 하자.
async insert2(dataList: any[]) {
const now = new Date();
const test = await Test.collection.insertOne({
testUser: "testUser2",
testUUID: "testUUID2",
dataList: dataList,
createdAt: now,
updatedAt: now,
});
return test;
}
해당 방법으로 insert시 mongoose에서 자체적으로 지원하는 createdAt 이나 updatedAt 등 Model 옵션을 사용하지 못한다. 직접 넣어주자.
이에 관련해서 현재 열려있는 issue
https://github.com/Automattic/mongoose/issues/11541 ⇒ 2022년 7월 3일 기준으로도 open
참고 문서
노가다 및 내 머리
'DataBase > MongoDB' 카테고리의 다른 글
[Error] MongoServerError: ns not found (0) | 2022.11.08 |
---|---|
[Mongoose] mongoose 에서 statics method와 instance method의 차이점 (0) | 2022.07.14 |
[MongoDB] MongoDB Version Upgrade 하는 방법 (Standalone) (0) | 2022.05.12 |
[MongoDB] MongoDB에서 계정 생성 (0) | 2022.05.10 |
[MongoDB] linux(unbuntu)에 mongodb 설치하기 (0) | 2022.05.08 |