본문 바로가기
IT/소프트웨어

PostgreSQL 성능 최적화, 남들이 안 쓰는 비법 3가지

by DrKo83 2026. 2. 17.
300x250
반응형

 

CHECK 제약조건으로 풀스캔 날려버리기

보통 데이터베이스 최적화라고 하면 인덱스 걸고, 쿼리 고치고, 정규화 풀고... 이런 뻔한 방법들만 떠올리잖아요. 물론 효과는 있지만, 가끔은 창의적으로 접근하면 훨씬 더 큰 성과를 낼 수 있어요.

제가 오늘 소개할 첫 번째 방법은 바로 CHECK 제약조건을 활용한 최적화예요. 예를 들어볼게요. 유저 테이블에 요금제 컬럼이 있고, 'free'랑 'pro' 두 가지만 들어갈 수 있다고 CHECK 제약조건을 걸어뒀다고 가정해보죠.

그런데 분석가가 실수로 'Pro'(대문자 P)로 검색하면 어떻게 될까요? 당연히 결과는 0건이에요. 문제는 PostgreSQL이 이걸 알면서도 전체 테이블을 스캔한다는 거예요. 10만 건 데이터를 다 뒤져서 "없네요~"라고 대답하는 거죠. 정말 비효율적이죠?

여기서 마법 같은 설정이 등장해요. constraint_exclusion을 'on'으로 설정하면, PostgreSQL이 CHECK 제약조건을 보고 "아, 이건 절대 나올 수 없는 값이네?"라고 판단해서 아예 스캔을 건너뛰어요. 실행 시간이 7.4ms에서 0.008ms로 뚝 떨어지는 거죠. 거의 1,000배 가까이 빨라지는 셈이에요.

PostgreSQL, 왜 이렇게 인기가 많을까?

최근 Stack Overflow의 2024년 개발자 설문조사에 따르면, PostgreSQL은 전 세계 개발자들이 가장 선호하는 데이터베이스 4위에 랭크됐어요. 사용자가 늘어날수록 이런 세세한 최적화가 더 중요해지겠죠.

다만 이 설정을 모든 쿼리에 적용하면 단순 쿼리에서는 오히려 플래닝 시간이 더 걸릴 수 있어요. 그래서 기본값은 'partition'으로 되어 있고요. 하지만 BI 툴이나 리포팅 환경처럼 사람이 직접 쿼리를 짜는 곳에서는 실수가 잦기 때문에, 'on'으로 설정해두면 불필요한 풀스캔을 막아줄 수 있어요.

날짜만 인덱싱하면 크기가 1/3로 줄어든다

두 번째 비법은 함수 기반 인덱스예요. 판매 테이블에 판매 일시와 금액이 있다고 해볼게요. 분석가들이 매일 일별 리포트를 뽑는데, 쿼리가 약 600ms 정도 걸린다고 불평하더라고요.

그래서 sold_at 컬럼에 B-Tree 인덱스를 걸었어요. 실행 시간은 187ms로 줄었지만, 인덱스 크기가 무려 214MB나 됐어요. 테이블 크기의 거의 절반이에요. 이건 좀 과하다 싶더라고요.

여기서 생각을 좀 바꿔봤어요. 분석가들은 일별 리포트만 필요한데, 우리는 밀리초 단위까지 인덱싱한 거예요. 과한 거죠. 그래서 날짜 부분만 인덱싱하는 함수 기반 인덱스를 만들었어요.

결과는? 인덱스 크기가 66MB로 줄었어요. 3배 이상 작아진 거죠. DB-Engines 랭킹에 따르면 2025년 1월 기준 PostgreSQL의 인기 점수는 계속 상승 중인데, 이런 효율적인 인덱스 관리가 더 많은 기업들의 선택을 받는 이유 중 하나예요.

함수 기반 인덱스의 숨겨진 함정

하지만 여기엔 함정이 있어요. 쿼리에서 정확히 똑같은 표현식을 써야만 인덱스를 탈 수 있다는 거예요. date_trunc 대신 ::date로 캐스팅하면 인덱스를 못 써요. 조직 내에서 모든 사람이 똑같은 표현식을 쓰게 강제하는 건 현실적으로 불가능하죠.

그래서 예전에는 뷰를 만들어서 해결했는데, PostgreSQL 18부터는 가상 생성 컬럼(Virtual Generated Column)을 쓸 수 있어요. 테이블에 컬럼을 추가하되, 실제 저장 공간은 차지하지 않고 접근할 때마다 표현식을 계산하는 거예요.

이렇게 하면 누구나 sold_at_date 컬럼을 쓰기만 하면 되고, 자동으로 인덱스를 타게 돼요. 표현식 불일치 문제도 해결되고, 타임존 혼동도 없애고, 인덱스도 작아지고... 일석삼조죠. 아쉽게도 아직 가상 컬럼에 직접 인덱스를 거는 건 지원이 안 돼요. PostgreSQL 19에서는 가능해질 거라고 하네요.

해시 인덱스로 유니크 제약조건 걸기

세 번째는 좀 특이한 방법이에요. URL을 저장하는 테이블이 있다고 해볼게요. 웹페이지 처리는 시간도 오래 걸리고 비용도 많이 들어서, 같은 URL을 두 번 처리하면 안 되잖아요.

그래서 보통 url 컬럼에 UNIQUE 인덱스를 거는데, 이게 B-Tree 방식이에요. 문제는 요즘 URL이 엄청 길다는 거예요. 어떤 웹앱은 아예 앱 상태 전체를 URL에 넣기도 하거든요. 저도 처음엔 이런 URL 보고 깜짝 놀랐어요.

100만 개 URL을 저장했더니, 테이블 크기는 160MB인데 B-Tree 유니크 인덱스가 154MB나 됐어요. 거의 같은 크기예요. B-Tree는 실제 값을 저장하기 때문에 값이 크면 인덱스도 커지는 거죠.

해시 인덱스의 놀라운 효과

여기서 해시 인덱스가 등장해요. 해시 인덱스는 실제 값 대신 해시값만 저장해서 훨씬 작아요. 문제는... PostgreSQL이 유니크 해시 인덱스를 지원 안 해요. 처음엔 막막했는데, 방법이 있더라고요.

Exclusion Constraint라는 특수한 제약조건을 쓰면 돼요. 이렇게 하면 해시 인덱스로 유니크를 강제할 수 있어요. 결과는? 인덱스 크기가 32MB로 줄었어요. 5배 작아진 거죠. 게다가 검색도 더 빨라졌어요.

2024년 Gartner 보고서에 따르면, 데이터베이스 운영 비용의 약 40%가 스토리지에서 발생한다고 해요. 인덱스 크기를 5분의 1로 줄이면 비용도 그만큼 아낄 수 있는 거죠. 요즘 클라우드 비용 부담이 큰 스타트업들한테는 정말 반가운 방법이에요.

단점도 있어요. 외래키 참조가 안 되고, INSERT ON CONFLICT 구문이 좀 까다로워요. 하지만 외래키가 필요 없고, MERGE 문으로 대체 가능하다면 충분히 쓸 만한 방법이에요.

실무에 바로 써먹을 수 있는 팁들

지금까지 소개한 세 가지 방법은 교과서에 잘 안 나와요. 하지만 실무에서는 정말 큰 차이를 만들어낼 수 있어요.

첫 번째, BI 툴 쓰는 곳에서는 constraint_exclusion을 켜두세요. 사람들이 하는 실수를 데이터베이스가 미리 막아줘요. 두 번째, 일별이나 월별 리포트가 많다면 날짜 부분만 인덱싱하세요. 가상 생성 컬럼과 함께 쓰면 금상첨화고요. 세 번째, 큰 텍스트나 URL에 유니크 제약조건이 필요하면 해시 인덱스를 고려해보세요. 스토리지 비용을 확 줄일 수 있어요.

데이터베이스 최적화는 결국 상황에 맞는 도구를 고르는 거예요. 남들이 안 쓴다고 나쁜 방법이 아니에요. 오히려 그래서 더 좋을 수도 있죠. 여러분의 프로젝트에 딱 맞는 방법을 찾아보세요. 작은 설정 하나가 성능을 10배, 100배 향상시킬 수 있다는 걸 기억하시고, 오늘 소개한 방법들 한번 시도해보시길 바라요!

300x250
반응형