본문 바로가기

[Project] 프로젝트 삽질기57 (feat 커넥션 풀 누수)

어가며

AWS Aurora PostgreSQL를 활용해서 서비스를 운영하고 있습니다. 성능 테스트를 하면서 Writer DB의 Connections 수가 특정 숫자 이상이 되면 커넥션을 더 이상 맺을 수 없어서 장애가 발생한다는 것을 파악했습니다. 장애를 겪을 것을 대비하여 RDS의 connections 수를 트래킹 하며 커넥션 수가 특정 개수 이상이 넘으면, slack으로 알림을 받도록 구성했습니다. 

 

얼마 전, max connections 수가 특정 수를 넘었다는 알림을 받았습니다. 알림을 받은 이후 시간이 지나도, 커넥션 수가 떨어지지 않는 문제가 생겼습니다. 그 과정에서 팀에서는 어떤 문제를 겪었고, 이 문제를 어떻게 해결했는지 공유하고자 글을 씁니다.

 

 

 

 

 


 

 

 

 

 

 

 

 

 

RDS 커넥션 풀 누수

저희 팀에서는 CloudWatch의 경보를 활용하여 Slack을 통해 인프라 모니터링을 할 수 있도록 환경을 구축했습니다. 어느 날, 위와 같은 Slack 메시지를 받고 AWS RDS 모니터링을 해보니, AWS RDS Aurora postgresql master DB에서 커넥션 부족 문제가 발생하고 있었습니다.  

 

 

 

 

DatabaseConnections의 개수가 오전 10시에 갑자기 40 이상으로 오르더니, 16시가 되어서는 80 이상으로 증가하는 문제가 발생했습니다. 그 후 시간이 지나도, DB에 연결된 Connections 개수가 유지되는 문제가 지속됐습니다.

 

 

 

DB Connection 수가 줄어들지 않자, 더 이상 DB에 연결할 수 없게 되었고, 결국 500 에러가 발생했습니다. 에러 메시지를 보니, timeout exceeded when trying to connect라는 로그가 출력되고 있었습니다. 

 

 

 

 

 

에러 로그를 보면서, 서버에서 커넥션 풀 누수 문제가 일어나고 있을 것이라 판단하여 서버를 재가동했습니다. 그 후 DB를 모니터링했을 때 커넥션 수가 확 줄어든 모습을 볼 수 있었습니다. 왜 이 문제가 나오고 있는지 확인하고자 디버깅했습니다.

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

RDS 세션 확인

먼저 문제 원인을 파악하기 시작했습니다. API 호출이 마무리 됐지만, API에서 사용한 커넥션을 반환하지 않고 계속 유휴 커넥션을 유지시키기 때문에 문제가 발생할 것이다라고 생각했습니다.

 

 

const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();

try {
       await queryRunner.commitTransaction()} 
catch (error) {
          await queryRunner.rollbackTransaction()} 
finally {
          await queryRunner.release()}

 

 

기본적으로 NestJS를 사용하면서 트랜잭션을 QueryRunner를 활용해서 관리하는데, 트랜잭션 활용 코드에서 문제가 있었을 것이라 판단하여 디버깅을 진행했습니다.

 

 

 

데이터베이스가 트랜잭션 연결 시 오랫동안 유휴 상태로 실행됨 - Amazon Aurora

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

 

 

AWS Aurora PostgreSQL 공식 문서를 확인하니, 저와 비슷한 문제를 겪을 경우, 어떻게 해결해야 하는지에 대해 자세하게 작성되어 있었습니다.

 

 

SELECT now() - state_change as idle_in_transaction_duration, now() - xact_start as xact_duration,* 
FROM  pg_stat_activity 
ORDER BY 1 DESC;

 

 

pg_stat_activity 쿼리를 통해 유휴 트랜잭션을 찾을 수 있다는 것을 확인했고, SQL 클라이언트인 pgAdmin을 활용해서 위 쿼리를 실행해서 idle 상태의 모든 연결을 나열하고 기간별로 정렬했습니다.

 

 

 

 

