전환 테이블은 PostgreSQL 10에서 도입된 기능으로, 문 수준 트리거의 REFERENCING 절을 통해 이 기능을 제공합니다. 이 메커니즘은 수정된 전체 결과 집합을 임시 테이블 구조로 드러내며—INSERT/UPDATE 작업에 대해 NEW TABLE, DELETE/UPDATE 작업에 대해 OLD TABLE—트리거 함수가 집합 기반 처리를 수행할 수 있도록 합니다. 행을 개별적으로 반복하는 대신, 영향을 받은 모든 행을 동시에 처리하는 단일 SQL 문을 실행할 수 있습니다.
CREATE TRIGGER bulk_audit_trigger AFTER UPDATE ON inventory REFERENCING NEW TABLE AS updated_items OLD TABLE AS previous_items FOR EACH STATEMENT EXECUTE FUNCTION log_inventory_changes();
log_inventory_changes() 내에서 updated_items를 쿼리하면 트리거링된 UPDATE의 모든 행을 반환해, INSERT INTO audit_log SELECT * FROM updated_items; 같은 효율적인 대량 작업을 수행할 수 있게 됩니다.
한 전자 상거래 플랫폼은 100,000개의 제품 기록에 영향을 미치는 야간 대량 가격 조정 동안 심각한 성능 저하를 경험했습니다. 기존 아키텍처는 감사 항목을 작성하기 위해 행 수준의 AFTER 트리거를 사용하고 있어 100,000개의 순차적인 INSERT를 price_history 테이블에 실행하면서 연결 풀 자원을 소진했습니다.
솔루션 1: 애플리케이션 측 배치 처리 팀은 트리거를 제거하고 Java 애플리케이션 내에서 JDBC 배치 삽입을 사용하여 감사를 처리하는 방안을 고려했습니다. 이는 데이터베이스 CPU 부하를 줄일 수 있었지만, 중대한 일관성 위험을 초래했습니다: 애플리케이션이 중간 배치에서 충돌할 경우, 완료된 가격 업데이트는 영구적으로 해당 감사 기록이 없어 SOX 준수를 위반하게 됩니다. 또한 이 접근 방식은 앱 서버와 PostgreSQL 간 복잡한 분산 트랜잭션 관리가 필요했습니다.
솔루션 2: 비동기 메시지 큐 또 다른 제안은 업데이트 중에 행 식별자를 Redis 스트림에 작성한 후, 백그라운드 작업자를 통해 감사를 처리하는 것이었습니다. 이는 쓰기 경로를 분리하였지만 즉각적인 트랜잭션 일관성을 희생했습니다. 비동기 작업자는 고부하 시 지연될 수 있으며, 감사 기록의 일시적인 간격이 발생하여 규제 감사자가 이를 표시할 수 있습니다. 또한, PostgreSQL과 Redis 간에 정확히 한 번만 실행되도록 보장하는 것도 상당한 인프라 복잡성을 더했습니다.
솔루션 3: 전환 테이블이 있는 문 수준 트리거
선택된 접근 방식은 행 트리거를 REFERENCING NEW TABLE AS new_prices를 사용하는 문 수준 트리거로 대체했습니다. 트리거 함수는 단일 집합 기반 운영을 수행했습니다: INSERT INTO price_history SELECT product_id, old_price, new_price, NOW() FROM new_prices;. 이 방법은 모든 수정을 한 번에 처리하면서 같은 트랜잭션 내에서 엄격한 ACID 준수를 유지했습니다.
결과: 야간 배치 완료 시간이 45초에서 300밀리초로 줄어들었습니다. WAL(Write-Ahead Log) 생성이 90% 감소했고, 시스템은 이전에 대규모 트리거 재귀로 인한 잠금 경쟁 스파이크를 제거했습니다.
전환 테이블은 BEFORE 트리거 및 뷰의 INSTEAD OF 트리거와 어떻게 상호작용하나요?
전환 테이블은 일반 테이블의 AFTER 트리거에만 독점적으로 사용할 수 있습니다. BEFORE 트리거는 최종 결과 집합이 생성되기 전에 개별 행에서 작동하므로 수정된 행의 전체 집합이 아직 존재하지 않습니다. 뷰의 INSTEAD OF 트리거는 실제 DML 작업의 결과를 관찰하기보다는 대체 실행 경로를 정의하기 때문에 전환 테이블을 사용할 수 없습니다.
트리거 함수가 NEW TABLE 또는 OLD TABLE 내의 데이터를 수정하여 최종 결과를 변경할 수 있나요?
아니요, 전환 테이블은 트리거 실행 중에만 접근할 수 있는 읽기 전용 스냅샷입니다. 이들은 문에 의해 영향을 받은 행의 불변 뷰를 나타내며 UPDATE, DELETE 또는 INSERT 작업으로 수정할 수 없습니다. 값이 커밋되기 전에 변경하려면, 직접 NEW 기록 변수를 조작하는 행 수준의 BEFORE 트리거를 사용하거나 영구 테이블에 대해 별도의 DML 작업을 수행해야 합니다.
왜 TRUNCATE 작업은 OLD TABLE 전환 테이블을 채우지 않나요?
비록 PostgreSQL이 문 수준에서 TRUNCATE에 대한 트리거를 지원하지만, 이 DDL 명령은 개별 행 전환 상태를 MVCC 시스템을 통해 생성하지 않고 모든 행을 제거합니다. TRUNCATE는 전환 관계를 구성하는 데 사용되는 튜플 버전 관리 메커니즘을 우회하므로 삭제된 행으로 OLD TABLE을 채울 수 없습니다. TRUNCATE 작업을 감사하는 것은 이벤트 트리거나 논리 디코딩 스트림과 같은 대체 메커니즘이 필요합니다.