분석설계고민

php에서 java로 전환하기(작성중)

닥치고개돌 2024. 1. 29. 13:07
728x90

10년 가까이 된 php코드를 java로 전환하면서 겪은 문제점, 개선점들을 정리하는중

1. table enum컬럼에 속지않기

table ddl을 봤을때 컬럼이 not null, enum이어서 엔티티에 enum타입의 객체로 선언했다.

그리고 db에서 A,B 등 의미없는 값을 사용하고 있었고 description조차 없어서 매번 컬럼과 상태값을 정리된 쪽지를 보고 매핑해서 찾고있었다. 이런 불편을 해소하고자 코드단에서는 명확한 의미를 가진 변수를 사용하여 두번 찾아보는 일이 없게 하기 위해 converter에서 객체를 매핑해주도록 개발했다.

하지만 문제는 enum타입이라고 너무 믿었던 나머지 실제 db값에 null, ""(null string)등 다른 값이 있었음.

converter에서 convertToEntityAttribute 부분에서 enum으로 선언된 값이 아니면 null로 처리하였는데 비즈니스 로직 부분에서 null point exception이 발생함.

이를 해결하고자 두가지 처리방안을 적용하였다.

 

  1. 비즈니스 로직의 객체비교 로직에서 null safe하게 처리
  2. converter에서 null을 리턴하지않도록 NONE이라는 예외 값처리 상태추가

 

아래는 예제 코드이다.

    /**
     * 문서 승인 상태
     */
    @Column(name = "now_state")
    @Convert(converter = InvoiceApprovalStatusConverter.class)
    private InvoiceApprovalStatus invoiceApprovalStatus;
@Getter
public enum InvoiceApprovalStatus {
    /**
     * 승인 요청전(대기)
     */
    WAIT("W", "승인 요청전"),
    /**
     * 승인 요청
     */
    REQUEST("T", "승인 요청"),
    /**
     * 승인 요청 읽음
     */
    READ("R", "승인 요청 읽음"),
    /**
     * 승인
     */
    APPROVE("S", "승인"),
    /**
     * 반려
     */
    REJECT("B", "반려"),
    /**
     * 취소
     */
    CANCEL("A", "취소"),
    /**
     * 쓰레기 데이터 처리
     */
    NONE("", "");

    private final String dbData;
    private final String value;

    InvoiceApprovalStatus(String dbData, String value) {
        this.dbData = dbData;
        this.value = value;
    }
}
@Converter
public class InvoiceApprovalStatusConverter implements AttributeConverter<InvoiceApprovalStatus, String> {

    @Override
    public String convertToDatabaseColumn(InvoiceApprovalStatus attribute) {
        return attribute.getDbData();
    }

    @Override
    public InvoiceApprovalStatus convertToEntityAttribute(String dbData) {
        return Arrays.stream(InvoiceApprovalStatus.values())
                .filter(v -> v.getDbData().equals(dbData))
                .findAny()
                .orElse(NONE);
                //.orElse(null); //null을 리턴하지 않도록 수정
    }
}

자바의 에러 80%라고 해도 과언이 아닌 null point exception은 여러가지 예방 및 해결 방법이 있다.

  1. 의미없는 null값을 넘기지 않기
  2. null여부를 비교하기
  3. 비교의 주체를 equals비교시 앞에 두도록 처리
  4. toString()대신 valueOf사용
  5. chaining 메소드 사용
  6. @NotNull등으로 DTO에서 입력받을때 부터 null예방하기
    등이 있다.
    여기서 1,3,5,6만 습관이 돼도 null point exception을 거의 만나보기 힘들것이고
    2,4번 등 확실히 예방할 수 있지만 코드가 지저분해져 잘 안쓰는데 Apache Commons에서 제공하는 StringUtils 사용하면 깔끔한 코드를 유지하면서 충분히 예방할 수 있을것이다.optional도 있지만 쓸곳 안쓸곳 구분해서 써야 깔끔한 코드가 되더라...

 

엑셀다운로드 OOM

지금생각하면 참 부끄럽지만 공유하기위해 정리

