AWS Lambda๋ฅผ ์ด์šฉํ•œ Image Resize ๋„์ž… (์ธ๋„ค์ผ ์ด๋ฏธ์ง€ ์„ฑ๋Šฅ ๊ฐœ์„ )

1. Lambda ๋„์ž… ์ด์œ 

๋งŒ์•ฝ 50X50 ์‚ฌ์ด์ฆˆ์˜ ์ด๋ฏธ์ง€๋ฅผ ๋กœ๋”ฉํ•˜๋Š”๋ฐ 10MB ์งœ๋ฆฌ 400X400 ์‚ฌ์ด์ฆˆ์˜ ์ด๋ฏธ์ง€๋ฅผ ์ด์šฉํ•œ๋‹ค๋ฉด ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„๊ฐ€ ์‹ฌํ•˜๋‹ค. 

์œ„์™€ ๊ฐ™์€ ๊ฒฝ์šฐ๊ฐ€ ํ•œ ๊ฐœ๊ฐ€ ์•„๋‹ˆ๋ผ ์—ฌ๋Ÿฌ ๊ฐœ์ผ ๊ฒฝ์šฐ ์‚ฌ์šฉ์ž๊ฐ€ ์ฒด๊ฐํ•˜๋Š” ์‘๋‹ต ์‹œ๊ฐ„์€ ๊ฝค ํด ๊ฒƒ์ด๋‹ค. 

 

์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ๋ฆฌ์‚ฌ์ด์ง•(RTesizing)๊ธฐ์ˆ ์„ ์‚ฌ์šฉํ•˜์—ฌ ์ด๋ฏธ์ง€ ์šฉ๋Ÿ‰์„ ์ค„์ž„์œผ๋กœ์จ ์†๋„๋ฅผ ๋น ๋ฅด๊ฒŒํ•˜๋Š” ๋ฐฉ๋ฒ•๋ก ์€ ์ด๋ฏธ ๋งŽ์€ ์‚ฌ๋žŒ๋“ค์ด ์‚ฌ์šฉํ•˜๊ณ ์žˆ๋‹ค. 

 

ํ•˜์ง€๋งŒ ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ์ž‘์—… ๊ฐ™์€ ๊ฒฝ์šฐ CPU์™€ ๋ฉ”๋ชจ๋ฆฌ๋ฅผ ๋งŽ์ด ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์— ๋กœ์ปฌ ์„œ๋ฒ„์—์„œ ์ž‘์—…์„ ๋Œ๋ ค๋ฒ„๋ฆฌ๋ฉด ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์š”์ฒญ์„ ๋ชป๋ฐ›๋Š” ํ˜„์ƒ์ด ์ƒ๊ธธ์ˆ˜ ์žˆ๋‹ค.

 

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

 

 

์ด๋Ÿฐ ์ƒํ™ฉ์—์„œ AWS Lambda์˜ Server Less ์„œ๋น„์Šค๋Š” ํ•ด๊ฒฐ์ฑ…์ด ๋  ์ˆ˜ ์žˆ๋‹ค. 

 

๋žŒ๋‹ค๋Š” ํŠน์ •ํ•œ ๋™์ž‘์„ ์ˆ˜ํ–‰ํ•˜๋Š” ๋กœ์ง์„ ์ €์žฅํ•˜๊ณ  ์š”์ฒญ์ด ๋“ค์–ด์˜ฌ ๋•Œ ๋กœ์ง์„ ์‹คํ–‰ํ•˜๋Š” ์„œ๋น„์Šค๋‹ค. ๋งˆ์น˜ ํ•จ์ˆ˜์ฒ˜๋Ÿผ ํ˜ธ์ถœํ•  ๋•Œ ์‹คํ–‰ํ•˜์—ฌ FaaS๋ผ๊ณ  ๋ถˆ๋ฆฐ๋‹ค. ์ด์ฒ˜๋Ÿผ ๋žŒ๋‹ค๋ฅผ ์ด์šฉํ•ด ๋…๋ฆฝ์ ์ธ ์„œ๋ฒ„์—์„œ ํ•จ์ˆ˜๋ฅผ ์‹คํ–‰ ์‹œ์ผœ, ๋กœ์ปฌ ์„œ๋ฒ„์˜ ๋ถ€๋‹ด์„ ์ค„์ผ์ˆ˜ ์žˆ์„ ๋ฟ๋งŒ ์•„๋‹ˆ๋ผ ์˜จ๋””๋ฉ˜๋“œ(on-demand) ํ˜•์‹์ด๋ผ ์“ด๋งŒํผ ์š”๊ธˆ์„ ์ง€๋ถˆํ•˜๋ฉด ๋˜์„œ ํ•ญ์ƒ ์ปดํ“จํŒ…์„ ์‹คํ–‰์‹œํ‚ค๋Š” ์ž์› ๋‚ญ๋น„๋ฅผ ์ค„์ผ์ˆ˜ ์žˆ๋‹ค. 

 

