PatientPal

6/10 ~ 6/16

후후후하하하 2024. 6. 11. 16:43

6/11
리팩토링, todo 기능 추가

프로필 이미지 등록할 필요, 이후 채팅에서도 사진 저장 필요, 이후 계약에서도 계약서 저장 필요

aws s3 도입, 왜?

확장성(Scalability)

파일 서버는 트래픽이 증가함에 따라 서버 인프라 및 용량 계획을 변경해야 되는데, S3가 확장 및 성능 부분을 대신 처리해준다.

내구성(Durability)

여러 영역에 여러 데이터 복사본을 저장하므로 한 영역이 다운되더라도 데이터를 사용할 수 있고, 복구가 가능하다.

 

MultipartFile 방식은 다수의 사용자로부터 동시에 요청이 들어올 경우, 서버의 스레드가 빠르게 소진될 수 있다는 문제가 있다.

 

안전하면서 대용량 트래픽을 어떻게 받을 수 있나?

 

방법 1? s3 presigned url

1.1. Multipart Upload - 대용량 이미지를 AWS S3에 업로드할 때는 Multipart Upload 기능을 사용하여 업로드를 분할할 수 있습니다. 이 기능은 대용량 파일을 작은 조각으로 나눠서 업로드합니다.

 

==============

6/12

이미지 관련 진행하려는데 는 전처리, 업로드 시에 서버에 부하가 많이 갈 뿐, 로직이 복잡하진 않다. 그래서 부하가 걸리는 부분 처리를 이미지 업로드와 이미지 가공(리사이징, 색변환 등)으로 나눌 수 있다.

참고 : https://www.inflearn.com/chats/278798/%EB%8C%80%EC%9A%A9%EB%9F%89-%EC%9D%B4%EB%AF%B8%EC%A7%80-%EC%84%9C%EB%B2%84-%EC%B2%98%EB%A6%AC-%EB%B0%8F-%EC%A0%80%EC%9E%A5-%EB%B0%A9%EC%8B%9D%EC%97%90-%EC%A7%88%EB%AC%B8%EB%93%9C%EB%A6%BD%EB%8B%88%EB%8B%A4

 

https://leeeeeyeon-dev.tistory.com/88

 

presigned url 과 aws s3로 일단 구현함. 이후 람다 적용 하자 (이미지 전처리?)

 

=======================

        @Test
        @WithCustomMockUserCaregiver
        void 프로필_미등록시_예외가_발생한다() throws Exception {
            // given
            CaregiverProfileUpdateRequest request = updateCaregiverProfileRequest();
            String testProfileImageUrl = "test-image-url";

            // presignedUrlService가 반환하는 값을 Mock으로 설정합니다.
            given(presignedUrlService.findByName(any(String.class))).willReturn(testProfileImageUrl);

            // caregiverService의 updateCaregiverProfile 호출 시 예외를 발생시킵니다.
            willThrow(new EntityNotFoundException(ErrorCode.CAREGIVER_NOT_EXIST))
                    .given(caregiverService)
                    .updateCaregiverProfile(any(String.class), any(CaregiverProfileUpdateRequest.class), eq(testProfileImageUrl));

            // when & then
            mockMvc.perform(patch("/api/v1/caregiver")
                            .contentType(MediaType.APPLICATION_JSON)
                            .content(objectMapper.writeValueAsString(request)))
                    .andDo(print())
                    .andExpect(status().isNotFound());
        }
}

 

이거 왜 실패하냐? 왜 204가 나오지? 404가 안나오고. willThrow가 전혀 안먹히는 중.

해결 =>

given(presignedUrlService.findByName(any())).willReturn(profileImageUrl);

 

위 코드가 들어가야 통과함. 기존에는 findByName(any(String.class))라고 넣었더니 계속 저 해당 given문이 실행이 안됐었음. 왜 any()라고 해야 통과하냐?? 왜냐하면 findByName 매개변수에 null 값이 들어갈 수가 있어서인가?? 애초에 path가 전역 변수로 선언되어있기때문에 값이 null이 들어갈 수도 있어서 인것 같은데. 맞는 듯. Mock을 이용한 테스트에서 매개변수 타입은 무조건 일치해야한다. findMyName()의 메서드 매개변수가 null 이 들어갈 수도 있는 상황에서 any(String.class)라고 String 타입을 강제하면 안된다. 만약 지역변수에 String path가 있다면? 지역변수에 String path = "image.jpg"를 선언하고 any(String.class)로 테스트 실행 시 성공한다.

즉, 전역변수를 인자로 받는다면, null이 들어갈 수 있기때문에 any(String.class)가 아닌, any()로 받아야 한다 ...!!!! 

그런데, serviceTest에서는 any(String.class)로 받았는데 통과했다..? 왜지? 전역변수가 아니라 그런가?

 

======================

6.14

로컬변수로 바꿔서 진행하니, 프로필 완전 등록 시 3개의 메서드가 호출되는데 이 메서드 중간에 다른 스레드가 끼어들어 url 주소가 변경될 수 있다는 동시성 문제가 존재함. uuid를 넘길 수 있는 방법 없나?

 

로컬 변수로 바꿔서 진행하니, 저장 url을 생성하는 uuid와 프로필 생성 메서드에서 findByName()시에 반환하는 url이 다름. uuid를 넘겨야하는데, 어떻게?

https://chatgpt.com/c/67dd940f-b9ad-4827-b3ae-8445bcb2c4f5

 

 

해결 => @RequestParam을 통해 프로필 등록시에 url 파라미터를 넘기면서 저장함.

 

======================================

 

6/14

PresignedUrlService에 PublicRead 제거해야함. 

업로드/다운로드 메서드 분리해야함??

프라이빗 버킷 정책 예시:

json

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Deny",
      "Principal": "*",
      "Action": "s3:PutObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}

버킷 private하게 막아두어야 한다.

*   - 이후 AWS 클라우드 프론트 or AWS Lambda 이용하여 성능 개선, 캐싱 (S3와 백엔드 서버 부담 저하)