초반엔 자바 힙메모리, 가비지컬렉터 정리된 글을 아무리 읽어도 이해가 잘 안갔는데 버그를 경험하고나니 퍼즐조각처럼 흩어져있던 지식들이 다 맞춰진 기분이 들었다.

정말 쉽게 설명하면 힙메모리는 객체가 담고있는 데이터가 올라가는 영역이고, 가비지 컬렉터는 힙메모리에 올라가있는 데이터 중 더이상 사용하지 않는 객체를 지워준다고 생각하면 된다.

Out of Memory가 난 이유는 리스트 전체 엑셀다운로드를 구현하던 중 객체에 리스트 전체를 불러와 담아놓고 엑셀 파일만들려 했기때문... (그러고는 디비 한번만 붙어서 디비사용 적게 했다고 좋아했다.. 사실 디비에도 더 무리가 가는 쿼리임)

데이터가 수십만개가 되니 힙메모리는 당연히 꽉찼고 oom이 발생, 자바 서버는 죽었고, 고객문의는 불나게오고, 오픈하려던 기능은 롤백하고... 대환장파티

새로운 서비스를 자바로 처음부터 만드는것도 힘들지만, 이미 동작하고 있는 코드를 java로 전환하는것도 만만치 않게 힘들다는 것을 느낌

특히 php로 10년 가까이 된 코드라면 어떻게 돌아가는지 신기한 코드여서 버그가 더 많이 나올수 밖에 없는 상황이었지만, oom은 자바의 개념을 모르고 최대 힙 메모리를 늘리거나 해제하면 되겠지 라는 생각에서 나온버그

처리방안 : 엑셀다운로드는 POI를 사용했고 (참고: https://techblog.woowahan.com/2698/), 전체 엑셀 다운로드는 페이징처리하여 2000건씩 다운로드 되도록 처리했다. 4개 테이블정도 조인된 데이터가 리스트를 만들고 OneToMany관계의 테이블도 있어 N+1 문제도 발생할 수 있지만 default_batch_fetch_size 설정을 통해 처리하였다.

엑셀다운로드 idle timeout

애증의 엑셀다운로드

이번엔 oom은 안나지만 대용량 다운로드받을때 idle timeout이 발생해서 다운로드 못하는 상황 발생

근본적인 문제는 고객에 수십,수백만 건의 데이터를 제한없이 다운로드 받을 수 있도록 기획된 정책이었고, 그게 동기적으로 다운받아지도록 개발된 상태이다.

하지만 그전에는 되고 지금은 왜 안되냐는 기획쪽 문의에 찾아본 결과

기존 php코드의 idle timeout시간은 3분, 쿠버네티스 환경의 java api는 앞의 로드밸런스에서 50초로 설정돼있어서 안되는 상황 파악.

로드밸런서는 쿠버네티스 환경 전체가 사용하고 있을 뿐만아니라 시간을 무한정 늘리는것도 말이 안되기에 정책적으로 해결하자는 협상이 극적으로 타결!

최종 청사진은 비동기로 다운받는 것이지만 지금 바로 고객이 불편하지않게 최대한 고쳐 놓는 것도 중요하다.

우선 속도가 오래 걸리는 이유로는

  1. 인덱스를 제대로 안타는 조회쿼리
  2. 정렬없이 다운로드 되는데 offset으로 개발된 페이징 처리
    두가지로 확인 됐다.

여기서 인덱스를 제대로 안타는 쿼리는, 150개 가까이 샤딩된 테이블에 몇백만 건씩 대용량 데이터가 들어간 테이블 전체에 즉각 조치하기 쉽지않아보여 추후 개선으로 남겨두고 당장 고쳐서 극적인 효과를 볼 수 있는 cursor방식의 리스트 조회로 개선을 진행하였다.

728x90

'분석설계고민' 카테고리의 다른 글

HttpStatus 잘 사용하는법  (0) 2024.03.21
msa란? msa 고도화  (0) 2024.02.04
트랜잭션에 대한 고민  (0) 2023.12.29