๋žŒ๋‹ค์˜ ์š”๊ธˆ์€ ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

 

 

 

 

 

 

 

2. S3 ๋ฒ„ํ‚ท ์ƒ์„ฑ

๋จผ์ € ๊ธฐ์กด ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•˜๋˜ S3๊ฐ€ ์žˆ๋‹ค๋ฉด ์ƒˆ๋กœ์šด S3 ๋ฒ„ํ‚ท์„ ์ƒ์„ฑํ•ด์ค€๋‹ค. 

ํ•˜๋‚˜ ๋ฒ„ํ‚ท์—์„œ ๊ฒฝ๋กœ๋ฅผ ๋‚˜๋ˆ  ํ•ด๋„ ๊ฐ€๋Šฅ์€ ํ•˜์ง€๋งŒ, ์žฌ๊ท€ ํ˜ธ์ถœ์ด ๋  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฒ„ํ‚ท์„ ๋‚˜๋ˆ„๋Š” ๊ฒƒ์„ AWS์—์„œ๋„ ๊ถŒ์žฅํ•˜๊ณ ์žˆ๋‹ค. 

 

 

๋‚˜์˜ ๊ฒฝ์šฐ ์ด๋ ‡๊ฒŒ ์ด 3๊ฐœ์˜ ๋ฒ„ํ‚ท์„ ์ด์šฉํ•˜๊ณ ์žˆ๋‹ค. 

 

1. jikgong-bucket: ๋‚˜์ค‘์— ๋’ค์—์„œ Lambda๋ฅผ ์ƒ์„ฑํ•  ๋•Œ ์†Œ์Šค ์ฝ”๋“œ๊ฐ€ 10MB ์ด์ƒ์ผ ๊ฒฝ์šฐ S3์— ์˜ฌ๋ ค์„œ ์‚ฌ์šฉํ•ด์•ผํ•˜๋Š”๋ฐ, ๊ทธ๋•Œ ์‚ฌ์šฉ

 

2. jikgong-image: ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•  ๋ฒ„ํ‚ท

 

3. jikgong-resize-bucket: jikgong-image์— ์˜ฌ๋ผ๊ฐ„ ์ด๋ฏธ์ง€๋ฅผ ๋ฆฌ์‚ฌ์ด์ง• ํ•ด ์ €์žฅํ•  ๋ฒ„ํ‚ท

 

S3 ์ƒ์„ฑ์€ ๋‹ค๋ฅธ ๋ธ”๋กœ๊ทธ์— ์ž˜ ๋‚˜์™€์žˆ์œผ๋‹ˆ ๋”ฐ๋กœ ๋‹ค๋ฃจ์ง„ ์•Š๊ฒ ๋‹ค. (๊ฐœ์ธ์ ์œผ๋กœ ์ธํŒŒ๋‹˜ ๋ธ”๋กœ๊ทธ๊ฐ€ ์ž์„ธํ•˜๊ฒŒ ์ž˜ ์•Œ๋ ค์ฃผ์‹œ๋Š”๋“ฏ)

 

 

 

 

 

 

3. IAM ์ •์ฑ…  & ์—ญํ•  ์ƒ์„ฑ

AWS IAM ์ •์ฑ…์„ ์ƒ์„ฑํ•ด์ค˜์•ผํ•œ๋‹ค. 

 

์ •์ฑ… ์ƒ์„ฑ

 

์ง์ ‘ JSON์„ ์ž…๋ ฅํ•ด์ค€๋‹ค. 

jikgong-image ๋ฒ„ํ‚ท์— ๋Œ€ํ•œ ๊ถŒํ•œ์€ s3:GetObject, jikgong-resize-bucket ์— ๋Œ€ํ•œ ๊ถŒํ•œ์€ s3:PutObject, s3:PutObjectAcl ์„ ๋ถ€์—ฌํ•ด์ค€๋‹ค. 

 

๋‚˜์˜ ๊ฒฝ์šฐ์—” PutObjectAcl ๊ถŒํ•œ๊นŒ์ง€ ๋ถ€์—ฌํ•ด์ค˜์•ผ Lambda ํ•จ์ˆ˜๊ฐ€ ์ •์ƒ์ ์œผ๋กœ ๋™์ž‘ํ–ˆ๋‹ค. ์—†์œผ๋ฉด Access Denied ์—๋Ÿฌ๊ฐ€ ๋–ด์—ˆ๋‹ค.

 

AWS Lambda ์‚ฌ์šฉ ์‹œ S3 Access Denied ์—๋Ÿฌ ํ•ด๊ฒฐ

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Action": [
                "logs:PutLogEvents",
                "logs:CreateLogGroup",
                "logs:CreateLogStream"
            ],
            "Resource": "arn:aws:logs:*:*:*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:GetObject"
            ],
            "Resource": "arn:aws:s3:::jikgong-image/*"
        },
        {
            "Effect": "Allow",
            "Action": [
                "s3:PutObject",
                "s3:PutObjectAcl" // ์ด๊ฑฐ ํ•„์š”
            ],
            "Resource": "arn:aws:s3:::jikgong-resize-bucket/*"
        }
    ]
}

 

 

 

 

 

์—ญํ•  ์ƒ์„ฑ

๋ฐ”๋กœ ์œ„์—์„œ ๋งŒ๋“  ์ •์ฑ…์„ ์‚ฌ์šฉํ•  ์—ญํ• ์„ ์ƒ์„ฑํ•ด์ค€๋‹ค.

 

 

 

 

 

 

4. Lambda ํ•จ์ˆ˜์— ์‚ฌ์šฉํ•  ํ•จ์ˆ˜ ์ƒ์„ฑ

์ด์ œ origin S3 ๋ฒ„ํ‚ท์— ์ด๋ฏธ์ง€๊ฐ€ ์—…๋กœ๋“œ ๋˜์—ˆ์„๋•Œ ์ž๋™์œผ๋กœ resized ๋ฒ„ํ‚ท์— ๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€๊ฐ€ ์ €์žฅ๋˜๋„๋ก ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค. ์—ฌ๋Ÿฌ๊ฐ€์ง€ ์–ธ์–ด๋ฅผ ์ง€์›ํ•˜์ง€๋งŒ ๋‚˜๋Š” Java17๋กœ ์ง„ํ–‰ํ•˜์˜€๋‹ค.

Node.js๋กœ ์ง„ํ–‰ํ•  ๊ฒฝ์šฐ ์ฝ˜์†”์˜ ์ฝ”๋“œ ํŽธ์ง‘๊ธฐ๋กœ ๋ฐ”๋กœ ์ฝ”๋“œ ์ž‘์„ฑ์ด ๊ฐ€๋Šฅํ•˜์ง€๋งŒ java17์€ ํ•ด๋‹น ๊ธฐ๋Šฅ์„ ์ง€์›ํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ธํ…”๋ฆฌ์ œ์ด ๋˜๋Š” ์ดํด๋ฆฝ์Šค๋ฅผ ์ด์šฉํ•ด ํ•จ์ˆ˜(๋ฉ”์†Œ๋“œ)๋ฅผ ์ž‘์„ฑ ํ›„ zipํŒŒ์ผ์„ ๋นŒ๋“œํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์ณ์•ผ ํ•œ๋‹ค.

 

- build.gradle ์˜์กด์„ฑ ์ถ”๊ฐ€

implementation group: 'com.amazonaws', name: 'aws-lambda-java-core', version: '1.2.1'
implementation group: 'com.amazonaws', name: 'aws-lambda-java-events', version: '3.7.0'
implementation group: 'com.amazonaws', name: 'aws-java-sdk', version: '1.11.969'

 

