-
Notifications
You must be signed in to change notification settings - Fork 2
Trouble Shooting
update 함수가 작동하지 않는 문제
해당 update문을 지날 때, response값으로는 변경된 값이 적용되나 실제도 DB상에 적용되지 않는 상황. member값이 순간동안 바뀌나 변경감지가 제대로 작동하지 않았음을 확인.
- @Dynamic을 추가하여 변경된 컬럼만 업데이트 쿼리를 날린다. update함수가 실행하지 못하는 이유가 쿼리의 문제라고 여겨 추가하였으나 변함없음 → @DYNAMIC은 단점이 많은 어노테이션으로 되도록 쓰지 않아야 한다.
- @Transactional을 추가하여 영속성을 유지. 다만 그럼에도 update쿼리 작동 안함
- memberRepository.save(member) 을 통해 저장 ⇒ update가 작동하지 않는 근본적인 원인을 해결하지 못함
JPARepository를 상속받은 Repository에서 정보를 불러와야 JPA의 함수가 작동할 수 있다. 즉, request의 헤더에서 파싱한 member정보는 JPA 자체가 시동되지 못한다. 즉, JPA의 변경감지가 작동하지 않아 update함수로 인하여 member값은 일시적으로 변하고 ResponseDto에 적용이 되지만 실질적으로 DB에 적용되지 않는다. 그렇기에 JPA의 변경감지를 상속받은 리포지토리에서 불러온 member값은 update로 변환된 값이 변경감지로 인식되어 DB값도 변경되게 된다.
multipartFile이 아닌 Base64파일을 S3에 업로드하게 된 상황
- 촬영한 사진은 이미지 파일이 아닌 Base64파일로 서버로 넘어오게 된다.
- @RequestParam으로 가져오려 했으나 Base64길이가 너무 길어 에러가 남
- @RequestBody로 변경하여서 받았지만 저장되지 않음. (MultipartFile로 변환이 필요함)
- Base64 를 File로 변환하고 File을 MultipartFile로 변환하여 시도 → contentType이 nullException이 뜨는 현상 발생
시도한 코드
public class Base64Util {
public File getImageFromBase64(String base64String, String fileName) {
String[] strings = base64String.split(",");
String extension;
switch (strings[0]) { // check image's extension
case "data:image/jpeg;base64":
extension = "jpeg";
break;
case "data:image/png;base64":
extension = "png";
break;
default:
extension = "jpg";
break;
}
byte[] data = DatatypeConverter.parseBase64Binary(strings[1]);
File file = new File(fileName + extension);
try (OutputStream outputStream = new BufferedOutputStream(new FileOutputStream(file))) {
outputStream.write(data);
} catch (IOException e) {
e.printStackTrace();
}
return file;
}
public MultipartFile convertFileToMultipartFile(File file) throws IOException {
FileItem fileItem = new DiskFileItem("file"
, Files.probeContentType(file.toPath())
, false, file.getName()
, (int) file.length(),
file.getParentFile());
try {
InputStream is = new FileInputStream(file);
OutputStream os = fileItem.getOutputStream();
IOUtils.copy(is, os);
} catch (IOException e) {
throw new IOException(e);
}
return new CommonsMultipartFile(fileItem);
}시도한 코드
switch (strings[0]) { // check image's extension
case "data:image/jpeg;base64":
extension = ".jpeg";
break;
case "data:image/png;base64":
extension = ".png";
break;
default: // should write cases for more images types
extension = ".jpg";
break;
}- 서버에 저장되는 사진을 삭제하는 로직을 추가→ 사진이 삭제 되지 않음
시도한 코드
private void removeNewFile(File targetFile) {
if(targetFile.delete()) {
log.info("파일이 삭제되었습니다.");
}else {
log.info("파일이 삭제되지 못했습니다.");
}
}- 서버에 사진을 저장하지 않기 위해 S3Uploader에서 바로 Base64파일을 업로드하는 로직 구현 → 버킷에 확장자 명이 안나타나는 문제 발생
시도한 코드
public String getImgUrlBase64(String base64){
String[] strings = base64.split(",");
String extension = switch (strings[0]) {
case "data:image/jpeg;base64" -> ".jpeg";
case "data:image/png;base64" -> ".png";
default -> // should write cases for more images types
".jpg";
};
byte[] data = DatatypeConverter.parseBase64Binary(strings[1]);
long now = new Date().getTime();
String fileName = UUID.randomUUID().toString() + now;
InputStream img = new ByteArrayInputStream(data);
ObjectMetadata metadata = new ObjectMetadata();
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, img, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}- fileName에 extention을 추가함 → 저장된 사진이 바로 다운로드가 되는 현상 발견 (버킷에 업로드 할 때 콘텐트 타입이 저장되지 않으면 형식을 알 수없어 바로 다운로드가 된다는 사실을 발견)
시도한 코드
byte[] data = DatatypeConverter.parseBase64Binary(strings[1]);
long now = new Date().getTime();
String fileName = UUID.randomUUID().toString() + now + extension;
metadata.setContentType("image/"+extension.substring(1));
metadata.setCacheControl("public, max-age=31536000");
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, img, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}시도한 코드
byte[] data = DatatypeConverter.parseBase64Binary(strings[1]);
long now = new Date().getTime();
String fileName = UUID.randomUUID().toString() + now + extension;
InputStream img = new ByteArrayInputStream(data);
ObjectMetadata metadata = new ObjectMetadata();
metadata.setContentLength(data.length);
metadata.setContentType("image/"+extension.substring(1));
metadata.setCacheControl("public, max-age=31536000");
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, img, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}- S3에 Base64를 바로 업로드 하여 서버에 파일 저장 방지
- S3 업로드 시 contentType을 반드시 설정하여 다운로드가 되지 않도록 수정
이미지 리사이징 로직이 작동하지 않음
-
콘솔로 찍어보니 imageIcon에서 getWidth, getHeight 값이 -1로 찍히는 것을 확인
-
길이와 넓이가 잘 나오지만 비율이 맞춰지지 않는 것을 확인
-
시도한 코드
publicImageSizeDtodefineSize(intheight, intwidth) {
if (height>width) {
double ratio = (double)width/ 320;
int resizedHeight = (int) (height/ ratio);
log.info(height+" "+width);
return (new ImageSizeDto(resizedHeight, 320));
}
int ratio =height/ 320;
int resizedWidth =width/ ratio;
log.info(height+" "+width);
return (new ImageSizeDto(320, resizedWidth));
}시도한 코드
Image resizedImage = image.getScaledInstance(sizeDto.getWidth(), sizeDto.getHeight(), Image.SCALE_SMOOTH);
ImageInputStream resizedImageInputStream = ImageIO.createImageInputStream(resizedImage);시도한 코드들
```java IOUtils.toByteArray(resizedImg).length resizedImg.readAllBytes().length ```- 리사이징 된 이미지 data 0 bite
시도한 코드
log.info("이미지 : "+ width + " " + height);
BufferedImage outputImage = new BufferedImage(sizeDto.getWidth(), sizeDto.getHeight(), inputImage.getType());
log.info("이미지 : " + sizeDto.getHeight() + " " + sizeDto.getWidth());
Graphics graphics2D = outputImage.getGraphics();
graphics2D.drawImage(resizedImage, 0, 0, null); // 그리기
graphics2D.dispose(); // 자원해제
ByteArrayOutputStream os = new ByteArrayOutputStream();
ImageIO.write(outputImage, "png", os);
log.info("이미지 DONE");
return new ByteArrayInputStream(os.toByteArray());해결한 코드
publicStringgetImgUrlBase64(Stringbase64) throwsIOException{
String[] strings =base64.split(",");
Stringextension = switch (strings[0]) {// check image's extension
case "data:image/jpeg;base64" -> ".jpeg";
case "data:image/png;base64" -> ".png";
case "data:image/webp;base64" -> ".webp";
default -> ".jpg";
};
byte[] data =DatatypeConverter.parseBase64Binary(strings[1]);
InputStream imgStream = new ByteArrayInputStream(data);
InputStream resizedImg = checkImage(imgStream);
ObjectMetadatametadata = new ObjectMetadata();
metadata.setContentLength(resizedImg.available());
metadata.setContentType("image/" + extension.substring(1));
metadata.setCacheControl("public, max-age=31536000");
long now = new Date().getTime();
StringfileName = now + "_" +UUID.randomUUID() + extension;
amazonS3Client.putObject(new PutObjectRequest(bucket, fileName, resizedImg, metadata)
.withCannedAcl(CannedAccessControlList.PublicRead));
return amazonS3Client.getUrl(bucket, fileName).toString();
}
public InputStream checkImage(InputStreamimgStream) throwsIOException{
BufferedImageinputImage =ImageIO.read(imgStream);
int imageHeight = inputImage.getHeight(null);
int imageWidth = inputImage.getWidth(null);
if (imageHeight > 320 && imageWidth > 320) return resize(inputImage, imageWidth, imageHeight);
else {
ByteArrayOutputStreamoutStream = new ByteArrayOutputStream();
ImageIO.write(inputImage,"png",outStream);
return new ByteArrayInputStream(outStream.toByteArray());
}
}
/*리사이즈 실행 메소드 */
public InputStream resize(BufferedImageinputImage, intwidth, intheight) throwsIOException{
ImageSizeDtosizeDto = defineSize(height,width);
Image resizedImage =inputImage.getScaledInstance(sizeDto.getWidth(),sizeDto.getHeight(),Image.SCALE_SMOOTH);
BufferedImageoutputImage = new BufferedImage(sizeDto.getWidth(), sizeDto.getHeight(),inputImage.getType());
Graphics graphics2D = outputImage.getGraphics();
graphics2D.drawImage(resizedImage, 0, 0, null);//그리기
graphics2D.dispose();//자원해제
ByteArrayOutputStreamos = new ByteArrayOutputStream();
ImageIO.write(outputImage, "png", os);
return new ByteArrayInputStream(os.toByteArray());
}
publicImageSizeDtodefineSize(intheight, intwidth) {
if (height>width) {
double ratio = (double)width/ 320;
int resizedHeight = (int) (height/ ratio);
return (new ImageSizeDto(resizedHeight, 320));
}
int ratio =height/ 320;
int resizedWidth =width/ ratio;
return (new ImageSizeDto(320, resizedWidth));
}서버 자동 배포 과정 중 깃 액션을 이용한 main 브랜치 레포지토리 빌드 과정이 실패함
- 원인을 확인해보니 properties 파일이 ignore 처리 되어 있어서 깃 허브 레포지토리에 존재하지 않는 것을 발견
- 기존 deploy.yml 파일에 존재하는 깃 액션 과정에 properties 파일을 생성하는 과정을 추가하기로 함
해결한 코드
- name: make application.properties
run:
touch ./src/main/resources/application.properties
shell: bash
- name: deliver application.properties
run:
echo "${{ secrets.PROPERTIES }}" > ./src/main/resources/application.properties
shell: bash- Settings>Secret>Actions 이동 후 NEW REPOSITORY CREATE 를 클릭하여 PROPERTIES 파일을 생성.
- 파일안에 값은 이그노어 처리했던 application.properties 파일안의 값을 그대로 넣어줌.
Https 배포를 위한 과정 중 하나인 ACM 인증서 발급이 계속해서 지연됨
- 기존에 시도한 CNAME 레코드를 이용한 ACM 발급 방법 대신 이메일 인증을 통한 ACM 발급을 시도함 -> 발급되지 않음
- CNAME 레코드를 통해 연결된 호스팅 영역의 NS 레코드 값과 구매한 도메인의 NS 레코드 값이 일치 하지 않는 것을 발견
- 호스팅 영역의 NS 레코드 값으로 구매한 도메인의 NS 레코드 값을 변경해줌
- 다시 ACM 인증서 발급 재시도
- 1분만에 ACM 인증서가 재발급 된 것을 확인함
Code Deploy를 이용한 Https 서버 배포 시도 중 AllowTraffic 단계에서 실패 발생
- 찾아보니 health checker 에러일 확률이 높다고 함
- 로드 밸런서를 적용하던 과정 중 생성하였던 태그 그룹을 확인
- health checker(상태 검사) 부분에서 계속 실패하는 것을 확인
- 상태검사 포트를 HTTP 에서 HTTPS 로 변경 하고 재시도 -> 여전히 해결되지 않음
- 서버의 경우 클라이언트와 달리 정상 배포 시 성공 코드는 200이 아닌 404라고 함
- health checker success code의 설정 값을 200에서 404로 변경해 줌
- health checker 가 정상으로 변경된 것을 확인
- Code Deploy를 이용한 서버 배포 과정 모두 성공
Code Deploy를 이용한 Https 서버 배포 시 최소 6분이상 시간이 소모되는 문제가 발생
- Code Deploy의 view 이벤트를 확인해 보니 AllowTraffic 단계에서 5분 이상의 시간이 지체되는 것을 확인
- health checker 과정을 수행하는데 많은 시간이 소요되는 것으로 보임
- health checker 설정을 변경해 보기로 함
- 타겟 그룹 attribute 시간 설정을 default 값인 300초에서 30초로 변경해 줌
- AllowTraffic 단계에서 소요되는 시간이 30초대로 단축
- 서버 배포 속도가 2분 내외로 단축되는 것을 확인
연관관계 설정된 테이블 조회 시 다수의 쿼리가 반복해서 도는 N+1 문제가 발생
- submitmission repository를 불러올 시 연관관계 설정된 member 클래스의 칼럼을 불러오지 않는 한 submitmission 클래스 안 member 칼럼의 FetchType을 Lazy 로 설정 시 eager 보다 N+1 문제로 인해 도는 쿼리를 충분히 줄일 수 있음
- 문제는 구현한 서비스 로직 중에는 submitmission repository의 member 클래스의 칼럼 역시 같이 불러오는 로직이 존재함
- n+1 문제를 완전히는 아니어도 억제하기 위해 Batch Size 방식과 Fetch Join 방식이 존재한다고 함
- 현재 발생하는 repository 조회 문제는 1:n이 아닌 N:1 관계이기에 Fetch Join 방식이 적합하다 생각함
-
Fetch Join 방식을 이용해 N+1 문제로 발생하는 쿼리를 억제 하기로 함
-
Fetch Join 을 사용하기 위해 SubmitMission Repository 파일의 jpa로 구현한 코드를 jpql 형식으로 변경
-
해결한 코드
@Query("select s from SubmitMission s join fetch s.member order by s.createdAt asc")
List<SubmitMission> findAllByOrderByCreatedAtAscFetchJoin();