데이터베이스 작업에서 GROUP BY 절의 성능은 대규모 데이터셋에서 중요한 영향을 미칩니다. PostgreSQL은 데이터를 그룹화하거나 정렬할 때, 정렬 순서에 따라 쿼리의 속도와 자원 소모량이 달라질 수 있습니다.
PostgreSQL 사용자는 종종 데이터를 다양한 기준으로 정렬하고 그룹화하는 분석 쿼리를 사용합니다. 이러한 연산자를 최적화하면 쿼리 실행 시간과 비용을 크게 줄일 수 있습니다. 이번 포스트에서는 GROUP BY 표현식에서 열의 순서를 선택하는 최적화 방법에 대해 다룹니다.
PostgreSQL은 이미 ORDER BY 조건에 따라 그룹화된 표현식 리스트를 재정렬하여 추가 정렬 작업을 제거하고 계산 리소스를 절약할 수 있습니다.
DBMS는 일반적으로 한 개 이상의 열로 테이블 데이터를 그룹화하기 위해 해싱(HashAgg) 방법 또는 사전 정렬 후 순차적으로 집계하는 방법(SortAgg)을 사용합니다. 여러 열을 기준으로 데이터를 정렬할 때, PostgreSQL은 각 값 쌍마다 비교 연산자를 호출해야 합니다.
예를 들어, ('UserX1', 'Saturday', $100)라는 행과 ('UserX1', 'Monday', $10)라는 행을 비교한다고 가정해 봅시다. 첫 번째 값이 동일하면 두 번째 값을 비교하고, 두 번째 값이 다르면 세 번째 값을 비교할 필요가 없습니다.
제안된 SortAgg 최적화 메커니즘은 바로 이러한 원칙을 기반으로 합니다. 비교 시 중복값이 적은 열(예: UserID)을 먼저 비교하면 비교 연산자 호출 횟수가 줄어들어 성능이 향상됩니다.
PostgreSQL은 데이터를 그룹화할 때 다음 두 가지 주요 집계 방법을 사용합니다:
PostgreSQL에서 데이터를 정렬하거나 그룹화할 때는 여러 열에 걸쳐 값을 비교합니다.
다음과 같은 순서를 따릅니다:
이 과정에서 고유 값(카디널리티, Cardinality)가 높은 열을 먼저 비교하면, 불필요한 비교 연산이 줄어들고 쿼리 속도가 개선됩니다.
카디널리티는 한 열에 존재하는 서로 다른 고유 값의 개수를 의미합니다.
고유 값이 많을수록, 정렬이나 비교 과정에서 일찍 차이가 발생해 성능이 좋아집니다.
CREATE TABLE shopping (
CustomerId bigint, CategoryId bigint, WeekDay text, Total money
);
INSERT INTO shopping (CustomerId, CategoryId, WeekDay, Total)
SELECT random()*1E6, random()*100,
'Day ' || (random()*7)::integer,
random()*1000::money
FROM generate_series(1, 1E6) AS gs;
VACUUM ANALYZE shopping;
WeekDay(고유 값 7개)부터 정렬.
EXPLAIN (ANALYZE, TIMING OFF)
SELECT CustomerId, CategoryId, WeekDay, Total
FROM shopping
ORDER BY WeekDay, Total, CategoryId, CustomerId;
Sort (cost=124363.84..126863.84 rows=1000000 width=30) (actual rows=1000000 loops=1)
Sort Key: weekday, total, categoryid, customerid
Sort Method: quicksort Memory: 102702kB
-> Seq Scan on shopping (cost=0.00..24706.00 rows=1000000 width=30) (actual rows=1000000 loops=1)
Planning Time: 1.699 ms
Execution Time: 1772.805 ms
CustomerId(고유 값 많음)부터 정렬.
EXPLAIN (ANALYZE, TIMING OFF)
SELECT CustomerId, CategoryId, WeekDay, Total
FROM shopping
ORDER BY CustomerId, CategoryId, WeekDay, Total;
Sort (cost=124363.84..126863.84 rows=1000000 width=30) (actual rows=1000000 loops=1)
Sort Key: customerid, categoryid, weekday, total
Sort Method: quicksort Memory: 102702kB
-> Seq Scan on shopping (cost=0.00..24706.00 rows=1000000 width=30) (actual rows=1000000 loops=1)
Planning Time: 0.197 ms
Execution Time: 466.041 ms
비교 결과
CustomerId와 WeekDay의 순서가 고정되어 있음.
SELECT CustomerId, WeekDay, SUM(Total)
FROM shopping
GROUP BY CustomerId, WeekDay;
카디널리티 높은 열 CustomerId를 먼저 그룹화.
결과적으로 더 적은 연산으로 그룹화 가능.
SELECT CustomerId, WeekDay, SUM(Total)
FROM shopping
GROUP BY WeekDay, CustomerId;
PostgreSQL은 pg_statistic 테이블에 저장된 통계를 사용하여 최적화합니다:
통계 확인
SELECT attname, n_distinct
FROM pg_stats
WHERE tablename = 'shopping';
결과 예시
컬럼명 | 값 |
---|---|
customerid | -0.511195 |
categoryid | 101.0 |
weekday | 8.0 |
total | 92032.0 |
효율적인 쿼리 최적화는 CPU 자원을 절약하고 대규모 데이터셋에서 놀라운 성능 향상을 가져옵니다. 여러분의 PostgreSQL 환경에서도 통계를 활용한 열 순서 최적화를 시도해 보세요!