Database/PostgreSql

[Postgresql] 대기시간 통계 내보기, 성능개선 (explain , p50 p90 p99 latency)

yujindonut 2023. 5. 18. 17:14
728x90

상황

select(*) 로 코드를 작성하는 의미없이 생각없이 코드를 작성했다.

같이 개발하는 동아리 선생님께서 정말로 감사하게도 코드리뷰를 남겨주었다!

당시, explain = 설명해달라는 것이라고 생각해서 이런 황당한 답변을 내놓았다. 정말 앞으로의 실행계획을 써버린것이다. 상대는 정말 황당 했을 것이다.

 

정우님을 황당하게 하지말자!

다시 알아보자 Explain!


Explain 이란?

Postgres에서 제공하는 명령이다. 

EXPLAIN  을 사용하여 내가 테스트 할 sql을 작성해서 실행시키면 PostgreSQL 실행계획기가 만든 실행계획을 보여준다.

- 실행 계획은 원하는 자료를 출력하기 위해서, 어떤 테이블이 테이블 전체 순차 검색을 하는지, 인덱스 검색을 하는지를 보여준다.

- 여러 테이블이 조인이 될 경우 각 테이블들의 조인 알고리즘은 어떤것을 사용할지를 보여준다.

SQL이 얼마의 시간이 걸릴지 각 SQL 구문에 대해서 각 단계별로 실행될 작업의 cost비용을 보여준다. 이 비용은 실행계획기가 그 작업을 실행하는데 그 만큼의 비용이 들 것이라고 미리 짐작한 값이다. 

 

비용은 두 부분의 숫자로 표시됨

- 앞부분은 그 작업의 결과로 첫번째 로우를 리턴하기 전까지의 비용

- 뒷부분은 마지막 로우를 리턴할 때까지의 비용

 

대부분의 쿼리들은 그 쿼리를 실행하는데 총 비용이 어느 정도인가를 파악하면 되지만, EXISTS 구문을 사용하는 서브쿼리와 같은 경우는 가장 적은 총 비용(뒷부분 값)보다는 가장 적은 시작 비용(앞부분 값)을 사용하도록 쿼리 계획을 짠다. 

LIMIT 구문의 경우는, 그 출력 범위가 전체 가운데 어느 부분인지를 파악해서, 총비용과 함께 고려해서 그 비용을 계산한다.

 

ANALYZE 옵션을 사용하면, 실제 해당 쿼리를 실행하고, 추청 비용과 함께 소요 비용, 소요 시간도 실제 처리된 각 계획 노드별 전체 로우 수도 보여준다. 이 옵션은 실행계획기가 추정하는 작업이 실 작업과 비교해서 얼마나 정확한지를 확인하는데 유용하게 사용된다.

 


자 그럼 사용해보자.

Filter 조건에 따라서, 해당 멤버의 수를 구하는 작업이다.

 

select (*) 

QueryDsl로 member 전체를 select하고 size를 구했다.

 

전체를 조회해오는 쿼리이다. 아래 hibernate가 친절하게 sql 문을 출력해주었고, 이걸 그대로 console창에 실행시켜보자!

explain analyze select
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                    member0_.~,
                from
                    users member0_
                        inner join
                    activity activity_
                    on member.id=activity_.user_id
                group by
                    member_.id;

analyze 옵션을 붙이지 않았을때는, 예상하는 비용과 시간을 보여준다. 실제로 쿼리를 실행시키지 않는다.

analyze 옵션을 붙였다. 실제로 걸린시간과 row개수를 보여주는 것을 알 수 있다.

실제 prod 서버의 결과

총 10번을 실행해보았다.

2.140ms, 1.951ms, 20.150ms, 1.835ms, 1.782ms, 1.587ms, 1.632ms, 1.577ms, 17.907ms, 1.567ms, 2.309ms

 

몇개의 튀는 값들이 보인다. 


select (member.id) 

explain analyze select
                    member0_.id as col_0_0_
				from
                    users member0_
                        inner join
                    activity activities1_
                    on member0_.id=activities1_.user_id
                group by
                    member0_.id;

