트리거 vs 코드 트랙잭션, 어디서 선을 그을까?

2025. 9. 2. 16:25Backend Development/Database

반응형

"분명 아무도 업데이트 안 했다는데 값이 바뀌어 있네...?"

이미 어느정도 만들어져 있는 서비스 단에서 백엔드를 개발하다보면 이런 미스터리한 경험을 한 번쯤 겪곤합니다. 범인은 종종 데이터베이스 트리거. 반대로 애플리케이션 코드에서 트랙잭션을 직접 열고 일련의 쿼리를 처리하기도 하죠. 둘 다 유용한 도구인데, 어디까지 DB에 맡기고 어디부터는 애플리케이션이 책임져야 할까요?


트리거‑트랜잭션이 뭔가요?

사실 이 둘을 비교하는 건 잘못된 비교일 수도 있습니다. 트리거 자체에서도 트랙잭션을 열고 사용하기도하고, 트리거 자체는 DB 내에서도 애플리케이션 수준으로 좀더 추상화된 개념이고, 트랙잭션은 DB 내의 기능으로 봐야하기 때문입니다. 

그렇기에 이 글에서는 트랜잭션이라는 것은 애플리케이션 코드에서 트랙잭션을 직접 핸들링하는 상황으로 퉁치고 가겠습니다. 

트리거: 

  • 테이블에 INSERT/UPDATE/DELETE가 일어나면 DB가 "자동으로" 실행하는 작은 프로그램.
  • 일반적으로는 코드만 보고 이러한 부분을 파악할 수 없음.
  • 비유하자면 센서가 달린 자동문 느낌

코드 트랙잭션:

  • 애플리케이션 코드 내에서 "BEGIN...COMMIT" 범위에서 여러 쿼리를 명시적으로 묶어 처리.
  • 코드에서 이러한 부분은 확실히 파악할 수 있지만, 가독성과 복잡성이 증가할 수 있음.
  • 이유하자면 사람이 여닫는 수동문

트리거가 빛나는 순간 (작고, 확실한 자동화)

  • 감사 로그(Audit)

행이 바뀔 때마다 누가-언제-무엇을 바꿨는지 별도 테이블에 기록. 애플리케이션을 몇 개 돌리든 누락 없이 남습니다. 

  • 데이터 가드(불변식)

예: 재고가 음수가 되면 안 됨, 특정 열 조합이 항상 일치해야 함. DB 차원에서 최후의 안정망 역할

  • 가벼운 파생 필드 업데이트

트리비얼한 카운트, 최근 변경 시각갱신 등. 한두 테이블에서 끝나고 비용이 작은 작업.

포인트: 트리거는 "한 방에 끝나는, 짧고 결정적"인 것에 강합니다. 비즈니스 플로우 전체를 끌고 가면 과부화/불투명/디버깅 지옥이 올 가능성이 높아집니다. 

트리거의 단점 (눈에 잘 안보이고, 팀을 태웁니다)

  • 가시성 저하

코드 리뷰에 잡히지 않습니다. "누가 언제 무엇을 실행했는지" 추적이 어렵습니다.

물론 이러한 부분을 보완하는 것들도 존재하지만, 그런 것들 필요로한다는 부분도 리스크로 다가올 수 있습니다. 

  • 복잡도 스파게티

트리거가 트리거를 부르고, 순서 의존이 생기면 예측이 힘듭니다.

이러한 부분은 첫째 가시성 저하 에서 비슷한 이야기이긴 하지만, 기본적으로 한눈에 보기 어려운 구조를 갖기때문에, 추적 또한 어렵습니다. 

  • 성능/배포 리스크

대용량 배치나 마이그레이션 시 트리거가 전부 실행되어 느려지거나 잠금 경합이 커짐.

편의를 위해서 특정 테이블의 트리거를 설정한 경우 그 이후부터는 해당 테이블에 대한 크고 작은 제약이 생기게 됩니다. 

  • 플랫폼 제약

CDC/복제, 서버리스 DB, 클라우드 매니지드 환경에서 제약이 있거나 동작 차이가 있어 이식성이 떨어질 수 있습니다. 

물론 현대의 대부분의 서비스에서도 트리거를 지원하는 경우가 있지만, 데이터 변경에 대한 의존성을 데이터 베이스가 가지고 있다면 그것만으로도 마이그레이션 등에 이슈가 생길 가능성이 있습니다. 

  • 테스트 난이도 상승

단위 테스트-리팩토링 루프에 잘 안 들어옵니다.

우선 테스트를 위해서 트리거의 작동을 코드로도 구현해서 테스트를 실행해야하기 때문에 테스트에 대한 비용이 증가하며, 기본적으로 코드를 기반으로 리팩토링을 진행하는 경우에 이러한 문제들이 잘 잡히지 않는 경우들이 생겨 의도치 않는 기술적 부채를 쌓아갈 가능성이 생깁니다. 


