Skip to main content Link Menu Expand (external link) Document Search Copy Copied

최근 API 응답 최적화 관련 질문을 받았꼬 해당 방법에 대해 진짜 시작부터 끝까지 하나의 플로우로 정리해봤다. 일단 API 는 유저 정보를 불러오는 API 이며 내부망 API 2번 및 DB 1번 호출이 포함되어 있다고 가정한다.

API 병목지점 찾기

  • 전체 호출에서 각 구간별 소요시간을 측정한다.
    • 만약 모니터링 도구와 연동되어있다면 APM 으로 API 호출 후 DB 쿼리 호출타임을 확인해보고 인덱스 잘 걸려있는지 체크한다.
    • 만약 APM 이 없다면 로그를 찍어서 grafana 로 통계를 내던 elk 로 체크하던 평균통계를 확인한다.

DB 병목?

  • DB select 쿼리 인덱스 잘 걸려있는지 explain 으로 확인한다.
    • 인덱스가 카디널리티가 낮은 필드에 걸려있다면 인덱스를 제거하거나 재설계한다.
  • read 전용 replica DB 를 추가하여 읽기 부하를 분산시킨다.
  • 쿼리에서 이상한 점 보이면 쿼리 튜닝을 진행한다. 이거 할 떄 마찬가지로 APM 툴 붙여놓으면 확인하기 편함. 아니면 따로 grafana 로 쿼리별 응답시간 통계내도 됨.
    • 서브쿼리 줄이기
    • 불필요한 필드 조회 줄이기
    • N+1 쿼리 체크
    • offset 대신 인덱스 타고 조회하도록
  • DB 세션이 부족한지도 체크해서 커넥션 풀 늘리기

내부망 API 병목?

  • 일단 동시요청 하도록 변경
    • 이거 스레드가 천천히 늘어나기떄문에 미리 warm-up 시켜놓기
  • gzip 페이로드 압축 굳이?
    • 내부망은 RTT 가 낮기 때문에 gzip 압축에 따른 CPU 오버헤드가 더 커질 수 있음
  • 이진포멧 페이로드 쓰는 gRPC 로 변경해서 더 빠르게 전송하도록 및 네트워크 대역폭도 아끼도록

마지막으로 전체적으로 개선

  • 유저 요청자체가 모두 타임아웃 걸릴정도로 시간이 오래걸린다면?
    • 서버에서 그냥 이벤트 id 를 먼저 발행 응답(클라에서 해당 id 로 폴링하던 sse, socket 으로 받던 비동기로 변경) 및 이벤트를 캐시나 db 에 올림.
    • 큐를 읽고 다른 곳에서 처리하고 이벤트 id 로 해당 이벤트 저장된 곳에 완료/실패 처리 기록
  • 유저정보 실시간 필요한지 확인
    • 실시간 제공 아니라면 캐싱 레이어 추가
  • DB, 내부망 API 호출 모두 캐싱 해서 개별 응답속도 개선할 수 있음
  • 어플리케이션에서 스레드 개수 적당한지 체크
    • Spring-boot 프레임워크에서 MVC 모델이면 톰캣 스레드풀 조정
    • 부족하면 WebFlux 모델로 변경해서 논블로킹 I/O 모델로 변경 고려(이건 내부망 API 가 논블로킹 지원해야함)
    • 내부망 API 요청에 N:M virtual thread 로 변경 고려. 외부 API 요청은 어차피 전송하고 CPU 안쓰고있어서 이거 써서 네이티브 스레드 쉬게해주는게 좋음.
  • 내부적으로 사용하는 공통 정보들을 어플리케이션 뜰 때 미리 로딩해놓는 warm-up 프로세스 만들어놓기