Aurora DSQL EXPLAIN 계획 읽기 - Amazon Aurora DSQL

Aurora DSQL EXPLAIN 계획 읽기

EXPLAIN 계획을 읽는 방법을 이해하는 것이 쿼리 성능을 최적화하는 데 중요합니다. 이 섹션에서는 Aurora DSQL 쿼리 계획의 실제 예제를 살펴보고, 다양한 스캔 유형이 작동하는 방식을 보여주고, 필터가 적용되는 위치를 설명하고, 최적화 기회를 강조합니다.

전체 스캔 예제

Aurora DSQL에는 PostgreSQL과 기능적으로 동일한 순차 스캔과 전체 스캔이 모두 있습니다. 이 두 가지의 유일한 차이점은 전체 스캔이 스토리지에서 추가 필터링을 활용할 수 있다는 것입니다. 따라서 거의 항상 순차 스캔보다 먼저 선택됩니다. 유사성으로 인해 더 흥미로운 전체 스캔의 예만 다룹니다.

전체 스캔은 대부분 프라이머리 키가 없는 테이블에서 사용됩니다. Aurora DSQL 프라이머리 키는 기본적으로 전체 커버링 인덱스이므로 Aurora DSQL은 PostgreSQL이 순차 스캔을 사용하는 많은 상황에서 프라이머리 키에 인덱스 전용 스캔을 사용할 가능성이 높습니다. 대부분의 다른 데이터베이스와 마찬가지로 인덱스가 없는 테이블은 잘못 확장됩니다.

EXPLAIN SELECT account_id FROM transaction WHERE transaction_date > '2025-01-01' AND description LIKE '%external%';
                                                   QUERY PLAN
----------------------------------------------------------------------------------------------------------------
 Full Scan (btree-table) on transaction  (cost=125100.05..177933.38 rows=33333 width=16)
   Filter: (description ~~ '%external%'::text)
   -> Storage Scan on transaction (cost=12510.05..17793.38 rows=66666 width=16)
        Projections: account_id, description
        Filters: (transaction_date > '2025-01-01 00:00:00'::timestamp without time zone)
        -> B-Tree Scan on transaction (cost=12510.05..17793.38 rows=100000 width=30)

이 계획은 서로 다른 단계에 적용된 두 개의 필터를 보여줍니다. transaction_date > '2025-01-01' 조건은 스토리지 계층에 적용되므로 반환되는 데이터의 양이 줄어듭니다. description LIKE '%external%' 조건은 데이터가 전송된 후 쿼리 프로세서에서 나중에 적용되므로 효율성이 떨어집니다. 스토리지 또는 인덱스 계층에 더 선택적인 필터를 푸시하면 일반적으로 성능이 향상됩니다.

인덱스 전용 스캔 예제

인덱스 전용 스캔은 스토리지 계층으로의 왕복이 가장 적기 때문에 Aurora DSQL에서 가장 최적의 스캔 유형이며 가장 많은 필터링을 수행할 수 있습니다. 하지만 인덱스 전용 스캔이 표시되었다고 해서 최상의 계획이 있다는 의미는 아닙니다. 발생할 수 있는 다양한 수준의 필터링으로 인해 필터링이 발생할 수 있는 다양한 위치에 계속 주의를 기울여야 합니다.

EXPLAIN SELECT balance FROM account WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb' AND balance > 100 AND status = 'pending';
                                  QUERY PLAN
-------------------------------------------------------------------------------
 Index Only Scan using idx1 on account  (cost=725.05..1025.08 rows=8 width=18)
   Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   Filter: (balance > '100'::numeric)
   -> Storage Scan on idx1 (cost=12510.05..17793.38 rows=9 width=16)
        Projections: balance
        Filters: ((status)::text = 'pending'::text)
        -> B-Tree Scan on idx1 (cost=12510.05..17793.38 rows=10 width=30)
            Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)

이 계획에서는 인덱스 조건인 customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'가 인덱스 스캔 중에 먼저 평가되는데, 이는 스토리지에서 읽는 데이터의 양을 제한하기 때문에 가장 효율적인 단계입니다. 스토리지 필터 status = 'pending'는 데이터를 읽은 후 컴퓨팅 계층으로 전송하기 전에 적용되므로 전송되는 데이터의 양이 줄어듭니다. 마지막으로 쿼리 프로세서 필터 balance > 100은 데이터가 이동된 후 마지막으로 실행되므로 효율성이 가장 낮습니다. 이 중 인덱스 조건은 스캔되는 데이터의 양을 직접 제어하기 때문에 최고의 성능을 제공합니다.

인덱스 스캔 예제

인덱스 스캔은 기본 테이블을 직접적으로 호출해야 하는 추가 단계가 있다는 점을 제외하면 인덱스 전용 스캔과 유사합니다. Aurora DSQL은 스토리지 필터를 지정할 수 있으므로 인덱스 직접 호출과 조회 직접 호출 모두에서 지정할 수 있습니다.

이를 명확히 하기 위해 Aurora DSQL은 계획을 두 개의 노드로 제공합니다. 이렇게 하면 스토리지에서 반환되는 행 측면에서 포함 열을 추가하는 것이 얼마나 도움이 되는지 명확하게 확인할 수 있습니다.

EXPLAIN SELECT balance FROM account WHERE customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb' AND balance > 100 AND status = 'pending' AND created_at > '2025-01-01';
                                                QUERY PLAN
----------------------------------------------------------------------------------------------------------
 Index Scan using idx1 on account  (cost=728.18..1132.20 rows=3 width=18)
   Filter: (balance > '100'::numeric)
   Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   -> Storage Scan on idx1 (cost=12510.05..17793.38 rows=8 width=16)
        Projections: balance
        Filters: ((status)::text = 'pending'::text)
        -> B-Tree Scan on account (cost=12510.05..17793.38 rows=10 width=30)
            Index Cond: (customer_id = '4b18a761-5870-4d7c-95ce-0a48eca3fceb'::uuid)
   -> Storage Lookup on account (cost=12510.05..17793.38 rows=4 width=16)
        Filters: (created_at > '2025-01-01 00:00:00'::timestamp without time zone)
        -> B-Tree Lookup on transaction (cost=12510.05..17793.38 rows=8 width=30)

이 계획은 여러 단계에서 필터링이 수행되는 방식을 보여 줍니다.

  • customer_id 의 인덱스 조건은 데이터를 조기에 필터링합니다.

  • status의 스토리지 필터는 컴퓨팅으로 전송되기 전에 결과의 범위를 좁힙니다.

  • balance의 쿼리 프로세서 필터는 전송 후 나중에 적용됩니다.

  • created_at의 조회 필터는 기본 테이블에서 추가 열을 가져올 때 평가됩니다.

자주 사용되는 열을 INCLUDE 필드로 추가하면이 조회가 제거되고 성능이 향상될 수 있습니다.

모범 사례

  • 필터를 인덱싱된 열과 정렬하여 필터링을 이전에 푸시합니다.

  • INCLUDE 열을 사용하여 인덱스 전용 스캔을 허용하고 조회를 방지합니다.

  • 통계를 최신 상태로 유지하여 비용 및 행 추정치가 정확한지 확인합니다.

  • 큰 테이블에서 인덱싱되지 않은 쿼리를 방지하여 비용이 많이 드는 전체 스캔을 방지합니다.