성능개선 회고
3주내내 야근하면서 성능개선 회고록

그룹장님이 이전에 성능개선을 하면서 쌓은 경험을 들려주시며 그 포인트를 중점으로 성능개선을 진행했다
서버(DB 제외) 에서 포인트 3가지가 있다
체크해야할 포인트를 3순위까지 정해 비즈니스 로직의 과정을 처음부터 끝까지 분석하며 수정한 내용들을 적어봤다
1순위. 루프가 많은경우
가장 먼저 해야 할 것은 본인이 성능개선하고자 하는 소스에 루프들을 찾는 것이다.
그 후 찾은 루프들 중에 개선할 수 있는 부분을 찾는다
특히 중첩루프는 피하는 것이 좋다
사례1
첫번째 사례는 들어온 데이터 중 기존 데이터가 있는지 판단하는 거였다.
문법 무시
const list1 = [1,2,3,4,5]; const list2 = [1,2,3]; const new_list = []; list1.map(item => { list2.map(item2 => { if(item == item2){ new_list.push(item) } }) })
위와 비슷한 구조로 되어있었다.
비즈니스 로직으로 왜 저렇게 만들었는지 구체적인 설명은 불가능하지만 변경할 수 있는 방법은 있었다
list2가 select로 불러오는 로직이기에 다음과 같이 바꿨다
const list1 = [1,2,3,4,5]; const set = new Set([1,2,3,]); list1.map(item => { if(set.has(item)){ new_list.push(item) } })
위 사항 개선으로(물론 여기서 select부분도 고쳐야 했고 데이터 비교의 변경사항도 있었지만 자세한 내용은 생략)
비교하던 로직이 측정 기준 38초 -> 2초로 개선할 수 있었다
그리고 데이터가 더 많으면 많을수록 개선효과는 당연히 커질수밖에 없어 성능 개선에 큰 효과를 봤다.
사례2
아래같은 상황들로 사용하는 경우가 빈번한데 이건 그리 효과가 크진 않았지만 여러 개가 쌓이다보니 효과가 어느정도 있었다
TS기준
const item = items.filter(item => item.key == '1') if(item){ // Do Something }
특정 키 값을 만족하는 데이터가 있는지 판단 후 있으면 특정 행동을 하도록 만든 로직이 있다
저기서 써야하는건 filter보단 some이다
filter와 some의 차이를 모르겠다면 아래 참고 링크가 도움이 될 것 같다
참고 : https://jh-7.tistory.com/11
filter는 배열의 모든 데이터를 확인해보고
some은 조건을 만족하는 순간 결과를 내뱉는다
결국 배열을 끝까지 돌 필요가 없는 상황에서는 그에 맞춰 함수를 사용하자.
2순위. 문자열
문자열이 왜 서버에 부하를 주냐? 라고 할 수 있는데
문자열이 생성되는 원리부터 봐야한다
문자열은 불변객체라 할 수 있는데 (자세한 내용은 검색을 해보면 아주 많이 나올 것이다)
결국 이 문자열이 불변 객체라 문자열에 대한 연산이 이루어질 때마다 새로운 문자열 객체가 생성된다
저 문자열 객체는 메모리 사용량을 증가시킨다
고로 무분별한 문자열 연산을 성능개선할 때 고려할 대상으로 염두해두자
문자열이 여러 파싱을 통해 한 결과물을 얻어낼 때,
중간 과정에 연산이 많다면 중간 불필요한 문자열이 모두 생긴다.
그럴때는 결과물만 뱉어내 줄 수 있는 StringBuilder 같은걸 사용하자
3순위. 잦은 인스턴스 생성
위와 비슷한 문제이다.
잦은 인스턴스 생성은 힙영역 메모리를 증가시키는 원인이 된다.
이에 더해 잦은 GC 발생으로 GC의 부하를 높일 수 있다.
+추가
이전에 변수 위치를 반복문 안이 아닌 밖에다 하고 인스턴스를 동일한 곳에 생성해야 효율이 좋다는 말을 들었던 것 같은데 찾아보니 꼭 그렇지만은 않은 것 같다
참고 : http://stackoverflow.com/questions/8803674/declaring-variables-inside-or-outside-of-a-loop
결국 인스턴스는 생성 위치가 중요하지 변수 할당의 위치는 크게 상관 없는 것으로 보인다
물론 인스턴스를 한번만 만들 수 있는걸 여러 번 만드는 건 당연히 안좋다
아래는 생성에 대한 개선 사례다
사례
특정 로직을 통하면 A 데이터가 사용자가 설정한 매핑으로 B데이터로 변경을 해준다.
예를 들면, B.name을 A의 A.id + A.name 으로 사용자가 설정했다면 여러 데이터를 동일한 방식으로 매핑되도록 변경해주는 로직이다
그럼 로직 중간에 B.name = A.id + A.name 이라는 설정이 담겨있는 객체가 있었다 (이하 mapper라는 변수로 호칭)
문제는 데이터가 100건이라면 mapper가 100번 호출되고 있었다.
mapper는 맨 처음 호출할 때 한번만 하면 되는데 그게 안에서 만들어지고 있었던 것.
datas.map(data : A =>{
const mapper = new Mapper();
const new_data : B = {};
B.properties.map(property =>{
result[property] = mapper.mapping(A, property);
})
result_list.push(new_data);
})
대충 위와 비슷한 식으로 되어있었다.
물론 이 소스만 보면 왜 저렇게 만들었냐 하겠지만 당연히 진짜 로직은 엄청나게 복잡하고 길었다.
해당 부분을 찾았을 때도 바로 찾은게 아니라 여러 번 보며 찾게 되었다.
문제는 저런 경우가 꽤 많아 모두 다 밖으로 빼서 변경하게 되었다.
setting_map = this.init(); // 여러가지 초기화가 필요한 애들을 묶어서 한방에 처리
datas.map(data : A =>{
const new_data : B = {};
B.properties.map(property =>{
result[property] = setting_map.get('mapper').mapping(A, property);
})
result_list.push(new_data);
})
위와같이 변경해 init에서 설정에 관련된 모든 애들을 처리한 후 필요한 애들을 꺼내서 사용하도록 변경했다.
기존 한개의 데이터를 처리할때 0.4초(100개일 때 40초) 걸리는 로직이 0.4초(100개일 때 10초)로 걸리도록 변경했다. 단순 선언 변경으로는 이정로도 큰 효과를 보지 못했을 텐데 설정 객체를 만들기위한 DB select 및 중첩문도 100 -> 1번으로 줄어들어 큰 효과를 보았다
위 내용 말고도 여러 가지 개선 부분이 있었지만, 크게 효과를 본 것들 대부분 위 내용이었고 진짜 포인트를 위주로 고치다 보니 개선 효과가 커 뿌듯했다.
'회사' 카테고리의 다른 글
라자다 SDK -> REST API 변환하면서 생긴 문제 정리 (2) | 2024.09.21 |
---|---|
성능개선 동등비교 정리 (0) | 2024.07.29 |
회사에서 인덱스의 중요성 경험한 사례 (0) | 2024.07.03 |
네이버 커머스 API 요청량 제한에 따른 카프카 조정 (0) | 2024.04.22 |
테스트 프로그램 구현 제안 회고 (0) | 2024.03.06 |