항해99 3기

[TIL] 2021.11.15 최종 프로젝트 진행중 - 게시물 수정 로직 변경 / nGrinder 준비

na_o 2021. 11. 15. 15:01
728x90

게시물 수정 로직을 다시 짜고 있다

전혀 생각 못했는데 레시피 수정 로직을 먼저 만드신 다른 팀원분의 PR을 읽어보는중

IN 절을 사용하신걸 봤다

IN절을 전혀 생각 못했다

나는 where 절로 하나하나씩 찾을 것만 생각했었다

그래서 JPA에서 IN 절을 사용하는 방법을 찾아봤다

https://happygrammer.tistory.com/158

 

JPA - 스프링 데이터 JPA에서 쿼리 메소드 안에 지원되는 키워드

쿼리 메소드는 스프링 데이터 JPA의 핵심적인 기능중 하나로 메소드 이름으로 쿼리를 생성할 수 있다는 장점이 있다. 메소드 이름으로 쿼리를 생성을 위해 인터페이스에서 사용할 사용자 쿼리

happygrammer.tistory.com

이 블로그 내용을 보면 JPA 공식 문서의 일부를 가져온 것이다

JPA에서 'In' 키워드가 있었다

keyword sample JPQL snippet
In findByAgeIn(Collection<Age> ages) … where x.age in ?1

매개변수에 리스트(List<String> 등...)를 넘겨주면 된다

 


게시물 수정 로직을 바꿨다

 

1. 해당 게시물에 첨부한 사진들을 DB에서 검색하기

2. requestDto에 새로 업로드할 이미지 파일, 삭제해야할 이미지 정보가 담겨있는데,

   업로드할 이미지 파일 개수, 삭제해야할 이미지 개수를 세어서

   게시물 작성 시 올린 사진 개수, 업로드할 이미지 파일 개수, 삭제해야할 이미지 개수를 모두 포함해서

   5장이 넘을 경우 exception 발생시키기

3. 새로 첨부한 이미지들을 S3에 올리고 DB에 저장하기

4. 삭제해야하는 이미지들은 DB에 삭제하고 S3에 삭제하기

5. 제목, 내용을 업데이트 시키기

 

크게 이런 흐름으로 진행된다

//requestDto

@Setter
@Getter
public class PutBoardRequestDto {
    private String title;
    private String content;
    private List<String> deleteImage;
    private MultipartFile[] image;
}

 

2번에서 생각치 못한 부분이 있는데

postman에서 image를 아무것도 첨부 안 한 상태로 request를 하면

 

리스트에 무언가가 담기긴 하지만 비어있는 상태로 들어온다

이런 경우도 따져야되기 위해서 다음과 같이 로직을 짰다

//게시물 수정 후 게시물에 이미지가 몇 장 있을 지 확인
    private void imageCountCheck(MultipartFile[] imageList, List<String> deleteImageList, List<BoardImage> boardImage) {
        if(boardImage != null && boardImage.size() > 0) {
            if(imageList != null && imageList.length > 0) {
                //imageList에 들어있는 실제 이미지 파일들의 개수
                int imageFileCount = getImageFileCount(imageList);
                int imageCount = boardImage.size() + imageFileCount;
                if(imageCount > maxImageCount)
                    throw new IllegalArgumentException("한 게시물에 이미지는 최대 5장 존재할 수 있습니다.");
            }
            if((imageList != null && imageList.length > 0) && (deleteImageList != null && deleteImageList.size() > 0)) {
                //imageList에 들어있는 실제 이미지 파일들의 개수
                int imageFileCount = getImageFileCount(imageList);
                int imageCount = boardImage.size() + imageFileCount - deleteImageList.size();
                if(imageCount > maxImageCount)
                    throw new IllegalArgumentException("한 게시물에 이미지는 최대 5장 존재할 수 있습니다.");
            }
        }
    }

    //imageList에 들어있는 실제 이미지 파일들의 개수 구하기
    private int getImageFileCount(MultipartFile[] imageList) {
        int imageFileCount = 0;
        for (MultipartFile image : imageList) {
            //이미지를 첨부 안 헀어도 리스트에 무언가가 들어옴
            //정확히 판단해내려면 리스트에 담겨있는 파일의 크기가 0이 넘는지 확인해야함
            if (image.getSize() > 0) {   //파일 크기가 0이 넘으면
                imageFileCount++;       //새로 올릴 파일이 존재하므로 count
            }
        }
        return imageFileCount;
    }

 

