개요
2024년 4월부터 12월까지 프로젝트 개발 및 운영을 진행하다가, 2025년 1월부터 새로운 프로젝트에 참여하게 되었다. 이후 운영 중인 코드의 개선 작업 요청이 들어왔고, 시스템이 많이 안정화된 상태라 이를 수행할 여유가 생겼다.
단순히 기존 코드 스타일이 마음에 들지 않아서 수정하는 것이 아니라, 정상적으로 동작하지 않는 부분을 바로잡고, 불필요한 사용자 로그를 정리하며 개선하는 것이 목표였다.
대대적인 개편보다는 점진적으로 코드를 개선해 나갔다.
참고로 지금 회사에서는 Mulesoft를 활용한 프로젝트들을 한다. Mulesoft는 Mulesoft는 여러 시스템, 애플리케이션, 데이터 소스를 쉽게 연결할 수 있도록 도와주는 Salesforce사의 솔루션 중 하나이다. (한마리도 중간다리 역할을 하는 소프트웨어.. )
1. Try catch + XA Transaction
기존 코드 의도 파악 :
1. Oracle 원본 데이터를 temp 테이블에 적재
2. temp 테이블 데이터를 Postgres로 적재
3. 작업 완료 후 Oracle temp 테이블을 TRUNCATE
4. 성공 시 Oracle 원본 테이블의 특정 컬럼을 'Y'로 업데이트, 실패 시 'N'으로 업데이트
이 과정이 배치(Batch) 작업으로 구성되어 있었으며, 데이터 100개 중 80개가 성공하고 20개가 실패할 가능성을 고려하여, 하나의 batch step을 추가하여 두 번 반복하는 구조로 되어 있었다.
근데 이 코드는 동작하지 않아 고치게 되었다.
해결 :
기존 코드에서는 예외 처리가 제대로 이루어지지 않았기 때문에, 이를 try-catch로 감싸서 위의 DB SQL 동작을 안전하게 실행하도록 수정하였다.
Oracle과 Postgres는 서로 다른 DB이므로, 간의 트랜잭션을 하나의 XA Transaction (분산 트랜잭션)으로 묶는다. XA Transaction을 활용하면 두 데이터베이스 간에 일관성을 유지하면서 트랜잭션을 처리할 수 있어, 두 DB에 대한 트랜잭션이 모두 성공하거나 모두 실패하게 된다.
- 로직 시작 시점에서 ALWAYS_BEGIN을 사용하여 트랜잭션을 시작.
- 두 데이터베이스(Oracle과 Postgres) 쿼리 로직은 ALWAYS_JOIN으로 설정하여 트랜잭션에 참여.
이렇게 함으로써, 예외 발생 시 전체 트랜잭션을 롤백하고, 성공 시 모든 작업을 커밋하여 안정적인 데이터 처리를 보장할 수 있다.
// pseudo code (C-style)
try {
retryUntilSuccessful(() => {
// Begin XA Transaction (ALWAYS_BEGIN)
beginXATransaction();
// Oracle 원본 데이터를 temp 테이블에 적재
insertOracleTempTable();
// Postgres에 데이터 적재
insertPostgresFromTemp();
// Oracle temp 테이블 truncate
truncateOracleTempTable();
// Oracle 원본 테이블 성공 상태 업데이트
updateOracleStatus("Y");
// Commit XA Transaction (DB Always Join)
commitXATransaction();
printf("Success\n");
} catch(Exception e) {
// Rollback XA Transaction
rollbackXATransaction();
// Oracle 원본 테이블 실패 상태 업데이트
updateOracleStatus("N");
// 에러 출력
printf("", e.message);
}
2. Error 처리
기존 코드 의도 파악 :
1. NAS 서버에 해당 path에 올라간 파일을 FTP 서버로 가져옴 -> 실패 시 에러 던짐.
2. 해당 NAS 서버에 파일이 없을 경우 다른 path로 접근하여 파일을 가져와 FTP 서버에 업로드 -> 실패시 에러 던짐
3. 그 이후 S3 업로드 -> 실패 시 에러 던짐
기존에는 각 작업에서 실패가 발생할 때마다 즉시 에러를 던져 처리하다 보니, 에러가 발생하면 그 즉시 작업이 중단되었고, 에러 로그도 여기저기 흩어져 있어 디버깅과 유지보수가 어려웠다.
해결 :
각 단계에서 즉시 에러를 throw하지 않고, 별도의 Error 객체(Notification)를 생성하여 에러 메시지와 실패 원인을 기록하고 전체 작업이 끝난 후, Error 객체를 한 번에 처리하여 모든 발생한 에러들을 통합적으로 로깅했다.
//pseudo code (java)
// Martin Fowler의 "Replace Throw with Notification" 글을 참고
notification = new Notification()
file = getFileFromNAS(primaryPath)
if (file is null) {
notification.addError("Primary path에서 파일 가져오기 실패")
file = getFileFromNAS(alternativePath)
if (file is null) {
notification.addError("Alternative path에서도 파일 가져오기 실패")
}
}
if (file is not null && !uploadToFTP(file)) {
notification.addError("FTP 업로드 실패")
}
if (file is not null && !uploadToS3(file)) {
notification.addError("S3 업로드 실패")
}
if (notification.hasErrors()) {
notification.logAllErrors()
} else {
print("모든 작업 성공적으로 완료")
}
마무리
리팩토링은 작은 규모로 매일 꾸준히 수행할 때 가장 효과적이며, 지속적인 유지보수와 코드 품질 관리를 위해 습관화하는 것이 중요한 거 같습니다.
'Diary' 카테고리의 다른 글
“오늘은 써야지”를 여섯 번 되뇌며, 세 번 썼다. (0) | 2025.04.13 |
---|---|
나의 항해는.. (0) | 2025.03.30 |
XA Transaction : (0) | 2025.03.24 |
[항해99 항해 플러스 1기 실제 후기] 10주차까지 마치며.. (0) | 2023.08.23 |