TroubleShooting

[TroubleShooting] ๋กค๋ฐฑ์ด ๋˜์ง€ ์•Š๋Š” S3 Bucket

soeun2537 2024. 10. 2. 17:45

๐Ÿ‘€ ํ˜„์žฌ ์ƒํ™ฉ ๋ฐ ๋ฐฐ๊ฒฝ ์„ค๋ช…

  • ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์„ ๊ตฌํ˜„ํ•˜๋Š” ๊ณผ์ •์—์„œ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•ด์•ผ ํ•˜๋Š” ์ƒํ™ฉ์ด ์ƒ๊ฒผ๋‹ค.
  • AWS S3 Bucket์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€๋Š” S3์— ์ €์žฅํ•˜๊ณ , ํ•ด๋‹น ์ด๋ฏธ์ง€์˜ URL์€ ์„œ๋ฒ„์˜ DB์— ์ €์žฅํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ–ˆ๋‹ค.
  • ์ด ๋ฐฉ์‹์€ ์Šคํ† ๋ฆฌ์ง€์™€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์—ญํ• ์„ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์จ, ์ด๋ฏธ์ง€๋Š” ํด๋ผ์šฐ๋“œ ์Šคํ† ๋ฆฌ์ง€์— ์•ˆ์ „ํ•˜๊ฒŒ ๋ณด๊ด€ํ•˜๊ณ , ๊ด€๋ จ ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ๋Š” DB์— ์ €์žฅํ•˜์—ฌ ์„œ๋ฒ„์˜ ๋ถ€๋‹ด์„ ์ค„์ด๊ณ  ์ด๋ฏธ์ง€ ์ €์žฅ ๋ฐ ์ œ๊ณต ์†๋„๋ฅผ ๋†’์ผ ์ˆ˜ ์žˆ๋Š” ์žฅ์ ์ด ์žˆ๋‹ค.

 

โ–ถ ๊ตฌํ˜„ ์ฝ”๋“œ

๐Ÿ”ฝ S3์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ๋ฐ ์‚ญ์ œํ•˜๋Š” ์„œ๋น„์Šค ํด๋ž˜์Šค์ธ S3ImageService

/**
 * S3์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ ๋ฐ ์‚ญ์ œํ•˜๋Š” ์„œ๋น„์Šค ํด๋ž˜์Šค์ž…๋‹ˆ๋‹ค.
 */
@Service
@RequiredArgsConstructor
public class S3ImageService {

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

    public final AmazonS3 amazonS3;

    /**
     * S3์— ํŒŒ์ผ ์—…๋กœ๋“œ
     */
    public String uploadImage(MultipartFile file) {
        String fileName = UUID.randomUUID() + "_" + file.getOriginalFilename();

        try {
            ObjectMetadata metadata = new ObjectMetadata();
            metadata.setContentLength(file.getSize());
            metadata.setContentType(file.getContentType());

            amazonS3.putObject(bucketName, fileName, file.getInputStream(), metadata);

            return amazonS3.getUrl(bucketName, fileName).toString();
        } catch (IOException e) {
            throw new S3Exception(S3ErrorCode.FILE_UPLOAD_ERROR);
        }
    }
    
    /**
     * S3์—์„œ ์ด๋ฏธ์ง€ ์‚ญ์ œ
     */
    public void deleteImage(String imageUrl) {
        String fileName = imageUrl.substring(imageUrl.lastIndexOf("/") + 1);
        amazonS3.deleteObject(bucketName, fileName);
    }
}

๐Ÿ”ฝ ์‚ฌ์ง„์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” Service ํด๋ž˜์Šค ๋ฉ”์„œ๋“œ

/**
 * ๊ฐ€๊ฒŒ ์‚ฌ์ง„์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
 */
@Transactional
protected void updateMarketImages(Market market, List<MultipartFile> updatedImages) {
    // ๊ธฐ์กด ์‚ฌ์ง„ ์กฐํšŒ
    List<MarketImage> marketImageList = marketImageRepository.findAllByMarketId(market.getId());

    // S3์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ ์‚ญ์ œ
    for (MarketImage marketImage : marketImageList) {
        s3ImageService.deleteImage(marketImage.getImageUrl());
    }

    // DB์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ URL ์‚ญ์ œ
    marketImageRepository.deleteAll(marketImageList);

    for (MultipartFile imageFile : updatedImages) {
    	// S3์— ์ƒˆ๋กœ์šด ์‚ฌ์ง„ ์—…๋กœ๋“œ
        String imageUrl = s3ImageService.uploadImage(imageFile);

        MarketImage updateImage = MarketImage.builder()
                .market(market)
                .imageUrl(imageUrl)
                .build();
		
        // DB์— ์ƒˆ๋กœ์šด ์‚ฌ์ง„ URL ์ €์žฅ
        marketImageRepository.save(updateImage);
    }
}

 

 

๐Ÿšจ ๋ฌธ์ œ ์ƒํ™ฉ

  • S3 Bucket์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œ(amazonS3.putObject)ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œ(amazonS3.deleteObject)ํ•˜๋Š” ์ž‘์—…์€ ํŠธ๋žœ์žญ์…˜์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š”๋‹ค.
  • ์‚ฌ์ง„์„ ์—…๋ฐ์ดํŠธํ•˜๋Š” ๋ฉ”์„œ๋“œ์—์„œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜์—ฌ ํŠธ๋žœ์žญ์…˜์ด ๋กค๋ฐฑ๋  ๊ฒฝ์šฐ, DB์—์„œ URL์„ ์ถ”๊ฐ€ํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ์ž‘์—…์€ ๋กค๋ฐฑ๋˜์ง€๋งŒ, S3 ๋ฒ„ํ‚ท์— ๋Œ€ํ•œ ์ž‘์—…์€ ๋กค๋ฐฑ๋˜์ง€ ์•Š๋Š”๋‹ค. ์ด๋Š” S3 ๋ฒ„ํ‚ท์— ๊ณ ์•„ ๊ฐ์ฒด๊ฐ€ ๋‚จ์•„ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์— ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค๋Š” ์˜๋ฏธ๋‹ค.
๊ณ ์•„ ๊ฐ์ฒด: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๊ฐ™์€ ๊ด€๋ฆฌ ์‹œ์Šคํ…œ์—์„œ ์ฐธ์กฐ๋˜์ง€ ์•Š๊ฑฐ๋‚˜ ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ์‚ญ์ œ๋œ ์ดํ›„์—๋„ ๋‚จ์•„ ์žˆ๋Š” ๋ถˆํ•„์š”ํ•œ ๋ฐ์ดํ„ฐ๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, S3์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€๊ฐ€ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ๋” ์ด์ƒ ์ฐธ์กฐ๋˜์ง€ ์•Š์„ ๊ฒฝ์šฐ ํ•ด๋‹น ์ด๋ฏธ์ง€๋Š” ๊ณ ์•„ ๊ฐ์ฒด๊ฐ€ ๋œ๋‹ค.
๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ: ์‹œ์Šคํ…œ์—์„œ ๋ฐ์ดํ„ฐ๊ฐ€ ์ผ๊ด€๋˜๊ณ  ์ •ํ™•ํ•˜๊ฒŒ ์œ ์ง€๋˜๋Š” ์ƒํƒœ๋ฅผ ์˜๋ฏธํ•œ๋‹ค. ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด ์„œ๋กœ ๋‹ค๋ฅธ ์‹œ์Šคํ…œ ๊ฐ„์˜ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ์ด๋Š” ๋ฐ์ดํ„ฐ์˜ ์‹ ๋ขฐ์„ฑ์„ ๋–จ์–ด๋œจ๋ฆฌ๋Š” ๋ฌธ์ œ๋ฅผ ์ดˆ๋ž˜ํ•œ๋‹ค. ์˜ˆ๋ฅผ ๋“ค์–ด, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์—๋Š” ์‚ญ์ œ๋˜์—ˆ๋‹ค๊ณ  ํ‘œ์‹œ๋˜์–ด ์žˆ์ง€๋งŒ S3์—๋Š” ์—ฌ์ „ํžˆ ์ด๋ฏธ์ง€๊ฐ€ ๋‚จ์•„ ์žˆ๋Š” ๊ฒฝ์šฐ๊ฐ€ ์ด์— ํ•ด๋‹นํ•œ๋‹ค.

 

 

โœ๏ธ ์›์ธ ๋ถ„์„

S3 Bucket ๊ด€๋ จ ์ž‘์—…์— ํŠธ๋žœ์žญ์…˜์ด ์ ์šฉ๋˜์ง€ ์•Š๋Š” ์ด์œ ๋Š” S3๊ฐ€ ์™ธ๋ถ€์˜ ๋…๋ฆฝ๋œ ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค๋กœ ๋™์ž‘ํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค. ํŠธ๋žœ์žญ์…˜์€ ์ผ๋ฐ˜์ ์œผ๋กœ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์™€ ๊ฐ™์€ ํŠธ๋žœ์žญ์…˜์„ ์ง€์›ํ•˜๋Š” ์‹œ์Šคํ…œ์—์„œ๋งŒ ๋™์ž‘ํ•œ๋‹ค.

  1. S3๋Š” ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค๊ฐ€ ์•„๋‹ˆ๋‹ค.
    • S3๋Š” ๋‹จ์ˆœํ•œ ํŒŒ์ผ ์ €์žฅ์†Œ๋กœ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์ฒ˜๋Ÿผ ํŠธ๋žœ์žญ์…˜ ๊ด€๋ฆฌ ๊ธฐ๋Šฅ์ด ํฌํ•จ๋˜์–ด ์žˆ์ง€ ์•Š๋‹ค. ์ฆ‰, S3๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์ €์žฅํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•  ๋•Œ ์ฆ‰์‹œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์„ ๋ฐ˜์˜ํ•˜๋ฉฐ, ์ด๋ฅผ ๋˜๋Œ๋ฆฌ๋Š” ๋กค๋ฐฑ ๊ธฐ๋Šฅ์ด ์—†๋‹ค.
  2. HTTP ๊ธฐ๋ฐ˜์˜ ์š”์ฒญ
    • S3์™€์˜ ์ƒํ˜ธ์ž‘์šฉ์€ HTTP ์š”์ฒญ์„ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง€๋ฉฐ, ๊ฐ ์š”์ฒญ์ด ๋…๋ฆฝ์ ์œผ๋กœ ์‹คํ–‰๋œ๋‹ค. HTTP ์š”์ฒญ์˜ stateless ํŠน์„ฑ์ƒ ๊ฐ ์ž‘์—…์€ ์™„๋ฃŒ๋œ ํ›„ ๋˜๋Œ๋ฆด ์ˆ˜ ์—†์œผ๋ฉฐ, ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ํŠธ๋žœ์žญ์…˜๊ณผ ๊ฐ™์€ ์ผ๊ด€๋œ ์ƒํƒœ๋ฅผ ๋ณด์žฅํ•  ์ˆ˜ ์—†๋‹ค.

๋”ฐ๋ผ์„œ S3์™€ ๊ฐ™์€ ์™ธ๋ถ€ ์Šคํ† ๋ฆฌ์ง€ ์„œ๋น„์Šค๋Š” ํŠธ๋žœ์žญ์…˜์ด ์ ์šฉ๋˜์ง€ ์•Š์œผ๋ฉฐ, S3์—์„œ์˜ ์ž‘์—…์€ ์„ฑ๊ณต ์—ฌ๋ถ€์™€ ๊ด€๊ณ„์—†์ด ์ฆ‰์‹œ ๋ฐ˜์˜๋˜๊ณ  ๋กค๋ฐฑ๋˜์ง€ ์•Š๋Š”๋‹ค.

 

 

๐Ÿ”จ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

โ–ถ 1. ์‹คํŒจ: try-catch๋ฅผ ํ™œ์šฉํ•œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ

๐Ÿ”ฝ ๊ตฌํ˜„ ์ฝ”๋“œ

/**
 * ๊ฐ€๊ฒŒ ์‚ฌ์ง„์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
 */
@Transactional
protected void updateMarketImages(Market market, List<MultipartFile> updatedImages) {
    // ๊ธฐ์กด ์‚ฌ์ง„ ์กฐํšŒ
    List<MarketImage> marketImageList = marketImageRepository.findAllByMarketId(market.getId());

    // S3์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ ์‚ญ์ œ
    try {
        for (MarketImage marketImage : marketImageList) {
            s3ImageService.deleteImage(marketImage.getImageUrl());
        }
    } catch (Exception e) {
        throw new RuntimeException("๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ S3์—์„œ ์‚ญ์ œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
    }

    // DB์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ URL ์‚ญ์ œ
    try {
        marketImageRepository.deleteAll(marketImageList);
    } catch (Exception e) {
        throw new RuntimeException("๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ DB์—์„œ ์‚ญ์ œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
    }

    // ์ƒˆ๋กœ์šด ์‚ฌ์ง„ ์—…๋กœ๋“œ ๋ฐ ์ €์žฅ
    for (MultipartFile imageFile : updatedImages) {
        try {
            // S3์— ์ƒˆ๋กœ์šด ์‚ฌ์ง„ ์—…๋กœ๋“œ
            String imageUrl = s3ImageService.uploadImage(imageFile);

            MarketImage updateImage = MarketImage.builder()
                    .market(market)
                    .imageUrl(imageUrl)
                    .build();

            // DB์— ์ƒˆ๋กœ์šด ์‚ฌ์ง„ URL ์ €์žฅ
            marketImageRepository.save(updateImage);
        } catch (Exception e) {
            throw new RuntimeException("์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.");
        }
    }
}

๐Ÿ”ฝ ๋ฌธ์ œ์ 

  • try-catch๋กœ S3 ์ž‘์—…์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•˜๊ณ  ์žˆ์ง€๋งŒ, S3์— ๋ฐ˜์˜๋œ ๋ณ€๊ฒฝ ์‚ฌํ•ญ์€ ๋กค๋ฐฑ๋˜์ง€ ์•Š์•„ ํ•ด๊ฒฐ์ฑ…์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.

 

โ–ถ 2. ์‹คํŒจ: ๋กœ์ง ๋ถ„๋ฆฌ ๋ฐ ์ˆœ์„œ ๋ณ€๊ฒฝ

๐Ÿ”ฝ ๋ฌธ์ œ์ 

  • DB ์‚ญ์ œ ํ›„ S3 ์‚ญ์ œ, DB ์—…๋กœ๋“œ ํ›„ S3 ์—…๋กœ๋“œ์™€ ๊ฐ™์ด ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๊ณ  ์ˆœ์„œ๋ฅผ ๋ณ€๊ฒฝํ•˜๋ฉด ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ์–ด๋А ์ •๋„ ์œ ์ง€ํ•˜๋Š” ๋ฐ ๋„์›€์ด ๋˜์ง€๋งŒ, ํŠธ๋žœ์žญ์…˜ ๋„์ค‘์— ์„œ๋ฒ„๊ฐ€ ๋‹ค์šด๋˜๊ฑฐ๋‚˜ ์˜ˆ๊ธฐ์น˜ ์•Š์€ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฒฝ์šฐ ์—ฌ์ „ํžˆ ์™„๋ฒฝํ•œ ํ•ด๊ฒฐ์ฑ…์ด ๋˜์ง€ ์•Š๋Š”๋‹ค.

 

โ–ถ 3. ์‹คํŒจ: ์˜ˆ์™ธ ์ฒ˜๋ฆฌ๋ฅผ ํ†ตํ•ด ์ˆ˜๋™์œผ๋กœ ํŒŒ์ผ ์‚ญ์ œ

  • ์‚ฌ์ง„ ์—…๋กœ๋“œ ์‹คํŒจ ์‹œ ์—…๋กœ๋“œ์— ์‹คํŒจํ•œ ์‚ฌ์ง„์„ ์ˆ˜๋™์œผ๋กœ ์‚ญ์ œํ•œ๋‹ค.
  • ์‚ฌ์ง„ ์‚ญ์ œ ์‹คํŒจ ์‹œ ์‚ญ์ œ์— ์‹คํŒจํ•œ ์‚ฌ์ง„์„ ์ˆ˜๋™์œผ๋กœ ๋‹ค์‹œ ์—…๋กœ๋“œํ•œ๋‹ค.

๐Ÿ”ฝ ๊ตฌํ˜„ ์ฝ”๋“œ

/**
 * ๊ฐ€๊ฒŒ ์‚ฌ์ง„์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
 */
@Transactional
protected void updateMarketImages(Market market, List<MultipartFile> updatedImages) {
    // ๊ธฐ์กด ์‚ฌ์ง„ ์กฐํšŒ
    List<MarketImage> marketImageList = marketImageRepository.findAllByMarketId(market.getId());

    // S3์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ ์‚ญ์ œ ๋ฐ ๋ณด์ƒ ๋กœ์ง ์ฒ˜๋ฆฌ
    for (MarketImage marketImage : marketImageList) {
        try {
            // S3์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ ์‚ญ์ œ
            s3ImageService.deleteImage(marketImage.getImageUrl());
        } catch (Exception e) {
            // ์‚ญ์ œ ์‹คํŒจ ์‹œ ๋ณต๊ตฌ ๋ณด์ƒ ๋กœ์ง
            System.err.println("S3์—์„œ ๊ธฐ์กด ํŒŒ์ผ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: " + e.getMessage());
            try {
                // S3์— ๋‹ค์‹œ ์—…๋กœ๋“œ ์‹œ๋„ (์‹คํŒจํ•œ ํŒŒ์ผ ๋ณต๊ตฌ)
                s3ImageService.uploadImageFromUrl(marketImage.getImageUrl());
            } catch (Exception restoreException) {
                System.err.println("S3์— ๊ธฐ์กด ํŒŒ์ผ ๋ณต๊ตฌ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: " + restoreException.getMessage());
                throw new RuntimeException("๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ ๋ณต๊ตฌํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", restoreException);
            }
            throw new RuntimeException("๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ ์‚ญ์ œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", e);
        }
    }

    // DB์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ URL ์‚ญ์ œ
    marketImageRepository.deleteAll(marketImageList);

    // ์ƒˆ๋กœ์šด ์‚ฌ์ง„ ์—…๋กœ๋“œ ๋ฐ ์ €์žฅ
    for (MultipartFile imageFile : updatedImages) {
        String imageUrl = null;

        try {
            // S3์— ์ƒˆ๋กœ์šด ์‚ฌ์ง„ ์—…๋กœ๋“œ
            imageUrl = s3ImageService.uploadImage(imageFile);

            MarketImage updateImage = MarketImage.builder()
                    .market(market)
                    .imageUrl(imageUrl)
                    .build();

            // DB์— ์ƒˆ๋กœ์šด ์‚ฌ์ง„ URL ์ €์žฅ
            marketImageRepository.save(updateImage);
        } catch (Exception e) {
            // ์—…๋กœ๋“œ ์‹คํŒจ ์‹œ, ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์‚ญ์ œ
            if (imageUrl != null) {
                try {
                    s3ImageService.deleteImage(imageUrl);
                } catch (Exception deleteException) {
                    System.err.println("S3์— ์—…๋กœ๋“œ๋œ ํŒŒ์ผ ์‚ญ์ œ ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ: " + deleteException.getMessage());
                }
            }
            throw new RuntimeException("์ƒˆ๋กœ์šด ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.", e);
        }
    }
}

๐Ÿ”ฝ ๋ฌธ์ œ์ 

  1. ํ•œ๋ˆˆ์— ๋ด๋„ ๋กœ์ง์ด ๋ณต์žกํ•ด์ ธ ๊ฐ€๋…์„ฑ์ด ๋งค์šฐ ๋–จ์–ด์ง„๋‹ค.
  2. ์ˆ˜๋™์œผ๋กœ ์—…๋กœ๋“œํ•˜๊ฑฐ๋‚˜ ์‚ญ์ œํ•˜๋Š” ๊ณผ์ •์—์„œ ๋˜๋‹ค์‹œ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.
  3. ์‚ญ์ œ์—์„œ๋Š” ์„ฑ๊ณตํ–ˆ์œผ๋‚˜ ์—…๋กœ๋“œ์—์„œ ์‹คํŒจํ•˜๊ฒŒ ๋œ๋‹ค๋ฉด ๋˜๋‹ค์‹œ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.
    • ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜๋ ค ํ–ˆ์œผ๋‚˜, ์‚ญ์ œ๋Š” ๊ธฐ์กด ์‚ฌ์ง„ ์กฐํšŒ ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜๊ณ , ์—…๋กœ๋“œ๋Š” ์š”์ฒญ๋ฐ›์€ Multipart ๋ฆฌ์ŠคํŠธ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ฒ˜๋ฆฌํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋กœ์ง์ด ๋งค์šฐ ๋ณต์žกํ•ด์ง„๋‹ค.

 

โ–ถ 4. ๋ณด๋ฅ˜: Spring Scheduler๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ S3 Bucket์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ์ •๋ฆฌํ•˜๋Š” ๋ณด์ƒ ํŠธ๋žœ์žญ์…˜

  • ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ํ†ตํ•ด ๋งค์ผ ์ž์ •์— S3 Bucket์˜ ๊ณ ์•„ ๊ฐ์ฒด๋ฅผ ์‚ญ์ œํ•œ๋‹ค.

๐Ÿ”ฝ ๊ตฌํ˜„ ์ฝ”๋“œ

/**
 * ์ฃผ๊ธฐ์ ์œผ๋กœ S3์—์„œ ๊ณ ์•„ ๊ฐ์ฒด ์‚ญ์ œ (๋งค์ผ ์ž์ • ์‹คํ–‰)
 */
@Scheduled(cron = "0 0 0 * * ?")
public void cleanUpOrphanImages() {
    // 1. S3 ๋ฒ„ํ‚ท์˜ ๋ชจ๋“  ํŒŒ์ผ ๋ชฉ๋ก ๊ฐ€์ ธ์˜ค๊ธฐ
    ObjectListing objectList = amazonS3.listObjects(bucketName);
    List<S3ObjectSummary> s3Objects = objectList.getObjectSummaries();

    // 2. ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ ๋ชจ๋“  ์ด๋ฏธ์ง€ URL ๊ฐ€์ ธ์˜ค๊ธฐ
    Set<String> dbImageUrls = marketImageRepository.findAll().stream()
            .map(MarketImage::getImageUrl)
            .collect(Collectors.toSet());

    // 3. S3์— ์กด์žฌํ•˜์ง€๋งŒ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์—†๋Š” ํŒŒ์ผ(๊ณ ์•„ ๊ฐ์ฒด) ์‚ญ์ œ
    for (S3ObjectSummary s3Object : s3Objects) {
        String imageUrl = amazonS3.getUrl(bucketName, s3Object.getKey()).toString();
        if (!dbImageUrls.contains(imageUrl)) {
            amazonS3.deleteObject(bucketName, s3Object.getKey());
        }
    }
}

๐Ÿ”ฝ ๋ฌธ์ œ์ 

  1. S3์—์„œ ๊ฐ์ฒด๋ฅผ ์‚ญ์ œํ•˜๋Š” ๊ณผ์ •๊ณผ ๋™์‹œ์— ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ƒํƒœ๊ฐ€ ๋ณ€๊ฒฝ๋˜๊ฑฐ๋‚˜ ์‹œ์Šคํ…œ์— ๋‹ค๋ฅธ ๋ณ€ํ™”๊ฐ€ ์ƒ๊ธธ ์ˆ˜ ์žˆ๋‹ค. ์ž์ •์— ์Šค์ผ€์ค„๋Ÿฌ๊ฐ€ S3์˜ ๊ณ ์•„ ๊ฐ์ฒด๋ฅผ ์‚ญ์ œํ•˜๋Š” ๋™์•ˆ ์ƒˆ๋กœ์šด ์‚ฌ์ง„์ด ์—…๋กœ๋“œ๋˜๊ฑฐ๋‚˜ ๊ธฐ์กด ์‚ฌ์ง„์ด ์‚ญ์ œ๋˜๋Š” ๊ฒฝ์šฐ๋ฅผ ์˜๋ฏธํ•œ๋‹ค.
  2. s3Objects์™€ dbImageUrls๋ฅผ ๋ชจ๋‘ ๋ฉ”๋ชจ๋ฆฌ์— ๋กœ๋“œํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํŒŒ์ผ์˜ ์ˆ˜๋‚˜ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์˜ ์ด๋ฏธ์ง€ URL ์ˆ˜๊ฐ€ ๋งŽ์„ ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ๋ถ€์กฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๋‹ค. ํŠนํžˆ ๋Œ€๊ทœ๋ชจ ํŒŒ์ผ ๊ด€๋ฆฌ ํ™˜๊ฒฝ์—์„œ๋Š” ์ด๋Ÿฌํ•œ ๋ฐฉ์‹์œผ๋กœ ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๊บผ๋ฒˆ์— ๋ฉ”๋ชจ๋ฆฌ์— ์˜ฌ๋ฆฌ๋Š” ๊ฒƒ์€ ๋น„ํšจ์œจ์ ์ด๋‹ค.

 

โ–ถ 5. ํ•ด๊ฒฐ1: S3 Bucket ์—…๋กœ๋“œ, S3 Bucket ์‚ญ์ œ, DB ์—…๋กœ๋“œ ๋ฐ ์‚ญ์ œ ๋กœ์ง 3๊ฐœ๋กœ ๋ถ„๋ฆฌ

  • ํด๋ผ์ด์–ธํŠธ ์•ฑ์—์„œ ์‚ฌ์ง„์„ ์—…๋กœ๋“œ ๋ฐ ์‚ญ์ œํ•˜๋Š” ์ฐฝ์„ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด, S3 ๋ฒ„ํ‚ท์— ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•˜๊ณ  URL์„ ๋ฐ˜ํ™˜ํ•œ๋‹ค. X ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด S3 ๋ฒ„ํ‚ท์—์„œ ํ•ด๋‹น ์‚ฌ์ง„์„ ์‚ญ์ œํ•œ๋‹ค. ์ดํ›„ ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด DB์— ์ €์žฅ๋˜๋„๋ก ํ•œ๋‹ค. ์ด ๋กœ์ง์„ 3๊ฐœ๋กœ ๋ถ„๋ฆฌํ•œ๋‹ค. 

๐Ÿ”ฝ ๊ตฌํ˜„ ์ฝ”๋“œ

/**
 * S3์—์„œ ๊ฐ€๊ฒŒ ์‚ฌ์ง„์„ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
 */
public void deleteMarketImage(String imageUrl) {
    s3ImageService.deleteImage(imageUrl);
}
/**
 * S3์— ๊ฐ€๊ฒŒ ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•ฉ๋‹ˆ๋‹ค.
 */
public MarketImageUrlResponse uploadMarketImage(MultipartFile uploadImage) {
    return MarketImageUrlResponse.builder()
            .imageUrl(s3ImageService.uploadImage(uploadImage))
            .build();
}
/**
 * ๊ฐ€๊ฒŒ ์‚ฌ์ง„์„ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
 */
@Transactional
protected void updateMarketImages(Market market, List<String> imageUrls) {
    // ๊ธฐ์กด ์‚ฌ์ง„ ์กฐํšŒ
    List<MarketImage> marketImageList = marketImageRepository.findAllByMarketId(market.getId());

    // DB์—์„œ ๊ธฐ์กด ์‚ฌ์ง„ URL ๋ชจ๋‘ ์‚ญ์ œ
    marketImageRepository.deleteAll(marketImageList);

    // DB์— ์ƒˆ๋กœ์šด ์‚ฌ์ง„ URL ๋ชจ๋‘ ์ €์žฅ
    for (String imageUrl : imageUrls) {
        MarketImage updateImage = MarketImage.builder()
                .market(market)
                .imageUrl(imageUrl)
                .build();

        marketImageRepository.save(updateImage);
    }
}

๐Ÿ”ฝ ๋ฌธ์ œ์ 

  1.  ๋กœ์ง์„ ๋ถ„๋ฆฌํ•˜์—ฌ ๊ฐ ์ž‘์—…์˜ ์ฑ…์ž„์„ ๋œ๊ณ  ์˜ค๋ฅ˜ ๋ฐœ์ƒ ๋ฒ”์œ„๋ฅผ ์ค„์˜€์ง€๋งŒ, ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ๋ฅผ ๋ˆ„๋ฅด์ง€ ์•Š์œผ๋ฉด S3 ๋ฒ„ํ‚ท์— ๊ณ ์•„ ๊ฐ์ฒด๊ฐ€ ์ƒ์„ฑ๋˜์–ด ์—ฌ์ „ํžˆ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•˜๊ฒŒ ๋œ๋‹ค.
    • ์ด ๋ฌธ์ œ๋ฅผ 6๋ฒˆ์—์„œ ์ด์–ด์„œ ํ•ด๊ฒฐํ–ˆ๋‹ค.
  2. ์‚ฌ์ง„ ์—…๋กœ๋“œ์™€ ์‚ญ์ œ๊ฐ€ ํด๋ผ์ด์–ธํŠธ์— ์˜ํ•ด ์ˆ˜๋™์œผ๋กœ ์ˆ˜ํ–‰๋˜๋„๋ก ๋‚˜๋ˆ„์–ด์กŒ๊ธฐ ๋•Œ๋ฌธ์—, ์‚ฌ์šฉ์ž๊ฐ€ ์„œ๋ฒ„์™€์˜ ๋ชจ๋“  ์ž‘์—…์„ ์˜ฌ๋ฐ”๋ฅด๊ฒŒ ์™„๋ฃŒํ•ด์•ผ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์ด ์œ ์ง€๋œ๋‹ค. ๋”ฐ๋ผ์„œ ํด๋ผ์ด์–ธํŠธ์™€ ์„œ๋ฒ„ ๊ฐ„์˜ ์˜์กด์„ฑ์ด ์ƒ๊ธด๋‹ค.

 

โ–ถ 6. ํ•ด๊ฒฐ2: Lifecycle ๊ทœ์น™์„ ์‚ฌ์šฉํ•˜์—ฌ ๊ฐ์ฒด์˜ ์ˆ˜๋ช… ์ฃผ๊ธฐ๋ฅผ ๊ด€๋ฆฌ

  • 5๋ฒˆ ๋ฐฉ๋ฒ•์—์„œ ๋ฐœ์ƒํ•œ ๋ฌธ์ œ์ธ, ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด์ง€ ์•Š์œผ๋ฉด S3 ๋ฒ„ํ‚ท์— ๊ณ ์•„ ๊ฐ์ฒด๊ฐ€ ๋‚จ๋Š” ์ƒํ™ฉ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด S3 Lifecycle ๊ทœ์น™์„ ๋„์ž…ํ•œ๋‹ค.
  • ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•  ๋•Œ temp ํด๋”์— ์ž„์‹œ ์ €์žฅํ•˜๊ณ , ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด main ํด๋”๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ด๋™์‹œํ‚ค๋Š” ๋ฐฉ์‹์ด๋‹ค.

๐Ÿ”ฝ S3 Bucket ์„ค์ •

  • S3 Bucket์—์„œ ์ˆ˜๋ช… ์ฃผ๊ธฐ ๊ทœ์น™ ์ƒ์„ฑ์„ ํด๋ฆญํ•œ๋‹ค.

  • ์ž„์‹œ๋กœ ํŒŒ์ผ์„ ์ €์žฅํ•  ํด๋” ์ด๋ฆ„์„ ์ ‘๋‘์‚ฌ๋กœ ์„ค์ •ํ•œ๋‹ค.

  • ๊ฐ์ฒด์˜ ๋งŒ๋ฃŒ ์ผ์ž๋ฅผ ์„ค์ •ํ•œ๋‹ค.

  • ์„ค์ • ์™„๋ฃŒ ์ดํ›„ ํ™”๋ฉด์ด๋‹ค.

๐Ÿ”ฝ ๊ตฌํ˜„ ์ฝ”๋“œ

1. tmp ํด๋”์— ์ด๋ฏธ์ง€ ์ž„์‹œ ์ €์žฅ

์‚ฌ์šฉ์ž๊ฐ€ ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•  ๋•Œ, ์•„์ง ์ตœ์ข…์ ์œผ๋กœ ์—…๋ฐ์ดํŠธํ•˜์ง€ ์•Š์•˜์œผ๋ฏ€๋กœ ์ด๋ฏธ์ง€๋ฅผ ์ตœ์ข… ์ €์žฅ์†Œ๊ฐ€ ์•„๋‹Œ S3์˜ temp ํด๋”์— ์ž„์‹œ๋กœ ์ €์žฅํ•œ๋‹ค.

/**
 * S3 temp ํด๋”์— ํŒŒ์ผ ์—…๋กœ๋“œ
 */
public String uploadImageToTemp(MultipartFile file) {
    String fileName = "temp/" + UUID.randomUUID() + "_" + file.getOriginalFilename();

    try {
        ObjectMetadata metadata = new ObjectMetadata();
        metadata.setContentLength(file.getSize());
        metadata.setContentType(file.getContentType());

        amazonS3.putObject(bucketName, fileName, file.getInputStream(), metadata);

        return amazonS3.getUrl(bucketName, fileName).toString();
    } catch (IOException e) {
        throw new S3Exception(S3ErrorCode.FILE_UPLOAD_ERROR);
    }
}

2. ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ ํด๋ฆญ ์‹œ ์ด๋ฏธ์ง€ ์ด๋™

์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด, temp ํด๋”์— ์ €์žฅ๋œ ์ด๋ฏธ์ง€๋ฅผ main ํด๋”๋กœ ์ด๋™์‹œํ‚จ๋‹ค.

 /**
 * S3 temp ํด๋”์—์„œ main ํด๋”๋กœ ํŒŒ์ผ ์ด๋™
 */
public void moveImagesToMain(List<String> imageUrls) {
    for (String imageUrl : imageUrls) {
        String decodedFileName = getDecodedFileName(imageUrl);

        String tempFileName = "temp/" + decodedFileName;
        String mainFileName = "main/" + decodedFileName;

        if (!doesDecodedImageExist(tempFileName)) {
            throw new S3Exception(S3ErrorCode.IMAGE_NOT_FOUND_ERROR);
        }

        // ํŒŒ์ผ ์ด๋™
        amazonS3.copyObject(bucketName, tempFileName, bucketName, mainFileName);

        // ์›๋ณธ temp ํŒŒ์ผ ์‚ญ์ œ
        amazonS3.deleteObject(bucketName, tempFileName);
    }
}
/**
 * ๊ฐ€๊ฒŒ ์ •๋ณด๋ฅผ ์—…๋ฐ์ดํŠธํ•ฉ๋‹ˆ๋‹ค.
 */
@Transactional
public void updateMarket(Long marketId, MarketUpdateRequest marketUpdateRequest) {
    // ๊ฐ€๊ฒŒ ์กฐํšŒ
    Market market = marketRepository.findById(marketId)
            .orElseThrow(() -> new MemberException(MarketErrorCode.NOT_FOUND_MARKET_ID));

    // ํ•œ ์ค„ ์†Œ๊ฐœ ์—…๋ฐ์ดํŠธ
    market.updateSummary(marketUpdateRequest.getSummary());

    // ์˜์—… ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
    market.updateBusinessHours(marketUpdateRequest.getOpenAt(), marketUpdateRequest.getCloseAt());

    // ํ”ฝ์—… ์‹œ๊ฐ„ ์—…๋ฐ์ดํŠธ
    market.updatePickUpHours(marketUpdateRequest.getPickupStartAt(), marketUpdateRequest.getPickupEndAt());

    // ๊ฐ€๊ฒŒ ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ
    updateMarketImages(market, marketUpdateRequest.getImageUrls());
    s3ImageService.moveImagesToMain(marketUpdateRequest.getImageUrls()); // S3 Bucket: temp -> main
}

3. S3์—์„œ ํŒŒ์ผ ์‚ญ์ œ ๋ฉ”์„œ๋“œ ์ˆ˜์ •

temp ํด๋”์™€ main ํด๋”๊ฐ€ ๋‚˜๋‰˜์—ˆ๊ธฐ ๋•Œ๋ฌธ์—, S3์—์„œ ํŒŒ์ผ์„ ์‚ญ์ œํ•˜๋Š” ๋กœ์ง๋„ ์ด์— ๋งž๊ฒŒ ๋ณ€๊ฒฝํ•ด์•ผ ํ•œ๋‹ค. temp ํด๋”์— ์žˆ๋Š” ํŒŒ์ผ์€ S3 Lifecycle ๊ทœ์น™์— ์˜ํ•ด ์ž๋™์œผ๋กœ ์‚ญ์ œ๋˜๋ฏ€๋กœ, main ํด๋”์— ์žˆ์„ ๊ฒฝ์šฐ์—๋งŒ ์‚ญ์ œํ•˜๋„๋ก ์ˆ˜์ •ํ•œ๋‹ค.

/**
 * S3์—์„œ ํŒŒ์ผ ์‚ญ์ œ
 * main ํด๋”์— ์žˆ์„ ๊ฒฝ์šฐ์—๋งŒ ์‚ญ์ œํ•ฉ๋‹ˆ๋‹ค.
 */
public void deleteImage(String imageUrl) {
    String decodedFileName = getDecodedFileName(imageUrl);
    String mainFileName = "main/" + decodedFileName;

    if (doesDecodedImageExist(mainFileName)) {
        amazonS3.deleteObject(bucketName, mainFileName);
    }
}

 

 

๐Ÿ” ๊ฒฐ๊ณผ ๊ด€์ฐฐ

5๋ฒˆ๊ณผ 6๋ฒˆ ๋ฐฉ๋ฒ•์„ ๊ฒฐํ•ฉํ•˜์—ฌ ๋ฌธ์ œ๋ฅผ ์„ฑ๊ณต์ ์œผ๋กœ ํ•ด๊ฒฐํ–ˆ๋‹ค.

  • ์‚ฌ์ง„์„ ์—…๋กœ๋“œํ•˜๋Š” API๋ฅผ ์‹คํ–‰ํ•˜๋ฉด ์ด๋ฏธ์ง€๊ฐ€ ์ผ์‹œ์ ์œผ๋กœ temp ํด๋”์— ์ €์žฅ๋œ๋‹ค. 
  • ์ดํ›„, ์‚ฌ์šฉ์ž๊ฐ€ ์‚ฌ์ง„ ์—…๋ฐ์ดํŠธ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ์ด๋ฏธ์ง€๊ฐ€ main ํด๋”๋กœ ์ด๋™๋œ๋‹ค.
  • temp ํด๋”์™€ main ํด๋”๊ฐ€ ๊ฐ๊ฐ ๋ช…ํ™•ํ•˜๊ฒŒ ๋ถ„๋ฆฌ๋˜์–ด ์ •์ƒ์ ์œผ๋กœ ์ž‘๋™ํ•œ๋‹ค.

๋กœ์ง์„ ๋ถ„๋ฆฌํ•จ์œผ๋กœ์จ ๊ฐ ์ž‘์—…์˜ ์ฑ…์ž„์„ ๋ช…ํ™•ํžˆ ํ•  ์ˆ˜ ์žˆ์—ˆ๊ณ , S3 Lifecycle ๊ทœ์น™์„ ํ†ตํ•ด ๊ณ ์•„ ๊ฐ์ฒด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ–ˆ๋‹ค. ์ด๋กœ์จ ๋ฐ์ดํ„ฐ ์ •ํ•ฉ์„ฑ์„ ํšจ๊ณผ์ ์œผ๋กœ ์œ ์ง€ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค.

 

 

๐Ÿ’ก ๊ณ ์ฐฐ

์ด๋ฒˆ ํ•ด๊ฒฐ์ฑ…์€ S3 ๋ฒ„ํ‚ท์— ๋‚จ๋Š” ๊ณ ์•„ ๊ฐ์ฒด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ ํšจ๊ณผ์ ์ด์—ˆ์ง€๋งŒ, S3์˜ PutObject, deleteObject, copyObject ์ž‘์—…์„ ๋ฐ˜๋ณตํ•˜๋ฉด์„œ ๋ฐœ์ƒํ•˜๋Š” ํŠธ๋ž˜ํ”ฝ ๋น„์šฉ์„ ๊ณ ๋ คํ•  ํ•„์š”๊ฐ€ ์žˆ๋‹ค. S3๋Š” ์š”์ฒญ์— ๋น„๋ก€ํ•ด ๋น„์šฉ์ด ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, ๋นˆ๋ฒˆํ•œ ์ด๋ฏธ์ง€ ์—…๋กœ๋“œ ๋ฐ ์‚ญ์ œ ์ž‘์—…์ด ์ด๋ฃจ์–ด์งˆ ๊ฒฝ์šฐ ์˜ˆ์ƒ์น˜ ๋ชปํ•œ ๋น„์šฉ ์ฆ๊ฐ€๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  1. ์„œ๋ฒ„ ์ €์žฅ์†Œ๋ฅผ ํ™œ์šฉํ•œ ๋น„์šฉ ์ ˆ๊ฐ: ํ˜„์žฌ๋Š” ์ด๋ฏธ์ง€๋ฅผ ์ฆ‰์‹œ temp ํด๋”์— ์ €์žฅํ•˜๊ณ  ์žˆ์œผ๋‚˜, ์ด๋ฅผ ์„œ๋ฒ„์˜ ๋กœ์ปฌ ์ €์žฅ์†Œ๋‚˜ ์บ์‹œ์— ์ž„์‹œ๋กœ ์ €์žฅํ•œ ํ›„, ํ•„์š”ํ•  ๋•Œ S3๋กœ ์—…๋กœ๋“œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์„ ํ†ตํ•ด S3์™€ ๋ฐ์ดํ„ฐ ์ „์†ก ํšŸ์ˆ˜๋ฅผ ์ค„์ด๊ณ , 4๋ฒˆ ๋ฐฉ์‹์—์„œ ์„ค๋ช…ํ•œ ์Šค์ผ€์ค„๋Ÿฌ๋ฅผ ํ™œ์šฉํ•ด ์ฃผ๊ธฐ์ ์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ S3๋กœ ์—…๋กœ๋“œํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ์ „ํ™˜ํ•˜๋ฉด ํšจ์œจ์„ฑ์„ ๋†’์ผ ์ˆ˜ ์žˆ๋‹ค.
  2. AWS Lambda ํ™œ์šฉ: AWS Lambda๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฒคํŠธ ๊ธฐ๋ฐ˜์œผ๋กœ ์ด๋ฏธ์ง€๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๋ฐฉ์‹๋„ ๊ณ ๋ คํ•ด ๋ณผ ์ˆ˜ ์žˆ๋‹ค. Lambda๋Š” ํŠน์ • ์ด๋ฒคํŠธ(ํŒŒ์ผ ์—…๋กœ๋“œ, ํŒŒ์ผ ๋ฏธ์‚ฌ์šฉ ๊ธฐ๊ฐ„ ๊ฒฝ๊ณผ ๋“ฑ)๊ฐ€ ๋ฐœ์ƒํ•  ๋•Œ ์ž๋™์œผ๋กœ ์‹คํ–‰๋˜๋ฏ€๋กœ, S3 ๋ฒ„ํ‚ท์— ์ €์žฅ๋œ ํŒŒ์ผ๋“ค์„ ์ฃผ๊ธฐ์ ์œผ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ๋ถˆํ•„์š”ํ•œ ๊ณ ์•„ ๊ฐ์ฒด๋ฅผ ์ž๋™์œผ๋กœ ์ •๋ฆฌํ•˜๊ณ , ํ•„์š”ํ•  ๋•Œ์—๋งŒ S3 ์ž‘์—…์„ ์ˆ˜ํ–‰ํ•˜๋Š” ํšจ์œจ์ ์ธ ๋ฐฉ๋ฒ•์„ ๋„์ž…ํ•  ์ˆ˜ ์žˆ๋‹ค.

 

์ถ”๊ฐ€์ ์ธ ๊ณ ์ฐฐ๋กœ, ํ˜„์žฌ ์‚ฌ์ง„์„ ์—…๋ฐ์ดํŠธํ•  S3 ๋ฒ„ํ‚ท์˜ ๊ธฐ์กด ์‚ฌ์ง„๊ณผ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค์— ์ €์žฅ๋œ URL์„ ๋ชจ๋‘ ์‚ญ์ œํ•œ ํ›„, ์ƒˆ๋กœ์šด ์‚ฌ์ง„์„ S3 ๋ฒ„ํ‚ท์— ์—…๋กœ๋“œํ•œ ํ›„ DB์— URL์„ ์ €์žฅํ•˜๋Š” ๋ฎ์–ด์“ฐ๊ธฐ ๋ฐฉ์‹์„ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋‹ค. ์ด ๋ฐฉ์‹์€ ๋ถˆํ•„์š”ํ•˜๊ฒŒ ๋งŽ์€ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐœ์ƒํ•˜๋ฏ€๋กœ, ๊ฐœ์„ ์ด ํ•„์š”ํ•˜๋‹ค๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

์ด์™€ ๊ด€๋ จ๋œ ์‚ฌํ•ญ์„ ๊ฐœ์„ ํ–ˆ์œผ๋ฉฐ, ์ž์„ธํ•œ ๊ฐœ์„  ๋‚ด์šฉ์€ ์—ฌ๊ธฐ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด ์ข‹์„ ๊ฒƒ ๊ฐ™๋‹ค.
 
 

๐Ÿ“ ์ฐธ๊ณ  ์ž๋ฃŒ