[WIL] 항해99 12주차 - 실전프로젝트(4)

Doyeon·2023년 4월 16일
0
post-thumbnail

이번주 한 일

Version 1-1 알고리즘 구현

예약, 일반, 공통 구역을 나누고 매시간 스케쥴링을 통해 각 구역별로 적절하게 배치되도록 구현했습니다.

Version 2 알고리즘 구현

기존 예약, 일반, 공통으로 나뉘었던 주차장 내 구획을 없애고 park_booking_by_hour 테이블을 추가로 만들어 Available컬럼 값을 통해 해당 시간에 예약이나 입차가 가능한지 체크하는 방식으로 수정했습니다.

알고리즘 시나리오 테스트

  • Version1-1, Version2 두 가지 알고리즘에 대하여 상황별 테스트를 진행하여 입차 안정성 및 주차장 운영 효율성 측정
  • 알고리즘 상황별 테스트

동시성제어 적용 및 테스트

  • 예약, 입차 → 분산락 적용
  • 동시성 제어 테스트 → 테스트코드(통합테스트), JMeter 테스트
  • 비관전락, 스핀락, 분산락 성능 테스트 진행

주차장 조회 FULLTEXT index 적용

  • 성능 개선을 위해 DB에서 키워드에 해당하는 주차장을 찾을 때 FULLTEXT index가 적용되도록 구현

테스트코드 작성

  • 동시성 제어 확인을 위한 예약, 입차 로직 통합 테스트 작성 완료
  • 단위 테스트 코드 작성중

피드백 정리

알고리즘 문서화

  • ‘알고리즘 정리 & 기록’ 페이지에 정리한대로 진행하면 된다.
    • version 1-1, version2 순서도 작성할 것
  • 최종발표 문서 작성
    • 프로젝트 소개 : 주차장 조회, 예약, 관리
    • 프로젝트 목적 : 예약차량의 입차 안정성과 주차장 운영 효율성 극대화
    • 적용한 코어 알고리즘 소개 : Version2
      • 이해하기 쉽게 잘 설명할 것
    • 핵심 트러블 슈팅 : version2를 구현하기까지 일련의 과정
      • 버전별 알고리즘 정리, 순서도, 차이점
      • 각 알고리즘 문제점을 해결하기 위해 새로운 버전을 만들었고, v2에 한계점도 있지만 최종적으로 결정하게 된 이유까지 설명
    • 첨부문서 : 각 알고리즘 상세 정리

테스트 코드 작성

테스트 코드 작성 범위

  • 커버리지를 높이기 위한 테스트코드는 지양
  • 목표 커버리지가 있으면 좋지만, 제일 중요한 것은 작성한 코드를 검증할 부분이 명확하게 있을 때 테스트코드를 만드는 것이 가장 좋다.
  • 단위 테스트만으로 코드가 검증이 된다면 단위 테스트 진행, 단위테스트만으로 불가능하다면 통합테스트 환경에서 테스트코드 작성 → 우리는 예약, 입차 동시성 제어 검증을 위한 통합테스트 코드를 작성한다.

테스트 후 DB에 저장되는 문제

  • 테스트환경에서 사용하는 DB를 따로 설정해볼 것 → 운영에서는 RDS MySQL을 사용하지만, 테스트 환경에서는 h2를 사용하도록 설정해본다.

동시성 제어

@Transactional 적용

  • @Transactional 적용
    public <T> T runOnLock(Long key, Supplier<T> task) {
            while (true) {
                if (!lock(key)) {
                    try {
                        log.info("락 획득 실패");
                        Thread.sleep(100);
                    } catch (InterruptedException e) {
                        Thread.currentThread().interrupt();
                        throw new CustomException(ErrorType.FAILED_TO_ACQUIRE_LOCK);
                    }
                } else {
                    log.info("락 획득 성공, lock number : {}", key);
                    break;
                }
            }
            try {
                return task.get();
            } finally {
                // Lock 해제
                unlock(key);
            }
        }
    public CarInResponseDto enter(CarNumRequestDto requestDto, Admin admin) {
            return redisLockRepository.runOnLock(
                    requestDto.getParkId(),
                    () -> enterLogic(requestDto, admin)
            );
        }

위 코드 enter(requestDto, admin)에서 enterLogic은 @Transactional 어노테이션이 붙어 있으나, Transactional은 적용되지 않는다.

  • TRACE로 로그 찍어서 트랜잭션 제대로 걸렸는지 확인해보기
  • AOP가 동작하기 위해서는 프록시가 동작해야 하고, 프록시 동작 조건은 빈이 호출되어야 한다.
  • 현재 코드는 enter 메서드 들어올 때 mgtService 프록시 빈으로 들어오고, 레디스 락을 잡고 enterLogic 수행하는 부분은 프록시 빈으로 들어오는게 아니라, 이미 프록시 빈으로 들어와 있던 mgtService의 메서드를 호출한 것 → enterLogic 에 있는 모든 AOP가 무시된다.
  • enterLogic을 바로 부르지 않고 mgtService 의존성 주입하여 호출하면 AOP(@Transactional)가 작동한다.
    private final MgtService mgtServiceSelf;
    
    public CarInResponseDto enter(CarNumRequestDto requestDto, Admin admin) {
        return redisLockRepository.runOnLock(
                requestDto.getParkId(),
                () -> mgtServiceSelf.enterLogic(requestDto, admin)
        );
    }
    • 위 방식은 self-injection이다. 셀프 주입은 좋은 방식이 아니므로, TransactionHandler를 이용하여 enterLogic이 트랜잭션 작동하도록 처리하는 것이 좋다.

Rock time 설정

  • wait time 설정
    • wait time이 길면 기다렸다가 작업은 수행할 것이니, 정확성은 보장되나, 성능 문제가 생긴다.
    • 보편적으로 안정성 보장을 위해 wait time을 길게 잡는다.
  • 우리 프로젝트는 보수적으로 타임을 설정하여 lease time 10초, wait time 15초로 설정해본다.

다음주 멘토링

  • 프로젝트 데모버전 시연
profile
🔥

0개의 댓글