admin 서버를 9시간 멈춘 OOM은 5중 조인이 아니라 기간 없는 정산 엑셀 다운로드 한 건이었다
2026년 10월 12일, 정산 관리 API를 서비스하는 admin 서버가 새벽 4시 46분부터 오후 1시 50분까지 약 9시간 응답하지 않았다. 고객사 문의로 알았고, 원인은 힙 포화였다. 장애 공유 문서는 정산 화면의 5중 조인 쿼리가 주문 데이터를 대량으로 올려 힙을 채웠다고 적었다.…
spring framework에 대한 내용입니다.
2026년 10월 12일, 정산 관리 API를 서비스하는 admin 서버가 새벽 4시 46분부터 오후 1시 50분까지 약 9시간 응답하지 않았다. 고객사 문의로 알았고, 원인은 힙 포화였다. 장애 공유 문서는 정산 화면의 5중 조인 쿼리가 주문 데이터를 대량으로 올려 힙을 채웠다고 적었다.…
강의를 만들면 빈 커리큘럼도 함께 생기게 하고 싶었다. 강의 생성 서비스에서 커리큘럼 생성 포트를 한 줄 부르자 테스트 두 개가 깨졌다. 하나는 DB의 유니크 제약이고, 하나는 아키텍처 테스트의 순환 금지 규칙이다. 순환은 의존성 역전 원칙(DIP)으로 풀었다. 인터페이스를 쓰는 쪽 패키지로…
커리큘럼 편집 서비스의 메서드는 모두 세 줄이다. 커리큘럼을 찾고, 도메인 메서드를 부르고, 저장한다. 로직은 전부 도메인에 있고 도메인 테스트는 꼼꼼하다. 강의는 이 서비스가 너무 단순하다며 테스트를 과제로 남겼다. 나는 그 과제를 하기 전에 서비스를 먼저 커밋했는데, 강의 코드에 있던 버그…
도메인 코드에서 수업을 지우는 방법은 리스트에서 빼는 것이다. JPA의 orphanRemoval은 그렇게 빠진 엔티티를 DB에서도 지워 준다. 편한 설정이지만 커리큘럼에서는 켤 수 없었다. 수업 이동이 “한 리스트에서 빼서 다른 리스트에 넣는” 동작이고, JPA 스펙은 고아가 된 엔티티를 다른 관계에 다시 넣지…
커리큘럼 편집 요청은 전부 숫자로 온다. “섹션 0번의 수업 2번을 섹션 1번의 0번 자리로 옮겨라” 같은 식이다. 리스트 인덱스로 애그리거트 구조 편집을 구현하면 코드는 짧다. 대신 remove가 한 번 일어나면 그 뒤 원소의 인덱스가 하나씩 당겨진다는 사실을 계속 의식해야 한다.…
강의 애그리거트에는 강사, 소개, 공개 상태가 들어 있다. 다음은 실제 학습 내용이다. 강의는 여러 수업으로 이뤄지고, 수업이 많으니 섹션으로 다시 묶는다. 수업과 섹션은 강의에 속한 것처럼 보이니 Course 아래에 매달고 싶어진다. 이 글의 커리큘럼 애그리거트 트리 설계는 그 선택지를 버리는…
수강 신청 DTO의 두 필드에 @NotNull을 걸고, 애플리케이션 서비스에는 @Validated를 달아 두었다. 그런데 리뷰에서 나는 그 사이에 있어야 할 @Valid가 빠져 있는 것을 발견했다. 그 상태에서는 DTO에 제약을 몇 개 걸어 두어도 Bean Validation 검사가 시작되지 않는다 같은 자리에 함정이…
어떤 프로퍼티 조합이 고유하고 그 값으로 엔티티 하나를 특정할 수 있으면 자연 키 후보로 본다. 회원의 이메일이 그랬고, 강의의 강사와 제목 조합도 그랬다. 그런데 두 경우에 서로 다른 결정이 나왔다. 이메일에는 @NaturalId를 붙였고, 강사와 제목에는 유니크 제약만 걸었다. Hibernate에서 @NaturalId를…
입력 값 검증을 애플리케이션 서비스 메서드 안에 직접 넣으면 첫 번째 오류를 만나는 순간 예외를 던지게 된다. 입력 항목이 열 개고 그중 다섯 개가 잘못됐다면 사용자는 다섯 번 제출하고 다섯 번 오류 메시지를 본다. 두 번째 제출에서야 두 번째 오류를…
상태를 가진 도메인을 개발할 때 가장 흔한 구조는 엔티티에 status 컬럼을 두고, 서비스 코드에서 if (course.getStatus() == DRAFT) 같은 조건문으로 전이를 검사하는 방식이다. 처음에는 잘 동작한다. 문제는 그 강의를 다루는 코드가 두 곳, 세 곳으로 늘어날 때다. 어느 상태에서 어느…