Amazon Redshift Serverless에서 쿼리를 실행하지 않는데도 컴퓨트(RPU) 비용이 계속 청구되는 이유와 해결 방법
- Redshift에서 워크로드를 바꾸지 않았는데 Amazon Redshift Serverless 비용이 증가했습니다.
- 컴퓨트 용량(RPU)이 기본 용량으로 내려가지 않고 계속 활성 상태로 유지되며, 쿼리를 실행하지 않는 유휴 시간대에도 과금이 발생합니다.
- SYS_QUERY_HISTORY를 봐도 이 지속적인 컴퓨트를 설명할 만한 쿼리가 확인되지 않습니다.
원인 요약
Redshift Serverless는 RPU가 활성화 되어 있는 동안 초 단위로 과금하며(최소 60초), 쿼리 부하가 사라져도 즉시 컴퓨트 용량을 기본 수준으로 줄이지 않을 수 있습니다.
공식 문서에 따르면 쿼리 또는 트랜잭션은 완료, 롤백 또는 중지된 이후에야 측정되고 기록되며, 열린 트랜잭션을 종료하거나 롤백하지 않으면 Redshift Serverless는 계속 RPU를 사용하는 것으로 안내되어 있습니다.
즉, 커밋되지 않은 채 열려 있는 트랜잭션이나 닫히지 않은 커서가 있으면, 그 사이 아무 쿼리가 실행되지 않아도 컴퓨트가 유지되어 비용이 발생합니다.
대표적인 두 가지 원인은 다음과 같습니다.
첫째, 애플리케이션이 BEGIN 이후 COMMIT/ROLLBACK을 누락했거나, 큰 결과 집합을 커서로 읽는 도중(예: JDBC/ODBC 드라이버의 autocommit 비활성화 + 작은 fetch size) Fetch 사이에 트랜잭션을 오래 열어 두는 경우입니다.
둘째, 커넥션 풀이 연결 상태 확인을 위해 주기적으로 보내는 경량/빈 쿼리(예: SELECT 1)도 모두 과금 대상 활동으로 처리되어, 실제 워크로드가 없어도 비용이 발생할 수 있습니다.
검증(재현) 결과
17시 40분경 base 8 RPU 워크그룹에서 트랜잭션을 열고(BEGIN) 커서를 선언한 뒤 50행만 Fetch하고 커밋하지 않은 채 두었습니다.
17시 43분 이후 어떤 사용자 쿼리도 실행하지 않았지만, SYS_SERVERLESS_USAGE의 charged_seconds가 매 1분 480초(= 8 RPU × 60초)씩 계속 청구되었습니다.
A. sys_serverless_usage를 통해 확인한 실제 사용량
SELECT start_time, end_time, compute_seconds, compute_capacity, charged_seconds FROM sys_serverless_usage ORDER BY end_time DESC;
참고 : https://docs.aws.amazon.com/ko_kr/redshift/latest/dg/SYS_SERVERLESS_USAGE.html
| start_time | end_time | compute_capacity | compute_seconds | charged_seconds | 비고 |
|---|---|---|---|---|---|
| 5:40 pm | 5:41 pm | 8 | 2 | 480 | 데이터 적재 시작 |
| 5:41 pm | 5:42 pm | 8 | 27 | 480 | BEGIN |
| 5:42 pm | 5:43 pm | 8 | 481 | 480 | Declare CURSOR + Fetch 50 |
| 5:43 pm | 5:44 pm | 8 | 481 | 480 | 이후 사용자 쿼리 없음. 트랜잭션만 열린 상태 |
| 5:44 pm | 5:45 pm | 8 | 481 | 480 | 유휴 상태이지만 비용 청구 중 |
| 5:45 pm | 5:46 pm | 8 | 481 | 480 | 유휴 상태이지만 비용 청구 중 |
| 5:46 pm | 5:47 pm | 8 | 481 | 480 | 유휴 상태이지만 비용 청구 중 |
| 5:47 pm | 5:48 pm | 8 | 481 | 480 | 유휴 상태이지만 비용 청구 중 |
같은 시점에 SVV_TRANSACTIONS에는 17:43:28에 시작된 트랜잭션이 락을 유지하고 있었으나, 실행 중인 사용자 쿼리는 없었습니다.
B. svv_transactions를 통해 확인한 현재 트랜잭션에 대한 정보
SELECT txn_owner, txn_db, xid, pid, txn_start, lockable_object_type, lock_mode FROM svv_transactions ORDER BY txn_start;
참고: https://docs.aws.amazon.com/ko_kr/redshift/latest/dg/r_SVV_TRANSACTIONS.html
| txn_owner | txn_db | xid | pid | txn_start | lockable_object_type | lock_mode |
|---|---|---|---|---|---|---|
| app_user | dev | 2649 | 1073840282 | 5:43 pm | transactionid | ExclusiveLock |
| app_user | dev | 2649 | 1073840282 | 5:43 pm | relation | AccessShareLock |
SYS_QUERY_HISTORY를 보면 마지막 사용자 구문인 FETCH가 17:43:28에 완료된 뒤로는 더 이상 실행된 쿼리가 없습니다. 그럼에도 위 표 A처럼 과금은 계속되었습니다.
C. sys_query_history를 통해 확인한 사용자 쿼리의 세부 정보
SELECT query_id, left(query_text,60) AS qtext, status, start_time FROM sys_query_history WHERE start_time > dateadd(minute,-20,getdate()) ORDER BY start_time DESC;
참고: https://docs.aws.amazon.com/ko_kr/redshift/latest/dg/SYS_QUERY_HISTORY.html
| query_id | qtext | status | start_time |
|---|---|---|---|
| 3243 | BEGIN | success | 5:42 pm |
| 3256 | DECLARE cur CURSOR FOR SELECT * FROM public.big | success | 5:43 pm |
| 3260 | FETCH 50 FROM cur | success | 5:43 pm |
D. 트랜잭션 종료 이후 상황
열려있던 트랜잭션을 닫았을 때 실제 Compute와 비용이 어떻게 되는지도 함께 확인했습니다. 아래와 같이 롤백(또는 커밋)을 통해 트랜잭션을 종료합니다.
둘 중 무엇을 쓰든 열린 트랜잭션이 닫히면서 커서가 잡고 있던 Compute 점유가 풀리게 됩니다.
ROLLBACK; -- 또는 COMMIT;
종료 직후부터 사용량을 다시 확인한 결과는 다음과 같습니다.
| start_time | end_time | compute_capacity | compute_seconds | charged_seconds | 비고 |
|---|---|---|---|---|---|
| 5:48 pm | 5:49 pm | 8 | 481 | 480 | 유지 중 - 청구 |
| 5:49 pm | 5:50 pm | 8 | 183 | 248 | ROLLBACK 직후 (17:49:22) - 부분 청구 |
| 5:50 pm | 5:51 pm | 8 | 1 | 480 | 트랜잭션은 닫혔으나 워크그룹 Warm 유지 - 청구 |
| 5:51 pm | 5:52 pm | 8 | 1 | 480 | Warm 유지 - 청구 |
| 5:52 pm | 5:53 pm | 8 | 1 | 480 | Warm 유지 - 청구 |
| 5:53 pm | 5:54 pm | 8 | 1 | 480 | Warm 유지 - 청구 |
| 5:54 pm | 5:55 pm | 8 | 1 | 480 | Warm 유지 - 청구 |
| 5:55 pm | 5:56 pm | 8 | 1 | 480 | Warm 유지 - 청구 |
| ... | ... | ... | ... | ... | ... |
| 6:04 pm | 6:05 pm | 8 | 1 | 480 | Warm 유지 - 청구 |
| 6:05 pm | 6:06 pm | 8 | 37 | 0 | 워크그룹 auto-suspend - 청구 0 |
ROLLBACK으로 트랜잭션을 닫는 순간 실제 컴퓨트 작업은 곧바로 멈춥니다.
위 표에서 compute_seconds가 481에서 1로 떨어지는 것이 그 신호이며, 더 이상 일을 하지 않는 상태입니다.
다만 Redshift Serverless 워크그룹은 트랜잭션이 닫혔다고 해서 즉시 꺼지지 않고, 잠시 warm 상태로 남아 base 8 RPU를 계속 청구합니다.
이번 결과에서는 약 15분간(17:50~18:05) charged_seconds가 480으로 유지되다가, 17:56에 워크그룹이 auto-suspend되면서 비로소 0으로 떨어졌습니다.
부하가 사라진 뒤에도 RPU가 일정 시간 유지될 수 있다는 점은 공식 빌링 문서에도 안내되어 있으며, warm 상태로 유지되는 시간은 상황에 따라 달라질 수 있습니다. (일반적으로 15분 이내에 종료됩니다.)
여기서 핵심은, 트랜잭션을 닫아야 워크그룹이 유휴 상태로 들어가 결국 비용이 0으로 떨어진다는 점입니다.
반대로 트랜잭션을 열어 둔 채로 두면 앞의 표 A처럼 charged_seconds가 480으로 계속 유지되어, 이 0으로의 전환 자체가 일어나지 않습니다.
진단 방법
1단계 - Compute Capacity가 유휴 상태에도 유지되는지 확인합니다.
CloudWatch의 ComputeCapacity/ChargedSeconds 또는 Redshift Serverless 콘솔의 컴퓨트 사용량 그래프에서 유휴 구간에 RPU가 기본 용량으로 내려가는지 봅니다.
누적 사용량은 SYS_SERVERLESS_USAGE로 확인 가능합니다.
SELECT start_time, end_time, compute_seconds, compute_capacity, charged_seconds FROM sys_serverless_usage ORDER BY end_time DESC;
2단계 - 완료된 쿼리를 확인합니다.
SYS_QUERY_HISTORY 테이블을 통해 Serverless에서 수행된 사용자 쿼리를 확인하실 수 있습니다.
해당 테이블에서 사용량을 정당화할 사용자 쿼리가 거의 없다면, 원인은 정상 워크로드가 아니라 열린 트랜잭션, 닫히지 않은 커서, 또는 커넥션 풀의 keep-alive 트래픽일 가능성이 높습니다.
3단계 - 열린 트랜잭션을 식별합니다.
SVV_TRANSACTIONS 테이블을 통해 트랜잭션에 대한 정보를 확인 가능하며, 이를 통해 오래 열려 있는 트랜잭션을 식별하고 종료합니다.
SELECT txn_owner, txn_db, xid, pid, txn_start, lockable_object_type, lock_mode FROM svv_transactions ORDER BY txn_start;
4단계 - 커서/Fetch 활동을 확인합니다.
SYS_CHILD_QUERY_TEXT을 통해 하위 쿼리의 SQL 텍스트를 확인할 수 있습니다.
JDBC/ODBC 드라이버가 커서로 결과를 읽는 경우, 개별 FETCH는 완료된 사용자 쿼리로 잘 드러나지 않고 SYS_CHILD_QUERY_TEXT에 child query로 나타날 수 있습니다.
SELECT user_id, query_id, child_query_sequence, sequence, text FROM sys_child_query_text WHERE text ILIKE '%fetch%' OR text ILIKE '%cursor%' ORDER BY query_id DESC;
참고: https://docs.aws.amazon.com/ko_kr/redshift/latest/dg/SYS_CHILD_QUERY_TEXT.html
5단계 - 커넥션 풀의 keep-alive/validation 쿼리를 점검합니다.
짧은 주기로 도는 SELECT 1 같은 검증 쿼리가 유휴 워크그룹에 과금을 유발하는지 확인합니다.
해결 및 모범 사례
- 가장 먼저 보실 부분은 트랜잭션이 제대로 닫히고 있는지입니다.
- BEGIN으로 트랜잭션을 시작하셨다면 반드시 COMMIT이나 ROLLBACK으로 마무리해 주시고, 정상 경로뿐 아니라 중간에 오류가 났을 때도 트랜잭션이 닫히도록 예외 처리 코드를 함께 점검해 보시면 좋습니다.
- 트랜잭션이 열린 채로 남아 있는 동안에는 쿼리가 돌지 않아도 컴퓨트가 계속 유지되기 때문입니다.
- 커서로 큰 결과를 읽으실 때는 커서를 오래 들고 있지 않는 것이 핵심입니다.
- 다 읽으셨으면 커서를 바로 닫아 주시고, FETCH와 FETCH 사이에 오래 멈춰 있지 않도록 해 주세요.
- Fetch size를 너무 작게 잡으면 결과를 끝까지 읽는 내내 트랜잭션이 길게 열려 있게 되니, 이 값도 한번 들여다보시길 권합니다.
- 이런 상황이 반복된다면 SESSION TIMEOUT을 걸어 두는 방법도 있습니다.
- CREATE USER나 ALTER USER에서 이 값을 설정해 두면 유휴 세션이나 비활성 상태로 열려 있는 트랜잭션이 알아서 정리됩니다.
- 따로 바꾸지 않으면 유휴 세션은 1시간, 열린 채 멈춰 있는 트랜잭션은 6시간 뒤에 종료됩니다.
- 커넥션 풀을 쓰고 계시다면 헬스체크 설정도 같이 살펴보시길 권합니다.
- 연결 상태를 확인하느라 주기적으로 도는 validation/heartbeat 쿼리의 간격을 늘리거나, 꼭 필요하지 않다면 꺼 두시고, 가능하면 TCP keep-alive로 대신하시는 편이 비용 면에서 낫습니다.
참고 문서
- Billing for Amazon Redshift Serverless: https://docs.aws.amazon.com/redshift/latest/mgmt/serverless-billing.html
- CREATE USER (SESSION TIMEOUT): https://docs.aws.amazon.com/redshift/latest/dg/r_CREATE_USER.html
- 주제
- 분석
- 언어
- 한국어
