안녕하세요
마구잡이로 다 처먹고 있는 요즘입니다
요즘 성능 개선에 꽂혀서 눈에 불을 켜고 그런 거만 찾고 있는데요
쿼리 튜닝만 주구장창하다가 이게 과연 효과가 있나 싶더라고요
그래서 반정규화를 적용해 봤습니다
스따뜨
저희 챗봇 서비스에는 마이페이지에 유저가 한 질문수를 보여줘야 하는 요구사항이 있습니다
원래는 쿼리를 최적화해서 마이페이지 요청이 들어올 때마다 질문수를 계산하곤 했는데요
이게 질문수가 많아지면 노답이 될것 같은 생각이 들었습니다
GET http://localhost:8080/api/v1/members/me
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 05 Jul 2025 04:29:49 GMT
{
"status": "OK",
"message": "OK",
"data": {
"email": "test1@email.com",
"nickname": "user1",
"createdAt": "2025-07-02T12:58:52",
"questionCount": 100023
},
"code": 200
}
Response code: 200; Time: 1600ms (1 s 600 ms); Content length: 152 bytes (152 B)
역시나 그렇죠? 마이페이지 불러오는데 1.6초나 걸려버렸습니다
그래서 그냥 질문 수 필드를 유저 테이블에 만들어버리기로 했습니다
정규화라는 개념이 있죠
RDB에서 데이터 중복을 줄이고 데이터 무결성을 개선하기 위해 데이터를 구조화하는 과정인데요, 우린 이걸 반대로 해서 질문수를 미리 구한다음 유저 테이블에 새로운 필드로 만들어버릴겁니다. 이걸 반정규화라 합니다
그럼 마이페이지 요청이 들어오면 질문수를 세는 쿼리를 실행할 필요없이 멤버만 조회하면 되겠죠?
GET http://localhost:8080/api/v1/members/me
HTTP/1.1 200
Content-Type: application/json
Transfer-Encoding: chunked
Date: Sat, 05 Jul 2025 04:30:30 GMT
{
"status": "OK",
"message": "OK",
"data": {
"email": "test1@email.com",
"nickname": "user1",
"createdAt": "2025-07-02T12:58:52",
"questionCount": 100023
},
"code": 200
}
Response code: 200; Time: 132ms (132 ms); Content length: 147 bytes (147 B)
단순하게 필드에 미리 계산한 값을 넣어두니 정말 드라마틱하게 성능이 좋아진 것을 볼 수 있습니다
그치만 반정규화를 하면 데이터 정합성이 깨진다는 단점이 있습니다
그래서 이를 위한 데이터 검증 배치 작업을 정의했습니다
@Scheduled(cron = "0 0 2 * * ?") // 매일 새벽 2시
private void validateQuestionCounts() {
List<Member> members = memberRepository.findAll();
for (Member member : members) {
int actualCount = memberRepository.countQuestionsByMemberId(member.getId());
if (member.getQuestionCount() != actualCount) {
member.updateQuestionCount(actualCount);
memberRepository.save(member);
}
}
}
그치만 이 코드도 동기 실행, N+1 문제로 인한 실행 시간 문제가 있었습니다
이거에 대한 해결기는
N+1 문제와 동기 처리의 환장 콜라보
환장 듀오 N+1 문제와 동기 처리 콜라보 해결기 시작합니다스따뜨 저희 챗봇 서비스에는 마이페이지에 유저가 한 질문수를 보여줘야 하는 요구사항이 있습니다 근데 그때그때 쿼리를 날려주기
dockerel.tistory.com
아래 포스트 확인해주세요
예~~
넵
마치겠습니다