티스토리 뷰

이번 글에서는 현재 진행중인 애완동물 분양 플랫폼 프로젝트에서 이미지가 포함된 게시글을 등록시 발생하는 유저 체감 지연시간을 최소화하자는 목표로 서비스 로직을 설계한 과정과 결과에 대해 공유드리겠습니다.

 

이미지 업로드 및 삭제 API 구현을 위한 백엔드 인프라 환경 및 사용기술은 아래와 같습니다.

Sever Infra : AWS EC2/Linux, AWS S3 
Proxy Server
 : Nginx 1.22.

WAS : tomcat (scale out - blue/green)
Language : Java 11
Framwork : Spring Boot 2.7.11
ORM : Spring Data JPA
DB : MySQL(Master/Slave)
CI/CD : Github Actions, AWS S3&CodeDeploy

 

문제 상황

이번 프로젝트에서는 서버를 통해 AWS S3에 이미지를 저장하는 것으로 인프라 환경을 구축했습니다. 해당 사항을 고려하여 게시글의 정보와 게시글에 대한 이미지를 동일한 요청으로 묶어 처리할 경우 S3로 이미지를 업로드하는 과정으로 인해 게시글 등록의 지연시간이 매우 오래 걸릴 것 같다는 판단이 들었습니다. 

 

하여 '이미지가 첨부된 게시글 등록시 유저의 체감 지연시간을 어떻게 최소화 할 것인가?'를 주요 아젠다로 잡고 문제 해결 방안을 고심했습니다.

 

 

해결 과정 및 결과

 

문제 해결 방안을 고심한 결과 위 절차와 같이 이미지를 별도로 비동기 업로드 처리 후 AWS S3에 업로드된 이미지의 URL을 응답 받아 DB에 저장하고, 저장한 이미지 URL에 해당하는 이미지 PK와 URL을 client에 응답하는 절차로 로직을 구상하게 되었습니다.

 

이후 유저가 게시글 최종 등록시 클라이언트가 비동기 처리로 응답 받은 이미지의 정보와 함께 게시글 등록을 요청한다면 사전에 정의된 아젠다를 해결할 수 있다고 판단했습니다.

 

 

하지만 게시글 작성 과정에서 이미지 삭제 또한 비동기 처리로 이루어지기 때문에 게시글이 최종적으로 등록된 이후 부모 게시글이 없는, 정확히는 부모게시글의 PK값을 가지지 못하는 이미지 row가 DB에 그대로 남게되는 결과가 발생합니다.

 

최대한 비용적인 부분에서 최소화하여 위와 같은 문제를 어떻게 해결할 것인지 고민해본 결과, Spring scheduler를 사용하여 매일 자정 부모 게시글 PK가 없는 이미지 데이터와 데이터에 해당하는 S3의 이미지 파일을 삭제하는 것으로 결론을 내렸습니다.

 

 

AWS S3에 업로드된 파일을 단일 요청으로 여러 개를 삭제한다고 하여 금전적 비용이 추가적으로 발생하지 않다는 사실을 확인했으나 서버 운영측면에서 좋지 못하다고 판단이 들었습니다.

 

 

추가 리서치 결과 AWS SDK 중에서 다중 삭제 API가 존재한다는 것을 확인 후 아래와 같은 다중 삭제 로직을 구현할 수 있었습니다.

 

/**
 * S3 파일 다중 삭제
**/
public String deleteFiles(List<String> keyNames){

    // AmazonS3Client 인스턴스를 생성하여 S3 클라이언트에 연결한다.
    final AmazonS3Client aswS3Client = awsS3config.amazonS3Client();

    // 사용할 S3 버킷 이름을 가져온다.
    final String bucket = awsS3Properties.getBucket();

    // 삭제할 객체 키(Key) 및 버전(KeyVersion)을 저장할 리스트를 생성한다.
    List<KeyVersion> keys = new ArrayList<>();

    // 전달받은 keyNames 리스트의 각 요소를 KeyVersion 객체로 생성하여 keys 리스트에 추가한다.
    for(String keyName : keyNames){
        keys.add(new KeyVersion(keyName));
    }

    // 삭제 요청 객체(DeleteObjectsRequest)를 생성한다.
    // 해당 버킷(bucket)에서 keys 리스트에 있는 객체들을 삭제하도록 설정하고, quiet 모드를 비활성화(false)한다.
    DeleteObjectsRequest deleteRequest = new DeleteObjectsRequest(bucket)
            .withKeys(keys)
            .withQuiet(false);

    // deleteObjects 메서드를 호출하여 객체를 삭제하고, 삭제 결과(DeleteObjectsResult)를 받는다.
    DeleteObjectsResult deleteResult = aswS3Client.deleteObjects(deleteRequest);

    // 요청으로 전달된 키의 개수(requestKeys), 성공적으로 삭제된 객체 수(successfulDeletes), 남은 키 수(remainingKey)를 계산한다.
    final int requestKeys = keyNames.size();
    final int successfulDeletes = deleteResult.getDeletedObjects().size();
    final int remainingKey = requestKeys - successfulDeletes;

    String result = "";

    // 남은 키가 0이 아닌 경우, 삭제되지 않은 이미지 파일 개수를 메시지에 포함하여 결과를 설정한다.
    if(remainingKey != 0 ){
        result = String.format("AWS S3 - [%d]개의 이미지가 삭제되지 않았습니다.", remainingKey);
    }

    // 그렇지 않은 경우, 모든 이미지 파일이 성공적으로 삭제되었음을 나타내는 결과를 설정한다.
    result = "AWS S3 - Success delete all image file";

    // 최종 결과를 반환한다.
    return result;
 }

 

위와 같은 설계를 통해 최종적으로 서비스내의 모든 이미지를 하나의 모듈로 처리할 수 있었으며 관계를 가지지 못하는 이미지 데이터 또한 깔끔하게 정리할 수 있게 되었습니다.

 

 

마무리

위 절차로 진행할 경우 여전히 파일 업로드 과정에서 클라이언트와 AWS S3 사이에 서버가 있어 발생하지 않아도될 지연시간이 발생한다는 생각이 듭니다. 최적의 조건으로는 클라이언트에서 바로 AWS S3에 업로드 후 관련 정보만 서버에 저장하는 것이 효율적이다고 판단을 했지만 한달이라는 짧은 프로젝트 기간동안 주요 기능 구현으로 인해 불가피하게 서버에서 처리하게 되었습니다.

 

 

참고 자료

AWS S3 요금 확인 - https://aws.amazon.com/ko/s3/pricing/
AWS S3 다중 삭제 API docs - https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/delete-multiple-objects.html

댓글
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday