AWS 비용절감 사례
AWS 비용은 지속적으로 모니터링되어야 예상치 못한 비용 지출 구간을 빠르게 파악하고 조치할 수 있습니다.
우리는 Cost Explorer를 확인하다가 이상한 패턴 하나를 발견했습니다.
그리고 그 작은 이상징후는 하루 수십 달러씩 새고 있는 ‘보이지 않는 구멍’이었습니다.
이 글은 개발용 RDS 인스턴스에서 발생한 고비용 IO 사용을 감지하고 performance_schema를 통해 원인을 분석하고 해결한 사례를 다룹니다.
Cost Explorer를 사용하여 RDS의 IO Usage 항목을 구체화해서 볼 수 있습니다. 서버 운영비와 달리 네트워크나 입출력 등은 서비스의 동작 여부에 따라 의도치 않은 비용이 발생할 수 있기 때문에 이상 패턴을 감지하는 것이 중요합니다. 결과적으로 하루 평균 $10-$40의 비용이 특정 리소스에서 지출되고 있었고 그 비용을 $0.2 이하로 낮추게 되었습니다.
데이터베이스의 IO 사용량은 1백만의 요청당 $0.2가 청구됩니다. 트래픽이 증가하거나 데이터 저장이 급증하지 않을 경우 그 패턴은 일정해야 합니다. 그런데 최근 이 사용량에 대한 비용이 매일 일정하지 않고 $100에서 $250 사이를 왔다 갔다 하고 있었습니다. 따라서 특정 데이터의 IO 사용량이 의도치 않게 높아졌을 거라고 가설을 세운 뒤 구체적으로 분석하게 된 케이스입니다.
위 스크린 샷은 저희가 사용하는 RDS 서비스에서 특정 데이터베이스의 IO 사용량 추위를 일 단위로 확인한 지표입니다. 위와 같은 지표를 보기 위해서는 서비스로 RDS를 필터하고 사용량 유형은 APN2-AUrora.StorageIOUsage(IOs)로 필터링합니다. 그리고 그룹 기준을 리소스로 분류하면 현재 운영하고 있는 모든 데이터베이스 별로 IO 사용량을 확인할 수 있습니다.
그림과 같이 해당 데이터베이스가 3월 8일을 기점으로 점차 IO 사용량 이 튀기 시작했고 어떤 날은 무려 2억건에 가까운 입출력을 기록했습니다. 문제는 이 데이터베이스는 개발환경에 구축된 것으로 사용량이 운영환경처럼 많지 않아야 했습니다.
결론적으로 4월 8일 이후로 아래의 스크린샷과 같이 입출력을 100만 건 이하로 줄였습니다. 그에 따라 하루에 $40 가까이 나가던 비용은 $0.2 아래로 줄어들게 되었습니다.
데이터베이스에서 어떤 입출력이 발생하고 있는지 확인하기 위해서 여러 방법을 사용할 수 있습니다. 클라우드와치에서 Read IOPS와 Write IOPS를 확인할 수 있으나 이 경우 좀 더 세부적으로 분석하기는 어렵습니다. 이럴 때 사용할 수 있는 것이 MySQL의 performance_schema 입니다. performance_schema에서 우리가 원하는 입출력이 어떤 테이블에서 많이 발생하고 있는지를 정확히 트래킹할 수가 있습니다.
먼저 performance_schema가 동작하고 있는지 확인합니다.
SHOW VARIABLES LIKE 'performance_schema';
또는 RDS에서 적용한 파라미터 그룹에서도 확인할 수 있습니다. performance_schema 의 값이 1 이라면 활성화되어 있습니다. 그러나 0이거나 위 쿼리의 결과가 off 로 나왔다면 입출력 통계지표를 확인할 수 없습니다. 이 값은 아래와 같이 파라미터 그룹에서 변경할 수 있습니다.
다만 이 파라미터는 static 파라미터이기 때문에 DB 인스턴스를 재부팅해야 변경 사항이 적용됩니다. dynamic 으로 표시된 파라미터만 재부팅 없이 적용이 가능합니다.
파라미터를 새로 설정했다면 하루 이틀 지나 데이터를 확인하면 됩니다.
아래 쿼리는 테이블별 I/O 통계를 볼 수 있는 쿼리입니다. COUNT_READ, COUNT_WRITE를 얼마나 활용하는지 각 OBJECT_NAME(table)의 통계치를 볼 수 있습니다.
SELECT OBJECT_SCHEMA, OBJECT_NAME, COUNT_READ, COUNT_WRITE, SUM_TIMER_READ / 1000000000 AS READ_TIME_MS, SUM_TIMER_WRITE / 1000000000 AS WRITE_TIME_MS FROM performance_schema.table_io_waits_summary_by_table WHERE COUNT_READ + COUNT_WRITE > 0 ORDER BY COUNT_READ + COUNT_WRITE DESC LIMIT 20;
결과는 아래와 같았습니다.
COUNT_READ는 select, COUNT_WRITE는 그 외 create, update, delete에 해당됩니다. select의 경우 스토리지에서 실제로 데이터를 읽은 횟수로 버퍼 캐시 히트는 제외됩니다. 따라서 통계치에 잡힌 것은 버퍼가 아닌 실제 디스크를 읽은 횟수입니다. 데이터를 보면 BATCH_STEP_EXECUTION과 SN_SABANGNET에서 읽기를 대량으로 실행했고 쓰기는 전체적으로 거의 활용되지 않았음을 알 수 있습니다.
다음은 테이블이 아닌 인덱스별 I/O 통계를 보는 쿼리입니다. 쿼리를 사용하는 중에 인덱스를 활용하게 되는데 마찬가지로 어떤 테이블에서 인덱스를 많이 활용하는지 파악할 수 있습니다.
SELECT OBJECT_SCHEMA, OBJECT_NAME, INDEX_NAME, COUNT_READ, COUNT_WRITE FROM performance_schema.table_io_waits_summary_by_index_usage WHERE COUNT_READ + COUNT_WRITE > 0 ORDER BY COUNT_READ + COUNT_WRITE DESC LIMIT 20;
Table과 유사한 패턴으로 상위 2개가 많이 조회되고 있습니다. 추가로 아래 쿼리를 활용하면 실제 어떤 쿼리가 많이 사용되었는지 파악할 수 있습니다.
SELECT DIGEST_TEXT, COUNT_STAR, SUM_TIMER_WAIT / 1000000000 AS EXEC_TIME_MS, SUM_ROWS_SENT, SUM_ROWS_EXAMINED FROM performance_schema.events_statements_summary_by_digest ORDER BY EXEC_TIME_MS DESC LIMIT 10;
위 쿼리는 누적되서 계속 쌓이게 되므로 날짜별로 끊어서 확인할 수는 없습니다. 만약 날짜별로 끊어서 보고 싶다면 테이블을 따로 하나 생성해서 스케줄링으로 매일 한번씩 저장하는 방법을 아래와 같이 고민할 수 있습니다.
-- 1. 매일 새벽 자동 스냅샷 저장 (예: 저장 테이블 생성) CREATE TABLE IF NOT EXISTS metrics_daily ( snapshot_time DATETIME, schema_name VARCHAR(64), table_name VARCHAR(64), count_read BIGINT, count_write BIGINT ); -- 2. 스케줄링으로 저장 INSERT INTO metrics_daily SELECT NOW(), OBJECT_SCHEMA, OBJECT_NAME, COUNT_READ, COUNT_WRITE FROM performance_schema.table_io_waits_summary_by_table;
원인을 분석한 결과, 특정 2개의 테이블에서 다량의 Read가 발생하고 있음이 확인되었습니다.
해당 2개의 테이블에서 과도한 IO가 발생한 원인을 분석한 결과,
ArgoCD의 AutoSync 설정으로 인해 의도하지 않게 두 개의 배치 잡이 개발환경에도 자동 배포되고 있음을 확인했습니다.
문제는 이 잡들이 운영 환경에서는 필요한 작업이지만, 개발 환경에서는 불필요하거나 지나치게 자주 실행되고 있었다는 점입니다.
첫 번째 잡은 개발 환경에서 전혀 필요하지 않기 때문에 ArgoCD 설정에서 제거하였습니다.
두 번째 잡은 단순 통계 생성 작업이었기에, 5분 주기에서 하루 1회로 주기를 조정했습니다.
조치 이후, 해당 인스턴스의 일일 IO 호출 수가 1억 건 이상에서 100만 건 이하로 감소하는 것을 Cost Explorer 및 CloudWatch 지표를 통해 확인했습니다.
이로 인해 하루 $30~40에 달하던 IO 비용이 $0.2 이하로 줄어들었습니다.
이 사례를 통해 AutoSync 설정 시 환경 구분 조건을 명확히 걸지 않으면 개발/운영 환경에 동일하게 리소스가 배포되어 예기치 않은 비용이 발생할 수 있음을 확인했습니다.
따라서 ArgoCD Application에 환경별 조건(targetRevision, namespace 등)을 명확히 분기하거나, AutoSync를 비활성화한 뒤 수동 배포로 전환하는 것이 바람직할 수 있습니다.
서비스를 운영하는 관점에서 개발을 하다보면 비용관점에서 무분별하게 리소스를 쓸 수 있습니다. 개발자들이 비용을 항상 염두에 두고 개발하는 것이 아니기 때문입니다. 특히 의도치 않은 비용이 갑자기 높아지는 경우도 왕왕 발생할 수 있습니다. 따라서 비용을 좀 더 타이트하게 관리하고자 한다면 매일 튀는 지표가 있는지 모니터링하는 것이 필요합니다. 특히 개발환경이나 테스트환경처럼 관심에서 벗어난 인스턴스도 소홀히 해서는 안됩니다.
Cost Explorer를 제대로 활용하면 이와 같은 사례에서도 확인할 수 있듯이 문제의 발생지점을 특정할 수 있고 올바른 원인분석법과 해결책으로 비용을 줄일 수 있습니다.
또한 위의 사례는 의도치 않은 과한 IO 비용을 줄이면서 개발 서버의 성능도 확보할 수 있었는데요. 높은 IO는 DB 응답시간을 지연시킬 수 있으며, CPU나 메모리 자원까지 과도하게 점유할 수 있습니다.
위 사례는 IOPS를 줄이기 위해 해당 Job을 제거고 의도적으로 빈도를 낮춤으로 해결되었지만 운영에서는 다음과 같은 고민을 해볼 수도 있습니다.
버퍼를 최대한 많이 활용하도록 인프라를 설계하고 관리하는 것이 좋습니다.
데이터 크기와 비즈니스 요구사항을 잘 고려하여, 가능한 최소 빈도로 배치 잡을 실행하는 것이 좋습니다.
자주 읽히는 쿼리는 인덱스 튜닝을 통해 디스크 접근을 줄이는 것이 좋습니다.
FinOps 커뮤니티에 함께 하실래요?
저는 최근 48%, $36000의 AWS 비용절감을 달성했습니다.
클라우드 비용을 효율화하고 싶은 분들, 비슷한 고민을 나누고 싶다면 제가 운영 중인 AWS-FINOPS-KR Slack 커뮤니티에 참여하세요. 실제 절감 사례, 질문, 전략 공유를 나누실 수 있습니다.