728x90
반응형

일을 하면서 서버 에러로그에 언제부턴가
HikariPool-1 - Connection is not available, request timed out after 3000ms
위와 같은 에러 문구가 특정 주기를 가지고 계속 노출되었다.

일단 여러가지 문제가 있었는데, 먼저 해결된 것부터 써보려고 한다.

HikariCP pool size

일단 내가 개발하는 서비스 특성상 특정 시간대 or CRM으로 푸시알림이 발송된 순간에 트래픽이 몰리는 경향이 있다.
그래서 내가 입사하기 이전부터 서비스에선 hikari cp 권장사항인 maximum-pool-size와 minimum-idle 개수를 다르게 줬었다.
물론 minimumIdle 설정 개수가 더 적었었다.

그렇게 잘 유지되다가 어느날부터 트래픽이 몰리면 이제 유휴에서 maximum개수가 될 때까지 connection pool을 만들게되는데 이 때 만드는 속도보다 커넥션을 더 많이 필요로 하게되면 그때부터 지연 응답이 되면서 해당 에러로그가 집계 되었다.

초창기보다는 트래픽이 어느정도 늘었기 때문에 내가 당시에 HikariCP Docs 를 읽고서 아래 이미지 처럼 쓰여있길래 일단 pool size 개수를 maximum, minimum 개수를 하나로 맞추자고 했었다.

그래서 개선하고 난 뒤에는 에러로그가 해소되었었다!!!

서버가 시작하자마자 바로 엄청난 트래픽이 호출되었을 때

일단 이 부분은 인프라 설정과도 관련이 있을 수 있는데, 톰캣 서버가 시작된 이후에 우린 spring actuator가 제공해주는 /actuator/health 를 통해 AWS ECS에서 헬스체크를 하고있다.
이 때 health check 성공 갯수의 호출 간격이 되게 짧았어서 성공하면 이제 connection pool은 1개씩 애플리케이션이 구동되고 생성되는데, 이 pool size가 다 차지 않았음에도 서비스를 시작하는 경우에도 마찬가지로 같은 에러로그가 나왔었다.

이 부분은 뭐 따로 우리쪽에서 해결할 것이 없이 인프라팀에 요청드려서 health check 간격을 좀 더 늘려서 해달라고 요청을 드렸었고, 이 이후에는 해당 설정 관련으로 에러로그가 따로 나오지는 않았던 것 같다.

아직 해결하지 못한 AWS Aurora Mysql Serverless 설정

이건 계속해서 아직도 발생하고 있다!!!
회사의 DBA분도 같이 계속해서 봐주고계신데, 아무리 생각해도 설정상 문제는 없는데 어떤 특정 시간대에 마구잡이로 위에서 나왔던 에러로그가 무수히 많이 찍혔었다.

처음으로 DB자체를 의심했던 순간이었다.
애초에 serverless로 db를 바꾸면서 갑자기 에러로그가 나오는 것을 보고 혹시나 하면서 우리 팀 내 개발자분들도 전부 DB를 의심했다.
그래서 처음에 DBA분도 aws에 문의를 해봐야겠다고 해서 질문을 드린후 이런 답변이 왔었다.

이전에 설명드린 대로 해당 이슈는 innodb buffer pool resize 과정에서 hash resize가 발생하여 wait/synch/sxlock/innodb/hash_table_lock 대기 이벤트가 발생했고, 이로 인해 고객님의 serverless cluster에서 hash resize 과정이 완료될때 까지 connection 이슈가 지속 되었던 것으로 확인됩니다.

현재 고객님이 사용하시는 serverless cluster는 mysql_aurora.3.06.1 버전을 사용 하시는 것으로 확인됩니다. 
Aurora mysql  3.08 버전부터 "확장 또는 축소 이벤트 중에 잠금 해시 테이블의 크기 조정이 길어져 발생하는 재시작 문제"가 해결되어, hash resize로 인한 재시작 및 connection 이슈가 완화된 것으로 확인됩니다. [+]
해당 serverless cluster를 mysql_aurora 3.08 이상 버전으로의 업그레이드를 고려해 보시는 것을 권해드립니다. 
[+] Aurora MySQL 데이터베이스 엔진 업데이트 2024-11-18(버전 3.08.0, MySQL 8.0.39와 호환) - https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraMySQLReleaseNotes/AuroraMySQL.Updates.3080.html 


