개발 slecs

이커머스 PG 플랫폼 PDF 기능 안정성 개선

목차

PG 플랫폼에서 PDF 관련 기능을 손보는 작업은 언뜻 단순해 보인다. 파일 하나, 클래스 하나. 근데 실제로 들어가면 생각보다 건드려야 할 레이어가 꽤 된다. 요청 파라미터가 들어오는 지점부터 SQL을 태우고, 결과를 PDF 형태로 내려주거나 JSP 화면에 연결하는 흐름까지 전부 연결되어 있기 때문이다. 이번에도 그 흐름 어딘가에서 발생한 불안정 포인트를 잡는 게 목표였다.

왜 내부 클래스인가

Spring MVC + MyBatis 구조에서 내부 클래스(inner class 혹은 static nested class 형태로 쓰는 DTO/VO 패턴)를 쓰는 이유는 단순하다. 특정 컨트롤러나 서비스에만 쓰이는 요청/응답 객체를 별도 파일로 쪼개는 게 오히려 관리 비용이 높을 때, 같은 파일 안에 묶어두면 응집도가 올라간다. 어디서 쓰는 객체인지 바로 보이니까.

문제는 이런 구조가 오래되면 내부 클래스가 비대해진다는 거다. 파라미터 바인딩 처리, null 방어 로직, 심지어 일부 비즈니스 계산까지 박혀 있는 경우가 생긴다. 이번 수정도 그 맥락에서였다. 요청 파라미터를 바인딩하는 시점에 검증이 충분히 안 되어 있었고, 특정 케이스에서 빈 값이 들어오면 이후 SQL 단에서 터지거나 PDF 렌더링 결과가 이상하게 나왔다.

수정 방향은 크게 세 가지였다.

  • 파라미터 수신 시점에 null/빈 문자열을 명시적으로 처리하고 기본값 설정
  • SQL 쿼리 side에서 <if test="param != null and param != ''"> 조건을 보강
  • 결과 매핑 후 PDF 생성 직전에 필수 필드 누락 여부를 한 번 더 체크

세 번째가 중요했다. PDF는 중간에 필드 하나 빠지면 레이아웃이 깨지거나 라이브러리 자체에서 NPE가 튀어나온다. 그걸 운영에서 발견하면 지저분해지니까 생성 직전에 방어선을 하나 더 두는 게 맞다.

트랜잭션과 예외 처리에서 고른 것들

PDF 기능 특성상 트랜잭션 범위를 어떻게 잡느냐가 애매하다. 데이터를 조회만 하고 PDF를 만들어 반환하는 흐름이면 @Transactional(readOnly = true)로 충분하다. 근데 조회 중에 이력을 남기거나 다운로드 카운트를 찍는 로직이 섞여 있으면 범위를 분리해야 한다.

이번 케이스에서는 조회+이력 기록이 같은 흐름에 있었다. 그래서 트랜잭션을 단순히 상위 서비스 메서드 하나에 다 걸지 않고, 이력 기록 부분을 별도 메서드로 뽑아 REQUIRES_NEW로 처리하는 방식을 택했다. 조회가 실패해도 이력은 남고, 이력 기록이 실패해도 PDF 조회 결과에 영향이 없도록.

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void saveDownloadHistory(String userId, String docId) {
    // 다운로드 이력 insert
    // 이 트랜잭션이 롤백돼도 상위 흐름에 영향 없음
    historyMapper.insert(userId, docId);
}

예외 처리는 RuntimeException을 그냥 위로 전파할지, 잡아서 응답 변환을 할지 매번 선택의 문제다. 이번에는 컨트롤러 레이어에 @ExceptionHandler로 PDF 관련 예외를 잡아서 사용자에게 의미 있는 메시지로 내려주도록 했다. 스택트레이스를 화면에 뿌리는 건 최악이니까.

로그는 입력 파라미터와 최종 PDF 파일 경로 혹은 크기 정도를 INFO로 남겼다. 운영에서 "왜 PDF가 이상하게 나왔어요?"라는 문의가 오면 저 로그 두 줄로 90%는 원인 파악이 된다.

CI/CD에서 이번에도 한 번 걸린 것들

배포 파이프라인은 언제나 코드 자체보다 더 많은 시간을 먹는다. 이번에도 비슷했다.

문제 원인 처리
SSH 타임아웃 빌드 시간이 길어지면서 연결 끊김 ServerAliveInterval + command_timeout 명시
키 인증 실패 개행 문자가 시크릿 값에 포함됨 base64 인코딩 후 파이프라인에서 디코딩
Gradle 캐시 충돌 CI 환경 캐시와 로컬 캐시 불일치 --no-daemon --rerun-tasks 플래그 추가
환경변수 누락 Secrets 항목 하나 빠짐 배포 체크리스트에 환경변수 항목 추가

특히 키 인증 쪽은 로컬에서 잘 되던 게 CI에서 안 되는 상황이라 처음엔 원인을 찾는 데 시간이 걸렸다. 개행 문자 문제는 시크릿 값을 복사할 때 맨 끝에 줄바꿈이 붙어서 들어가는 케이스인데, 이게 에러 메시지에 명확히 안 찍혀서 헷갈린다. 앞으로는 시크릿 등록할 때 echo -n으로 개행 없이 저장하거나 base64로 감싸는 걸 루틴으로 가져가는 게 맞겠다 싶었다.

배포 순서 자체는 항상 같다. 로컬 기동 확인 → 개발 DB에서 SQL 검증 → 스테이징 배포 후 기능 확인 → 운영 배포. 이 순서를 지키면 운영에서 처음 확인하는 상황은 거의 없다. 작업 규모가 작을수록 이 순서를 생략하고 싶어지는데, 그럴 때 한 번씩 터진다는 걸 경험으로 알고 있다.

코드 수정 자체는 크지 않았다. 그런데 이런 류의 작업이 쌓이면 나중에 레거시 시스템의 신뢰도를 가르는 기준이 된다. null 체크 하나, 트랜잭션 범위 정리 하나, 로그 한 줄. 당장 기능에 영향을 주지 않아서 미루기 쉽지만, 그게 쌓이면 운영 이슈로 돌아온다. 오히려 기능 추가보다 이런 안정화 작업이 총괄 입장에서는 더 신경 쓰이는 부분이기도 하다.

댓글 0

첫 댓글 달아줘.