- lambda ํ•จ์ˆ˜ ์ž‘์„ฑ

์•„๋ž˜ ํ•จ์ˆ˜๋Š” AWS์—์„œ ์ œ๊ณตํ•˜๋Š” ์˜ˆ์ œ์ฝ”๋“œ๋ฅผ ๋‚˜์—๊ฒŒ ๋งž๊ฒŒ ์ˆ˜์ •ํ•œ ์ฝ”๋“œ์ด๋‹ค. 

package jikgong;

import com.amazonaws.services.lambda.runtime.LambdaLogger;
import com.amazonaws.services.lambda.runtime.events.models.s3.S3EventNotification.S3EventNotificationRecord;
import com.amazonaws.services.s3.model.CannedAccessControlList;
import com.amazonaws.services.s3.model.PutObjectRequest;
import java.awt.Color;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import javax.imageio.ImageIO;
import com.amazonaws.AmazonServiceException;
import com.amazonaws.services.lambda.runtime.Context;
import com.amazonaws.services.lambda.runtime.RequestHandler;
import com.amazonaws.services.lambda.runtime.events.S3Event;
import com.amazonaws.services.s3.AmazonS3;
import com.amazonaws.services.s3.model.GetObjectRequest;
import com.amazonaws.services.s3.model.ObjectMetadata;
import com.amazonaws.services.s3.model.S3Object;
import com.amazonaws.services.s3.AmazonS3ClientBuilder;

public class ResizeHandler implements RequestHandler<S3Event, String> {

    private static final float MAX_HEIGHT = 60;
    private final String JPG_TYPE = (String) "jpg";
    private final String JPG_MIME = (String) "image/jpeg";
    private final String JPEG_TYPE = (String) "jpeg";
    private final String JPEG_MIME = (String) "image/jpeg";
    private final String PNG_TYPE = (String) "png";
    private final String PNG_MIME = (String) "image/png";
    private final String GIF_TYPE = (String) "gif";
    private final String GIF_MIME = (String) "image/gif";

    public String handleRequest(S3Event s3event, Context context) {
        LambdaLogger logger = context.getLogger();
        try {
            S3EventNotificationRecord record = s3event.getRecords().get(0);
            String srcBucket = record.getS3().getBucket().getName(); // ์›๋ณธ ๋ฒ„ํ‚ท ์ด๋ฆ„
            String key = record.getS3().getObject().getUrlDecodedKey(); // ๊ฐ์ฒด์˜ ํ‚ค (ํŒŒ์ผ ๊ฒฝ๋กœ ๋ฐ ์ด๋ฆ„)
            String dstBucket = "jikgong-resize-bucket"; // ์ˆ˜์ •๋œ ์ €์žฅ๋  ๋ฒ„ํ‚ท ์ด๋ฆ„


            // ํŒŒ์ผ ํ™•์žฅ์ž ์ถ”์ถœ
            Matcher matcher = Pattern.compile(".*\\.([^\\.]*)").matcher(key);
            if (!matcher.matches()) {
                logger.log("Unable to infer image type for key " + key);
                return "";
            }
            String imageType = matcher.group(1);
            // ์ง€์›ํ•˜์ง€ ์•Š๋Š” ์ด๋ฏธ์ง€ ํ˜•์‹์ธ ๊ฒฝ์šฐ ๋กœ๊ทธ๋ฅผ ๋‚จ๊ธฐ๊ณ  ๋ฆฌํ„ด
            if (!(JPG_TYPE.equals(imageType)) && !(JPEG_TYPE.equals(imageType)) && !(PNG_TYPE.equals(imageType)) && !(GIF_TYPE.equals(imageType))) {
                logger.log("Skipping non-image " + key);
                return "";
            }

            // S3์—์„œ ์ด๋ฏธ์ง€ ๊ฐ€์ ธ์˜ค๊ธฐ
            AmazonS3 s3Client = AmazonS3ClientBuilder.defaultClient();
            S3Object s3Object = s3Client.getObject(new GetObjectRequest(srcBucket, key));
            InputStream objectData = s3Object.getObjectContent();

            // ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ์ฒ˜๋ฆฌ
            BufferedImage srcImage = ImageIO.read(objectData);
            int srcHeight = srcImage.getHeight();
            int srcWidth = srcImage.getWidth();
            int width = (int) (srcWidth * (MAX_HEIGHT / srcHeight)); // ๋น„์œจ์— ๋งž์ถฐ ๋„ˆ๋น„ ๊ณ„์‚ฐ
            int height = (int) MAX_HEIGHT;

            // ์ƒˆ ์ด๋ฏธ์ง€ ๋ฒ„ํผ ์ƒ์„ฑ ๋ฐ ๊ทธ๋ž˜ํ”ฝ ์„ค์ •
            BufferedImage resizedImage = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics2D g = resizedImage.createGraphics();
            g.setPaint(Color.white);
            g.fillRect(0, 0, width, height);
            g.setRenderingHint(RenderingHints.KEY_INTERPOLATION, RenderingHints.VALUE_INTERPOLATION_BILINEAR);
            g.drawImage(srcImage, 0, 0, width, height, null);
            g.dispose();

            // ๋ฐ”์ดํŠธ ๋ฐฐ์—ด๋กœ ๋ณ€ํ™˜
            ByteArrayOutputStream os = new ByteArrayOutputStream();
            ImageIO.write(resizedImage, imageType, os);
            InputStream is = new ByteArrayInputStream(os.toByteArray());
            ObjectMetadata meta = new ObjectMetadata();
            meta.setContentLength(os.size()); // ๋ฉ”ํƒ€๋ฐ์ดํ„ฐ ์„ค์ •
            meta.setContentType(getMimeType(imageType)); // MIME ํƒ€์ž… ์„ค์ •

            // ๋ฆฌ์‚ฌ์ด์ฆˆ๋œ ์ด๋ฏธ์ง€๋ฅผ S3์— ์ €์žฅ
            logger.log("Writing to: " + dstBucket + "/" + key);
            try {
                s3Client.putObject(new PutObjectRequest(dstBucket, key, is, meta).withCannedAcl(CannedAccessControlList.PublicRead));
            } catch (AmazonServiceException e) {
                logger.log(e.getErrorMessage());
                System.exit(1);
            }
            logger.log("Successfully resized " + srcBucket + "/" + key + " and uploaded to " + dstBucket + "/" + key);
            return "Ok";
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    // MIME ํƒ€์ž…์„ ๋ฐ˜ํ™˜ํ•˜๋Š” ๋ณด์กฐ ๋ฉ”์†Œ๋“œ
    private String getMimeType(String imageType) {
        switch(imageType) {
            case JPG_TYPE:
            case JPEG_TYPE:
                return JPG_MIME;
            case PNG_TYPE:
                return PNG_MIME;
            case GIF_TYPE:
                return GIF_MIME;
            default:
                return "";
        }
    }
}

 

 

build.gradle์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ฝ”๋“œ๋ฅผ ์ถ”๊ฐ€ํ•˜๊ณ  `gradle build`๋ฅผ ์ง„ํ–‰ํ•˜์ž.

task buildZip(type: Zip) {
    from compileJava
    from processResources
    into('lib') {
       from configurations.runtimeClasspath
    }
}

 

 

๋‚˜๋Š” ์•„๋ž˜์˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด zip ํŒŒ์ผ์„ ์ƒ์„ฑํ•ด์ฃผ์—ˆ๋‹ค. 

./gradlew buildZip

 

 

์ƒ์„ฑ์ด ์™„๋ฃŒ๋˜๋ฉด ์•„๋ž˜ ๊ฒฝ๋กœ์— zip ํŒŒ์ผ์ด ์ƒ์„ฑ๋œ๋‹ค.

 

์ด๋ ‡๊ฒŒ ์ƒ์„ฑํ•œ zip ํŒŒ์ผ์„ S3์— ์—…๋กœ๋“œํ•ด์ค€๋‹ค. 

ํŒŒ์ผ ์‚ฌ์ด์ฆˆ๊ฐ€ ์ปค S3๋ฅผ URL์„ ํ†ตํ•ด AWS Lambda์— ์ ์šฉํ•  ์˜ˆ์ •์ด๋‹ค.

 

 

 

 

5. Lambda ํ•จ์ˆ˜ ์ƒ์„ฑ

ํ•จ์ˆ˜ ์ด๋ฆ„์„ ์ž…๋ ฅํ•˜๊ณ , 

์‚ฌ์šฉํ•  ์–ธ์–ด(์ž‘์„ฑํ•œ ํ•จ์ˆ˜ ์–ธ์–ด)๋ฅผ ์„ ํƒํ•ด์ค€๋‹ค. ์œ„์—์„œ Java17์„ ์ด์šฉํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ๋‚˜๋Š” Java17 ์„ ํƒํ–ˆ๋‹ค.

๊ทธ๋ฆฌ๊ณ  ๊ธฐ์กด ์‹คํ–‰ ์—ญํ•  ๋ณ€๊ฒฝ์—์„œ, ์œ„์—์„œ ์ƒ์„ฑํ–ˆ๋˜ ์—ญํ• ์„ ์„ ํƒํ•ด์ค€๋‹ค. 

 

 

์ฝ”๋“œ ์†Œ์Šค๋ฅผ ์—…๋กœ๋“œ ํ•ด์ค˜์•ผํ•œ๋‹ค. ์•„๊นŒ ์œ„์—์„œ zipํŒŒ์ผ์„ S3์— ์—…๋กœ๋“œํ–ˆ๋Š”๋ฐ, ๊ทธ URL์„ ์—ฌ๊ธฐ ์ž…๋ ฅํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

 

๋ฐ”๋กœ ๋ฐ‘์— ๋Ÿฐํƒ€์ž„ ์„ค์ •์„ ํŽธ์ง‘ํ•ด์ค€๋‹ค.

์‚ฌ์šฉํ•œ ์–ธ์–ด์™€ ํŒจํ‚ค์ง€, ํด๋ž˜์Šค, ๋ฉ”์†Œ๋“œ ๋ช…์„ ์ž…๋ ฅํ•ด์ค€๋‹ค. ํŒจํ‚ค์ง€๋ช…์€ ์œ„์—์„œ ์ž‘์„ฑํ•œ ํ•จ์ˆ˜ ๋งจ ์œ„์— ๋ช…์‹œ๋˜์–ด์žˆ์„ ๊ฒƒ์ด๋‹ค. 

 

 

 

 

๋งˆ์ง€๋ง‰์œผ๋กœ ํŠธ๋ฆฌ๊ฑฐ๋ฅผ ์ถ”๊ฐ€ํ•ด์ค€๋‹ค. 

 

 

์ด๋ ‡๊ฒŒ ๋ฆฌ์‚ฌ์ด์ง•์„ ์ˆ˜ํ–‰ํ•˜๋Š” AWS Lambda ์„ค์ •์ด ๋๋‚ฌ๋‹ค. 

์ถ”๊ฐ€๋กœ ๋žŒ๋‹ค์—์„œ ์ž‘์—…ํ•œ ๋กœ๊ทธ๋“ค์€ ๋ชจ๋‹ˆํ„ฐ๋ง -> Cloud Watch Logs ์—์„œ ํ™•์ธํ•  ์ˆ˜ ์žˆ๋‹ค. 

 

 

 

 

 

 

 

6. ๊ฒฐ๊ณผ

S3์— ์ด๋ฏธ์ง€๋ฅผ ์—…๋กœ๋“œํ•˜์—ฌ AWS Lambda์—์„œ ๋ฆฌ์‚ฌ์ด์ง•์„ ์ž˜ ์ˆ˜ํ–‰ํ•˜๋Š”์ง€ ํ…Œ์ŠคํŠธํ•ด๋ณด์•˜๋‹ค. 

 

์™ผ์ชฝ์ด ์›๋ณธ ์ด๋ฏธ์ง€์˜ ํฌ๊ธฐ์ด๊ณ , ์˜ค๋ฅธ์ชฝ์€ ๋ฆฌ์‚ฌ์ด์ง• ์ž‘์—…์„ ์™„๋ฃŒํ•œ ์ด๋ฏธ์ง€์ด๋‹ค. 

์ƒ๋‹นํ•œ ํฌ๊ธฐ ์ฐจ์ด๋ฅผ ๋ณด์—ฌ์ค€๋‹ค. 

 

 

 

 

 

 

 

7. ํ”„๋กœ์ ํŠธ์—์„œ ๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€ ํ™œ์šฉ

์œ„์—์„  jikgong-image ๋ฒ„ํ‚ท์— ๋“ค์–ด๊ฐ€๋Š” ๋ชจ๋“  ์ด๋ฏธ์ง€์— ์ „๋ถ€ ํŠธ๋ฆฌ๊ฑฐ๊ฐ€ ๊ฑธ๋ ค resize๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ๋˜๋Š”๋ฐ ์‹ค์ œ๋ก  ์ธ๋„ค์ผ ์ด๋ฏธ์ง€์— ๋Œ€ํ•ด์„œ๋งŒ resize๋ฅผ ์ง„ํ–‰ํ•ด์•ผํ•œ๋‹ค. 

 

๋”ฐ๋ผ์„œ ์•„๋ž˜์™€ ๊ฐ™์ด Prefix ์กฐ๊ฑด์„ ๋‘์–ด thumbnail๋งŒ ์ฒ˜๋ฆฌํ•˜๋„๋ก ๋ณ€๊ฒฝํ•˜์˜€๋‹ค. 

 

 

์ด์ œ ์ด๋ฏธ์ง€๋ฅผ ์ €์žฅํ•  ๋•Œ ์ธ๋„ค์ผ์— ๋Œ€ํ•ด์„  storeName ์•ž์— thumbnail prefix๋ฅผ ๋ถ™์—ฌ์ฃผ๋ฉด ๊ทธ ์ด๋ฏธ์ง€์— ๋Œ€ํ•ด์„œ๋งŒ resize๋ฅผ ์ง„ํ–‰ํ•˜๊ฒŒ ๋  ๊ฒƒ์ด๋‹ค.

 

- ์ €์žฅํ•  ๋•Œ prefix ์„ค์ •

// unique ์ด๋ฆ„ ์ƒ์„ฑ
String storeImageName = createStoreImageName(extension);
// ์ธ๋„ค์ผ ์ด๋ฏธ์ง€๋ผ๋ฉด Prefix๋ฅผ ๋“ฑ๋กํ•ด AWS Lambda๊ฐ€ ์‹คํ–‰๋˜๋„๋ก ์„ธํŒ…
String prefix = isFirst ? "thumbnail_" : "";
storeImageName = "jobPost/" + prefix + storeImageName;

 

- thumbnail url ์กฐํšŒ

    // ๋ฒ„ํ‚ท์—์„œ ์ด๋ฏธ์ง€ ์กฐํšŒ
    public String getImgPath(String fileName) {
        if (amazonS3Client.doesObjectExist(bucket, fileName)) {
            return amazonS3Client.getUrl(bucket, fileName).toString();
        } else {
            throw new CustomException(ErrorCode.S3_NOT_FOUND_FILE_NAME);
        }
    }

    // resize๋ฒ„ํ‚ท์—์„œ ์ด๋ฏธ์ง€ ์กฐํšŒ
    public String getThumbnailImgPath(String fileName) {
        if (amazonS3Client.doesObjectExist(resize_bucket, fileName)) {
            return amazonS3Client.getUrl(resize_bucket, fileName).toString();
        } else {
            // resize_bucket์— ํŒŒ์ผ์ด ์—†์„ ๊ฒฝ์šฐ, ๋ฉ”์ธ bucket์—์„œ ๊ฒ€์ƒ‰
            if (amazonS3Client.doesObjectExist(bucket, fileName)) {
                log.warn("resize_bucket ์—์„œ ์กฐํšŒ๋ฅผ ์‹œ๋„ํ–ˆ์ง€๋งŒ, bucket์—๋งŒ ์กด์žฌ");
                return amazonS3Client.getUrl(bucket, fileName).toString();
            } else {
                // ๋‘ ๋ฒ„ํ‚ท ๋ชจ๋‘์— ํŒŒ์ผ์ด ์—†์„ ๊ฒฝ์šฐ ์˜ˆ์™ธ ๋ฐœ์ƒ
                throw new CustomException(ErrorCode.S3_NOT_FOUND_FILE_NAME);
            }
        }
    }

 

 

 

 

๋‚˜์˜ ํ”„๋กœ์ ํŠธ์—์„œ ๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ๋Š” ๋ชจ์ง‘ ๊ณต๊ณ  ๋ชฉ๋ก์„ ์กฐํšŒํ•  ๋•Œ์ด๋‹ค. ์ด๋•Œ ๋งŽ์€ ์ธ๋„ค์ผ ์ด๋ฏธ์ง€๋“ค์„ ์ž‘์€ ํฌ๊ธฐ๋กœ ๋ณด์—ฌ์ฃผ๊ฒŒ ๋œ๋‹ค. ๋งŒ์•ฝ ์—ฌ๊ธฐ ์‚ฌ์šฉ๋  ์ด๋ฏธ์ง€๋กœ ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ์‚ฌ์šฉํ•˜๊ฒŒ ๋  ๊ฒฝ์šฐ ๋กœ๋”ฉ ์‹œ๊ฐ„์ด ๊ธธ์–ด์ง€๊ฒŒ๋œ๋‹ค. 

 

๋”ฐ๋ผ์„œ ์ผ์ž๋ฆฌ ๋ชจ์ง‘๊ณต๊ณ  ๋ชฉ๋ก ์กฐํšŒ ์‹œ thumbnail ์ด๋ฏธ์ง€๋ฅผ ์ฐพ์•„ resize๋œ url์„ ๋ฐ˜ํ™˜ํ•  ์ˆ˜ ์žˆ๋„๋ก ์ฝ”๋“œ๋ฅผ ์งฐ๋‹ค. 

์•„๋ž˜๋Š” ์ผ๋ถ€ ์ฝ”๋“œ์ด๋‹ค.

// querydsl
Page<JobPost> jobPostPage = jobPostRepository.getMainPage(worker.getId(), techList, workDateList, scrap, meal, park, location, sortType, pageable);
List<JobPostListResponse> jobPostListResponseList = jobPostPage.getContent().stream()
        .map(jobPost -> {
            // Thumbnail ์ด๋ฏธ์ง€ ์ถ”์ถœ
            Optional<JobPostImage> thumbnailImage = jobPost.getJobPostImageList().stream()
                    .filter(JobPostImage::isThumbnail)
                    .findFirst();

            // Thumbnail URL์„ ์ถ”์ถœํ•˜๊ณ , ์ธ๋„ค์ผ ์ด๋ฏธ์ง€๊ฐ€ ์—†์„ ๊ฒฝ์šฐ null ์ฒ˜๋ฆฌ
            String thumbnailS3Url = thumbnailImage
                    .map(JobPostImage::getStoreImgName)  // Optional์ด ๋น„์–ด์žˆ์ง€ ์•Š๋‹ค๋ฉด getStoreImgName ์‹คํ–‰
                    .map(s3Handler::getThumbnailImgPath)  // s3Handler๋ฅผ ์‚ฌ์šฉํ•ด url ์กฐํšŒ
                    .orElse(null);  // Thumbnail ์ด๋ฏธ์ง€๊ฐ€ ์—†์œผ๋ฉด null ๋ฐ˜ํ™˜

            return JobPostListResponse.from(jobPost, location, thumbnailS3Url);
        })
        .collect(Collectors.toList());

 

 

 

 

 

 

8. ์„ฑ๋Šฅ ๋น„๊ต

๋™์ผํ•œ ์‚ฌ์ง„์— resize๋œ ์ธ๋„ค์ผ ์ด๋ฏธ์ง€ url์„ ๋žœ๋”๋ง ํ–ˆ์„ ๋•Œ Latency์™€, ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ๋žœ๋”๋ง ํ–ˆ์„ ๋•Œ Latency์ด๋‹ค. 

 

์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ฐ๋งŒ ๊ณ„์‚ฐํ•˜๋ฉด

๋ฆฌ์‚ฌ์ด์ง•๋œ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ฐ ์ด 926ms๊ฐ€ ๊ฑธ๋ ธ๊ณ , ์›๋ณธ ์ด๋ฏธ์ง€๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š”๋ด 19090ms๊ฐ€ ๊ฑธ๋ ธ๋‹ค.

 

๋Œ€๋žต 95.15%์˜ ์„ฑ๋Šฅ์„ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ์—ˆ๋‹ค.