local

실제 prod 서버의 결과

 

총 10번을 실행해보았다.

2.159ms, 0.636ms, 0.635ms, 0.626ms, 0.900ms, 0.624ms, 1.972ms, 1.114ms, 0.621ms, 0.974ms, 4.595ms

 

 

한두번 실행할때는 모르지만, 여러번 실행해보았을때 큰 폭으로 요청의 속도차이가 나는 것을 확인할 수 있다.


더 나아가, Jmeter에서도 속도 개선을 위해 실행을 해보았었다.

https://vanillacreamdonut.tistory.com/347

 

[Querydsl] Jmeter를이용한 속도 테스트 / Entity 조회와 id 조회 성능 차이

문제 상황 1000명 정도 되는 유저의 멤버 리스트 조회하는 과정에서 쿼리가 총 2번 나가야하는 상황이 발생했어요. 1. 멤버 정보를 전체 조회하는 쿼리 + 필터링 포함 2. 필터링에 포함되는 멤버를

vanillacreamdonut.tistory.com

결과값에서 나왔던 median, 90% line, 99%line 이 대체 뭘까!

알아보자!


p50 p90 p99 latency(대기시간)

대기 시간은 데이터 패킷이 출발지에서 목적지까지 이동하는 데 걸리는 총 시간

 

p50 – 50번째 대기 시간 백분위수 : 요청의 50%가 p50 값보다 빠르다
p90 – 90번째 대기 시간 백분위수 : 요청의 90%가 p90 값보다 빠르다.

p99 – 99번째 대기 시간 백분위수 : 요청의 99%가 p99 값보다 빠르다.

 

위에 결과를 보면 알다시피, 평균을 내보면 큰차이가 보이지 않는다. 하지만 간혹가다 한두개의 API의 latency가 큰 것을 알수있다. 

latency가 적게하기 위해서는 평균을 빠르게 개선하기보다, 한두개의 튀는 API의 느린 요청을 개선하는것이 중요함을 알 수 있다.

HTTP 기반 웹 애플리케이션의 P99 대기 시간이 2밀리초 이하라고 하면 99 웹 호출의 %가 2밀리초 미만의 응답으로 서비스된다는 의미이다.

 

왜 한두개의 튀는 값이 나오면 좋지않은걸까? 왜 균일한 API 요청이 좋은걸까?

사람들은 종종 데이터 세트를 설명하기 위해 최소값, 평균값, 중앙값 또는 최대값과 같은 기본 통계 측정값을 사용한다. 이러한 통계의 문제점은 데이터를 설명하는 데 적합하지 않다는 것이다.

 

위의 예에서는 500개 또는 1000개의 row로 실행을 해보았다. 만약, 이게 만개 또는 10만개라고 생각을 해보자. 만개의 10%면 1000개의 요청, 1%면 100개의 요청이다. 몇천 몇만 몇천만개의 요청에 대한 대기시간이 길어질수록, 성능이 안좋은 것이다.

 

평균과 중앙값은 종종 이상값을 가리는 반면 최소값이나 최대값은 이상값을 제공할 수 있다. 네트워크 관리자는 99th 네트워크 대기 시간의 백분위수를 최적화하여 최대 로드 시 전체 응답 시간을 개선하는 것을 목표로 한다고 한다. 오탐률이 높지 않기 때문에 모니터링을 위해 백분위수 기반 경고를 사용한다. 이런 경고는 변동성이 훨씬 적으므로 중요한 성능 저하 이벤트를 나타낸다.

따라서 P99 대기 시간은 거의 모든 대기 시간보다 크므로 이를 최적화하는 것은 최악의 경우 성능을 최대화하는 것과 같다. 그러나 P99는 최대값이 아니므로 이상값의 영향을 받지 않을 것으로 예상되는것.

 

따라서 99%, 90%의 값이 낮을 수록 빠른 API라 할 수 있겠다!

 


 

참조글

 

728x90