Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: 사용하지 않는 이미지 제거 기능 #193

Open
wants to merge 17 commits into
base: main
Choose a base branch
from
Open

Conversation

kevstevie
Copy link
Collaborator

issue: #192

as-is

  • 사용되지 않는 이미지가 s3에서 삭제되지 않음

to-be

  • 이미지를 업로드했을 때 UnusedImage 테이블에 저장
  • 리뷰가 업로드 됐을 때 UnusedImage 테이블에서 제거
  • 리뷰가 제거됐을 때 s3에서 이미지 제거
  • 스케쥴링을 통해 오전 4시마다 24시간이 지난 이미지 제거

Copy link
Member

@jinyoungchoi95 jinyoungchoi95 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

수고하셨습니다 :)

코맨트 확인부탁드리고, 기능들 테스트도 함께 추가되면 좋을 것 같네요 👍

import org.springframework.transaction.event.TransactionalEventListener;

@Component
@Async(value = "asyncTaskExecutor")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

생각해볼만한점이 하나 있을 것 같은데요. (이전에 저희가 만들때는 고려하지 않았던 내용들도 있지만 고민해보면 좋을 것 같아서 코맨트 남겨두어요.)

  • asyncTaskExecutor를 사용하면 다른 컴포넌트랑 동일한 스레드를 사용하게 될 텐데 괜찮을까요? 분리하지 않고 사용하는 이유가 있을까요?
  • @Transactional을 호출하고 있는데, async하게 돌아가는 다른 스레드들이 메인 스레드의 connection을 가져가서 사용해도 괜찮은가요?

}

