MongoDB 트랜잭션 타임아웃 해결을 위한 프로세스 구조 개선
1. 개요
프로젝트 수행 중 대용량 데이터를 처리하는 배치 프로세스에서 발생한 트랜잭션 장애를 해결하기 위해 처리 구조를 개선한 경험을 정리하였습니다. 무거운 연산 작업이 포함된 트랜잭션의 기술적 제약을 파악하고, 이를 극복하기 위해 선택한 지연 변환 방식의 적용 과정을 공유하고자 합니다.
2. 배경
초기 설계와 달리 고도화 과정에서 요구사항이 변화함에 따라 배치 프로세스 중 간헐적으로 ‘NoSuchTransaction’ 에러가 발생하는 현상이 있었습니다.
2.1. 요구사항의 변화
| 구분 | 초기 설계 및 요구사항 | 변경된 요구사항 |
|---|---|---|
| 파일 형식 | 단일 엑셀 파일 | 엑셀, PDF, 워드 |
| 저장 형태 | 핵심 데이터(Key-Value)만 추출 | 원본 형태 보존 및 전체 바이트 변환 |
| 동기화 방법 | 정기 배치 스케줄링 (새벽) | 정기 배치 + 사용자 수동 동기화 버튼 |
● 초기 설계: 단일 엑셀 파일 수신 및 주요 데이터 추출 위주로 구성되어 DB의 도큐먼트 용량 제한에 대한 부담이 없었고 연산 부하가 낮았습니다.
● 요구사항 변경: 단일 파일이 아니라 업로드된 모든 파일을 수신하며 PDF, 워드 형식도 포함되었습니다. 특히 엑셀은 원본 형태를 보존하여 변환하는 방식으로 변경되어 파일 용량과 연산 부담이 크게 증가하였습니다.
● 동기화 방식: 정기적인 배치 스케줄링뿐만 아니라, 사용자가 필요 시 버튼을 클릭하여 즉각적으로 외부 데이터를 동기화하는 기능을 추가로 지원해야 했습니다.
2.2. 기술적 제약 사항
발생한 문제는 MongoDB의 트랜잭션 처리 메커니즘과 밀접한 관련이 있습니다.
● Multi-document Transaction: 여러 도큐먼트에 대한 CRUD 작업을 하나의 논리적 단위로 묶어 원자성(Atomicity)을 보장하는 기능입니다.
● Transaction Lifetime Limit: 트랜잭션이 시작된 후 강제로 종료되기까지의 시간 제한을 의미합니다. MongoDB는 시스템 리소스 고갈 방지를 위해 기본적으로 60초의 제한을 둡니다.
무거운 파일 변환 로직이 트랜잭션 내에 포함되면서 대량 처리 시 이 임계치를 초과하게 되었고, 이로 인해 세션이 강제 종료되는 오류가 발생하였습니다.
3. 대안 검토
3.1. 서버 설정값 상향
서버 설정 수정을 통해 트랜잭션 유지 시간을 60초 이상으로 늘리는 방법이 있었습니다. 코드 수정 없이 즉각적인 해결이 가능하다는 장점이 있으나, 이는 데이터 증가량에 따라 언제든 재발할 수 있는 임시방편에 불과하였습니다. 또한, 긴 트랜잭션 유지는 데이터베이스 Lock 경합을 유발하여 서비스 전체의 성능 저하를 초과할 위험이 크다고 생각하였습니다.
3.2. 파티셔닝 적용
전체 데이터를 10개 단위로 나누어 각각 별도의 트랜잭션으로 처리하는 파티셔닝 방식을 적용해 보았습니다. 작업 단위를 쪼개어 개별 트랜잭션의 실행 시간을 60초 이내로 단축하려 했으나, 특정 데이터 항목 하나에 수십 개의 엑셀 파일이 포함되었다거나 파일 하나의 크기가 매우 큰 경우가 있었습니다. 파일은 고객사와 거래하는 벤더 사가 업로드하는 것으로 정해진 규격이 없기 때문에 모든 가능성을 열어두고 처리해야 했습니다. 결과적으로 아무리 파티션을 작게 나누더라도, 개별 항목의 연산 부하가 큰 경우에는 타임아웃 에러를 완벽히 해결할 수 없다고 판단하였습니다.
3.3. 트랜잭션 세분화
데이터 수신, 동기화, 파일 변환 및 저장의 각 단계마다 트랜잭션을 개별적으로 실행하는 방식을 검토했습니다. 이 방식은 DB 세션 점유 시간을 최소화할 수 있으나, 작업 중간에 오류가 발생할 경우 이미 커밋된 이전 단계의 데이터를 수동으로 롤백해야 하는 보상 트랜잭션 로직을 직접 구현해야 하고 어디까지 처리되었는지 추적해야 하는 등 관리적 부담이 생깁니다. 또한 가장 무거운 파일 변환 및 저장 단계에서 다시 트랜잭션을 열더라도 단일 항목 내 파일 개수가 많으면 해당 시점에서 다시 타임아웃이 발생할 가능성이 존재했습니다.
3.4. 최종 해결책: 지연 변환 방식 채택
여러 대안들을 검토해보고 적용해본 결과 트랜잭션 범위 내에서 무거운 연산 작업을 분리하는 지연 변환 구조를 최종 해결책으로 선택했습니다.
● Lazy Conversion: 자원이 많이 소모되는 작업을 즉시 처리하지 않고, 결과값이 실제로 필요한 시점(조회 시점)까지 늦추는 설계 패턴입니다.
주요 동작 흐름
1. 배치/동기화 단계: 데이터 수신 및 기본 정보 업데이트만 수행하여 트랜잭션 점유를 최소화합니다.
2. 조회 단계: 사용자가 상세 화면에 진입하여 파일 단위로 조회를 요청합니다.
3. 변환 체크: 시스템은 해당 데이터의 '변환 여부 상태값'을 확인합니다.
4. 실시간 변환: 미변환 상태인 경우에만 개별 파일 단위로 변환을 실행하고 결과를 반환합니다.
4. 문제 해결 및 고려사항
4.1 지연 변환의 당위성 고찰
조회(Query) 기능에 변환(Command) 로직을 결합하는 설계가 적절한지에 대한 고민이 있었습니다. 하지만 사용자가 수동 동기화 버튼을 클릭한 후 즉시 결과를 확인해야 하는 비즈니스 시나리오를 고려할 때, 조회 시점의 처리는 필수적이었습니다. 또한 일괄 처리해야 하는 배치와 달리 개별 조회 시점에는 부하가 분산되므로 리소스 최적화 측면에서 더 유리하다고 판단했습니다.
4.2 운영 환경의 제약 사항
가장 이상적인 것은 파일 정보 테이블을 분리하고 추가적인 스케줄링을 이용해서 변환 작업을 미리 해두는 것이라고 생각합니다. 그러나 실시간 운영 중인 환경에서 대규모 스키마 변경과 데이터 이관은 서비스 가용성 리스크가 높았습니다. 따라서 서비스 연속성을 유지하면서 즉각적인 장애 해결이 가능한 지연 변환 방식을 선택하였습니다.
5. 회고
구조 개선 이후 배치 작업의 안정성이 확보되었으며, 작업 단위와 작업 시점에 대해 여러 방면으로 고민할 수 있었습니다. 기능상, 시나리오상 하나의 작업단위로 보일지라도 구현에서의 트랜잭션 작업 단위와는 별개로 생각하고 무거운 연산 작업은 트랜잭션 외부로 분리하거나 실행 시점을 분산시키는 것이 서비스 전체의 완성도를 높이는 전략임을 알게 되었으며 ‘지연 변환’에 대한 기술적 고민을 통해 좀 더 유연한 프로그래밍적 사고를 갖출 수 있게 되었습니다.
Void