비어있지만 비어있지 않은(?) 경우를 대비해

리스트에 있는 값을 하나하나 확인했다

리스트에 담겨있는 파일이 진짜로 있는지 판단하기 위해 파일의 크기(몇 바이트인지)가 0보다 크면

카운트를 세는 것으로 로직을 짰다

 

방금 센 카운트 가지고 게시물 작성 시 올린 사진 개수, 업로드할 이미지 파일 개수, 삭제해야할 이미지 개수를 모두 포함해서 5장이 넘을 경우 exception 발생시키도록 했다

 


3번 로직에서 새로 첨부한 이미지가 없으면 3번 로직을 아예 안 타게 했고,

if(imageList != null && imageList.length > 0) { //새로 첨부한 이미지가 존재할 때
...
else 로직은 안 짰다

4번 로직에서 삭제할 이미지가 없으면 4번 로직을 아예 안 타게 했다

if(deleteImageList != null && deleteImageList.size() > 0) { //삭제할 이미지가 존재할 때
...
else 로직은 안 짰다

이것 때문에 게시물 수정 시 이미지 삭제도, 추가도 안했으면 제목과 내용만 바뀌게 된다

 

 

3번 로직에서 이미지를 업로드 하다가 exception이 나면 지금까지 작업하던 S3에 업로드한 이미지들을 다시 되돌려놓고 싶었다

이미지를 업로드하다가 exception이 발생하면 업로드한 이미지 URL들을 가지고 하나하나 S3에서 삭제해줬다

어떤 경우에 exception이 발생시킬 수 있는 지 잘 모르겠어서 테스트는 못 해 봤다

//게시물 수정 시 새로 첨부한 이미지들은 DB와 S3에 넣기
    private void updateNewImageFiles(Board board, MultipartFile[] imageList) {
        if(imageList != null && imageList.length > 0) { //새로 첨부한 이미지가 존재할 때
            //S3에 업로드
            List<String> uploadImageUrlList = new ArrayList<>();    //S3에 업로드된 이미지URL을 담는 용도
            String uploadImageUrl = null;
            try {
                for(MultipartFile imageFile : imageList) {
                    uploadImageUrl = s3Uploader.upload(imageFile, "boardImage");
                    if(uploadImageUrl == null)    //이미지 업로드에 실패했을 때
                        throw new NullPointerException("이미지 업로드에 실패하였습니다.");
                    uploadImageUrlList.add(uploadImageUrl);
                }
            } catch(Exception exception) {
                //지금까지 업로드한 이미지들을 롤백: S3에서 삭제
                for(String url: uploadImageUrlList) {
                    deleteS3(url);
                }
            }

            //DB에 Insert
            for(String imageUrl : uploadImageUrlList) {
                BoardImage newBoardImage = new BoardImage(imageUrl, board);
                boardImageRepository.save(newBoardImage);
            }
        }
    }

 

다른 팀원분이 맡은 레시피 수정 API에서(레시피가 게시판과 거의 동일함)

삭제해야할 이미지들을 삭제하는 로직을 맨 뒤로 뺀 걸 봤다

이유가 있었다

초반부터 삭제하는 로직을 짰으면 롤백이 일어났을 때 S3에서 삭제한 이미지들은 되돌릴 수 없다

 

그걸 읽고 나도 나름 의식하고 있었다

위 내용을 처음 봤을 때 와닿지 않았다

하지만 로직을 계속 생각해보니 맨 뒤로 빼는게 맞는것 같다

 


프로젝트의 부하 테스트를 위해 nGrinder를 사용해보려고 한다

jmeter라는 부하테스트를 위해 쓰는 것도 있지만 nGrinder를 쓸 예정이다

jmeter보다는 웹에서 돌아가는 nGrinder를 쓰는 것이 히스토리도 남기 때문이다

 

https://opentutorials.org/module/351/3334

 

소개 - nGrinder

소개 2012-12-29 11:44:10

opentutorials.org

 

[nGrinder]

- 부하테스트/스트레스 테스트(Load Test)를 하기 위한 소프트웨어

- 일부러 시스템에 부하를 발생시키는 것

- 얼마만큼의 부하를 버틸 수 있는 지 평가한다

- NHN에서 제작한 오픈소스

- 오픈소스 부하 테스트 도구인 grinder를 기반으로 작성되었다

- 웹 기반으로 테스트할 수 있는 장점

- 복수의 장비를 이용해서 대상 시스템에 큰 부하를 발생할 수 있다

- 스크립트를 작성해서 테스트 시나리오를 만들 수 있다

 

대상 시스템의 부하를 발생시키는 것 뿐만 아니라 실제로 사용자가 그 시스템을 사용하는 유사한 환경을 만들어 부하를 테스트할 수 있다


[엔그라인더를 구성하는 컴포넌트]

controller

- 웹 기반의 GUI 시스템

  - 웹을 통해 부하테스트를 진행할 수 있다

  [컨트롤러가 제공하는 주요 기능]

  - 유저 관리:

    여러 사람들이 엔그라인더 시스템을 접속해

    테스트 시나리오에 따라서 테스트를 진행할 수 있는 멀티 유저 기반 시스템을 제공

  - 에이전트 관리

  - 부하테스트 실시 & 모니터링

  - 부하 시나리오 작성 테스트 내역을 저장하고 재활용 가능

 

agent

- 부하를 발생시키는 대상/주체

- 컨트롤러의 지휘를 받음

   에이전트가 실제로 대상 시스템의 부하를 발생시키는 것은 에이전트에 의해 부하가 발생

   에이전트가 동작하도록 시작 명령을 내려줌

- 복수의 머신에 설치해서 controller의 신호에 따라 일시에 부하를 발생시킨다

   컨트롤러는 하나, 여러 개의 컴퓨터에 에이전트를 설치해두고 컨트롤러에 등록해두면

   컨트롤러에서 각각의 에이전트에 명령을 내려 에이전트틀이 테스트하려고 하는 시스템에

   일시적인 트래픽/부하를 발생시킨다

 

[필요사항]

- java 기반으로 제작된 시스템이기 때문에 크로스 플랫폼이라는 장점을 가지고 있다

- java JDK 1.6 이상(JRE 아님)

- tomcat 6.X 이상


https://opentutorials.org/module/351/3338

 

설치준비 - AWS EC2 - nGrinder

설치 전에  필자는 아마존 웹서비스에 ngrinder를 설치할 것이다. 대규모 트래픽을 발생시키기 위해서는 많은 장비가 필요하다. 많은 장비를 항상 유지하는 것은 어려운 일이기 때문에 필요한 만

opentutorials.org

 

[AWS를 사용하는 이유]

부하시스템은 항상 필요한 시스템이 아니다

많은 부하를 발생시키기 위해서는 고용량/고사양의 시스템이 필요하고 여러 개의 컴퓨터가 필요할 수도 있다

일상적으로 항상 운영하는 것은 어렵거나 불가능하기 때문에 클라우드 시스템을 이용한다면 부하 테스트를 하는 데 큰 도움이 될 수 있다

 

[필요할 때마다 로드 테스트 소프트웨어를 꺼내 쓴다 라는 개념으로 사용 가능]

- 시스템 세팅 -> 이미지 생성 -> 필요할 때 이미지 기반으로 인스턴스 생성 후 부하테스트 ->

  부하테스트가 끝나면 인스턴스를 죽여 다음에 이미지 가지고 다시 사용

- 필요할 때마다 로드 테스트 소프트웨어를 꺼내 쓴다 라는 개념으로 사용 가능

  높은 사양의 컴퓨터를 적은 비용으로 사용할 수 있다

 

 

 

Ubuntu Server 16.04 LTS 64비트 선택

인스턴스 이름은 ngrinder

 

 

t2.small 선택

- t2.micro 선택 시 메모리가 부족해서 통보 없이 시스템이 종료됨

- small 이상을 권장

- 많은 부하를 테스트하고 싶다면 small보다 큰거 선택

- 커질수록 비싸짐

선택 후 '검토 및 시작' 누름

 

 

'보안그룹 편집' 선택

 

 

아래와 같이 설정 후 '검토 및 시작'

 

 

키 페어 없는 사람은 만들고, 있는 사람은 기존꺼 써도 됨

선택 후 '인스턴스 시작'

 

 

git bash 열어서

ssh -i 키페어저장한경로 ubuntu@퍼블릭IPV4주소
//ex:
//ssh -i /e/keypair.pem ubuntu@15.165.203.62

Are you sure you want to continue connecting (yes/no/[fingerprint])? -> yes 직접 입력

 

AWS에 접속 성공..!