당시 개발했던 기능 소개
2025년 올해, 회사에서 했던 개발 업무 중 가장 기억에 남는 경험을 꼽으라면 결재문서 데이터를 연동하고 알림을 발송하는 기능에 트랜잭션 아웃박스 패턴을 적용한 것입니다. 회사 내에서 기안되는 결재문서 중에는 CEO까지 결재해야 하는 문서들이 있는데요. 해당 문서들에 비리나 이상, 위법성 등이 없는지 감사팀에서 매번 감사를 해왔는데, 제가 운영하던 감사시스템에 이렇게 CEO까지 결재하는 문서들을 연동해서 새로운 감사 대상 문서가 기안되면 감사팀에게 알림을 보내고, 결재가 진행되다가 반려가 되면 반려 알림 등을 보내도록 하는 기능입니다. "일상감사 기능"이라고도 불렀고, CEO가 결재하는 문서들을 감사하는 만큼 비즈니스 중요도가 매우 높은 업무였습니다.
데이터 연동 방법에 대한 고민
먼저 데이터를 연동하는 방법부터 고민을 했었습니다. 처음에는 감사시스템에 데이터 수신용 API를 만들고 전자결재시스템에서 새로운 문서가 기안되거나 기안된 문서의 결재가 진행될 때마다 제가 만든 API를 호출하게 해서 결재문서 데이터들을 공급받을까 했지만, 네트워크 오류 등으로 API 호출 등이 실패했을 때 재시도 전략을 어떻게 할 건지 등이 고민됐습니다. 예를 들어 API 호출에 실패해서 재시도해도 그 재시도가 또 실패하면? 등등.. 결국에는 API 호출을 통한 방법으로는 결재문서 데이터가 누락될 수 있다는 점에 보였는데, 이 "일상감사 기능"은 앞서 설명했듯이 비즈니스 중요도가 매우 높아서 데이터가 누락되는 일은 최대한 지양해야 했습니다. 따라서 다른 방법들을 알아봤는데요, 사내에 이기종 DB 간의 데이터 교환을 특정 주기로 해주는 솔루션(통상 EAI 라고 부르는)이 있어서 이를 활용하기로 했습니다.

결재문서시스템 DB에서 연동해야 하는 데이터들(처리여부 == N)을 조회하고, 이 데이터들을 감사시스템 DB에 적재하고, 결재문서시스템 DB에서 조회했던 데이터들의 처리여부를 갱신(N → Y)합니다. 이 작업을 10분마다 수행하도록 스케쥴해주면, 오류 등이 발생해서 데이터 연동에 실패해도 자연스럽게 10분마다 재시도될 수 있도록 설정할 수 있었습니다. (결과적 정합성이 보장됨) 물론 이렇게 하면 결재문서에 상태변화가 생겨도 즉각적으로 감사시스템에 연동되는 건 아니지만, 감사팀 분들과 소통하며 완전한 실시간성의 필요성보다는 누락없이 안정적으로 데이터를 연동하는 것이 더 중요함을 도출했었기 때문에 이 방법을 통해 데이터를 연동하기로 했었습니다.
※CEO까지 결재하는 문서들은 하루에 많으면 2개 정도 기안되는 상황이라, Kafka같은 컴포넌트의 사용은 연동 대상 데이터 양에 비해 과한 방법이라는 생각이 들어 고려하지 않았습니다
알림을 보내는 방법에 대한 고민
이후에는 알림을 보내는 방법을 고민했습니다. 사내 표준 상 EAI 솔루션으로 주고받는 데이터들은 실제 감사 업무에서 사용하는 테이블에 연동하는게 아니라 인터페이스 용도로 만든 테이블에 둬야 했기 때문에, 이 인터페이스 테이블에 있는 데이터들을 업무 테이블로 연동하고 알림을 보내야 했습니다.
사실 알림을 보내는 것에 대해서 처음에는 연동 후에 알림 보내지 뭐.. 라는 생각이었습니다. 하지만 개발 과정에서 대표적으로 다음과 같은 엣지 케이스가 발생 가능함을 알게 됐습니다.
1. 결재문서가 기안되고 감사시스템에 연동됨
2. 감사팀에서 확인 후 해당 결재문서를 이상없음으로 처리
3. 이후 해당 결재문서의 결재가 진행되다가 중간에 반려되고, 문서 내용이 수정된 채로 재기안됨
4. 하지만 반려 알림이나 재기안 알림이 발송되지 않음
5. 감사팀 입장에서는 이전에 이상 없다고 처리한 문서의 내용이 바뀌어 있게 됨
이 기능이 비즈니스 중요도가 높은 업무였던 만큼, 위와 같은 상황이 펼쳐지면 감사 업무에 굉장한 리스크가 될 수 있다는 생각이 들었습니다. 따라서 결재문서 데이터 연동과 알림 발송이라는 두 작업의 원자성을 보장해줘야 한다는 생각이 들었고, 어떻게 해야 데이터가 연동된 후 알림이 어떤 상황이든 발송될 수 있도록 하지? 를 고민했습니다.
처음에는 다음과 같이 데이터 연동에 성공하면 그 결과에 따라 알림을 발송하는 방법을 떠올렸는데요.

