일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- java 자료구조 활용
- 격파르타합격후기
- java map
- java list 출력
- 항해15기
- 항해99후기
- java알고리즘
- 비전공자sqld
- java list 저장
- 인터프린터언어
- 작은수제거하기
- 노베이스부트캠프
- 프로그래머스
- 프로그래머스제일작은수
- 격파르타비전공자
- java알고리즘문제풀이
- 격파르타장점
- java최솟값구하기
- 격파르타후기
- java map 출력
- java기본자료형
- sqld자격증합격
- javaJVM
- javaJRE
- java참조자료형
- java map 저장
- java set 저장
- 컴파일
- 코딩부트캠프후기
- java set 출력
- Today
- Total
코딩과 결혼합니다
Aggregation으로 수많은 쿼리를 한방 쿼리로. 본문
아주 복잡하고 보기 싫은 내 코드.. ㅎㅎㅎ
/**
* 게시물 전체 조회 + 페이징
*/
async getPosts(
page: number = 1,
limit: number = 10,
category: Category
): Promise<ResponsePostsDto> {
//카테고리 값 검증
this.validateCategory(category);
const currentPage = page || 1;
const pageSize = limit || 10;
const skip = (currentPage - 1) * pageSize;
const query = { category };
const posts = await this.postRepository.findWithPagination(
query,
skip,
pageSize
);
// 게시물 DTO 변환
const responsePosts = await Promise.all(
posts.map(async (post) => {
const empathyCount = await this.postEmpathyService.getEmpathyCount(
post._id.toString()
);
const images = category === "review" ? post.images : [post.images[0]];
const profileImg =
post.memberId?.profileImg || process.env.DEFAULT_PROFILE_IMAGE_URL;
// 댓글 수 조회
const comments = await this.commentService.findCommentsByPost(post);
const totalCommentCount = comments.reduce(
(total, comment) => total + (comment.commentCount || 0),
0
);
return new postinfo(
post._id.toString(),
post.nickname,
post.title,
post.text,
images,
post.createdAt,
post.ip,
profileImg,
empathyCount,
totalCommentCount
);
})
);
return new ResponsePostsDto(
SMESSAGES.RETRIEVED_SUCCESSFULLY,
category,
responsePosts
);
}
전체 조회를 하면서 각 포스트의 공감 수와, 총 댓글 수를 함께 보여주며 문제가 생겼다.
물론 프런트에서 정보들을 조합할 수도 있고, 방법은 여러 가지였지만
프런트 작업하시는 분도 나와 같은 아직은 귀엽고 부족한 인턴이기에 다른 일에 허덕이고 계셔서 😂
일단 기간을 맞추고 이후에 상의를 통해 더 좋은 방향을 찾아보기로 하였다.
일단 상세조회를 하면 그 포스트에 딸린 댓글들이 나오고,
그 댓글들에서 더 보기를 누르면 sub댓글들이 나오는 형식이다. (댓글, 대댓글 스키마 분리)
총 댓글 수를 셀 때마다 일일이 댓글, sub 댓글을 세어볼 수는 없는 것이기에 sub댓글이 등록될 때마다
async updateCommentCount(commentId: string): Promise<void> {
await this.commentModel.findByIdAndUpdate(commentId, {
$inc: { commentCount: 1 }
}).exec();
}
이렇게 commentCount가 1씩 올라가게 하였다.
그렇게 post에서는 각 comment의 commentCount들을 더해서 총 댓글 수를 구하는 것이다.
- 포스트 전체조회
- 각 포스트에 달린 댓글을 조회
- 그 댓글들의 commentCount 조회
- commentCount를 모두 더해줌
- totalCommentCount 반환
이런 순서인데,,, 그렇다,, 너무 별로다. 별로인걸 알지만 일단 고!!!!!!!!!해보았다.
전체 조회를 할 때마다 저렇게 하나하나 쿼리가 나가고 있다.
postMan으로 응답까지 걸리는 시간을 확인해 보았을 때에는 223ms
나는 이전에 jpa를 공부했을 때 배웠던 fetch join을 떠올렸다.
바로 한 방쿼리로 데이터를 가져오는 것.
nest.js도 그런 기능이 있지 않을까? 싶었는데, 있었다. 바로 Aggregation
/**
* 게시물 전체 조회 + 페이징
*/
async getPosts(
page: number = 1,
limit: number = 10,
category: Category
): Promise<ResponsePostsDto> {
//카테고리 값 검증
this.validateCategory(category);
const currentPage = page || 1;
const pageSize = limit || 10;
const skip = (currentPage - 1) * pageSize;
const query = { category };
const posts = await this.postRepository.findWithAggregation(
query,
skip,
pageSize
);
const responsePosts = posts.map((post) => new postinfo(
post._id.toString(),
post.nickname,
post.title,
post.text,
post.images,
post.createdAt,
post.ip,
post.profileImg,
post.empathyCount,
post.commentCount
));
return new ResponsePostsDto(
SMESSAGES.RETRIEVED_SUCCESSFULLY,
category,
responsePosts
);
}
위는 서비스 코드 아래는 repository 코드이다.
async findWithAggregation(
query: any,
skip: number,
limit: number
): Promise<any[]> {
return this.postModel.aggregate([
{ $match: query }, // 쿼리로 필터링
{ $skip: skip }, // 페이징 처리
{ $limit: limit }, // 페이지 크기
{
$lookup: {
from: 'comments', // comments 컬렉션과 조인
localField: '_id', // posts의 _id 필드
foreignField: 'post', // comments의 post 필드
as: 'comments' // 결과를 comments 배열로
}
},
{
$lookup: {
from: 'empathies', // empathies 컬렉션과 조인
localField: '_id', // posts의 _id 필드
foreignField: 'postId', // empathies의 postId 필드
as: 'empathies' // 결과를 empathies 배열로
}
},
{
$addFields: {
commentCount: { $size: '$comments' }, // 댓글 수 계산
empathyCount: { $size: '$empathies' } // 공감 수 계산
}
},
{
$project: {
title: 1,
text: 1,
commentCount: 1,
empathyCount: 1,
images: 1,
createdAt: 1,
ip: 1,
profileImg: { $ifNull: ['$memberId.profileImg', process.env.DEFAULT_PROFILE_IMAGE_URL] } // 프로필 이미지
}
}
]).exec();
}
코드가 좀 길지만...
이렇게 한 방의 쿼리로 데이터들을 가져왔다.
시간도 223ms에서 40ms로 줄일 수 있었다.
Aggregation Pipeline
데이터베이스에서 필요한 데이터를 한 번의 쿼리로 집계할 수 있다.
애플리케이션이 여러 번의 쿼리를 실행할 필요가 없으므로 서버의 부담이 줄어든다.
Aggregation을 사용하여 댓글 수와 공감 수 같은 집계 데이터를 서버에서 직접 계산하여 프런트엔드에서 별도로 추가 요청을 보내지 않아도 되고, 데이터의 일관성을 유지한다.
캐싱, 프런트에서 데이터 조합 그리고 스키마를 변경하는 등 여러 방법이 있었다. 일단은 이렇게 최적화를 해보았으나, 프로젝트의 규모나 상황에 따라서 프런트분과 상의를 통해 가장 최선의 방법이 있다면 그 방법을 채택해야겠다.
'2세 > Nest.js' 카테고리의 다른 글
Mongoose와 Spring JPA (0) | 2024.08.19 |
---|---|
몽고DB 스키마 인덱스 삭제하기 (0) | 2024.08.14 |
비동기 | async와 await (0) | 2024.08.13 |
[오류] MongooseError: The `uri` parameter to `openUri()` must be a string, got "undefined". | @nestjs/config (0) | 2024.08.13 |
[오류]Cannot find module '@nestjs/common' or its corresponding type declarations. (0) | 2024.08.12 |