RDS S3

1.RDS

1)Data 저장소 종류

  • RDBMS

    • 관계형 데이터베이스
    • 전통적으로 많이 사용하는 테이블 기반의 데이터베이스
    • 오버헤드가 큼(실제 데이터 저장이외의 다른 메타 데이터가 많이 필요)
    • 강한 트랜잭션을 사용하기 때문에 CUD 작업이나 오랜 시간 동안 안정적으로 저장하는 애플리케이션에 많이 이용
    • Amazon Aurora, RDS(Oracle, MySQL, Maria DB, Postgre SQL), Redshift 등을 지원
  • Key-Value Database

    • Key를 이용해서 데이터를 구분하는 형태로 저장
    • 구조가 만들어진 테이블 대신에 구조가 없는 문서 구조의 컬렉션을 이용
    • 컬렉션 내부의 데이터로 자유로운 형태가 가능하지만 어느 정도는 정형화 됨
    • 조인의 개념이 존재하지 않고 링크 나 포함의 개념으로 표현
      • 링킹: 포인터를 기억
      • 임베딩: 객체 안에 저장
    • 유효성 검사를 하지 않기 때문에 읽기-쓰기 속도가 빠름
    • Amazon에서는 Dynamo DB
  • In-Memory DB

    • 데이터를 메모리에 저장해서 사용하는 데이터베이스
    • 속도가 중요한 애플리케이션에 이용
    • Jenkins 나 Argo CD 등이 내부적으로 사용
    • Amazon에서는 Elastic Cache 그리고 Memory DB for Redis를 제공
  • Document DB

    • 데이터를 하나의 문서로 취급하는 데이터베이스
    • Amazon의 Document DB(Mongo DB 호환)
  • Wide Column

    • 관계형 데이터베이스 처럼 테이블, 컬럼, 로우 의 개념을 갖지만 컬럼 과 로우의 형태를 정해지지 않은 구조
    • Amazon에서는 Keyspace를 제공
  • Graph

    • 데이터를 그래프 형태로 제공
    • 소셜 네트워킹, 추천 엔진, 부정 탐지 등에 활용
    • Amazon 에서는 Neptune을 제공
  • TimeSeries

    • 시간(순서)이 중요한 데이터를 저장
    • 사물 인터넷, 산업용 텔레메트리, DevOps 등 에서 많이 활용
    • Amazon에서는 Timestream이라는 데이터베이스로 지원
  • Ledger

    • 블럭 체인에서 사용하는 원장 저장
    • Amazon에서는 Ledger Database Service로 지원

2)Amazon의 RDS

  • 개요
    • 관계형 데이터베이스 6종류를 클라우드에 최적화된 상태로 제공하는 서비스
    • Amazon Aurora, MySQL, Maria DB, Oracle, MS SQL Server, IBM DB3 를 지원
    • VPC 안에 인스턴스 형태로 구축(VPC안에서는 무료)
    • Managed Service이므로 업데이트 및 관리는 AWS에서 수행
    • AWS Database Migration Server를 사용하면 기존 데이터베이스를 이전하거나 복제하는 것도 가능
    • 사용에 따른 요금만 부과하는 경우도 있지만 Oracle 이나 MS SQL Server를 사용하는 경우에는 라이센스 비용도 지불해야 합니다.
  • 장점
    • Managed Service
    • EC2와 연동이 쉽고 같은 네트워크에 구성하면 통신 비용이 무료
  • 단점
    • 사용자가 자유롭게 사용할 수 없다는 단점

AZ vs Cluster

  • 클러스터: 논리적으로 구분
  • AZ: 물리적으로 구분, 재해 시 지장받지 않음
  • 다중AZ DB 인스턴스 : 인스턴스 두 개 생성 후 동기화
    • 로드밸런서를 달아 읽기를 두 곳중 하나에서 실행
    • 쓰기는 한곳에서만 가능
    • 비용이 더 비쌈

S3 자격 증명 관리

  • 자체 관리 선택 후 암호 입력

S3 VPC

  • VPC는 가용영역에 생성하기 때문에 수정이 불가

spring 연습할 것

  • transaction
  • reactive
  • batch
  • JPA는 인터페이스, hibernate가 실제 구현체
  • AOP
  • Interception

2.S3

1)AWS를 이용한 백업

  • 백업의 형태
    • 온프레미스 환경의 데이터를 AWS로 백업
    • AWS에 구축한 시스템을 백업
  • 백업을 위한 인프라 설계 사항
    • 스토리지 게이트웨이를 이용한 자동 백업: 온프레미스 환경에 스토리지 게이트웨이를 만들어서 백업용 스토리지를 만들고 자동 백업
    • S3와 글레이셔로 수명주기를 관리: 로그 파일을 S3에 백업을 하고 온라인 보관 기간을 넘은 파일을 글레이셔에 아카이브
    • 용량의 대부분을 차지하는 이미지 파일이나 데이터베이스는 스토리지 게이트웨이보다는 S3를 이용해서 백업
  • AWS의 백업 방법
    • 스토리지 게이트웨이
      적은 노력으로 자동 백업 환경을 구축할 수 있음
      비용이 다른 방식보다 비쌈
    • S3
      백업 대상이 파일인 경우 간편
      범용적인 파일 저장 서비스이고 온라인 파일 서버처럼 사용 가능
      서로 다른 가용 영역에 3중화 되어 있으므로 99.99999999% 정도의 가용성
    • Glacier
      파일을 압축해서 저장
      가장 저렴
      읽는 속도가 느림

2)S3 개요

  • S3(Simple Storage Service)는 인터넷 스토리지 서비스
  • 용량에 관계없이 파일을 저장할 수 있고 웹에서 파일에 접근할 수 있으면 안정성이 뛰어나고 가용성이 높으며 무제한 확장이 가능
  • 대용량 백업을 EC2(인스턴스) 와 EBS(저장 장치)를 통해 구현한다면 많은 비용이 들고 노력이 요구되지만 S3를 이용하면 쉽게 구축이 가능
  • 정적 웹 사이트를 배포하고자 하는 경우 S3에서는 별다른 설치없이 바로 배포가 가능
  • S3 자체가 수천 대 이상의 매우 성능이 좋은 웹 서버로 구성이 되어 있어서 Auto Scaling 이나 Load Balancing 을 신경쓰지 않아도 됨
  • 파일 업로드 와 다운로드를 HTTP 프로토콜로 처리하기 때문에 별도의 클라이언트 나 Active X 없이 사용 가능
  • 넷플릭스의 콘텐츠 그리고 Airbnb의 사용자 사진이나 백업 데이터 그리고 정적 파일을 Amazon S3에 저장해서 사용하고 있음
  • S3를 사용하지 않으면 로드밸런서와 파일 서버를 직접 만들어야 함

3)기본 개념

  • 객체
    • 파일과 메타 데이터로 이루어진 데이터 저장의 기본 단위
    • 키가 객체의 식별자가 되고 값이 객체의 데이터
    • 객체의 크기는 1KB 부터 5TB 까지
    • 메타 데이터는 MIME 형식으로 확장자를 통해서 자동 설정되고 사용자가 임의 지정도 가능
    • 근본적으로 파일이름은 확장자도 포함 -> 실행하기 위해 사용자가 파일 종류 판단해야 함
      • MIME 타입: email에서 마지막 . 뒤에 이름을 추가 생성해서 어떻게 실행될지 결정
  • Bucket
    • S3에서 생성하는 최상위 디렉터리
    • 리전 별로 생성되고 계정 별로 100개 까지 생성 가능
    • 객체를 바로 저장할 수 있고 디렉터리를 만들어서 저장하는 것도 가능
    • 접속 제어 및 권한 관리가 가능: 읽기만 가능이나 쓰기 가능 설정 가능
    • URL로 접근 가능
  • 요금은 저장 용량과 데이터 전송량 그리고 HTTP Request 개수로 책정