idle_in_transaction_duration 컬럼은 각 세션이 트랜잭션 내부에서 대기하는 시간을 나타냅니다. now() - state_change를 사용하여 현재 시간에서 세션이 마지막으로 상태를 변경한 시간을 뺍니다. 마지막으로 pg_stat_activity 테이블에서 데이터를 가져오고 idle_in_transaction_duration 컬럼을 기준으로 내림차순으로 정렬하여 반환합니다. 

 

결과를 확인해 보니, 최대 6시간 동안 세션이 idle인 상태로 유지되고 있는 것을 확인했습니다.  

 

 

SELECT pg_terminate_backend(pid);

 

 

위 쿼리를 입력해서, 유휴 트랜잭션의 연결을 종료시킬 수 있지만, 아쉽게도 슈퍼 유저 권한으로 데이터베이스에 접근한 것이 아니기 때문에, 해당 쿼리를 입력해서 연결을 종료시킬 수 없었습니다.

 

 

 

 

 

 

 

 


 

 

 

 

 

 

session timeout 파라미터 설정

그럼, 세션이 idle 상태로 유지되고 있을 경우, 특정 시간이 지나면 자동으로 timeout 되도록 환경을 구축하고 싶었습니다. 다행스럽게도 AWS RDS 파라미터 그룹을 활용해서 이 옵션을 제어할 수 있었습니다. 현재 AWS Aurora PostgreSQL 13.9 버전을 활용하고 있는데, idle_in_transaction_session_timeout 옵션을 설정하면, 자동으로 timeout 설정을 처리할 수 있었습니다. 

 

 

 

 

 

옵션을 살펴보니, 값이 86400000으로 설정되어 있었고, 즉 24시간이 지나면 자동으로 timeout 되도록 구성되어 있었습니다. 옵션을 테스트를 위해 1분 정도가 지나면 자동으로 세션이 종료되도록 설정했습니다. 그렇게 파라미터를 변경하고 테스트했지만, idle 상태인 데이터베이스 세션이 종료되지 않았습니다.

 

 

 

 

 

 

RDS for PostgreSQL 오류 "FATAL: remaining connection slots are reserved"(나머지 연결 슬롯이 예약되어 있음) 문