이렇게 하면 알림 발송이 실패할 수도 있기 때문에, 데이터는 연동됐지만 알림 발송에 실패하는 케이스가 발생할 수 있게 됩니다.
두 번째론 알림 발송 결과에 따라 데이터를 연동하는 방법을 떠올렸는데요.

이렇게하면 데이터 연동이 실패할 수도 있기 때문에 알림은 발송됐지만 데이터 연동은 실패하는 케이스가 발생할 수 있게 됩니다.
세 번째로는 데이터 연동 트랜잭션 내에서 알림 발송 결과에 따라 트랜잭션을 커밋 or 롤백하는 방법을 떠올렸는데요.

마찬가지로 알림 발송엔 성공했지만 데이터 연동은 실패하는 케이스가 발생할 수 있었습니다.
즉 데이터 연동과 알림 발송이라는 두 작업의 원자성을 하나의 DB 트랜잭션으로 정확히 보장해주는 건 매우 힘들었습니다. 2 phase commit 같은 분산 트랜잭션도 알아봤지만 연동해야 하는 데이터 양에 비해 오버 엔지니어링이 될 거라는 생각이 들었는데요. 찾아보니 최소 1번은 이벤트를 전달하는 방법으로 트랜잭션 아웃박스 패턴(트랜잭셔널 아웃박스 패턴)이 있었고, 이를 활용해 다음처럼 설계할 수 있겠다는 생각에 도입을 하게 됐습니다.

2번 과정을 보면, 결재문서이력 테이블 (EAI를 통해 전달받은 데이터가 있는 인터페이스 테이블)을 처리해서 업무 테이들에 결재문서 데이터들을 연동하고, 감사팀에게 발송할 메시지를 별도의 outbox테이블에 적재하는데요. 이 과정들을 하나의 DB 트랜잭션에서 처리합니다. 그리고 outbox테이블에 있는 메시지들을 조회해서 알림을 발송하는 별도의 작업이 10분 주기로 수행되도록 만들었습니다. 이렇게 하면 outbox테이블을 조회해서 알림을 발송하는 작업이 10분마다 수행되므로, 알림 발송이 실패해도 성공할 때까지 재시도가 자연스럽게 진행되게 됩니다. 따라서 최소 1번은 알림을 보낼 수 있게 되는 것이고, 이를 통해 결재문서 데이터 연동과 알림 발송이라는 두 작업이 결과적으로 모두 진행되게 하여 원자성을 보장할 수 있었습니다.
정말 트랜잭션 아웃박스 패턴이 필요했을까?
'데이터 연동 & 알림 발송'이라는 기능을 구현하는 과정이 트랜잭션 아웃박스 패턴을 곁들이면서 다소 복잡해지긴 했습니다만, 데이터 연동과 알림 발송이라는 두 작업의 원자성을 보장해야 하는 것이 우선이었던 만큼 이런 복잡함은 감수해야 했다는 생각이었습니다. 그리고 감사팀 입장에서 왜 이 기능이 필요할지를 직접 고민해보면서 두 작업의 원자성이 필요했던 것을 스스로 도출했던 것이었고, 이를 보장하는 방법들을 여러 가지로 알아보고 스스로 가장 괜찮은 방법이라고 생각되는 것을 적용했던 만큼 솔직하게는 나름대로의 뿌듯함도 있었습니다.
하지만 시간이 흐르고 다시 이 기능 개발을 회고해봤습니다. 정말 트랜잭션 아웃박스 패턴이 필요했던 것일까? 다시 시간을 되돌린다고 해도 데이터 연동과 알림 발송이라는 두 작업의 원자성은 필요했던 상황이라고 생각합니다. 하지만 정말 트랜잭션 아웃박스 패턴이 당시의 최선이었을까?
뭐 다시 회고해보니, 굳이 필요하지 않았었다.. 라는 생각이 들었습니다. ㅎㅎ..
다시 돌아가서, 데이터를 연동하고 알림을 발송하던 것을 한 트랜잭션 내에서 처리하던 형태를 살펴봤습니다.

이 방법을 당시 적용하지 않았던 것은 앞서 설명드렸듯이 커밋 과정에서 예외가 터지면 알림은 발송됐지만 트랜잭션은 롤백되어 데이터가 연동되지 않기 때문이었습니다.
하지만 다시 생각해보면, 어차피 데이터 연동 & 알림 발송은 10분 주기로 반복되고 있습니다. 즉 이렇게 알림이 발송됐지만 데이터가 연동되지 않은 케이스가 발생됐다고 해도, 10분 뒤 혹은 그 뒤에 이 프로세스가 재수행되면서 결국 데이터가 연동됩니다. 결국은 이 방법을 써도 결과적으로는 데이터 연동과 알림 발송의 원자성이 보장되는 것입니다.
트랜잭션 아웃박스 패턴은 결국 쓸 필요가 없던 거였습니다 ㅎㅎ.. 데이터 연동과 알림 발송이라는 두 작업의 원자성을 보장해야 한다는 제약을 스스로 도출해냈었고, 이를 위한 여러 방법을 스스로 고민하면서 오버 엔지니어링 없이 가장 적절한 선택을 했던 거라고 생각했는데, 사실 이조차도 오버 엔지니어링이었겠다는 생각에 당혹스럽기도 하고 멘탈이 조금.. 힘들기도 했습니다. "아 내가 더 넓은 관점에서 보지 못했던 건가? 좁은 관점에서 기술 적용에만 순간적으로 몰두했던 거였나? 불필요한 과한 설계를 내가 적용하고 있던 걸까?" 라는 생각에 스스로를 조금 갉아먹기도 했던 것 같습니다.
..그래도.. 앞으로가 중요한 것 아니겠습니까! 데이터 정합성 보장이라는 좁은 관점에 매몰되어 10분 주기로 반복 수행되고 있다는 더 큰 관점에서의 맥락을 놓쳤었고, 결국 간단한 길을 두고 다소 돌아간 셈이 됐지만, 이를 통해 어떻게 구현할까 보다 왜 필요한가를 좀 더 신중하게 다시 살펴보자는 교훈을 얻을 수 있었습니다. 완벽함을 위한 '노력'이 사실은 복잡함을 더할 수 있는 '욕심'이 될 수 있음을 배웠고, 덕분에 단순함이 주는 강력함도 느낄 수 있었습니다.
또한 이 경험을 성장을 위한 발판으로 삼고, 제가 했던 고민의 흔적들로 남길려고 합니다. 단순히 데이터 정합성을 고려하지 않고 개발했다면 트랜잭션 아웃박스 패턴이라는 일종의 오버 엔지니어링은 없었겠지만, 반면 데이터 정합성을 지켜주는 방법과 기술들에 대해 이렇게 고민하지 않았을 거라고 생각합니다. 이 경험에서 학습한 것들을 발판으로 삼아, 앞으로 기술적인 의사결정들을 내릴 때마다 조금 더 넓은 관점에서, 조금 더 신중하게 고민하게 하는 양분으로 삼으려고 합니다.
앞으로 더 휼룽한 개발자, 엔지니어가 되기를 바라며! ㅎㅎ.. 부끄럽네여
'생각정리, 일상' 카테고리의 다른 글
| 이상적인 개발자에 대한 고민 (0) | 2025.10.22 |
|---|---|
| 간만의 사이드 프로젝트 중간 회고 (1) | 2025.08.31 |