
PostgreSQL에서 UPDATE는 기존 행을 제자리에서 덮어쓰는 방식이 아니라 새 row version을 만든다. 이 구조는 MVCC 동시성을 가능하게 하지만, 업데이트가 많을수록 heap과 index에 추가 작업이 생길 수 있다. HOT update는 이 비용을 줄이기 위한 PostgreSQL의 heap-only tuple 최적화다.
결론부터 말하면 HOT update는 직접 켜는 기능이 아니라 조건이 맞을 때 PostgreSQL이 선택하는 최적화다. 기준은 두 가지다. 업데이트가 index가 참조하는 컬럼을 바꾸지 않아야 하고, 새 row version이 기존 row와 같은 heap page에 들어갈 공간이 있어야 한다.

HOT update는 무엇을 줄일까?
PostgreSQL 공식 문서 기준으로 MVCC 환경의 UPDATE는 테이블에 새 row version을 추가한다. 이때 상황에 따라 업데이트된 행을 나타내기 위한 새 index entry도 필요할 수 있다. index가 많고 업데이트가 잦은 테이블에서는 이 index 작업과 이후 정리 비용이 부담이 된다.
HOT update는 새 index entry를 만들지 않고 heap 안에서 row version chain을 이어 가는 최적화다. index는 원래 row의 page item identifier를 가리키고, heap 쪽에서 새 version으로 연결되는 구조를 사용한다. 그래서 index 값을 바꾸지 않는 업데이트에서는 index를 매번 새로 갱신하지 않아도 된다.
중요한 점은 HOT update가 UPDATE 자체를 없애는 기능은 아니라는 것이다. 새 row version은 여전히 만들어진다. 다만 새 version을 같은 page 안에 두고 index entry 생성을 피할 수 있으면, index bloat와 vacuum 부담을 줄이는 데 도움이 된다.
HOT update가 성립하는 조건
PostgreSQL 문서는 HOT가 가능한 조건을 명확히 설명한다. 첫째, 업데이트가 table의 index가 참조하는 컬럼을 수정하지 않아야 한다. 여기에는 일반 index뿐 아니라 expression index나 partial index에서 참조하는 컬럼도 판단 대상이 될 수 있다. 다만 PostgreSQL core의 summarizing index인 BRIN은 예외적으로 다뤄진다.
둘째, 기존 row가 들어 있던 page에 업데이트된 row version을 넣을 충분한 공간이 있어야 한다. 이 조건 때문에 update-heavy 테이블에서는 fillfactor가 중요해진다. fillfactor를 낮추면 새로 insert할 때 page를 끝까지 채우지 않고, 이후 같은 page 안 업데이트를 위한 여유 공간을 남길 수 있다.

| 상황 | HOT 가능성 | 이유 |
|---|---|---|
| index가 없는 설명 컬럼만 자주 바꾼다. | 높아질 수 있음 | index가 참조하는 값이 바뀌지 않기 때문이다. |
| primary key나 indexed status 컬럼을 바꾼다. | 낮음 | index entry가 새 값을 반영해야 한다. |
| row가 커져서 같은 page에 들어가지 않는다. | 낮음 | 새 row version이 다른 page로 가면 HOT 조건을 만족하지 못한다. |
| BRIN만 관련된 컬럼을 바꾼다. | 상황에 따라 가능 | PostgreSQL 문서는 summarizing index를 일반 index 조건에서 제외해 설명한다. |
fillfactor는 언제 낮출까?
fillfactor는 테이블 page를 insert 시점에 얼마나 채울지 정하는 storage parameter다. PostgreSQL 18 문서 기준 테이블의 기본값은 100이고, 값의 범위는 10부터 100까지다. 작은 값을 지정하면 insert가 page를 지정한 비율까지만 채우고 나머지 공간을 update를 위해 남겨 둔다.
CREATE TABLE accounts (
id bigint GENERATED ALWAYS AS IDENTITY PRIMARY KEY,
email text NOT NULL,
profile jsonb NOT NULL,
last_seen_at timestamptz
) WITH (fillfactor = 80);
이 설정은 update-heavy 테이블에서 검토할 만하다. 예를 들어 primary key나 조회용 index 컬럼은 그대로 두고, last_seen_at, 카운터, 상태 설명, 프로필 일부처럼 non-indexed 컬럼이 자주 바뀌는 테이블이라면 HOT 비율을 높일 여지가 있다.
반대로 거의 업데이트되지 않는 테이블에서는 낮은 fillfactor가 공간 효율을 떨어뜨릴 수 있다. 문서도 업데이트되지 않는 테이블은 완전히 채우는 쪽이 적합하다고 설명한다. 따라서 fillfactor는 모든 테이블에 일괄 적용하는 설정이 아니라, 업데이트 패턴과 저장 공간 비용을 함께 보고 조정하는 값이다.
운영에서는 무엇을 확인할까?
HOT 여부는 추측으로만 판단하지 않는 편이 좋다. PostgreSQL의 pg_stat_all_tables 또는 pg_stat_user_tables에서 n_tup_upd, n_tup_hot_upd, n_tup_newpage_upd를 볼 수 있다. 문서 기준 n_tup_hot_upd는 successor version이 index에 필요하지 않았던 HOT 업데이트 수를 의미한다.