고객님께서 aurora mysql의 버전 업그레이드가 상황적으로 어려우시다면, 다음과 같은 사항들을 점검해보시는 것을 권해드립니다.
▼ hash_table_lock 대기 이벤트는 주로 세 가지 이유로 발생할 수 있습니다. [+]
(1). 버퍼 풀의 크기가 너무 작아서 자주 접근하는 페이지를 모두 메모리에 유지할 수 없을 때
(2). 워크로드가 많아서 데이터 페이지가 자주 제거되고 다시 로드될 때
(3). 버퍼 풀에서 페이지를 읽는 중에 오류가 발생할 때 (이는 데이터 손상을 의미할 수 있습니다)
[+] synch/sxlock/innodb/hash_table_locks - https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/ams-waits.sx-lock-hash-table-locks.html 


현재 고객님의 serverless 클러스터는 minAcu 0.5, maxAcu 45로 설정되어 있는 것으로 확인됩니다. serverless 클러스터는 워크로드 변화에 따라 자동으로 scailing 되는데, 이 과정에서 minAcu과 maxAcu 값, 그리고 실제 워크로드 양에 따라 scailing의 규모가 달라집니다. 
많은 워크로드가 갑자기 들어오면 innodb buffer pool의 대규모 resize가 발생하고, 이때 hash resize도 함께 일어나게 됩니다. 따라서 우선적으로 워크로드를 점검해 보시는 것을 권해드립니다.

고객님께서는 performance insight를 사용 중이시므로, minAcu를 2.0 이상으로 올리시는 것을 권장드립니다. 또한, 고객님의 환경에서 테스트를 통해 워크로드 양에 따른 Acu 최적화도 진행해 보시는 것을 권해드립니다. [+]
[+] 클러스터에 대한 최소 Aurora Serverless v2 용량 설정 선택 - https://docs.aws.amazon.com/ko_kr/AmazonRDS/latest/AuroraUserGuide/aurora-serverless-v2.setting-capacity.html#aurora-serverless-v2.viewing.performance-insights 

이렇게 그 당시에 답변이 왔었고, 결국에 우리 버전이 3.08 보다 낮은 버전을 사용했었고, 이 때 serverless mysql이 트래픽이 많이 몰리는 순간 스케일 아웃을 진행할 때 innodb buffer pool의 resize가 발생하니까 이 때 순단이 될텐데, 커넥션을 이 당시에 맺을 수 없으니까 애플리케이션 입장에서는 커넥션을 당연히 사용할 수 없는 상황이고 그에 따라서 connection 이슈가 슈루루룩 하고 쌓였던 것이다.

이 때마다 고객들에게 서비스를 제대로 제공하지 못하는 것에 나는 굉장히 조금 아쉬웠었다.

아직도 이 minAcu 라는 값은 회사 내에서 계속 적정 값을 찾아서 튜닝을 하고 있는 중이다.

마무리

결국 서비스를 잘 운영하다가도 이벤트로 트래픽이 확 증가하는 순간에는 미리 예측한 모수정도를 위한 서비스 증설, db 사양 반영을 한다면 어느정도 대응은 가능하겠지만 의도치 않은 or 이벤트를 의도했지만 예상한것보다 더 많이 들어오는 경우는 무조건적으로 이렇게 간헐적인 에러가 나고, 그 에러를 해소하기 위해 별도의 사양 증설은 무조건적으로 필요한 것 같다.
100% 막을 수 있다면 너무 좋겠지만? 이 나름대로 해소하는 재미가 있다고 생각한다.

그리고 이런걸 빠르게 대처하려면 그만큼 다른 피쳐들을 쳐내는것도 물론 중요하겠지만 모니터링을 많이 그리고 꼼꼼히 사소한거여도 사내 메신저에 빠르게 공유하는 습관을 많이 들여야겠다고 생각한다. (그리고 혼자 머리 싸매는것보다 빠르게 공유하여 주변 개발자분들과 같이 해결해보려고 하는 자세를 가지는게 난 더 중요한 것 같다.)

이 에러 무조건적으로 해결하면 블로그를 써야지~ 했는데 끝날 기미가 안보여서 이정도로 먼저 기록해보려고 한다!

번외 : 앞으로 블로그 좀 더 잘쓰고 많이 쓰려고 노력해야겠다. 너무 놀았던것 같다 ㅋㅋㅋㅋ 🔥🔥🔥🔥🔥

728x90
반응형

'DB' 카테고리의 다른 글

