2024. 5. 14. 15:13ㆍ개발공부
왜 당신의 API 응답이 느린가요? 이런 문제를 해결해야 할 필요가 있을수 있습니다.
백엔드 개발자로서, 프론트엔드 웹에 데이터를 제공하기 위한 HTTP REST API이든, 내부에서 사용을 위한 RPC API이든 우리는 항상 다양한 API들을 작성하고 있습니다.
이 API들은 서비스 초기 단계에는 잘 작동할 수 있지만, 사용자 수가 증가함에 따라 처음에는 빠르게 응답하던 API들이 점점 느려지기 시작하면서 사용자들이 “당신의 시스템에 문제가 있어요. 그저 웹 페이지를 보기만 하는데 왜 이렇게 느린거죠?” 이러한 불만을 표출하기 시작할 수 있습니다. 이때, 여러분은 API 성능을 최적화 하는 방법을 고려해야 할 필요가 있습니다.
API 성능을 향상시키기 위해서는 먼저 인터페이스의 느린 응답을 초래할 수 있는 문제점을 파악해야 합니다. API 설계는 많은 측면을 고려애야 합니다.
개발 언어 수준은 일부분에 불과합니다. 설계가 잘못된 부분이 성능 병목 현상을 초래할 것입니다. API 성능에 영향을 미칠 수 있는 많은 요소들이 있을 수 있으며, 다음과 같이 요약할 수 있습니다.
- 데이터베이스의 느린 쿼리
- 복잡한 비지니스 로직
- 낮은 성능의 코드
- 부족한 리소스
네번째 포인트인 부족한 리소스는 상대적으로 해결하기 쉽습니다. 시스템에 일정한 확장성이 있다면 서버를 추가함으로써 해결 할 수 있습니다. 최적화가 필요한 다른 포인트에 대해서는 시나리오에 따라 해결책을 제시해야 합니다. 낮은 성능 코드에 대해서는 사실 저는 이전에 Java 코드의 성능 최적화에 대한 글을 작성한 적이 있으며, 관심있는 독자들은 읽어보세요.
이 글에서는 저는 몇 가지 효과적인 API 성능 최적화 기술을 요약하였습니다. 필요한 독자들에게 도움이 되길 바랍니다.
병렬 호출
이제 주문을 할 수 있는 전자 상거래 시스템이 있다고 가정해봅시다. 이 기능은 재고 시스템을 호출하여 재고 검사와 차감을 수행해야 하며, 사용자 주소 정보도 가져와야 합니다. 마지막으로 위험 관리 시스템을 호출해 이 거래가 위험하지 않다는 것을 확인해야 합니다. 이 인터페이스의 대부분의 설계는 순차 실행 인터페이스로 설계될 수 있습니다. 결국, 다음 단계로 진행하기 전에 사용자 주소 정보를 얻고 재고 차감을 완료해야 하기 대문입니다. 의사(Pseudo) 코드는 다음과 같이 작성될 수 있습니다.
만약 이 기능을 분석하면, 여러 메서드 호출간의 강한 의존성이 없다는 것을 알 수 있습니다. 또한 이 세 서비스의 호출은 시간이 많이 걸립니다. 이 서비스들의 시간 소모적인 호출은 다음과 같이 분포된다고 가정합니다.
- stockService.check() 150ms.
- addressService.getByUser() 200ms.
- riskControlSerivce.check() 300ms.
이 API가 순차적으로 호출된다면, 전체 API의 실행 시간은 650ms(150ms + 200ms + 300ms)입니다. 이를 병렬 호출로 전환할 수 있다면, 시간은 300ms가 되며, 성능은 50% 향상됩니다. 의사(Pseudo) 코드는 아래와 같이 작성될 수 있습니다.
대규모 트랜잭션을 피해라.
소위 말하는 대규모 트랜잭션이란 오랜 시간이 걸리는 트랜잭션을 말합니다. 스프링의 @Trasaction 을 사용하여 트랜잭션을 관리할 경우, 실수로 대규모 트랜잭션을 시작하지 않았는지 주의해야 합니다. 스프링의 트랜잭션 관리 원칙은 여러 트랜잭션을 하나의 실행으로 병합하는 것이기 떄문에, API에서 다수의 데이터베이스 읽기 및 쓰기가 있고 이 API의 동시 접근량이 상대적으로 높은 경우, 대규모 트랜잭션으로 인해 데이터베이스에 과도한 데이터가 Lock 상태가 되며 많은수의 block이 발생하여 데이터 베이스 연결 풀이 고갈될 수 있습니다. 위의 예를 수정하는게 좋습니다.
많은 사람들이 작성한 비지니스 코드에서 이러한 종류의 코드가 등장했다고 믿습니다. RPC와 같은 non-DB 작업이 영송성 계층 코드와 혼합되어 있습니다. 이러한 코드는 확실히 대규모 트랜잭션 입니다. 사용자의 주소를 조회하고 재고를 차감할 필요가 있을 뿐만 아니라, 주문 데이터와 주문 세부 정보를 insert(삽입) 해야 합니다. 이러한 일련의 작업은 동일한 트랜잭션으로 병합해야 합니다. RPC 응답이 느린 경우, 현재 스레드가 항상 데이터베이스 연결을 차지하여 동시성 시나리오에서 데이터베이스 연결이 고갈됩니다. 뿐만 아니라, 트랜잭션이 롤백되어야 하는 경우, 롤백이 느려서 API 응답이 느려집니다.
이때, 비지니스를 축소하는것을 고려해야 합니다. 비트랜잭션 작업과 트랜잭션 작업을 다음과 같이 분리할 수 있습니다.
또는 스프링의 프로그래밍 방식인 트랜잭션의 TransactionTemplate 을 사용할 수 있습니다.
적절한 인덱스 추가
서비스가 운영 초기 단계에 있을 때, 시스템이 저장해야 할 데이터의 양은 적습니다. 데이터베이스가 데이터를 빠르게 저장하고 접근하기 위해 인덱스를 추가하지 않았을 수도 있습니다. 하지만 비지니스가 성장함에 따라 단일 테이블의 데이터의 양이 계속 증가하고, 데이터베이스의 쿼리 성능이 저하됩니다. 이때, 데이터베이스 테이블에 적절한 인덱스를 추가해야 합니다. 테이블의 인덱스를 보려면 아래와 같이 명령어를 사용할 수 있습니다. (여기서는 MySQL로 예를 듭니다.)
SHOW INDEX FROM `your_table_name`;
ALTER TABLE 명령어를 이용해 인덱스를 추가합니다.
ALTER TABLE `your_table_name` ADD INDEX index_name(username);
때떄로, 몇몇 인덱스를 추가하더라도 데이터 쿼리가 여전히 느릴 수 있습니다. 이때는 explain 명령어를 사용하여 실행 계획을 보고 SQL 문이 인덱스를 활용하고 있는지 확인할 수 있습니다. 예를들어 아래와 같이 분석결과를 얻을 수 있습니다.
EXPLAIN SELECT * FROM product_info WHERE type=0;
일반적으로, 인덱스 누락의 몇 가지 경우가 있습니다.
- 가장왼쪽 접두사(prefix) 원칙이 충족되지 않았습니다. 예를 들어, tb1 에 대해 결합 인데스 idx(a,b,c) 를 생성했지만, SQL문이 select * from tb1 where b='xxx' and c='xxxx'; 와 같이 작성되었을 경우입니다.
- 인덱스 열에 산술 연산이 있는 경우 입니다.
- select * from tb1 where a%10=0;
- like 키워드를 사용한 Fuzzy 쿼리입니다.
- select * from tb1 where a like '%aaa';
- not in 또는 not exist 키워드를 사용한 경우
적은 데이터를 반환
만약 많은 양의 데이터를 쿼리로 조회할때, 모든 데이터를 반환할 필요가 없습니다. 우리는 페이징 형식으로 점진적으로 데이터를 제공할 수 있습니다. 이 방법으로 네트워크를 통해 전송되는 데이터 양이 줄어들고, 데이터 인코딩 및 디코딩 시간이 줄어들며, API 응답 속도가 빨라집니다.
그러나 전통적으로 limit offset 페이징 방법이 사용됩니다. (select * from product limit 10000,20) 페이지 수가 많을 때, 쿼리는 점점 느려집니다. 이는 limit offset 페이징 방식의 원칙이 10000개의 데이터를 찾아내고 이전의 9980개의 데이터를 버리느 ㄴ것이기 때문에 우리는 지연된 상관 관계를 사용해 SQL을 최적화 할 수 있습니다.
select * from product where id in (select id from product limit 10000,20);
캐시 사용
캐싱은 공간을 시간으로 바꾸는 해결책 입니다. 일부 사용자에 의해 자주 접근되는 데이터는 메모리에 직접 캐시됩니다. 메모리의 읽기 속도가 디스크 IO보다 훨씬 빠르게 때문에 적절한 캐싱을 사용하여 API의 성능을 향상 시킬 수 있습니다. 간단히 말해, Java HashMap, ConcurrentHashMap 또는 로컬 캐시(ex: caffeine), 분산 캐시 미들웨어(ex) Memcached, Redis)를 사용할 수 있습니다.
결론
여기에서 다섯가지 일반적인 API 성능 최적화 요령을 나열했습니다. 이 요령들은 시스템에 일정한 동시성 요청이 있을 떄만 효과적입니다. 만약 여러분의 시스템 트래픽이 크지 않다면, 위의 제안들을 신중하게 고려해주세요. 더 효과적인 해결책을 찾아보셔야 합니다. 마지막으로 이 글이 여러분들에게 도움되었기를 바랍니다.
https://rutgo-letsgo.tistory.com/400
Five API Performance Optimization Tricks that Every Java Developer Must Know(모든 자바 개발자가 알아야 할 다섯 가
💡 원본글 Five API Performance Optimization Tricks that Every Java Developer Must Know Five API Performance Optimization Tricks that Every Java Developer Must Know Why is your API response so slow? Maybe you need to solve these problems. medium.com
rutgo-letsgo.tistory.com
'개발공부' 카테고리의 다른 글
JPA로 생성한 entity column에서 unique키 삭제하기 (0) | 2024.05.08 |
---|---|
keyCode 표 (0) | 2024.05.03 |
[Spring JPA] JPQL (0) | 2024.04.30 |
프로젝트 build 및 배포 (0) | 2024.04.26 |
Spring Data JPA Query (0) | 2024.04.26 |