4)외부에서 사용(Application)하고자 하는 경우

  • IAM에서 S3 Full Access 권한을 가진 사용자를 생성하고 그 사용자에 대한 Access Key와 Secret Access Key를 발급 받아서 사용
  • 키 발급
    • IAM 서비스에 접속
    • 새 사용자 생성을 눌러서 사용자를 생성하는데 권한은 S3 Full Access 와 Cloud Front Action
      기존 사용자가 있는 경우 생성하지 않아도 됨
    • 사용자를 선택하고 [보안 자격 증명] 탭에서 [액세스 키 생성]을 선택해서 키를 발급받고 csv 파일을 다운로드

5)버킷 생성

  • S3 서비스에서 시작
  • 버킷 생성
    • 버킷만들기 -> 외부에서 접근하려면 [ACL 활성화됨] 클릭 -> [모든 퍼블릭 액세스 차단 해제]
    • 버킷을 생성하면 업로드는 할 수 있지만 외부에서 다운로드는 할 수 없도록 생성됨
    • 다운로드가 가능하도록 하려면 버킷의 정책을 수정해야 함
  • 버킷의 정책 수정: 빨간색 부분은 실제 버킷으로 수정 - 버킷에 저장된 객체의 목록 가져오기 와 목록에 삽입, 삭제, 다운로드 권한입니다.
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Sid": "PublicListGet",
            "Effect": "Allow",
            "Principal": "*",
            "Action": [
                "s3:List*",
                "s3:Get*",
                "s3:Put*",
                "s3:Delete*"
            ],

            "Resource": [
                "arn:aws:s3:::itstudybucket",
                "arn:aws:s3:::itstudybucket/*"
            ]
        }
    ]
}
  • 버킷의 권한 중에서 CORS 정책 추가
[
	{
    	"AllowedHeaders": [
        	"*"
    	],
    	"AllowedMethods": [
        	"GET",
        	"PUT",
        	"POST",
        	"DELETE"
    	],
    	"AllowedOrigins": [
        	"*"
    	],
    	"ExposeHeaders": ["Access-Control-Allow-Origin"]
	}
]

6)Spring Boot Application에서 S3 사용

  • Spring Web, Lombok, Spring Dev Tools 의 의존성을 가진 프로젝트 생성
  • Spring Boot Application에서 AWS를 사용하기 위한 의존성을 build.gradle의 dependencies에 추가
implementation 'io.awspring.cloud:spring-cloud-starter-aws:2.3.1'
  • application.properties 파일에 S3 사용을 위한 속성을 설정
    • 이 부분을 외부에 노출시키게 되면 AWS 계정이 중지가 될 수 있으므로 소스 코드에 추가하는 것은 조심해야 합니다.
application.properties
spring.application.name=FileUpload
cloud.aws.credentials.access-key=
cloud.aws.credentials.secret-key=

cloud.aws.s3.bucket=dragonhailstone
cloud.aws.region.static=ap-northeast-2
cloud.aws.stack.auto=false

# 스프링에서 파일 업로드 할 때 제약 사항
# 파일한개 크기 20MB까지
spring.servlet.multipart.max-file-size=20MB
# 파일 전체 크기 20MB까지
spring.servlet.multipart.max-request-size=20MB
  • 파일 업로드 처리를 할 때 발생할 예외 처리할 클래스를 생성: FileUploadFailedException
FileUploadFailedException.java
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Component;
import org.springframework.web.ErrorResponse;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.multipart.MaxUploadSizeExceededException;

@Component
@RestControllerAdvice
public class FileUploadFailedException {
   @ExceptionHandler(MaxUploadSizeExceededException.class)
   protected ResponseEntity<ErrorResponse> handleMaxUploadSizeExceededException(MaxUploadSizeExceededException ex) {
       ErrorResponse response = ErrorResponse.builder(ex, HttpStatus.BAD_REQUEST, "용량 초과").build();
       return new ResponseEntity<>(response, HttpStatus.BAD_REQUEST);
   }
}
  • 업로드할 파일 경로를 만드는 메서드를 소유한 클래스를 생성 - CommonUtils
    • 이 클래스의 메서드 내용은 서비스 클래스에 구현해도 되지만 되도록이면 서비스 클래스에서는 업무 로직 과 관련된 부분만 수행하는 것이 좋습니다.
CommonUtils.java
public class CommonUtils {
    private static final String FILE_EXTENSION_SEPARATOR = ".";
    private static final String CATEGORY_PREFIX = "/";
    private static final String TIME_SEPARATOR = "_";