쿼리 속도 개선기  (0) 2022.08.07
[DB] 옵티마이저  (0) 2022.08.05
728x90
반응형

인덱스란?
번역을하면 바로 색인이라는 단어로 번역된다.
색인은, 검색하면 책속의 낱말이나 어떤 챕터나 구절들을 빠르게 찾아볼 수 있게 쪽수 정보를 나타내주는 것을 뜻한다.
이 개념을 DB에 적용시킨 것이다.

인덱스는 어떻게 구성하는가?

인덱스는 CREATE INDEX 키워드로 구성이 가능하다.

Team 이라는 테이블이 이렇게 있다.

id(primary key) name member_id grade
1 홍길동 1 1
2 가나다 2 2
3 고길동 3 3
4 나길동 4 4

 

이름만으로 인덱스를 구성하고 싶다면 아래와 같이 수행해주면 된다.
CREATE INDEX <인덱스명> 테이블(칼럼);

CREATE INDEX team_index team(name);

이렇게 해주게 되면 name의 오름차순 순서로 정렬되게 된다.

왜 정렬이 되는가?
기본적으로 Mysql에서는 BTree 자료구조로 인덱스를 구성하게 된다.
b트리는 이진트리와 같게 기본적으로 정렬을 통해 구성해주게 된다.
-> 데이터 탐색에 용이하도록 구성하는 것이다.

인덱스를 생성하면 아래와같이 주소값을 참조하고 있는 구성이 완료된다.
| name | 주소값 |
|------|-----------------------|
| 팀1 | name이 팀1인 어떤 데이터의 주소값 |
| 팀2 | name이 팀2인 어떤 데이터의 주소값 |

이렇게 되었을 때 다시 SELECT 쿼리를 수행하게되면,

SELECT * FROM team WHERE name = '팀1';

name만을 인덱스 구성한 인덱스 주소 구성을 바라보게 될 것이다.
여기서 이제 트리구조의 탐색 알고리즘이 뒷받침하여 탐색하게 되는데,
가장 가운데 row부터 맞는지 검색을 들어가서 팀1 조건에 부합하는 데이터를 찾게된다.

찾게되면 name이 팀1 인 데이터가 예시에선 지금 2개로 구성했으니, 2개만을 조회해서 결과 반환을 해주게 될 것이다.

이런식으로 탐색을 빠르게 해줄 수 있다.

그럼 한개만의 칼럼만 인덱스를 구성할 수 있나요?

놉. 그렇지는 않다.
예를 들어서 팀에 속한 멤버가, 항상 등번호를 갖고있어야하고 적어도 그 팀에있는 멤버인 홍길동, 1번은 같이 묶여다닌다.
그렇게 된다면 인덱스에 그 둘을 같이 묶어 구성해주는 것이다.
말로 표현한걸 도식화하면 아래와 같다.
| member_id | number | 주소값 |
|-----------|--------|--------------------------|
| 1 | 1 | id가 1이며 등번호도 1인 데이터의 주소값 |
이게 근데 겹칠 수 있는 데이터라면 일반적인 인덱스로 구성시킬 수 있겠지만,
등번호가 유니크한 고유값으로 묶인다면 Unique Index를 구성해줄 수 있다.

CREATE INDEX member_index member(name, number);

인덱스로 탐색한 이후는?

자, 이제 우리는 인덱스로 탐색하는 방법을 조금은 안 것 같다.
여기서 인덱스가 유니크가 아닌경우를 좀 더 보려고한다.
일단 유니크 인덱스가 아니라면, 같은 조건으로 묶여있는 데이터가 여러건 있다는 것인데,
이미 인덱스에서 한번 체로 거른 수준처럼 데이터가 걸러졌는데 이후는 full scan을 수행하여 그 데이터들 중 완전한 조건에 부합하는 데이터만 추려 조회하여 결과를 내준다.

이렇게 되니까 인덱스 참 좋은것 같은데 그럼 칼럼마다 다 생성해주면 좋은거 아닌가?

놉. 그렇지 않다.
왜냐면 데이터는 테이블 자체에 저장이 될텐데, 인덱스는 처음에 b-tree 구조이고 정렬을 한다고 했었다.
인덱스를 많이 구성하게 되면, 그 인덱스들이 원하는 조건대로 재정렬을 해주어야 하기 때문에 성능 저하가 발생할 수 있다.
그리고 이 인덱스가 구성되는게 논리적인게 아니고 주소값을 참조하는 값들이 계속 생성하여 디스크에 저장되기 때문에 저장 용량의 한 부분을 차지하게 된다.