SELECT
relname,
n_tup_upd,
n_tup_hot_upd,
n_tup_newpage_upd,
round(100.0 * n_tup_hot_upd / NULLIF(n_tup_upd, 0), 2) AS hot_percent
FROM pg_stat_user_tables
WHERE relname = 'accounts';
n_tup_hot_upd 비율이 낮다고 해서 바로 fillfactor만 낮추면 된다는 뜻은 아니다. 업데이트가 indexed column을 바꾸고 있다면 page 공간을 남겨도 HOT가 되기 어렵다. 반대로 indexed column은 그대로인데 n_tup_newpage_upd가 눈에 띈다면 같은 page에 공간이 부족한 패턴일 수 있으므로 row 크기와 fillfactor를 함께 볼 만하다.
인덱스 설계와 함께 봐야 하는 이유
HOT update를 생각할 때 가장 흔한 실수는 “자주 조회하니까 일단 index를 더 만들자”와 “자주 업데이트되니까 fillfactor를 낮추자”를 따로 판단하는 것이다. 자주 업데이트되는 컬럼에 index를 추가하면 해당 컬럼 변경은 HOT 조건을 만족하기 어려워진다. 조회 성능을 위해 만든 index가 업데이트 비용과 저장 공간 정리 비용을 키울 수 있다.
따라서 자주 바뀌는 컬럼은 먼저 질문을 나눠 보는 편이 좋다. 그 컬럼이 실제 검색 조건이나 정렬 조건에 필요한가? 필요하다면 update 빈도와 조회 이득 중 어느 쪽이 더 큰가? partial index나 별도 테이블 분리가 더 나은가? HOT는 이런 설계 판단을 대신해 주지는 않지만, update-heavy 테이블에서 index를 늘릴 때 반드시 떠올려야 할 기준을 제공한다.
VACUUM을 대체하는 기능일까?
HOT update는 vacuum을 완전히 대체하지 않는다. PostgreSQL 문서는 HOT에서 중간 row version이 normal operation 중에도 제거될 수 있다고 설명하지만, 테이블의 dead tuple 관리와 통계 갱신, 오래 열린 트랜잭션 문제는 여전히 별도로 봐야 한다. autovacuum을 꺼 두거나 오래된 snapshot이 계속 유지되면 HOT만으로 저장소 관리 문제가 해결되지 않는다.
운영 기준은 세 가지로 정리할 수 있다. 먼저 자주 업데이트되는 컬럼이 index에 걸려 있는지 확인한다. 다음으로 같은 page 공간을 남길 필요가 있는 테이블에만 fillfactor 조정을 검토한다. 마지막으로 pg_stat_user_tables의 HOT 관련 카운터와 dead tuple, autovacuum 상태를 함께 본다. HOT update는 튜닝 스위치라기보다 PostgreSQL 업데이트 비용을 이해하는 기준에 가깝다.
FAQ
HOT update를 강제로 켤 수 있나?
일반 설정처럼 강제로 켜는 방식은 아니다. 조건이 맞을 때 PostgreSQL이 HOT update를 사용할 수 있다. 사용자가 조정할 수 있는 부분은 index 설계, row 크기, fillfactor, update 패턴이다.
fillfactor를 낮추면 항상 빨라질까?
항상 그렇지는 않다. 낮은 fillfactor는 page에 여유 공간을 남기지만, 같은 데이터를 저장하는 데 더 많은 page가 필요할 수 있다. 업데이트가 거의 없는 테이블이나 읽기 위주 테이블에서는 공간 효율과 cache 효율이 더 중요할 수 있다.
HOT 비율만 보면 충분할까?
충분하지 않다. n_tup_hot_upd와 n_tup_upd의 비율은 좋은 출발점이지만, indexed column 변경 여부, n_tup_newpage_upd, dead tuple, autovacuum 상태를 같이 봐야 한다.
참고 자료
'프로그래밍 > 서버, DBMS' 카테고리의 다른 글
| Nginx proxy_next_upstream 기준 (0) | 2026.10.02 |
|---|---|
| PostgreSQL CTE materialization 기준 (0) | 2026.09.29 |
| Nginx HTTP/3 기준 (0) | 2026.09.22 |
| PostgreSQL WITHOUT OVERLAPS 기준 (0) | 2026.09.20 |
| PostgreSQL pipeline mode 기준 (0) | 2026.09.18 |