max_connections 제한에 도달하지 않았는데도 Amazon Relational Database Service(RDS) for PostgreSQL에 연결할 때 “FATAL: remaining connection slots are reserved for non replicate superuser connections”(나머지 연결 슬롯은 비복제

repost.aws

 

 

왜 종료되지 않는가, 살펴봤을 때, idle_in_transaction_session_timeout 파라미터는 세션의 state가 idle in transaction인 경우에 특정 시간이 지나면 종료되는 파라미터였습니다. 현재 문제가 되고 있는 세션은 idle 상태이기 때문에, 다른 방식으로 timeout을 설정해야 했습니다.

 

 

 

 

 

 

 

20.11. Client Connection Defaults

20.11. Client Connection Defaults 20.11.1. Statement Behavior 20.11.2. Locale and Formatting 20.11.3. Shared Library Preloading 20.11.4. Other Defaults 20.11.1. Statement Behavior client_min_messages …

www.postgresql.org

 

 

 

살펴보니, PostgreSQL 버전이 14 이상인 경우 idle_session_timeout 파라미터를 활용해서 세션을 종료할 수 있다는 것을 알았습니다. idle_session_timeout은 트랜잭션의 유무와 상관없이 세션 자체의 유휴 시간을 관리한다는 점에서 현재 저희 팀은 idle_session_timeout 파라미터를 관리해야 함을 깨달았습니다.

 

하지만 현재 AWS Aurora PostgreSQL 버전은 13.9 버전으로, 14 버전이 아니기 때문에 idle_session_timeout 파라미터를 사용할 수 없다는 문제가 발생했습니다. idle_session_timeout 파라미터를 활용하기 위해선 DB의 메이저 버전을 업그레이드시켜야 하는 문제가 발생했습니다. 당장 데이터베이스의 메이저 버전을 업그레이드하기엔 위험 부담이 있었습니다. 그럼 왜 idle in transaction 상태가 아닌 idle 상태로 커넥션 상태가 유지되는지 살펴봐야 할 필요가 있었습니다.

 

 

 

 

 

 

 


 

 

 

 

 

 

 

문제 원인 분석

일단 정확하게 idle 상태가 왜 일어났을까? 분석하기 위해 세션 확인을 하는 쿼리를 다시 분석했습니다.

 

 

 

 

 

쿼리 결과를 통해 pid값과 port번호를 살펴볼 수 있었는데, pid를 활용해서 RDS에서 어떤 로그가 출력됐는데 파악할 수 있다면, 커넥션 풀 누수 문제를 확인할 수 있다고 판단했습니다. 이를 위해 CloudWatch Log Insights를 활용하여 로그를 분석했습니다. 

 

 

 

fields @message
| filter @message like /[pid]/
| sort @timestamp desc
| limit 1000

 

 

로그 그룹으로 분석하고자 하는 Aurora DB 그룹을 선택하고, 위와 같이 쿼리를 입력했습니다. 이를 통해 DB 세션이 시작될 때 문제가 되는 Query의 pid값을 찾을 수 있었습니다. 해당 SQL문을 분석해 보니, 특정 데이터를 추가할 때 데이터를 잘 추가하고, COMMIT 한 후 세션을 종료하지 않는 것을 파악할 수 있었습니다. 분석을 통해 어떤 API에서 커넥션 풀 누수가 일어나고 있는지 특정 지을 수 있었습니다. 

 

 

 

 

 

 

문제 되는 API를 확인해서 문제를 해결한 결과 커넥션 풀 누수 문제가 반복되지 않는 것을 확인할 수 있었습니다.

 

 

 

 

 

 

 


 

 

 

 

 

 

앞으로의 과제

문제 해결을 하면서, 현재는 TypeORM, QueryRunner를 활용하는 방식으로 트랜잭션을 제어하기 때문에, 개발자가 DB 커넥션을 한 후, 커밋, 롤백, 릴리즈를 제대로 하지 않는다면 동일한 문제를 겪을 수 있다고 생각했습니다. 그래서 동일한 문제를 겪지 않기 위해 여러 대안을 검토하기도 했습니다.

 

 

1. 트랜잭션 관리 방법 수정

먼저 트랜잭션을 다르게 관리하는 방법을 검토하기도 했습니다. 커스텀 데코레이터를 활용하는 방법, 인터셉터를 활용하는 방법 등으로 트랜잭션 관리 코드를 개선할 수 있다고 판단했습니다. 이 부분은 조금 더 검토해 본 후, 조직에 적용할 수 있다면 적용해보려 합니다. 

 

 

 

트랜잭션 다루기 (feat. NestJS, TypeORM)

Nest JS에서 트랜잭션을 올바르게 다루기 위해 겪었던 고민과 문제에 대한 해결방법을 정리한 글입니다.

www.timegambit.com

 

[NestJS] Interceptor를 활용한 TypeORM Transaction 코드 개선하기

TypeORM에서 제공해 주고 있는 QueryRunner 트랜잭션을 사용하려면 commit, rollback, release 처리를 수동으로 해주어야 합니다. 그래서 코드가 길어짐과 동시에 가독성이 떨어지게 되고, 트랜잭션을 수행

annahxxl.tistory.com

 

Advanced Transaction Management with NestJS & TypeORM

IMPORTANT NOTE: If you want to follow along easier in the advanced transaction management part, clone the following repository…

medium.com

 

 

 

2. 단위 테스트 작성

수동으로 커넥션을 맺는 모든 코드에 단위 테스트 코드를 작성한다면 안정성을 올릴 수 있다고 판단했습니다. 하지만 이 방법은, 당장은 적절하지 않을 수 있다고 판단했습니다. 물론 이렇게 관리하면 더할 나위 없이 좋겠지만, 한정된 리소스에서 최대의 효율을 추구해야 하는 상황인데, 모든 DB 커넥션을 맺는 코드에 단위 테스트를 작성하는 것 자체가 엄청난 비용일 수 있다고 생각했습니다. 그래서 한 번에 커넥션을 맺는 모든 코드의 단위 테스트를 작성하기보다, 점진적으로 단위 테스트를 작성하여 안정성을 올리는 방법을 선택하고자 합니다.   

 

 

 

 

3. DB 메이저 버전 업그레이드

현재 사용하고 있는 Aurora PostgreSQL 13.9 버전을 14 이상의 버전으로 업그레이드해야, idle_session_timeout 파라미터를 활용할 수 있다고 확인했습니다. 그렇기 때문에, 개발자의 실수로 커넥션이 idle 상태로 유지되는 기존과 같은 문제가 나오더라도, DB 차원에서 자동으로 timeout 처리해 줄 수 있도록 DB를 업그레이드해야 했습니다. 처음으로 DB 버전을 어떻게 관리할 것인가에 대해 고민할 수 있었습니다.

 

 

Amazon Aurora PostgreSQL DB 클러스터 업그레이드 - Amazon Aurora

13.6부터 시작하는 Babelfish for Aurora PostgreSQL 13 기반 버전에서 14.6부터 시작하는 Aurora PostgreSQL 14 기반 버전으로 메이저 버전 업그레이드를 수행할 수 있습니다. Babelfish for Aurora PostgreSQL 13.4 및 13.5는

docs.aws.amazon.com

 

 

현재 AWS Aurora PostgreSQL DB의 메이저 버전을 업그레이드할 때 블루 그린 배포를 지원하는 것을 파악했습니다. 블루 그린 배포를 통해 DB의 메이저 버전을 빠르게 업그레이드할 수 있도록 구성해보려 합니다.

 

 

 

 

 

 

 

 


 

 

 

 

 

 

 

 

 

 

 

마치며

앞으로도 팀에서 했던 크고 작은 문제 해결 과정을 공유해보려 합니다. 팀의 성장에 기여할 수 있는 개발자가 되겠습니다. 

 

 

 

 

 

 

 


 

 

 

출처

 

 

데이터베이스가 트랜잭션 연결 시 오랫동안 유휴 상태로 실행됨 - Amazon Aurora

이 페이지에 작업이 필요하다는 점을 알려 주셔서 감사합니다. 실망시켜 드려 죄송합니다. 잠깐 시간을 내어 설명서를 향상시킬 수 있는 방법에 대해 말씀해 주십시오.

docs.aws.amazon.com

 

RDS for PostgreSQL 오류 "FATAL: remaining connection slots are reserved"(나머지 연결 슬롯이 예약되어 있음) 문

max_connections 제한에 도달하지 않았는데도 Amazon Relational Database Service(RDS) for PostgreSQL에 연결할 때 “FATAL: remaining connection slots are reserved for non replicate superuser connections”(나머지 연결 슬롯은 비복제

repost.aws

 

20.11. Client Connection Defaults

20.11. Client Connection Defaults 20.11.1. Statement Behavior 20.11.2. Locale and Formatting 20.11.3. Shared Library Preloading 20.11.4. Other Defaults 20.11.1. Statement Behavior client_min_messages …

www.postgresql.org

 

Amazon Aurora PostgreSQL DB 클러스터 업그레이드 - Amazon Aurora

13.6부터 시작하는 Babelfish for Aurora PostgreSQL 13 기반 버전에서 14.6부터 시작하는 Aurora PostgreSQL 14 기반 버전으로 메이저 버전 업그레이드를 수행할 수 있습니다. Babelfish for Aurora PostgreSQL 13.4 및 13.5는

docs.aws.amazon.com