AWS
SVG 자동 최적화 파이프라인: S3 이벤트 알림과 Lambda로 구축하기
2025.05.12
•
9 min read
안녕하세요, 코멘토 프론트엔드 개발자 윤종석입니다. 오늘은 오류 해결에서 시작해서 새로운 기능을 알게 되어 귀찮고 빼먹기 쉬운 업무를 편리하게 만든 과정을 공유해보려고 합니다.
잊어버리지 않으려면 자동화를 해야 하는데
코멘토는 아이콘 대부분을 SVG 형식으로 사용하고 있습니다. 디자인 팀에서 커스텀 아이콘이나 이미지를 점점 더 많이 만들어주시면서 사용도 늘고 있는 추세인데요. 어느 날 아이콘 변경 요청을 받았고 AWS S3에 업로드되어있는 SVG 파일을 교체하면서 문제가 시작됐습니다.
아이콘을 교체하고 나니 앱에서 아이콘이 노출되지 않는 문제가 있었는데요. 앱 개발자 수빈님이 조사한 끝에, Figma에서 추출한 SVG에 잘못된 값이 들어가는 문제가 있었고 앱에서 아이콘이 보이지 않았던 이유가 이 값인 것을 발견하셨습니다. Figma의 플러그인을 사용해서 추출하면 이런 문제가 없는 것까지는 알아냈는데요. 다만, 플러그인을 사용할 권한이 계정 전부에 있지는 않았고, 권한이 있더라도 개발자가 플러그인을 사용하는 것을 잊어버리면 문제는 여전히 발생할 가능성이 있었습니다.
다행히, 다른 방법을 찾을 수 있었는데요. SVGOMG같은 사이트를 이용해서 SVG를 최적화하면 용량도 줄어들면서 문제가 되는 이상한 값도 없어지는 것을 발견했습니다. 간단하게 웹으로 처리할 수 있어서 플러그인보다 괜찮은 해결책이라고 생각했는데요. 하지만 여전히 개발자가 업로드 전에 직접 최적화를 하지 않으면 해결되지 않는 문제가 있었습니다. 그래서 우리가 이미지를 AWS S3에 업로드할 때 이런 작업을 자동으로 처리할 방법이 없는지 찾아보게 되었습니다.
이런 기능이 분명히 있을 것 같아서 ChatGPT에 질문하니 역시 답변을 바로 얻을 수 있었습니다. S3에는 이벤트 알림이라는 기능이 있습니다. S3에서 객체가 생성되거나 변경되는 등의 이벤트가 발생하면 AWS 내 Lambda, SNS 같은 서비스를 실행할 수 있는데요. 이번에는 SVG 객체를 생성하면 최적화하는 Lambda 함수를 실행하는 이벤트 알림을 만들었습니다.

SVG 최적화 Lambda 만들기
위에서 소개한 SVGOMG 같은 SVG 최적화 서비스는 SVGO라는 SVG 최적화 패키지를 사용하고 있습니다. Node.js 기반의 패키지라서 Lambda에서도 간단하게 사용할 수 있습니다. Lambda 함수의 일부분을 보면서 간단하게 어떤 과정을 거치는지 함께 보겠습니다.
import { S3Client, GetObjectCommand, PutObjectCommand } from "@aws-sdk/client-s3";
import { optimize } from "svgo";
...
// 어떤 파일로 이벤트가 발생했는지 확인
const record = event.Records[0];
const bucket = record.s3.bucket.name;
const key = decodeURIComponent(record.s3.object.key.replace(/\\+/g, " "));
// SVG 파일 다운로드
const object = await s3.send(new GetObjectCommand({ Bucket: bucket, Key: key }));
const svgData = await object.Body.transformToString();
// SVGO로 최적화
const optimizedSVG = optimize(svgData, { multipass: true }).data;
// 최적화된 파일을 덮어쓰기
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: optimizedSVG,
ContentType: "image/svg+xml",
}));
먼저 가장 중요한 파일 변환 과정입니다. Lambda 함수의 handler로 넘어오는 event 객체를 이용해 어떤 곳에 파일이 생성되어서 이벤트가 발생했는지 확인합니다. 그 후 GetObject
명령어를 사용해서 파일을 가져와 문자열로 변환 후 svgo
패키지의 optimize
함수에 전달합니다. 최적화된 SVG 파일이 리턴되고 그 값을 PutObject
명령어를 사용해서 기존 파일을 수정합니다. 이렇게 하면 SVG 파일을 업로드하자마자 최적화된 파일로 교체됩니다.
// 기존 파일의 ACL 가져오기
const aclResponse = await s3.send(new GetObjectAclCommand({ Bucket: bucket, Key: key }));
const grants = aclResponse.Grants;
// 기존 ACL 다시 적용
await s3.send(new PutObjectAclCommand({
Bucket: bucket,
Key: key,
ACL: aclResponse.Grants.some(grant => grant.Grantee.URI?.includes("AllUsers")) ? "public-read" : "private"
}));
파일 교체로 끝나는 것이 아니라 기존 파일의 접근 권한(ACL)을 그대로 가져와서 적용해주는 작업까지 해줍니다. 여기까지 했으면 이제 만든 함수를 아까 확인한 이벤트 알림에 연결해보겠습니다.
S3 이벤트 알림 생성하기


S3 버킷에 들어가서 상단 메뉴의 속성을 선택한 후 아래로 내려오면 이벤트 알림 메뉴가 있습니다. 여기서 ‘이벤트 알림 생성’ 버튼을 클릭해서 새로운 알림을 생성합니다.

이벤트 이름을 정해주고 적용될 파일의 접두사와 접미사를 설정할 수 있습니다. 이번에는 SVG를 대상으로 하는 알림이기 때문에 images 폴더 아래의 SVG 파일만 대상으로 합니다.
그 아래에는 어떤 이벤트가 발생했을 때 알림이 실행되는지 정할 수 있는데 최초에 파일을 업로드할 때만 만들기 위해 전송(s3:ObjectCreated:Put
) 이벤트만 체크했습니다.

마지막으로 이벤트 알림이 어디로 가는지 설정합니다. Lambda 이외에도 다른 서비스를 활용할 수 있습니다. 여기서는 위에서 만든 Lambda 함수를 선택합니다. ARN을 직접 입력하거나 ‘Lambda 함수에서 선택’을 눌러서 목록 중에 선택할 수도 있습니다. 여기까지 설정하고 저장하면 이제 SVG 파일을 업로드할 때마다 이벤트 알림이 실행됩니다.
이제 잘 되는지 확인해볼까요? 인터넷에서 구한 아무 샘플 SVG나 구해서 업로드를 해보겠습니다.


업로드 시점에서는 3.1KB였던 SVG 파일이 업로드 후 2.2KB로 저장되어있는 것을 볼 수 있습니다. 문제 없이 잘 되는 것 같네요.
무한 최적화 막기
…사실은 그렇지 않았습니다. 이렇게 만들었는데 나중에 Lambda 콘솔에 들어가서 보니 에러가 찍히고 있었거든요. 아까 전송(Put) 이벤트에 알림을 설정했었죠. 다시 한번 최적화한 SVG를 업로드하는 코드를 보겠습니다.
// SVGO로 최적화
const optimizedSVG = optimize(svgData, { multipass: true }).data;
// 최적화된 파일을 덮어쓰기
await s3.send(new PutObjectCommand({
Bucket: bucket,
Key: key,
Body: optimizedSVG,
ContentType: "image/svg+xml",
}));
보시다시피 PutObjectCommand
를 쓰고 있습니다. 결국 파일이 업로드됨 → 이벤트 알림이 발생함 → 파일이 최적화되고 업로드됨 → 이벤트 알림이 발생함 → … 의 무한 루프가 발생하고 있었습니다.
S3에는 파일에 추가할 수 있는 태그가 있습니다. 이미 최적화가 되었다는 태그를 추가해서 이 루프를 막아보겠습니다.
// 파일을 저장할 때 태그를 추가합니다.
await s3.send(new PutObjectTaggingCommand({
Bucket: bucket,
Key: key,
Tagging: {
TagSet: [
{ Key: "processed", Value: "true" }
]
}
}));
...
// 나중에 파일을 최적화하려고 할 때 태그를 확인합니다.
const tagResponse = await s3.send(new GetObjectTaggingCommand({ Bucket: bucket, Key: key }));
const tags = tagResponse.TagSet;
// 이미 처리된 파일이라면 처리하지 않음
if (tags.some(tag => tag.Key === "processed" && tag.Value === "true")) {
return;
}
파일을 업로드하고 그 뒤에 processed
라는 태그를 추가합니다. 이벤트 알림이 들어올 때 태그를 확인해서 이미 처리된 파일에는 아무 작업도 하지 않고 함수를 종료합니다. 이렇게 하면 파일을 계속 최적화 시도하는 것을 막을 수 있습니다.
마무리하며
작은 문제에서 시작한 일이었는데 빠르게 편리한 자동화로 이어지는 즐거운 일이었습니다. 나중에 반복 문제를 발견해서 수정한 것만 빼면 몇 시간만에 방법을 찾고 구현해서 적용하는 작업이었는데, 새로운 기능도 배우고 나중에 다른 부분으로도 확장할 수 있는 여지가 있는 기능을 알게 된 것도 기쁘네요.
비슷한 문제를 고민하고 계시거나, 우연히 이 글을 읽으셨다고 해도 도움이 되는 글이었다면 좋겠습니다. 읽어주셔서 감사합니다.