@Transactional
public ImageUploadResponse uploadImage(final MultipartFile file) {
String extension = validateExtension(file);
String key = createKey(extension);
PutObjectRequest request = createRequest(file, key);
PutObjectRequest request = createPutRequest(file, key);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

Comment on lines 53 to 57
unusedImageRepository.save(
UnusedImage.builder()
.key(key)
.build()
);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s3Client.putObject를 넘어서 save 동작 시 문제가 발생한다면, s3에 저장된 object는 어떻게 되나요?


@Transactional
public void deleteUsingImage(final String imageUrl) {
if (imageUrl != null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

null 체크를 하시는 이유가 있을까요?


@Scheduled(cron = "0 0 4 * * *")
@Transactional
public void deleteUnusedImages() {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요정도 사이즈가 되니 ImageService가 담기에는 책임이 너무 많아보이기는 하네요.

public void deleteUnusedImages() {
LocalDateTime deleteBoundary = LocalDateTime.now().minusDays(1L);
List<UnusedImage> unusedImages = unusedImageRepository.findAllByCreatedAtBefore(deleteBoundary);
unusedImageRepository.deleteAllByCreatedAtBefore(deleteBoundary);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. deleteAll은 쿼리가 어떻게 날아가나요?
  2. 개인적으로는 deleteBoundary를 LocalDateTime을 받는 것보단 LocalDate만 받는건 어떨까싶긴하네요. 스케쥴 돌아가는 시점에 따라서 시분초까지 계산하기에는 누락건이 발생할 수 있을 것 같아서요.

Copy link

Quality Gate Passed Quality Gate passed

The SonarCloud Quality Gate passed, but some issues were introduced.

1 New issue
0 Security Hotspots
30.6% Coverage on New Code
0.0% Duplication on New Code

See analysis details on SonarCloud

Copy link
Collaborator

@Cyma-s Cyma-s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

안녕하세요 주드
잊어버리신 줄 알았는데 해오셨네요 감사합니다
개인적으로 궁금한 부분 질문 남겼는데 확인해주세요 😄


public ImageService(final S3Client s3Client, @Value("${cloud.aws.s3.bucket}") final String bucketName) {
public ImageService(final S3Client s3Client, @Value("${cloud.aws.s3.bucket}") final String bucketName, unusedImageRepository unusedImageRepository) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final 이 빠진 것 같아여 👍

}

@Transactional
public void deleteImageWhenReviewDeleted(final String imageUrl) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

메서드 이름 굿 👍

.build();
}

@Scheduled(cron = "0 0 4 * * *")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

신기하네요 이거


import java.util.List;

public interface ImageUploader {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의존성 분리 굿 👍🏻

import static com.woowacourse.matzip.environment.ProfileUtil.PROD;

@Profile({LOCAL, PROD})
@Component
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Primary 같이 빈의 우선순위를 표현해줄 필요는 없나요? 아직은 없지만 여러 ImageUploader 가 생긴다면 빈 우선순위가 필요할 것 같아서요

);
private static final String DELIMITER = ".";

public static void validateExtension(final String fileName) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extension 이 아닌 fileName 을 매개변수로 받고, 전체적인 fileName 에 대한 검증을 수행하고 있으니 validateFileName 이 더 괜찮은 네이밍 같다는 생각이 듭니당 어떻게 생각하세요?

import static com.woowacourse.matzip.environment.ProfileUtil.LOCAL;
import static com.woowacourse.matzip.environment.ProfileUtil.PROD;

@Profile({LOCAL, PROD})
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LOCAL 도 포함하신 이유가 있나요?


void deleteImage(final String imageUrl);

void deleteImages(final List<UnusedImage> unusedImages);
Copy link
Collaborator

@Cyma-s Cyma-s Dec 14, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

매개변수의 타입 자체가 UnusedImage 이기 때문에 메서드 이름도 그와 동일하게 deleteUnusedImages 로 하는 게 더 좋을 것 같아요. 매개변수 타입이 추상화된 클래스였다면 deleteImages 로 추상화해서 표현해도 될 것 같은데, 현재는 구체 클래스이다 보니 명확하게 명시해주는 건 어떨까요?


String uploadImage(final MultipartFile file);

void deleteImage(final String imageUrl);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

deleteByImageUrl 이라는 네이밍은 어떠신가요 매개변수가 Image 나 파일이 아니라서 네이밍이 조금 어색할 수도 있을 것 같아요

Comment on lines 20 to 21
private final ImageUploader imageUploader;
private final UnusedImageRepository unusedImageRepository;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

의존성이 훨씬 깔끔하네요

Copy link

@kevstevie
Copy link
Collaborator Author

kevstevie commented Jul 20, 2024

변경사항

  • 스케쥴링에 따른 삭제 역할을 다른 클래스로 분리
  • async로 작동하기 때문에 transaction이 필요없다고 판단하여 eventlistner로 전환
  • s3 api 사용 시간동안 db 커넥션을 점유하지 않도록 @Transactional 제거
  • 실패 가능성이 높은 s3 api 사용을 db로직보다 위에 둠으로써 파일처리와 db작업이 성공/실패를 동시에 보장할 수 있도록 순서 수정

todo
테스트..

Copy link
Collaborator

@Cyma-s Cyma-s left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

바쁜 일정 중에 기능 구현을 또 해주셨네요 👍 트랜잭션 관련해서 궁금한 게 있어 리뷰 남겼습니다. 시간 될 때 확인해주세요 감사합니다


@Profile(TEST)
@Component
public class TestImageManager implements ImageManager {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

👍

}

@Override
public String uploadImage(final MultipartFile file) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

만약 이미지 업로드 실패하면 어떻게 되나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

실패하는 경우 s3에서 어떤 응답을 주는지 못찾아서 그 로직이 없는데 추가하겠습니다 감사합니다


@Override
public void deleteImages(final List<UnusedImage> unusedImages) {

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

불필요한 공백 🫡

}

@Override
public void deleteImages(final List<UnusedImage> unusedImages) {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이 메서드도 실패하는 경우에는 어떻게 처리되나요?

this.unusedImageRepository = unusedImageRepository;
}

@Scheduled(cron = "0 0 4 * * *")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Transactional 은 없어도 되나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

넵 select와 delete가 한 트랜잭션으로 묶일 필요가 없다고 생각했고 시간이 오래걸리는 s3 api 사용 시간동안 db 커넥션을 물고 있지 않는게 나아서 제거 했습니다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants