ํด๋น ๋ด์ฉ์ ํ์ผ ์ ๋ก๋ ๊ธฐ๋ฅ์ ๊ตฌํํ๊ธฐ ์ํด ๊ฒํ ํ๋ ๊ธฐ์ ๊ณผ ๋น์ฆ๋์ค ๋ก์ง์ ๊ดํ ๋ด์ฉ์ ๊ดํ ๊ธฐ๋ก์ด๋ค.
1. ํ์ผ ์ ๋ก๋๋ฅผ ์ํ ๊ธฐ์ ๊ฒํ
๊ฒ์๊ธ ํ์ผ ์ ๋ก๋๋ฅผ ์ํด ํ์ผ์ ๋ฏธ๋ฆฌ ์ ๋ก๋ํ๊ณ ๊ฒ์๊ธ์ ๊ดํ ๋ฉํ ๋ฐ์ดํฐ๋ฅผ ์ ์ฅํ๋ ๋ฐฉ์์ผ๋ก ๋น์ฆ๋์ค ๋ก์ง์ ๊ตฌ์ฑํ๋ค.
๊ฒ์๊ธ์ ์ ์ฅํ๋ ์์ ์ ํ์ผ ์ผ๊ด ์ ๋ก๋๋ฅผ ํ๋ฉด ์๋ต ์ง์ฐ์ผ๋ก ์ฌ์ฉ์ ๊ฒฝํ์ ์ ์ํฅ์ด ์์ ์ ์๋ ์ ์ด ๊ทธ ์ด์ ์๋ค.
์ด๋ฅผ ์ํด Multipart file ์ Presigned URL ๋ ๊ฐ์ง ๋ฐฉ์์ ๋์ ๋ฐฉ์๊ณผ ์ฅ๋จ์ ์ ๋น๊ตํ๋ค.
2. Multipart File?
multipart ๋ ํ๋์ HTTP ์์ฒญ ๋ฐ๋์ ์ฌ๋ฌ ๊ฐ์ ํํธ(๋ถ๋ถ) ๋ฅผ ํฌํจํ ์ ์๊ฒ ํด์ฃผ๋ MIME ํ์ ์ด๋ค.
ํ๋์ ๋ฉ์์ง ๋ณธ๋ฌธ ์์ ์ฌ๋ฌ ํํธ๋ฅผ ๊ฒฝ๊ณ๋ก ๊ตฌ๋ถํด์ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ ๋ฐฉ์์ด๋ค.
์ฃผ๋ก ํ์ผ ์ ๋ก๋ ์, ํ ์คํธ ๋ฐ์ดํฐ์ ๋ฐ์ด๋๋ฆฌ ๋ฐ์ดํฐ๋ฅผ ๋์์ ์ ์กํ๋๋ฐ ์ฌ์ฉํ๋ค.
๋ชจ๋ multipart ํ์ ์ ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๋ถํ๊ธฐ ์ํด boundary parameter ๊ฐ ํ์ํ๋ค.
๋ฐ์ด๋๋ฆฌ ํ๋จ์ ์ปจํ ์ธ ํ์ ์ ๋ช ์ํ๊ณ , ์์ฒญ ๋ฐ๋์ ์ฒจ๋ถํ ๋ฐ์ดํฐ๋ฅผ ์ ๋ ฅํ์ฌ ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ค.
๋ง์ฝ ๋์์, ์ด๋ฏธ์ง ํ์ผ์ธ ๊ฒฝ์ฐ ๋ฐ์ด๋๋ฆฌ ํํ๋ก ๋ณํ๋์ด ๋ฐ์ดํฐ๋ฅผ ์ ์กํ๋ค.
์๋ ์์๋ฅผ ํ๋ฒ ํ์ธํด๋ณด์.
์๋จ์ Content-type ์ด multipart/form-data ์ ์ ์ธํด ์ฌ๋ฌ ํ์ผ์ ์ ๋ก๋ํ ๊ฒ์์ ๋ช ์ํ๋ค.
๊ฐ ์ ๋ก๋ ํ ํ์ผ๋ค์ boundary๋ฅผ ํตํด ๋ฐ์ดํฐ๋ฅผ ๊ตฌ๋ถํ๋ค.
boundary ํ๋จ์ ์ ๋ก๋ํ ํ์ผ ํ์ ์ Content-type ํค๋๋ฅผ ์ ์ํ ํ, ์ ๋ฌํ ํ์ ์ ํ์ผ์ ์ฒจ๋ถํด ์ ์กํ๋ค.
ํ์ง๋ง, Multipart ๋ ์๋ฒ๊ฐ ํ์ผ์ ์ ์กํ๊ธฐ ์ํด ์๋ฒ์์ํ์ผ์๋ณต์ฌํด์์์๋ณด๊ดํด์ผ ํ๋ค.
์ด๋ ์๋ฒ ๋ถํ์ ํธ๋ํฝ ์ฒ๋ฆฌ์ ์์ด ์ํฅ์ ๋ฏธ์น ์ ์๋ ๋จ์ ์ด ์๋ค.

3. Presigned URL?
Presigned URL ์ ํด๋ผ์ฐ๋ ์คํ ๋ฆฌ์ง(์: AWS S3, Google Cloud Storage ๋ฑ)์์ ์ธ์ฆ๋ ์ฌ์ฉ์๊ฐ ๋ค๋ฅธ ์ฌ๋์๊ฒ ์ ํ๋ ์๊ฐ ๋์ ๋ฆฌ์์ค์ ์ ๊ทผ์ ํ์ฉํ๋ ์ผ์์ ์ธ URL ์ด๋ค.
์ธ์ฆ๋ URL๋ฅผ ํตํด ํด๋น ๋ฐ์ดํฐ๋ฅผ ์กฐํ ๋๋ ์ ๋ก๋ํ๊ณ , ์ ํ ์๊ฐ์ด ์ง๋๋ฉด URL์ด ๋ง๋ฃ๋์ด ๋ณด์ ์ธก๋ฉด์์ ์ฅ์ ์ด ์๋ค.
Presigned URL์ ๋์ ๋ฐฉ์์ ํ์ธํด๋ณด์.
ํด๋ผ๋ฆฌ์ธํธ๋ ํด๋ผ์ฐ๋๋ก ๋ถํฐ ์กฐํ ๋๋ ์ ๋ก๋ ๋ชฉ์ ์ URL์ ์์ฒญํ๋ฉด ์ฌ๋ฐ๋ฅธ ์ฌ์ฉ์์์ ํ์ธํ ํ์ Presigned URL ์ ๋ฐํํ๋ค.
Presigned URL ์ ์ฌ์ฉํ๋ฉด ํ์ผ ์ ๋ก๋์ ๊ดํ ์๋ฒ์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๋ค.
์์ ๋งํฌ๋ฅผ ํตํด ํด๋ผ์ด์ธํธ๊ฐ S3๋ก ํ์ผ ์ ๋ก๋๋ฅผ ํ์ฌ API์๋ฒ์ ํ์ผ ์ ๋ก๋๋ฅผ ์ํ ๋ฆฌ์์ค๊ฐ ๋ฐ์ํ์ง ์๊ธฐ ๋๋ฌธ์ด๋ค.
API ์๋ฒ๋ ์ธ์ฆ๋ ๊ฐ์ ํตํด S3 ๋ก ๋ถํฐ ์์ ๋งํฌ๋ฅผ ์ ๋ฌ๋ฐ์ ํด๋ผ์ด์ธํธ์๊ฒ ์ ๋ฌ๋ง ํ๊ณ ,
ํด๋ผ์ด์ธํธ๋ ์์ ๋งํฌ๋ฅผ ํตํด ์ง์ S3 ๋ก ์ ๋ก๋ํ๋ค. ์๋์์ ์ค์ ํ ์คํธ ๊ฒฐ๊ณผ๋ฅผ ํตํด ํ์ธํด๋ณด๋๋ก ํ์.

4. Multipart File vs Presigned URL ํ ์คํธ ํด๋ณด์.
(1) ํ ์คํธ ์ํ ์์กด์ฑ ์ค์
ํ ์คํธ๋ฅผ ์ํด spring ์์ ์ ๊ณตํ๋ Spring Cloud AWS ๋ฅผ ์ฌ์ฉํ๋ค. Spring Cloud AWS ๋ AWS SDK ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก S3 ์ ๊ด๋ จ๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ฉฐ ์ฐ๊ฒฐ ์ค์ ๊ณผ ๊ด๋ จ๋ ํ๋กํผํฐ๋ฅผ ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํด ํธ๋ฆฌํ๊ฒ AWS ๊ธฐ๋ฅ์ ์ฌ์ฉํ ์ ์๋ค.
dependencies {
implementation 'io.awspring.cloud:spring-cloud-aws-starter-s3:3.0.0'
}
(2) ํ ์คํธ ๋น์ฆ๋์ค ๋ก์ง
import java.io.IOException;
import java.net.URL;
import java.time.Duration;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import io.awspring.cloud.s3.S3Resource;
import io.awspring.cloud.s3.S3Template;
import lombok.RequiredArgsConstructor;
import com.cooper.presigned_url.service.FileHandler;
@Component
@RequiredArgsConstructor
public class AwsS3FileHandler implements FileHandler {
@Value("${aws.bucket.name}")
private String bucketName;
private final S3Template s3Template;
@Override
public URL createSignedUrl(final Long postId, final String fileName) {
final String filePath = String.format("%s/%s", postId, fileName);
return s3Template.createSignedPutURL(bucketName, filePath, Duration.ofMinutes(1));
}
@Override
public URL getSignedUrl(final Long postId, final String path) {
String key = path.replaceFirst("^/", ""); // object key ๋ path ์์ / ๋ฅผ ์ ๊ฑฐํด์ผ ํ๋ค.
return s3Template.createSignedGetURL(bucketName, key, Duration.ofMinutes(1));
}
@Override
public URL uploadFile(final Long postId, final MultipartFile file) throws IOException {
final String fileName = file.getOriginalFilename();
final String filePath = String.format("%s/%s", postId, fileName);
final S3Resource s3Resource = s3Template.upload(bucketName, filePath, file.getInputStream());
return s3Resource.getURL();
}
}
(3) AWS ํ ์คํธ ํ๊ฒฝ
Multipart Filter ๊ณผ Presigned URL ์ CPU ์ฌ์ฉ๋ ๋น๊ต๋ฅผ ์ํด AWS EC2 t3.micro ๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ํ ์คํธ๋ฅผ ์งํํ๋ค.

(4) CPU Utilization, Packet ์ ์ก๋ ๋น๊ต
5MB ์ฌ์ด์ฆ์ pdf ํ์ผ์ ์ ๋ก๋ํ๊ณ Cloudwatch ๋ฅผ ํตํด ๊ฒฐ๊ณผ๋ฅผ ๋น๊ตํ๋ค.
๋จ์ผ ์์ฒญ์ ๋จ์ผ ํ์ผ ์ ๋ก๋ ๋น๊ต ๊ฒฐ๊ณผ,
CPU Utilization ์์ 10๋ฐฐ ์ ์ CPU ์ฌ์ฉํ๊ณ , Network I/O ํจํท ์๊ฐ 400๋ฐฐ ์ ๊ฒ ํจํท์ ์ก์์ ํ๋ ๊ฒ์ ํ์ธํ๋ค.
๋น๋ก, ํด๋ผ์ด์ธํธ์์ S3 ๋ก ํ์ผ ์ ๋ก๋ํ๋ ๊ณผ์ ์์ ์ ์ก๋ ํจํท์ด ํฌํจ๋์ง ์์์ง๋ง
ํด๋น ์๋ฒ๊ฐ ๋ค๋์ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๊ฒฝ์ฐ๋ฅผ ๊ณ ๋ คํด๋ณด์์ ๋ Presigned URL ์ด Multipart File ์ ๋นํด ์ ์ CPU ๋ฆฌ์์ค ์ฅ์ ์ด ์๋ค.

(5) ์๋ต ์๊ฐ ๋น๊ต
์ค์ ์๋ต ์๊ฐ ๋ํ Presigned URL ์์ ์ข์ ๊ฒฐ๊ณผ๊ฐ ์์๋ค.
์ด์ ์ ํ์ธํ๋ฏ์ด Network I/O ์ ์ก ํจํท์ ๊ฐฏ์ ์ฐจ์ด๊ฐ ์ฝ 400๋ฐฐ ์ฐจ์ด๊ฐ ์์๋ค. ์ ๋ก๋ ํ์ผ์ ์ก์์ ์ ์ํ ๋๋์ ํจํท ์ ์ก๊ณผ ์๋ฒ์์ ํ์ผ์ ์์ ๋ณด๊ดํ๊ธฐ ์ํ ๋ฆฌ์์ค ์ฌ์ฉ์ผ๋ก ์ธํด ์๋ต ์ง์ฐ ๋ฐ์ํ์ ๊ฒ์ผ๋ก ์์๋๋ค.
๋ ๋ฐ์ดํฐ๋ฅผ ๋น๊ตํ์ ๋ CPU ์ฌ์ฉ๋๊ณผ Network I/O ๊ด์ ์์ Presigned URL ๊ฐ ๋ฆฌ์์ค๋ฅผ ํจ์จ์ ์ผ๋ก ์ฌ์ฉํ๊ณ ๋ฎ์ ์๋ต ์๋๋ฅผ ๊ธฐ๋ํ ์ ์๋ค.

5. ๊ฒฐ๋ก
ํ์ผ ์ ๋ก๋๋ฅผ ์ํ ๋๊ฐ์ง ๋ฐฉ์์ ๊ฒํ ํด๋ณด์๋ค.
๋ฉํฐํํธ ํ์ผ์ ๊ฒฝ์ฐ,
์๋ฒ์ ํ์ผ์ ์์ ๋ณด๊ดํด์ผ ํ๋ ์ด์ ๋ก ์๋ฒ๋ก ์ ์กํ๋ ํจํท์ ์๊ณผ CPU ๋ฆฌ์์ค์ฌ์ฉ ๊ด์ ์์ ์ธํ ๋จ์ ์ด ์์ง๋ง
์ง์ ํ์ผ์ ๊ฒ์ฆํ๋ ๋ก์ง์ ์ถ๊ฐํ ์ ์์ด ๋ณด์์์ ์ฅ์ ์ด ์๋ค.
Presigned URL ์ ๊ฒฝ์ฐ,
ํด๋ผ์ด์ธํธ์๊ฒ ์์ URL ์ ์ ๊ณตํด S3 ์ ์ง์ ์ ๋ก๋ ํ๋ ๋ฐฉ์์ผ๋ก ์๋ฒ์ ๋ถํ๋ฅผ ์ค์ผ ์ ์๋ ์ฅ์ ์ด ์๋ค.
ํ์ง๋ง ํ์ผ ์ด๋ฆ์ ๋์ผ ์ฌ๋ถ๋ฅผ ๊ฒ์ฆ๋ง ๊ฐ๋ฅํ๋ฏ๋ก ๋ฉํฐํํธ ํ์ผ์ ๋นํด ํ์ผ ๊ฒ์ฆ ๋ก์ง์ด ์๋์ ์ผ๋ก ๋น์ฝํ ๋จ์ ์ด ์กด์ฌํ๋ค.
์ด์ ๊ฐ์ ๋๊ฐ์ง ๋ฐฉ์์ ์ฅ๋จ์ ๊ณผ ๋ฐฉํฅ์ฑ์ ๊ณ ๋ คํ์ฌ ํ์ผ ์ ๋ก๋ ๋ฐฉ์์ ์ ์ฉํด๋ณด์.
Reference
- ํ ์คํธ ์ํ ์ฝ๋ : https://github.com/pbg0205/BE-tutorials/tree/main/presigned_url
- S3 Presigned URL https://docs.aws.amazon.com/ko_kr/AmazonS3/latest/userguide/using-presigned-url.html