티스토리 뷰

이번에는 제가 실무에서 처음 겪었던 API 속도 문제와 이를 해결하기 위해 사용했던 DB 튜닝 경험에 대해 이야기해보려고 합니다.

처음에 접근했을때 API 속도는 현저히 느린부분이 많았습니다. 이러한 부분을 해결하기 위해 여러가지 테스트를 하면서 DB쪽 Layer에서 속도가 떨어지는 부분을 확인할 수 있었습니다.

 

DB 튜닝은 다양한 방법으로 접근 할 수 있습니다.

  • 쿼리 최적화
  • 데이터 구조 최적화
  • 인덱스 튜닝
  • 서버 메모리 설정
  • 데이터베이스 설정
  • Application 단계의 설정

현재 시점에서 저가 할 수 있는 부분으로 DB 튜닝을 하고자 했습니다.

쿼리 최적화 진행했던 방법은 다음과 같습니다.

서브 쿼리를 Join으로 변경하기

서브쿼리를 지우고 Join으로 변경 함으로서 쿼리 최적화를 진행 할 수 있습니다.

서브쿼리는 연산이기 때문에 row 하나당 연산을 해야하는 N+1 문제가 발생합니다.

그리고 해당 결과값을 조합해야 하기 때문에 컬럼에 대한 최적화도 힘들게 됩니다.

left join을 inner join으로 변경하기

inner join을 left join으로 바꿨을때 속도가 향상된 경우도 있었고

left join을 inner join으로 바꿧을때 향상된 경우도 있었습니다.

join 최적화를 공부했을땐 inner join 자체가 빠르다는 개념으로 알고 있었지만 실무에서 적용해보니 다르니 이거에 대해 한번 궁금했었는데 찾아보니 다음과 같은 이유라고 합니다.

 Here's the lowdown: there's not a clear-cut answer to whether a left or inner join is faster, that's because the speed is not really about the join type.

The database engine is looking at your tables and trying to figure out the best way to get the data you need. It doesn't really care if the join is inner or left, it's focusing more on things like the table sizeindexes, and columns used in conditions.


Chances are, if both your queries are well-optimized, you'll witness similar performance whether you use a left or inner join. Optimization, that's a catchy word here and it's all about knowing how to play the game right.


So, next time you're hammering out a SQL statement, keep this in mind. Indexes are your friend, they can make lookups faster


Join 자체에서는 inner join, left join 모두 최적화가 잘 되어 있으며 inner, left에 대한 속도에 관한 이유보다는 DB에서 테이블 크기, 인덱스, 열과 같은 항목에 중점으로 보기 때문에 상황에 맞게 사용 하는것이 맞다 라고 합니다.

Where 절을 더 추가하기

where절을 더추가하면 명령문이 추가되면 적은 데이터 상에서는 느릴 수 있겠지만 많은 데이터 에서 사용할 경우

데이터 양 감소가 되면서 쿼리 실행속도가 빨라지는 경향이 있었습니다.

그리고 옵티마이저에서는 데이터를 필터링을 빨리 처리하기 때문에 다른 연산을 하기 전 데이터 양을 줄이기 때문

에 쿼리 실행이 효율적으로 됩니다.

인덱싱

인덱싱이 안되어 있는 부분들을 B-Tree 인덱스로 진행했습니다. 인덱싱 했던 기준은 Join문에서 상당히 오래 연산시간이 진행된다면 join 비교 컬럼을 인덱싱 처리를 진행 했습니다.

역정규화

이 부분을 진행했던이유는 N+1 발생 때문에 했던 이유인데 코드로 설명을 드리자면

private final UserRepository userRepository 
private final AccessaryRepository accessaryRepository

public List<User> getUserList(){
	return userRepository.getList()
            .stream()
            .map(user -> {
                    List<Ring> rings = accessaryRepository.getRing(user)
                    Necklace necklace = accessaryRepository.getNecklace(user)
                    Bracelet bracelet = accessaryRepository.getBracelet(user)
                    int accessaryCount = accessaryRepository.getCount(user)

                    user.wearRings(rings)
                    user.wearNecklace(necklace)
                    user.wearBracelet(bracelet)
                    user.updateAccessaryCount(accessaryCount)
            }).collect()
}

List를 조회하고 List 갯수 만큼 하위 정보도 조회를 하고 있는 것으로 보입니다. 하지만 이렇게 조회할 경우 N+1발생하여 느린 조회 결과가 발생합니다.

만약 위 데이터를 테이블로 간단하게 만들면

좀 이상한 예시지만.. 참고용으로 ㅎㅎ 실제는 이것보다 더 복잡

 

이 테이블을 보면 ring과는 1:N 관계이지만 나머지는 1:1 관계로 매칭되어 있습니다.

여기서 저는 1:1 매칭부분을 역정규화를 통해서 user 테이블에 넣었습니다.

그리고 갯수 같은 부분도 user 테이블에 추가를하여 연산을 따로하지않고 미리 계산된 갯수를 확인하여 처리를 진행 했습니다.

하지만 해당 방법은 Trade off 인데 SELECT 성능이 좋아지지만 INSERT 부분에서는 성능이 떨어지는

Trade off 상황이 발생합니다.

그래도 사용자가 많이 사용되는 부분을 성능을 향상시키고 덜 사용되는 부분을 낮추는것이 좋다 판단을 하여 진행했습니다.

변경된 이미지

private final UserRepository userRepository 

public List<User> getUserList(){
	return userRepository.getList()
} 

## SQL
SELECT * FROM user
JOIN ring ON ring.user_seq = user.seq

간략하게 변경이 되었으며 해당 방법으로 30%이상 속도가 개선되었습니다.

그외..

Having절 보단 Where을 사용하기.. DISTINCT를 지향하기, UNION 보다는 UNION ALL 사용하기 등 다양한 쿼리 최적화 부분이 많지만 해당 부분은 사용하지 않았던 부분이라 생략하도록 하겠습니다.

공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2026/04   »
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
글 보관함