[TIL] 241203 MSA 프로젝트 과제 실습

MONA·2024년 12월 3일

나혼공

목록 보기
40/92

MSA 개인 과제 실습

일단 구성해야 할 아키텍처는 다음과 같다

먼저 기본 프로젝트를 모두 생성하고, 유레카 서버에 정상 등록되는지 확인했다

본격 개발 시작

진행하면서 어려움을 겪은 부분을 정리해보았다

  1. 엔티티 설정 시 Id 필드를 자동생성되게 하지 않아서 상품 등록 시 에러 발생

RDB로 PostgreSQL을 사용하는데, 이미 DB가 생성된 후라 DB를 드랍하거나 설정을 바꿔야 했다.
그냥 설정을 바꿨다.

그냥 아무런 조건 없이 들어가있는 bigint

수정 후

드디어 상품 하나 등록했다 ㅋ ㅋ ㅋ ㅋ ㅋ

  1. Order 추가 시 포함된 Product가 유효한지 feign client로 체크해야 하는데 요청이 product까지 가지 않음

진짜 모든걸 다 해봤다.
디버그도 엄청 찍고, 설정도 엄청 세세하게 해보고 별짓을 다했는데..
네트워크 문제도 아니고 로직도 멀쩡하고 유레카도 잘 떴는데..

@FeignClient(name="product", fallback = ProductClientFallback.class)
public interface ProductClient {

    @GetMapping("/products/validate")
    ResponseDto validateProducts(@RequestParam("productIds") List<Long> productIds);

}

그냥 @RequestParam List<Long> productIds으로 보내던 걸 이름을 명시하고 해결됐다.
눈물..

  1. user 테이블 생성 안됨

postgreSQL을 쓰는데 예약어인 몇몇가지는 테이블명으로 쓰지 않는 게 좋다.
order라던가.. user라던가..
그냥 복수형으로 테이블명을 매핑해주면서 해결

  1. Zipkin spans 제대로 안잡히는 문제

분명 order-product-order로 spans가 3이 나와야 하는 요청인데 자꾸 order 하나만 잡혔다.
이전에도 겪었던 문제인데 이번에도 똑같은 실수를 했다.
의존성 누락...
제대로 추가해주었다.

	implementation 'org.springframework.boot:spring-boot-starter-actuator'
	implementation 'io.micrometer:micrometer-tracing-bridge-brave'
	implementation 'io.github.openfeign:feign-micrometer'
	implementation 'io.zipkin.reporter2:zipkin-reporter-brave'
  1. 캐싱 적용 시 직렬화 실패

public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private Long id;

    @ElementCollection
    @CollectionTable(name = "order_product_ids", joinColumns = @JoinColumn(name = "order_id"))
    private List<Long> productIds;


}

Order의 productIds 필드를 ElementCollection으로 해두었는데 캐싱 시 lazy initialization 상태에서 직렬화되려다 실패함.

Hibernate가 Session 밖에서 lazy-loaded 필드를 초기화하려고 할 때 발생하는 문제

  • Order 엔티티에서 productIds 필드는 @ElementCollection으로 설정되어 있음. Hibernate는 이 컬렉션을 PersistentBag으로 관리함
  • PersistentBag은 lazy-loaded로 기본 동작하므로, 해당 컬렉션이 Session 밖에서 접근될 때 초기화되지 않은 상태에서 예외가 발생
  • 이 상황에서 Jackson이 이를 직렬화하려 하다 보니 LazyInitializationException이 발생

해결

  • DTO를 캐싱
  • Hibernate 엔티티(Order) 대신 DTO(OrderResponse)를 Redis에 캐싱하도록 변경
  • 불필요한 Lazy Initialization 문제를 피할 수 있음
// 주문 단건 조회
    @Cacheable(cacheNames = "orderCache", key="#orderId")
    public ResponseDto getOneOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElse(null);
        if(order == null) {
            return new ResponseDto(ResponseDto.FAILURE, "해당하는 주문이 없습니다", null);
        }
        OrderResponse orderResponse = OrderResponse.builder()
                .orderId(order.getId())
                .productIds(order.getProductIds())
                .build();
        return new ResponseDto(ResponseDto.SUCCESS, "주문 조회 성공", orderResponse);
    }

요랬는뎅

// 주문 단건 조회
    @Cacheable(cacheNames = "orderCache", key="#orderId")
    public ResponseDto getOneOrder(Long orderId) {
        Order order = orderRepository.findById(orderId).orElse(null);
        if(order == null) {
            return new ResponseDto(ResponseDto.FAILURE, "해당하는 주문이 없습니다", null);
        }
        OrderResponse orderResponse = OrderResponse.builder()
                .orderId(order.getId())
                .productIds(new ArrayList<>(order.getProductIds()))
                .build();
        return new ResponseDto(ResponseDto.SUCCESS, "주문 조회 성공", orderResponse);
    }

요래됐슴당

흑흑

문제상황

  • 원인: Hibernate 엔티티와 Lazy Initialization
    • Hibernate는 데이터베이스에서 가져온 엔티티 객체를 Session이라는 컨텍스트에서 관리한다
    • @ElementCollection과 같은 관계 필드가 기본적으로 Lazy Loaded로 설정되어 있다면, 해당 필드의 데이터는 실제로 접근하기 전까지 초기화되지 않는다
    • 만약 Session이 닫힌 상태(예: Service 레이어에서 반환 후)에 Lazy Loaded 필드에 접근하면 LazyInitializationException이 발생한다
    • Redis와 같은 캐시 시스템은 데이터를 직렬화해서 저장하기 때문에, 엔티티의 lazy-loaded 필드가 직렬화 도중 초기화되지 않아 문제가 발생함
  1. 첫 조회 시
    • DB에서 데이터를 가져와 DTO로 변환하거나 그대로 응답
    • Hibernate는 세션이 열려 있으므로 Lazy Loaded 필드(ProductIds)를 정상적으로 초기화 가능
  2. Reids에서 데이터 조회 시
    • 캐싱된 데이터를 직렬화/역직렬화하는 과정에서 Lazy Loaded 필드가 포함된 객체를 역직렬화
    • 이 필드는 여전히 Hibernate의 지연 로딩 프록시 객체 상태일 수 있음
    • Redis에서 직렬화된 객체를 가져온 이후, 이 필드에 접근하려고 하면 세션이 없으므로 LazyInitializationException이 발생

그게 뭐가 문젠데

  • 캐싱 시 직렬화/역직렬화 과정에서, Hibernate의 Lazy Loaded 필드가 초기화되지 않은 상태로 저장된 것이 문제이다
  • 첫 번째 요청 시
    • Lazy Loaded 필드는 실제 데이터로 초기화되어 응답.
    • 그러나 Redis에 저장된 객체는 Hibernate Proxy 객체(Lazy Loading 상태)로 저장
  • 두 번째 요청 시
    • Redis에서 가져온 데이터는 그대로 역직렬화되지만, productIds 필드가 초기화되지 않은 상태(Lazy Loaded Proxy)
    • 응답 시 Lazy Loaded Proxy에 접근하려고 하면 Hibernate 세션이 없으므로 에러 발생

한마디로

세션이 닫혀있는데도 Hibernate의 지연 로딩 필드에 접근하려고 했기 때문에 문제 발생

profile
고민고민고민

0개의 댓글