

 Amazon Redshift는 패치 198부터 새 Python UDF 생성을 더 이상 지원하지 않습니다. 기존 Python UDF는 2026년 6월 30일까지 계속 작동합니다. 자세한 내용은 [블로그 게시물](https://aws.amazon.com/blogs/big-data/amazon-redshift-python-user-defined-functions-will-reach-end-of-support-after-june-30-2026/)을 참조하세요.

# 쿼리 튜닝을 위한 진단 쿼리
<a name="diagnostic-queries-for-query-tuning"></a>

아래 쿼리들은 쿼리 성능에 영향을 미칠 수 있는 쿼리 또는 쿼리 테이블의 문제를 식별하는 데 사용됩니다. 이러한 쿼리는 [쿼리 분석 및 개선 사항](c-query-tuning.md)에서 언급한 쿼리 튜닝 프로세스와 함께 사용하는 것이 좋습니다.

**참고**  
이러한 쿼리는 Amazon Redshift 프로비저닝된 클러스터를 대상으로 합니다. 이러한 쿼리는 Redshift Serverless 작업 그룹에는 사용할 수 없습니다.

**Topics**
+ [튜닝에 가장 적합한 쿼리 식별](identify-queries-that-are-top-candidates-for-tuning.md)
+ [데이터 스큐 또는 미정렬 행이 포함된 테이블 식별](identify-tables-with-data-skew-or-unsorted-rows.md)
+ [중첩 루프가 포함된 쿼리 식별](identify-queries-with-nested-loops.md)
+ [쿼리의 대기열 대기 시간 검토](review-queue-wait-times-for-queries.md)
+ [테이블별 쿼리 알림 검토](review-query-alerts-by-table.md)
+ [통계가 누락된 테이블 식별](identify-tables-with-missing-statistics.md)

# 튜닝에 가장 적합한 쿼리 식별
<a name="identify-queries-that-are-top-candidates-for-tuning"></a>

다음은 지난 7일 동안 실행한 쿼리 문 중에서 가장 많은 시간이 소요된 문 50개를 구분하는 쿼리입니다. 결과를 사용하면 비정상적으로 오래 걸리는 쿼리를 식별할 수 있습니다. 또한 자주 실행되는 쿼리(결과 집합에 두 번 이상 나타나는 쿼리)를 식별할 수 있습니다. 이러한 쿼리들은 튜닝을 통해 시스템 성능을 개선하기 좋은 후보들입니다.

이 쿼리는 식별된 각 쿼리와 연결되어 있는 알림 이벤트의 수도 반환합니다. 이러한 알림 이벤트를 통해 쿼리 성능을 개선하는 데 필요한 세부 정보를 알아낼 수 있습니다. 자세한 내용은 [쿼리 알림 검토](c-reviewing-query-alerts.md) 섹션을 참조하세요.

```
select trim(database) as db, count(query) as n_qry, 
max(substring (qrytext,1,80)) as qrytext, 
min(run_minutes) as "min" , 
max(run_minutes) as "max", 
avg(run_minutes) as "avg", sum(run_minutes) as total,  
max(query) as max_query_id, 
max(starttime)::date as last_run, 
sum(alerts) as alerts, aborted
from (select userid, label, stl_query.query, 
trim(database) as database, 
trim(querytxt) as qrytext, 
md5(trim(querytxt)) as qry_md5, 
starttime, endtime, 
(datediff(seconds, starttime,endtime)::numeric(12,2))/60 as run_minutes,     
alrt.num_events as alerts, aborted 
from stl_query 
left outer join 
(select query, 1 as num_events from stl_alert_event_log group by query ) as alrt 
on alrt.query = stl_query.query
where userid <> 1 and starttime >=  dateadd(day, -7, current_date)) 
group by database, label, qry_md5, aborted
order by total desc limit 50;
```

# 데이터 스큐 또는 미정렬 행이 포함된 테이블 식별
<a name="identify-tables-with-data-skew-or-unsorted-rows"></a>

다음은 데이터 분산이 균일하지 못하거나(데이터 스큐), 정렬되지 않은 행의 비율이 높은 테이블을 찾아내는 쿼리입니다.

`skew` 값이 낮으면 테이블 데이터가 올바로 분산된 것을 의미합니다. 테이블의 `skew` 값이 4.00 이상이면 데이터 분산 스타일을 수정하는 것이 좋습니다. 자세한 내용은 [최적이 아닌 데이터 분산](query-performance-improvement-opportunities.md#suboptimal-data-distribution) 섹션을 참조하세요.

테이블의 `pct_unsorted` 값이 20%보다 높으면 [VACUUM](r_VACUUM_command.md) 명령을 실행하는 것이 좋습니다. 자세한 내용은 [정렬되지 않았거나 잘못 정렬된 행](query-performance-improvement-opportunities.md#unsorted-or-mis-sorted-rows) 섹션을 참조하세요.

그 밖에도 각 테이블마다 `mbytes` 값과 `pct_of_total` 값을 살펴봐야 합니다. 이러한 열은 테이블 크기를 비롯해 원시 디스크에서 테이블이 사용하는 공간 비율을 나타냅니다. 원시 디스크 공간에는 Amazon Redshift가 내부 사용 목적으로 예약하는 공간도 포함되므로 사용자가 사용할 수 있는 디스크 공간 크기인 공칭 디스크 용량보다 더 커야 합니다. 이 정보를 사용하여 여유 디스크 공간이 가장 큰 테이블 크기의 2.5배 이상인지 확인합니다. 이 정도 크기의 공간을 사용할 수 있도록 유지하면 시스템이 복합 쿼리를 처리할 때도 중간 결과를 디스크에 작성할 수 있습니다.

```
select trim(pgn.nspname) as schema, 
trim(a.name) as table, id as tableid, 
decode(pgc.reldiststyle,0, 'even',1,det.distkey ,8,'all') as distkey, dist_ratio.ratio::decimal(10,4) as skew, 
det.head_sort as "sortkey", 
det.n_sortkeys as "#sks", b.mbytes,  
decode(b.mbytes,0,0,((b.mbytes/part.total::decimal)*100)::decimal(5,2)) as pct_of_total, 
decode(det.max_enc,0,'n','y') as enc, a.rows, 
decode( det.n_sortkeys, 0, null, a.unsorted_rows ) as unsorted_rows , 
decode( det.n_sortkeys, 0, null, decode( a.rows,0,0, (a.unsorted_rows::decimal(32)/a.rows)*100) )::decimal(5,2) as pct_unsorted 
from (select db_id, id, name, sum(rows) as rows, 
sum(rows)-sum(sorted_rows) as unsorted_rows 
from stv_tbl_perm a 
group by db_id, id, name) as a 
join pg_class as pgc on pgc.oid = a.id
join pg_namespace as pgn on pgn.oid = pgc.relnamespace
left outer join (select tbl, count(*) as mbytes 
from stv_blocklist group by tbl) b on a.id=b.tbl
inner join (select attrelid, 
min(case attisdistkey when 't' then attname else null end) as "distkey",
min(case attsortkeyord when 1 then attname  else null end ) as head_sort , 
max(attsortkeyord) as n_sortkeys, 
max(attencodingtype) as max_enc 
from pg_attribute group by 1) as det 
on det.attrelid = a.id
inner join ( select tbl, max(mbytes)::decimal(32)/min(mbytes) as ratio 
from (select tbl, trim(name) as name, slice, count(*) as mbytes
from svv_diskusage group by tbl, name, slice ) 
group by tbl, name ) as dist_ratio on a.id = dist_ratio.tbl
join ( select sum(capacity) as  total
from stv_partitions where part_begin=0 ) as part on 1=1
where mbytes is not null 
order by  mbytes desc;
```

# 중첩 루프가 포함된 쿼리 식별
<a name="identify-queries-with-nested-loops"></a>

다음은 중첩 루프에 대한 알림 이벤트가 기록된 쿼리를 식별하는 쿼리입니다. 중첩 루프 조건을 해결하는 방법에 대한 자세한 내용은 [중첩 루프](query-performance-improvement-opportunities.md#nested-loop) 섹션을 참조하세요.

```
select query, trim(querytxt) as SQL, starttime 
from stl_query 
where query in (
select distinct query 
from stl_alert_event_log 
where event like 'Nested Loop Join in the query plan%') 
order by starttime desc;
```

# 쿼리의 대기열 대기 시간 검토
<a name="review-queue-wait-times-for-queries"></a>

다음은 최근 쿼리가 실행에 앞서 쿼리 대기열의 슬롯이 열릴 때까지 대기한 시간을 나타내는 쿼리입니다. 대기 시간이 높은 추이가 발견되면 쿼리 대기열 구성을 수정하여 처리량을 개선하는 것이 좋습니다. 자세한 내용은 [수동 WLM 구현](cm-c-defining-query-queues.md) 섹션을 참조하세요.

```
select trim(database) as DB , w.query, 
substring(q.querytxt, 1, 100) as querytxt,  w.queue_start_time, 
w.service_class as class, w.slot_count as slots, 
w.total_queue_time/1000000 as queue_seconds, 
w.total_exec_time/1000000 exec_seconds, (w.total_queue_time+w.total_Exec_time)/1000000 as total_seconds 
from stl_wlm_query w 
left join stl_query q on q.query = w.query and q.userid = w.userid 
where w.queue_start_Time >= dateadd(day, -7, current_Date) 
and w.total_queue_Time > 0  and w.userid >1   
and q.starttime >= dateadd(day, -7, current_Date) 
order by w.total_queue_time desc, w.queue_start_time desc limit 35;
```

# 테이블별 쿼리 알림 검토
<a name="review-query-alerts-by-table"></a>

다음은 테이블 자체에 대한 알림 이벤트가 기록된 테이블을 찾아내는 동시에 가장 자주 기록되는 알림 유형을 식별하는 쿼리입니다.

식별된 테이블에서 행의 `minutes` 값이 높으면 테이블에서 해당 테이블에 대한 [ANALYZE](r_ANALYZE.md) 또는 [VACUUM](r_VACUUM_command.md) 실행과 같은 일상적인 유지 관리가 필요한지를 확인합니다.

임의의 행에서 `count` 값이 높을 때 `table` 값이 NULL이라면 STL\$1ALERT\$1EVENT\$1LOG에서 연결된 `event` 값에 대한 쿼리를 실행하여 알림이 잦은 이유를 조사하세요.

```
select trim(s.perm_table_name) as table, 
(sum(abs(datediff(seconds, s.starttime, s.endtime)))/60)::numeric(24,0) as minutes, trim(split_part(l.event,':',1)) as event,  trim(l.solution) as solution, 
max(l.query) as sample_query, count(*) 
from stl_alert_event_log as l 
left join stl_scan as s on s.query = l.query and s.slice = l.slice 
and s.segment = l.segment and s.step = l.step
where l.event_time >=  dateadd(day, -7, current_Date) 
group by 1,3,4 
order by 2 desc,6 desc;
```

# 통계가 누락된 테이블 식별
<a name="identify-tables-with-missing-statistics"></a>

다음은 통계가 누락된 테이블에 대한 쿼리 수를 제공하는 쿼리입니다. 이 쿼리가 어떤 행이든 반환하는 경우에는 `plannode` 값을 살펴보면서 해당 테이블을 확인한 후 [ANALYZE](r_ANALYZE.md)를 실행하세요.

```
select substring(trim(plannode),1,100) as plannode, count(*) 
from stl_explain 
where plannode like '%missing statistics%' 
group by plannode 
order by 2 desc;
```