코드 트랙잭션의 장점 (명시성/테스트/관찰성)

  • 명시적 제어

어떤 순서로 무엇을 왜 하는지 PR에 드러납니다.

그렇기에 롤백이나 재시도, 타임아웃 정책을 설계하는데 용이합니다. 

  • 도메인 규칙 집중

비즈니스 로직이 흩어지지 않고 한 곳에 모입니다.

코드 리뷰만으로도 시스템을 파악할 수 있고, 온보딩 비용과 도메인 책임 여부를 확실하게 나눌 수 있습니다. 

  • Observability

로그, 메트릭, 트레이싱으로 병목/실패 포인트를 잡기 좋습니다.

물론 데이터 베이스에서도 이와 같은 시스템을 만들 수는 있으나, 일반적으로 통용되는 방법으로는 애플리케이션 로그를 통해 확인하는 것이 올바릅니다. 

  • 확장성

외부 시스템 호출, 메시지 큐, 비동기 워크플로우(Saga 등) 연결이 자연스럽습니다.

서비스에 맞게 애플리케이션을 빌드하는 개발자 입장에서는 비즈니스의 흐름을 예측하기 쉽지 않기 때문에 항상 확장성 측면을 고려할 필요가 있습니다. 


코드 트랙잭션의 고민거리와 대처

DB 트리거의 편의성과 간편함 그리고 확실성(무결성/일관성)에 대해서는 폄하할 생각은 없습니다. 그래서 코드 트랙잭션에서 발생할 수 있는 문제점들과 대처방안에 대해서도 알아봅시다.

  • 여러 서비스 간 일관성

DB 에서 일괄적으로 처리되던 데이터 변경 작업이 애플리케이션 단으로 올라오게 되면 각 애플리케이션에서 처리를 하게 되는 상황이 발생하여, 오히려 멱등성을 깨트릴 수 있습니다.

그러한 문제를 예방하기 위해 Outbox 패턴(DB에 이벤트 적재 > 비동기 발생)/Saga로 설계 하는 등의 방법들을 사용할 수 있습니다.

  • 쿼리 수 증가

영향을 받는 범위만 갱신(affected set), 배치/큐로 비동기화로 처리할 수 있습니다.

물론 이는 데이터의 실시간 성을 크게 떨어트리므로, 상황에 맞게 사용할 필요가 있습니다. 


간단 의사결정 체크리스트

  • 규칙이 데이터 자체의 안전망인가? (예: 음수 재고 금지) -> 트리거 OK
  • 여러 테이블/외부 시스템을 가로지르는 시나리오인가? -> 코드 트랙잭션
  • 복잡도가 커질 여지가 있는가?(분기, 조건, 순서) -> 코드
  • 즉시성 vs 일관성: 즉시 일관성이 필수? -> 코드 트랙잭션(또는 작은 트리거) / 허용가능 -> Outbox+비동기
  • 운영/디버깅 가시성이 중요한가? -> 코드
  • 대량 배치/이관이 자주 있는가? -> 트리거 최소화

트리거 운영 팁

그럼에도 불구하고 다양한 상황과 조건으로 인해서 트리거를 사용해야하는 상황이 있습니다.

그런 상황에는 위에서 서술된 문제나 운영 중에 발생할만한 문제를 없앨만한 다른 시스템을 필요로 합니다.

  • 트리거 문서화: 테이블 주석/위키에 "언제 무엇을" 명시.
  • 배치 전 스위치: 대량 작업 시 트리거 일시 비활성(또는 조건부 우회) 전략 마련
  • 마이그레이션에 포함: 트리거도 스키마 버전 관리에 포함(DDL 스크립트/리뷰 필수)
  • 테스트: 최소 한 개의 통합 테스트 스위트는 트리거 켠 상태로 돌리기
  • 관찰성: 트리거 내부 로깅(가능한 범위) + 애플리케이션 레이어의 트레이싱 연결

마무리: 좋은 기술보단 좋은 결정

  • DB 트리거는 "브레이크 라스트(최후의 안전망)"와 "작은 자동화"에 딱 좋습니다.
  • 도메인 로직의 무대는 코드 트랙잭션이 훨씬 안전하고 투명합니다.
  • 즉시성이 덜 급하면 Outbox/비동기 집계를 섞어 성능/안정성/가시성을 함꼐 챙기세요.

오늘 팀의 한 기능을 골라 "트리거로 줄일 수 있는 작은 자동화 1개"와 "코드 트랜잭션으로 더 투명해질 로직 1개"를 각각 찾아보면 어떨까요? 당신은 어디까지 DB에 맡기고 있나요?

반응형