PostgreSQL 테이블별 디스크 사용량 조회
PostgreSQL에서 어느 테이블이 공간을 많이 사용하는지 확인할 때 참고하던 쿼리입니다. 원래 메모는 pg_class와 pg_namespace를 조회해 전체 크기가 큰 순서대로 20개를 보는 방식이었습니다.
테이블별 전체 크기
다음은 스키마 이름을 추가하고 일반 테이블로 대상을 제한한 쿼리입니다. 현재 접속한 데이터베이스 안의 테이블을 조회합니다.
SELECT
n.nspname AS schema_name,
c.relname AS table_name,
pg_size_pretty(pg_total_relation_size(c.oid)) AS total_size,
pg_size_pretty(pg_table_size(c.oid)) AS table_size,
pg_size_pretty(pg_indexes_size(c.oid)) AS indexes_size,
c.reltuples::numeric AS estimated_rows
FROM pg_class AS c
JOIN pg_namespace AS n ON n.oid = c.relnamespace
WHERE c.relkind = 'r'
AND n.nspname NOT IN ('pg_catalog', 'information_schema')
AND n.nspname !~ '^pg_toast'
ORDER BY pg_total_relation_size(c.oid) DESC
LIMIT 20;크기는 숫자 값으로 정렬하고, 출력할 때만 pg_size_pretty로 단위를 바꿉니다. 단위가 붙은 문자열을 정렬 기준으로 쓰면 크기 순서와 달라질 수 있습니다.
용량 함수의 범위
| 함수 | 포함하는 범위 |
|---|---|
pg_relation_size | 기본적으로 해당 relation의 main fork |
pg_table_size | 테이블의 TOAST와 보조 저장 영역 포함, 인덱스 제외 |
pg_indexes_size | 테이블에 연결된 인덱스 |
pg_total_relation_size | 테이블과 인덱스·TOAST를 포함한 전체 |
pg_relation_size를 테이블 전체 크기로 해석하면 TOAST나 보조 저장 영역을 놓칠 수 있습니다. 전체 크기는 pg_table_size와 pg_indexes_size의 합으로 볼 수 있습니다.
위 쿼리의 relkind = 'r'에는 일반 테이블과 개별 파티션이 포함됩니다. 파티션 부모와 모든 자식의 합계를 한 행으로 보여 주는 쿼리는 아니므로, 파티션 테이블은 자식까지 묶어 별도로 집계해야 합니다.
추정 행 수 해석
reltuples는 COUNT(*) 결과가 아니라 통계 갱신 과정에서 얻은 추정치입니다. 최근 데이터 변경이 아직 반영되지 않았거나, 통계가 준비되지 않은 관계에서는 값의 의미를 확인해야 합니다.
용량이 큰 테이블을 찾는 데는 유용하지만, 이 값만으로 정확한 데이터 건수나 조회 속도를 보장할 수는 없습니다.
용량 확인 이후의 판단
테이블이 크다는 이유만으로 VACUUM이나 인덱스 삭제를 바로 결정하지는 않습니다. 보관할 데이터인지, 재사용 가능한 빈 공간인지, 인덱스가 필요한지부터 구분해야 합니다.
일반 VACUUM은 죽은 행의 공간을 다시 사용할 수 있도록 정리하지만, 보통 그 공간을 운영체제에 반환하지는 않습니다. VACUUM FULL은 테이블을 다시 쓰며 강한 잠금과 추가 공간이 필요합니다. 통계의 dead tuple 비율 하나를 모든 테이블의 실행 기준으로 삼기 어렵습니다.