PostgreSQL 함수·트리거 성능 최적화
PostgreSQL에서 함수와 트리거의 성능 병목을 파악하고 해결하는 핵심 원리와 실무 적용 방법, 점검 목록을 정리한 전략
목차
개요
애플리케이션 성능 문제에서 함수와 트리거는 의외로 큰 영향을 미친다. 작은 로직 하나가 트랜잭션 지연을 초래할 수 있다. 이 글은 초보자도 이해할 수 있도록 원리와 실무 적용법을 차근차근 정리한다. 또한 관련 키워드인 postgres 트리거 성능 튜닝, plpgsql 함수 최적화, trigger 성능 개선 postgres 관점에서 접근한다.
성능 분석의 첫 단계
실행 계획과 통계 확인
먼저 느린 쿼리의 실행 계획을 확인한다. 함수 내부에서 실행되는 쿼리도 마찬가지다. EXPLAIN(ANALYZE, BUFFERS)로 실제 I/O와 비용을 파악한다. 통계가 오래되면 ANALYZE를 실행해 최신 통계를 유지한다.
로그와 대기 이벤트
postgres 로그와 pg_stat_activity, pg_stat_statements를 함께 보면서 병목 위치를 찾는다. 트랜잭션이 길게 열려 있거나 잠금(lock)이 발생하면 트리거 로직을 의심한다.
PL/pgSQL 함수 최적화 원칙
불필요한 쿼리 최소화
함수 안에서 반복적으로 같은 쿼리를 실행하는 상황이 흔하다. 가능한 경우 단일 쿼리로 집계하거나 조인으로 대체한다. 커서나 루프를 통한 행별 처리(row-by-row)는 가능한 피한다.
타입과 변환 비용
데이터 타입 불일치로 인한 암묵적 변환은 성능 저하를 초래한다. 파라미터와 컬럼 타입을 일치시키고, 필요 시 명시적 캐스팅을 사용한다.
IMMUTABLE/STABLE/VOLATILE 지정
함수 속성 지정은 옵티마이저에게 중요한 힌트다. 값이 변하지 않는 함수는 IMMUTABLE로 표시하면 캐시와 쿼리 재작성에 유리하다. 상태에 따라 적절한 속성을 선택한다.
트리거 성능 개선 전략
트랜잭션 범위 최소화
트리거는 트랜잭션 안에서 실행된다. 트리거가 오래 걸리면 전체 트랜잭션이 지연된다. 가능하면 작업을 비동기화하거나 별도의 배치로 분리한다. 트리거 내부에서 외부 요청을 호출하는 패턴은 위험하다.
FOR EACH ROW vs FOR EACH STATEMENT
행 단위 트리거는 많은 행을 처리할 때 큰 부하가 된다. 집합 연산으로 처리 가능하면 문장 단위 트리거로 바꾸거나 AFTER 트리거에서 집계 쿼리를 수행한다.
트리거 체인 관리
여러 트리거가 동일 테이블에서 연쇄적으로 실행되면 예측하기 어려운 성능 저하가 발생한다. 트리거 수를 줄이고 역할을 명확히 분리한다.
실전 예시
아래 예시는 행별 처리에서 집합 처리로 바꾼 간단한 사례다.
-- 기존: FOR EACH ROW에서 매번 업데이트 실행 (비효율)
CREATE FUNCTION notify_user_changes() RETURNS trigger AS $$
BEGIN
UPDATE users SET last_activity = now() WHERE id = NEW.user_id;
RETURN NEW;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tg_notify
AFTER INSERT ON logs
FOR EACH ROW EXECUTE FUNCTION notify_user_changes();
-- 개선: FOR EACH STATEMENT로 집합 업데이트 수행
CREATE FUNCTION notify_user_changes_batch() RETURNS trigger AS $$
BEGIN
UPDATE users SET last_activity = (SELECT MAX(created_at) FROM logs WHERE logs.user_id = users.id)
WHERE id IN (SELECT DISTINCT user_id FROM logs WHERE created_at > now() - interval '1 day');
RETURN NULL;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tg_notify_batch
AFTER INSERT ON logs
FOR EACH STATEMENT EXECUTE FUNCTION notify_user_changes_batch();
테스트와 검증
부하 테스트
변경 전후에 부하 테스트를 실행해 영향 범위를 확인한다. 단일 요청 지연뿐 아니라 동시성 시나리오에서의 행동을 관찰한다.
모니터링 지표
- 평균 응답 시간
- 트랜잭션 밀도 및 지연 시간
- 블로킹(lock) 발생 빈도
- I/O 및 CPU 사용률
점검 목록
- 함수 내부 쿼리의 실행 계획 확인
- 불필요한 반복 쿼리 제거
- FOR EACH ROW 사용 여부 재검토
- 함수 속성(IMMUTABLE/STABLE) 설정 검토
- 트리거 체인 간 의존성 최소화
- 비동기 처리로 분리 가능한지 평가
마무리
함수와 트리거는 편리하지만 잘못 설계되면 성능 병목이 된다. postgres 트리거 성능 튜닝과 plpgsql 함수 최적화는 작은 설계 변경으로 큰 개선을 만든다. 실무에서의 점검과 모니터링을 통해 trigger 성능 개선 postgres 효과를 꾸준히 확인하는 것이 중요하다.