[AWS Aurora] Aurora I/O Planning
l Version : Aurora
Amazon Aurora 는 상용 데이터베이스의 성능 및 가용성과 오픈 소스 데이터 베이스의 단순성 및 비용 효율성을 결합한다 . Aurora 클러스터는 공유 및 분산 Aurora 스토리지 볼륨에 연결된 하나 이상의 인스턴스로 구성된다 . 이 새로운 스토리지 엔진은 관계형 데이터베이스에서 일반적으로 사용되는 기존 블록 스토리지 장치를 자동 크기 조정 , 자동 복구 , 로그 기반 동적 스토리지 볼륨으로 대체한다 . 이 새로운 스토리지 엔진은 클라우드용으로 설계되었으므로 데이터베이스가 전통적으로 스토리지 시스템과 상호작용하는 방식에서 몇 가지 근본적인 변경이 이루어 졌다 . 이 글은 기존 데이터베이스와 비교하여 Aurora 에서 I/O 가 작동하는 방식 , Aurora I/O 비용을 모니터링하는 방법 및 Aurora I/O 를 최적화하는 방법을 설명한다 .
[ 데이터베이스 I/O 개요 ]
높은 수준에서 관계형 데이터베이스는 전통적으로 블록 스토리지와 상호작용한다 . 이 블록 스토리지에서 데이터는 페이지라는 행 블록에 저장된다 . 페이지 크기는 데이터베이스 엔진마다 다르며 동일한 데이터베이스 엔진 내에서도 엔진의 구성 매개변수에 따라 다르다 . Aurora 는 Amazon Aurora MySQL 에 대해 16KB 페이지 크기와 Amazon Aurora PostgreSQL 에 대해 8KB 페이지 크기를 지원한다 . 페이지 크기는 데이터베이스 엔진이 수행하는 가장 작은 I/O 작업이다 .
[ 읽기 작업 ]
MySQL 또는 PostgreSQL 과 같은 관계형 데이터베이스가 디스크에서 페이지를 읽을 때 해당 페이지는 버퍼풀이라는 세그먼트의 메모리에 저장된다 . 해당 페이지는 버퍼풀로 읽혀지는 다른 페이지의 메모리 압력으로 인해 제거되거나 엔진이 다시 시작될 때까지 무기한 남아 있을 수 있다 . 응용 프로그램이 SELECT 쿼리를 실행할 때 엔진은 버퍼풀에 이미 있는 페이지를 사용하여 쿼리를 수행하려고 시도한다 . 쿼리를 완료하는데 필요한 페이지 중 버퍼 풀에 존재하지 않는 페이지는 쿼리가 완료되기 전에 먼저 디스크에서 버퍼풀로 읽어와야 한다 .
Aurora 를 사용하면 읽기가 기본 MySQL 또는 PostgreSQL 과 동일한 방식으로 처리된다 . 즉 , 버퍼풀을 채우는데 필요한 만큼 스토리지 볼륨에서 페이지별로 데이터를 읽고 버퍼풀 내부의 해당 페이지에 대해 SELECT 쿼리를 실행한다 . 이와 같이 읽기 I/O 작업은 스토리지 볼륨에서 페이지 또는 익스텐트를 읽을 때 누적된다 . 쿼리를 실행하는데 필요한 모든 페이지가 이미 버퍼풀에 존재하는 쿼리인 경우 스토리지 볼륨에서 읽을 이유가 없기 때문에 해당 쿼리에 대한 I/O 는 0 이다 . 스토리지 볼륨에서 페이지를 검색 해야하는 쿼리의 경우 읽은 각페이지는 하나의 읽기 I/O 로 계산된다 .
여러 요인에 따라 각 페이지가 얼마나 가득 차 있는지 따라서 동일한 행 수를 유지하는데 필요한 페이지수를 결정할 수 있다 . 이러한 요인 중하나는 인덱스의 지정된 채우기 비율이다 , 채우기 비율 100 은 인덱스가 생성될 때 각 페이지가 100% 용량에 최대한 가깝게 채워 져야함을 나타낸다 . 그 결과 동일한 수의 행을 저장하는데 사용되는 페이지수가 줄어들어 읽기 I/O 효율성이 최대화 된다 . 예를 들어 테이블의 행 크기가 100 바이트이고 채우기 비율이 100 인 기본 키가 생성된 경우 대략 160 개의행이 16KB 페이지에 기록된다 . 그러나 채우기 비율이 50 이고 각 페이지가 50% 용량으로 채워지면 결과는 각각 80 행이 있는 두 페이지가 된다 . 즉 , 채우기 비율이 50 인 동일한 행 집합을 읽으면 읽기 I/O 작업이 두 배 더 많이 발생한다 . 채우기 비율은 기본 키 또는 인덱스를 생성하거나 다시 작성할 때 적용된다 .
후속 DML 작업이 페이지를 채우는 정도는 다른 여러 요인에 따라 다르다 . 예를 들어 , 테이블의 기본 키를 순차적인 자동 증가 정수로 지정하면 새 행이 새 페이지에 깔끔하게 쌓이기 때문에 각 페이지가 용량까지 채워지고 총 페이지 수가 최소화된다 . 반대로 GUID 또는 여러 열로 구성된 자연 키와 같이 기본 키가 무작위인 경우 새 행이 끝에 추가되지 않고 인덱스 중간에 삽입 될 수 있기 때문에 페이지가 분할되는 경향이 있다 . 이런 식으로 인덱스가 단편화 ( 조각화 ) 되고 같은 수의 행을 저장하는 데 필요한 페이지 수가 크게 늘어난다 . 조각난 페이지는 버퍼풀에서 많은 공간을 낭비하고 읽기 I/O 를 증기 시킨다 .
[ 쓰기 작업 ]
관계형 데이터베이스가 INSERT, UPDATE, DELETE 와 같은 쓰기 작업을 수행 할 때 몇 가지 일이 발생한다 . 먼저 수정할 페이지를 버퍼풀에 넣고 해당 페이지에 대한 변경 기록을 트랜잭션 로그에 기록한다 . 이 로그의 이름은 플랫폼마다 다르지만 MySQL 에서는 redo 로그 , PostgreSQL 에서는 WAL 로그라고 한다 . 플랫폼에 따라 디스크에 기록되는 MySQL 바이너리 로그 (binlog) 와 같은 다른 로그도 있지만 이 글에서는 redo 로그에 초점을 맞춘다 .
redo 로그가 기록된 후 페이지는 버퍼풀에서 수정되고 체크포인트로 알려진 프로세스가 다른 모든 더티 페이지와 함께 해당 페이지를 디스크에 쓸 때까지 버퍼풀에 더티 페이지로 남아 있다 . 체크포인트 프로세스는 쓰기 집약적이며 상당한 디스크 I/O를 유발할 수 있다 .
Aurora 를 사용하면 페이지를 버퍼풀에 배치하고 , redo 로그에 쓰고 , 버퍼풀에서 페이지를 수정하는 동일한 작업이 유지되며 , 각 redo 로그 레코드는 페이지에 대한 변경 사항을 나타낸다 . 가장 큰 차이점은 Aurora 는 체크포인트를 발행하지 않는다는 것이다 . 대신 Aurora 스토리지 볼륨에 기록되는 데이터는 전체 페이지에 비해 크기가 매우 작은 redo 로그 뿐이다 . 즉 , 일반적으로 더티 8KB 또는 16KB 페이지를 디스크에 기록하여 생성되는 모든 디스크 I/O 가 제거된다 . 대신 페이지는 스토리지 노드에서 주기적으로 구체화되며 경우에 따라 요청시에도 구체화된다 . 이러한 I/O 작업에는 요금이 부과되지 않는다 . 이러한 변경의 순효과는 모든 쓰기가 3 개의 가용 영역에 분산된 6 개의 스토리지 노드로 전송된다는 사실에도 불구하고 총 쓰기 I/O 작업이 전반적으로 감소한다는 것이다 . (1 개의 쓰기로 계산됨 )
실제 로그 레코드의 크기는 다양하지만 Aurora 클러스터에 대한 쓰기는 가능한 경우 4KB 단위로 전송된다 . 기록기에 크기가 4KB 미만인 여러 로그 레코드가 동시에 있으면 단일 쓰기 작업으로 일괄 처리 된다 . 예를 들어 50 바이트 로그 레코드와 3000 바이트 로그 레코드를 단일 쓰기 작업으로 결합할 수 있다 . 4KB 보다 큰 로그 레코드는 4KB 단위로 분할된다 .
Aurora 클러스터에 읽기 전용 인스턴스가 포함된 경우 스토리지 볼륨으로 전송된 동일한 쓰기가 해당 읽기 전용 인스턴스에도 전송된다 . 이렇게 하면 각 판독기의 버퍼풀이 최신 상태로 유지된다 . 리더가 로그 레코드를 수신하고 이미 읽기 전용 인스턴스의 버퍼 풀에 있는 페이지에 해당하면 로그 레코드에 설명된 변경 사항이 리더의 버퍼풀에 있는 페이지에 적용된 다음 로그 레코드 자체가 삭제 된다 . 버퍼풀에 페이지가 없으면 로그 레코드는 단순히 폐기된다 . 두 경우 모두 스토리지 볼륨에 대한 I/O 활동이 발생하지 않는다 .
[I/O 작업 ]
이제 Aurora 에서 I/O 의 기본 사항을 다루었으므로 Aurora MySQL 을 사용하는 보다 구체적인 예를 살펴본다 . 아래 스크립트를 실행할 때 column_name1 은 테이블의 기본키이며 현재 버퍼풀은 아무것도 채워져 있지 않은 상태를 가정한다 .
UPDATE <tablename> SET <column_name2> = ‘XXX’ WHERE <column_name1> = YYY;
작업 순서는 아래와 같다 .
1. 클러스터형 인덱스를 탐색하여 수정할 행이 포함된 페이지에 도달 ( 암시적 읽기 I/O)
2. 트랜잭션 시작 및 업데이트를 위한 undo 로그 공간 할당 ( 데이터베이스 엔진 I/O)
3. 수정 중인 행이 포함된 페이지 업데이트 ( 사용자 SQL 쿼리 I/O)
4. 트랜잭션 시스템에 undo 로그 기록 ( 데이터베이스 엔진 I/O)
5. 트랜잭션 커밋 ( 데이터베이스 엔진 I/O)
이 예에서는 암시적 I/O 작업이 여러 개 있지만 쓰기 작업의 일부 또는 전체를 단일 쓰기 I/O 로 일괄 처리할 수 있다 . 또한 쓰기 작업임에도 불구하고 읽기 I/O 는 여전히 필요했다 . 마지막으로 주의할 점은 데이터베이스 엔진의 일반적인 트랜잭션 프로세스의 일부인 I/O 작업이 있다는 것이다 . 쿼리를 전혀 포함하지 않는 다른 I/O 작업들도 있다 . 예를 들어 인덱스 생성이다 . 이 시나리오 에서는 새 인덱스를 만드는데 필요한 데이터를 읽기 위한 암시적 읽기 I/O 와 스토리지 볼륨에 새 인덱스를 쓰기 위한 명시적 I/O 쓰기가 있다 .
[I/O 모니터링 ]
Aurora 클러스터로 최적의 I/O 비용을 보장하려면 모니터링부터 시작하는 것이 가장 좋다 . Amazon CloudWatch 지표에서 [Billed] Volume Read IPOS (Count) 를 사용하면 모든 Aurora 인스턴스에 서 소비하는 초당 읽기 I/O 수를 모니터링 할 수 있다 . [Billed] Volume Write IPOS (Count) 지표는 모든 Aurora 인스턴스에서 소비한 쓰기 IPOS 수를 모니터링 할 수 있다 . 모니터링이 설정되고 평균 읽기 및 쓰기 I/O 요청 수에 대한 기준이 결정되면 CloudWatch 경고 알림을 설정하여 I/O 사용량이 급증하는 경우 알림을 받을 수 있다 .
[MySQL 관련 고려 사항 ]
Aurora 클러스터의 쓰기 노드와 읽기 노드 간의 복제에는 추가 I/O 가 필요하지 않다 . 그러나 일부 사용 사례에서는 binlog 를 활성화 하여 Aurora 외부의 대상으로 복제 해야 할 수도 있다 . Aurora MySQL 에서 binlog 를 활성화하면 각 트랜잭션에 대해 쓰기 I/O 요청이 하나 이상 증가하고 내부 binlog 구조를 유지하기 위해 추가 쓰기 I/O 가 필요할 수 있다 . 사용자가 binlog 를 읽을 때 추가 읽기 I/O가 발생한다 .
느린 쿼리 로그를 활성화 하여 장기 실행 쿼리를 캡처하는 것은 최적화 해야 하는 쿼리를 찾는데 유용한 방법일 수 있다 . 또는 성능 스키마 개체를 사용하여 많은 행을 처리하거나 성능 조정이 필요한 SQL 쿼리 목록을 가져올 수 있다 .
[PostgreSQL 관련 고려 사항 ]
PostgreSQL 에서 테이블의 행은 튜플로 유지된다 . 행이 삭제될 때 해당 행을 나타내는 튜플은 물리적으로 제거되지 않는다 . 단순히 삭제된것으로 표시된다 . 또한 업데이트 작업으로 인해 이전 튜플을 삭제된 것으로 표시하고 동일한 행을 나타내기 위해 새 튜플을 삽입한다 . 시간이 지남에 따라 삭제된 튜플의 수가 증가하여 데이터베이스 엔진이 이러한 삭제된 튜플을 통과할 때 불필요한 I/O 와 그에 따른 성능 저하가 발생할 수 있다 . Vacuum( 진공 청소기 ) 프로세스는 데드 튜플을 제거한다 . PostgreSQL vacuum 프로세스에서 생성되는 여러 유형의 데이터베이스 엔진 읽기 및 쓰기 I/O 작업이 있다 .
pgBadger 를 사용하여 로그를 분석하여 느리게 실행되는 쿼리를 표시할 수 있으며 pg_stat_statements 는 값 비싼 SQL 문을 캡처하기 위한 강력한 저수준 도구가 될 수 있다 . 또한 Aurora PostgreSQL 은 쿼리 계획 관리를 지원한다 . 이 기능은 계획 안정성과 계획 적응성을 모두 제공한다 .
주기적으로 테이블 및 인덱스 확장을 검토하고 적절하게 vacuum 프로세스를 실행하는 것이 가장 좋다 . 더 선호되는 접근 방식은 auto vacuum 을 활성화하는 것이다 . 이는 vacuum 프로세스를 자동화 하고 더 나은 성능과 더 낮은 I/O 소비를 제공하는데 도움이 된다 .
기본적으로 모든 업데이트는 색인된 속성이 수정되지 않은 경우에도 새로 생성된 페이지에 연결하기 위해 새로운 색인 항목을 추가 해야한다 . HOT(Heap Only Tuple) 의 경우 업데이트 된 튜플 체인을 유지하면서 가능한 경우 이전 튜플과 동일한 페이지에 새 튜플이 생성된다 . 새 페이지가 생성되지 않았기 때문에 인덱스는 계속해서 동일한 페이지를 가리킬 수 있으며 수정할 필요가 없다 .
다음 쿼리는 비 HOT 업데이트 수를 반환한다 . 테이블에 대한 비 HOT 업데이트 수가 많으면 채우기 비율을 수정하여 향후 일반 또는 HOT 업데이트에 사용하지 않는 데이터 페이지의 추가 디스크 공간을 남길 수 있다 .
select schemaname, relname, n_tup_upd, n_tup_hot_upd from pg_stat_all_tables order by n_tup_upd;
[Aurora 전용 기능 ]
Cloning - 복제 기능을 사용하면 Aurora 클러스터의 복사본을 빠르게 생성할 수 있다 . 생성 시 데이터가 복사되지 않기 때문에 프로세스가 빠르고 I/O 집약적이지 않다 . 대신 Aurora 클론은 클론이 생성될 당시 존재했던 페이지가 변경될 때 새 페이지를 생성하는 쓰기시점의 복사 메커니즘을 사용한다 . 이러한 I/O 작업은 다른 Aurora 클러스터의 I/O 작업과 정확히 동일한 방식으로 작동하고 비용이 청구된다 . 변경을 일으킨 클러스터는 I/O 작업이 청구되는 클러스터이다 .
Global databases - Amazon Aurora 글로벌 데이터베이스는 리전간 지연 시간이 짧은 글로벌 읽기 및 재해복구를 제공한다 . Aurora 글로벌 데이터베이스를 사용하면 기본 클러스터에 단일 지역 클러스터와 동일한 방식으로 I/O 비용이 발생한다 . 그러나 전역 복제본을 유지 관리하려면 모든 쓰기 I/O 가 보조 지역으로 전성되고 보조 클러스터에 기록되어야 한다 . 하나의 기본 클러스터와 하나의 복제본 클러스터가 있는 시나리오에서 쓰기 I/O 는 기본 클러스터의 쓰기 I/O 의 3 배이다 . ( 기본 + 전송 + 보조 ). 읽기 I/O 의 경우 기본 및 보조 클러스터의 독립적인 읽기를 기반으로 요금이 청구 된다 .
Parallel queries (Aurora MySQL) – Aurora MySQL 의 병렬 쿼리는 필터링 및 집계 작업의 대부분을 스토리지 엔진으로 푸시하여 특정 분석 쿼리에 대한 성능 향상을 제공한다 . 그러나 병렬 쿼리로 처리된 페이지는 버퍼풀에 저장되지 않는다 . 따라서 읽기 I/O 요청이 증가할 수 있다 .
Cluster cache management (Aurora PostgreSQL) – Aurora PostgreSQL 의 클러스터 캐시 관리 기능은 클러스터에서 기본 장애 조치 노드로 지정된 리더 노드가 작성자와 유사한 버퍼풀을 유지하도록 한다 . 이를 통해 장애조치 (failover) 시 새 기록기의 성능이 향상된다 . 리더 (Reader) 가 버퍼풀에서 유사한 페이지 세트를 유지하려면 스토리지 볼륨에서 주기적으로 읽어야 한다 . 이 활동으로 인해 해당 인스턴스에 대한 읽기 I/O 활동이 증가한다 .
[ 일반적인 모범 사례 ]
다음은 Aurora 에서 I/O 를 계획할 때 모범 사례이다 .
인덱스 검토
l 모든 인덱스가 쓰기 I/O 볼륨에 추가되므로 사용하지 않는 인덱스를 제거
l 테이블 스캔 또는 기타 높은 I/O 활동을 처리하기 위해 새 인덱스를 작성
l 기본키의 경우 키 값에 자동 증가 정수를 사용
l 테이블과 인덱스를 생성할 때 적절한 채우기 비율을 고려
l 가능하면 커버링 인덱스를 사용
l 특정 쿼리에 대해 분석된 페이지를 최소화 하려면 가능한 경우 파티션을 사용
실적이 저조한 쿼리 식별
l Amazon RDS 성능 개선 도우미와 같은 도구를 사용하여 성능이 낮은 쿼리를 식별
l I/O 대기가 높은 쿼리의 실행 계획을 검사
l 높은 I/O 대기 쿼리를 다시 작성하거나 이러한 쿼리를 지원하는 적절한 인덱스를 다시 생성
하드웨어 고려
l 99.98% 이상의 목표로 버퍼캐시 적중률을 모니터링한다 . 이 측정 단위는 디스크에서 페이지를 읽는 것이 아니라 버퍼풀에서 페이지를 찾는 빈도를 반영한다 .
l 버퍼 캐시 적중률이 낮으면 메모리가 더 많은 인스턴스 유형을 사용하는 것이 좋다 .
l 더 많은 메모리를 제공하는 더 큰 인스턴스 크기로 확장하면 보고 워크로드에 대한 전체 읽기 I/O 가 줄어들어 전체 I/O 비용이 감소할 수 있다 .
내장된 Aurora 기능 사용
l 높은 I/O를 초래할 수 있는 논리적 내보내기를 사용하는 대신 Aurora 의 특정시점 복구 기능을 활용한다 .
l 전체 데이터베이스를 스캔해야 하는 논리적 내보내기 도구를 사용하는 것과 비교하여 클론을 생성하려면 I/O 가 필요하지 않다 .
[ 목적에 맞게 구축된 데이터베이스 ]
올바른 작업에 올바른 도구를 사용하는 것이 중요하다 . Aurora 는 처리량이 많은 대규모 병렬 OLTP 시스템으로 설계 되었다 . Aurora 는 초당 수십만개의 쿼리로 수천개의 연결을 처리하는데 탁월하다 . 이러한 쿼리는 일반적으로 단일 행 또는 작은행 집합에서 작동한다 . Aurora 에는 분석 쿼리와 관련하여 향상된 기능을 제공하는 Aurora MySQL 병렬 쿼리와 같은 특정 기능이 있지만 대규모 테이블 스캔 및 대규모 집계를 중심으로 설계된 워크로드는 상당한 수의 읽기 IOPS 를 생성한다 . 이 시나리오에서는 Amazon Redshift, Amazon Athena, S3 Select 또는 Amazon EMR 과 같은 다른 도구가 이러한 워크로드에 더 적합할 수 있다 . 마찬가지로 쓰기가 많고 읽기가 거의 없는 로그 중심의 워크로드의 경우 Amazon Kinesis Data Firehose 와 같은 도구를 Amazon Simple Storage Service(Amazon S3) 와 함께 사용하는 것이 가장 적합할 수 있다 .
[ 참고자료 ]
l https://dataintegration.info/planning-i-o-in-amazon-aurora
2022-03-20 / Sungwook Kang / http://sungwookkang.com
AWS RDS, MySQL, Aurora, RDS Aurora, Aurora MySQL, Aurora Storage, Aurora PostgreSQL, Aurora I/O Plan, 오로라 읽기 쓰기 계획 , 오로라 스토리지 계획 , 오로라 활용