    public static String buildFileName(String category, String originalFileName) {
        //원본 파일 경로에서 .의 마지막 위치를 찾아냅니다.
        int FileExtensionIndex = originalFileName.lastIndexOf(FILE_EXTENSION_SEPARATOR);
        //파일의 확장자 추출
        String fileExtension = originalFileName.substring(FileExtensionIndex);
        //파일 이름 추출
        String fileName = originalFileName.substring(0, FileExtensionIndex);
        //현재 시간 추출
        String now = String.valueOf(System.currentTimeMillis());
        
        // 동일한 파일이름을 만들지 않기 위해서 중간에 현재 시간을 추가
        // 파일 이름이 키가 되서 저장되기 때문에 중복된 파일 이름이 있으면 뒤의 파일이 업데이트
        return category + CATEGORY_PREFIX + fileName + TIME_SEPARATOR + now + fileExtension;
    }
}
  • 파일 업로드 DB 사용 이유: 파일 업로드를 빠르게 확인하기 위해

    • DB: 파일 이름 저장 -> 조회시 DB에서 조회
  • 빌더 패턴: 속성 설정이 많이 필요할 때 필요한 속성만 설정 가능

  • 비지니스 로직을 처리하기 위한 서비스 클래스를 생성: AwsS3Service - 실제 구현을 할 때는 이 부분은 인터페이스를 먼저 만들고 클래스를 만들어야 합니다.

  • 서비스 메서드를 가진 인터페이스 생성

AwsS3Service.java
import org.springframework.web.multipart.MultipartFile;

public interface AwsS3Service {
   public String uploadFile(String category, MultipartFile multipartFile);
}
  • 서비스 클래스를 생성
AwsS3ServiceImpl.java
package com.gmail.dragonhailstone.fileupload;

import com.amazonaws.auth.AWSCredentials;
import com.amazonaws.auth.AWSStaticCredentialsProvider;
import com.amazonaws.auth.BasicAWSCredentials;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.PutObjectRequest;
import jakarta.annotation.PostConstruct;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.io.InputStream;

@Slf4j
@RequiredArgsConstructor
@Service
public class AwsS3ServiceImpl implements AwsS3Service {
    private AmazonS3 amazonS3Client;

    //properties에서 값을 가지고 와서 설정
    @Value("${cloud.aws.credentials.access-key}")
    private String accessKey;

    @Value("${cloud.aws.credentials.secret-key}")
    private String secretKey;

    @Value("${cloud.aws.s3.bucket}")
    private String bucketName;

    @Value("${cloud.aws.region.static}")
    private String region;

    //생성자가 호출된 후에 수행할 메서드
    @PostConstruct
    public void setS3Client(){
        AWSCredentials credentials = new BasicAWSCredentials(accessKey, secretKey);
        amazonS3Client = AmazonS3ClientBuilder.standard()
                .withCredentials(new AWSStaticCredentialsProvider(credentials))
                .withRegion(region)
                .build();
    }

    //업로드 할 파일 존재 여부를 리턴해주는 메서드
    private boolean validateFileExists(MultipartFile multipartFile) {
        boolean result = true;
        if(multipartFile.isEmpty()){
            result = false;
        }
        return result;
    }

    @Override
    public String uploadFile(String category, MultipartFile multipartFile) {
        boolean result = validateFileExists(multipartFile);
        if(result == false){
            return null;
        }
        //파일 경로 생성
        String fileName = CommonUtils.buildFileName(category, multipartFile.getOriginalFilename());
        //파일 업로드 준비
        ObjectMetadata objectMetadata = new ObjectMetadata();
        objectMetadata.setContentType(multipartFile.getContentType());
        try(InputStream inputStream = multipartFile.getInputStream()){
            amazonS3Client.putObject(new PutObjectRequest(bucketName, fileName, inputStream, objectMetadata)
                    .withCannedAcl(CannedAccessControlList.PublicRead));
        }catch(IOException e){
            return null;
        }
        return amazonS3Client.getUrl(bucketName, fileName).toString();
    }
}