Where 조건에서 복합 칼럼 인덱스를 안타게 구성할 수도 있는지?

당연히 구성해볼 수 있다.
이것은 테이블을 만들고 실제 select 후 실행계획을 분석해보도록 하자.

demo2 테이블 DDL


자 테이블 구조는 위와같이 DDL을 정의해놓은 상태이다.

이제 EXPLAIN 키워드를 사용해서 인덱스를 타는지 안타는지 보게 될 것이다.
일단 기본적으로 member_id와 grade 팀순위를 묶었다.
-> 대충 구성하려고 하다보니 이상한 데이터 구조가 되어버렸다. ㅋㅋㅋㅋㅋㅋㅋ
일단 이 부분은… 넘어가도록…ㅎㅎ
각설하고!

EXPLAIN SELECT *
FROM test.demo2
WHERE member_id = 1;

EXPLAIN SELECT *
FROM test.demo2
WHERE grade = 1;

 

위의 두개 쿼리를 각각 실행한 결과이다.

위의 DDL에서 member_id를 먼저 구성하고, 그 뒤에 grade를 넣은 인덱스를 구성했다.

member_id를 먼저 구성해주었기 때문에 member_id로 선정렬된 인덱스를 탐색하게 될 것이니

member_id를 조건에 넣어주면 인덱스를 통해 데이터를 추려주는게 가능하다.

member_id를 통한 조회

 

grade를 통한 조회

하지만, 복합으로 구성된 상태에서는 member_id로 선정렬이 되어있기에 grade만을 where조건에 넣어주면 인덱스 탐색이 불가능하여 보는것처럼 인덱스를 타지 못하게 쿼리가 구성된다.

왜 이렇게 됐을까?
인덱스 구성한 ddl을 보면 member_id 로 시작하게 된다.

 

그래서 member_id가 Index에 먼저 구성되어 있기 때문에 우선적으로 추려볼 수는 있는 과정을 거치는 것이다.
그래서 grade만을 조회할 때는 grade로만 정렬이되거나, index의 가장 앞단에 grade로 잡혀있는 둘다 없기 때문에 가장 좋지 않은 Full scan 데이터를 조회하게 되는 것이다.

 

자 그럼 커버링 인덱스는 뭐야?

 

우선 앞의 내용을 천천히 다시 되짚어보자. (누구나 충분히 이해할 수 있을거라고 생각한다.)

 

우리는 여태 인덱스를 구성할 때 인덱스로 정할 n개의 칼럼들 + 주소 참조값을 가진 별도의 인덱스를 구성한다고 했다.

 

근데 해당 조건에 부합하는 row의 모든 데이터가 아니라 인덱스에 포함된 데이터만 조회한다면 사실 테이블 스캔이 필요 없는거 아냐?

-> 이게 바로 커버링 인덱스이다.

 

조건에 부합하는걸 갖고 디스크가서 데이터를 조회할 필요를 줄이기 때문에 성능상으로 굉장히 이득을 볼 수 있다!

 

그러면 이제 인덱스를 구성하기 좋게 만드려는 조건들을 나열해볼 수 있지 않나?

그렇다. 인덱스를 잘 설계하기 위해서는 어떻게 만들어줘야 하는지 이쯤 되면 조금은 이해가 될 수 있어보인다.

 

일단 단일 칼럼 인덱스라면 중복도가 낮은 데이터를 잡아주는게 무조건 유리할 것이다.

-> 이래야 조건에 부합하는 데이터의 Full scan을 하더라도 빠르게 찾아낼 수 있을 것이다.

 

복합 컬럼 인덱스라면...

일단 자주 엮이는 칼럼들을 우선적으로 묶어주는데, 그 조합의 유니크함이 필요할 것이다.

칼럼의 갯수가 너무 많아지면 반대로 또 인덱스 용량이 무거워지기 때문에 좋지 않을 것이다.

 

정리

일단 정리 차원에서 인덱스를 정리해봤다.

이전에 공부했던 것보다 지금 공부하면서 정리하는게 좀 더 많이 이해할 수 있게 된 것 같다.

계속 조금씩 점진적으로 깊게 공부하는 방법을 천천히 체득시켜야겠다.

728x90
반응형

'CS > 데이터베이스' 카테고리의 다른 글

쿼리 개선 2  (0) 2022.08.11
쿼리 작성 및 최적화  (0) 2022.08.11

+ Recent posts