HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
👻
개발 기록
/
🏢
회사 개발 노트
/
왓챠 노트
왓챠 노트
/
🧶
이미지를 동적으로 저장할 때 생각할 점
🧶

이미지를 동적으로 저장할 때 생각할 점

1. 배경: 저장 기능의 부재

매년 진행되는 연말결산 이벤트는 한 해 동안 서비스를 사랑해준 사용자들에게 특별한 데이터를 선물하는 중요한 마케팅 캠페인이었습니다. 하지만 기존에는 결과를 이미지로 저장하는 기능이 없어 유저가 화면을 캡처해야 했습니다. 이는 유저 환경에 따라 크기가 다른 이미지가 SNS에 공유되는 문제가 있습니다.
이 문제를 해결하고 캠페인의 마케팅 효과를 극대화하기 위해, '사용자 결과 데이터의 동적 이미지 저장 및 공유' 기능을 핵심 목표로 설정했습니다.
 
일정이 중요한 이벤트인만큼 새로운 기능을 도입하기 위해 피그잼으로 팀내에 계획을 공유했어요.
일정이 중요한 이벤트인만큼 새로운 기능을 도입하기 위해 피그잼으로 팀내에 계획을 공유했어요.
 

2. 기술적 접근: 수천 개의 결과물, 어떻게 효율적으로 저장할 것인가?

가장 큰 기술적 허들은 결과 이미지의 가짓수였습니다. 사용자마다 데이터가 모두 다르기 때문에 수천, 수만 개에 달하는 결과 이미지를 미리 만들어두는 것은 현실적으로 불가능했습니다. 따라서 사용자가 요청하는 시점에 이미지를 동적으로 생성하고 저장하는 기술이 반드시 필요했습니다.
이 문제를 해결하기 위해 다음과 같은 투 트랙(Two-Track) 전략을 설계하고 실행했습니다.
 

트랙 1: 실시간 동적 이미지 생성 (일반 사용자 대상)

대부분의 사용자는 이 방식으로 자신의 결과 이미지를 저장하게 됩니다. 사용자가 결과 페이지에 처음 진입하면 3~5초 길이의 인트로 애니메이션이 재생됩니다.
인트로 화면, 시작하기를 누르면 화면이 확대되며, 캐릭터가 말하는 시간이 있습니다.
인트로 화면, 시작하기를 누르면 화면이 확대되며, 캐릭터가 말하는 시간이 있습니다.
 
바로 이 시간을 활용해 다음의 과정을 진행했습니다.
 
  1. 백그라운드 이미지 생성: 인트로 애니메이션이 재생되는 동안, 서버에서는 결과 데이터만 담긴 별도의 '스크린샷 전용 페이지'를 Playwright를 이용해 해당 페이지를 스크린샷으로 캡처하여 이미지 파일을 생성합니다.
  1. 버튼 활성화를 통한 UX 향상: 이미지 생성이 완료되기 전까지 '이미지 저장' 버튼은 비활성화 상태로 둡니다. 백그라운드에서 생성이 완료되면 버튼을 활성화시켜, 사용자가 기다림 없이 매끄럽게 이미지를 저장할 수 있도록 사용자 경험을 설계했습니다.
 
💡 왜 Puppeteer가 아닌 Playwright를 선택했나요?
  • Playwright는 Puppeteer보다 더 높은 안정성과 효율성을 제공합니다. 두 도구 모두 강력한 브라우저 자동화 라이브러리지만, Playwright는 자동 대기(Auto-waiting) 기능이 내장되어 있어 액션(.screenshot(), .click() 등) 실행 시 필요한 요소가 나타날 때까지 자동으로 기다립니다. 반면 Puppeteer에서는 개발자가 직접 waitForSelector 같은 대기 코드를 반복적으로 작성해야 해서 코드가 복잡해지고 네트워크 상황에 따라 테스트가 불안정해질 수 있습니다. Playwright를 사용하면 코드가 간결해지고 예측 불가한 타이밍 이슈가 거의 없어 안정성이 높아집니다.
  • Playwright는 Puppeteer의 주요 개발자들이 Microsoft에서 만든 차세대 라이브러리로, 브라우저 컨텍스트 기반의 효율적인 리소스 관리와 더 직관적이고 간결한 API를 제공합니다. 이로 인해 개발 과정이 '가볍게' 느껴지고, 서버에서 실행되는 자동화 스크립트의 안정성도 향상됩니다. 결과적으로, 트래픽이 많은 상황에서도 빠르고 끊김 없는 사용자 경험을 제공할 수 있었습니다.
 

트랙 2: 선제적 이미지 생성 (헤비 유저 대상) 및 서버 부하 분산

실시간 이미지 생성 방식은 트래픽이 폭주할 경우 심각한 서버 부하를 유발할 수 있다는 잠재적 위험이 있었습니다. Playwright가 이미지를 생성할 때마다 서버는 headless Chromium 인스턴스를 실행해야 하는데, 이는 상당한 컴퓨팅 자원을 소모하기 때문입니다.
이 문제를 해결하기 위해, 활동성이 높은 헤비 유저들을 미리 선별하여 이들의 결과 이미지를 캠페인 시작 전에 미리 생성해두는 전략을 세웠습니다.
  1. 자동화 스크립트 개발: Playwright를 이용해 특정 사용자 목록을 받아 순차적으로 결과 이미지를 생성하고, 생성된 이미지를 Amazon S3 버킷에 업로드하는 자동화 스크립트를 작성했습니다.
  1. 안정적인 운영 확보: 스크립트 실행 시, 터미널에 전체 진행률(예: 532/1000 완료)을 표시하고, 이미지 생성에 성공한 사용자와 실패한 사용자를 별도의 로그 파일로 관리하도록 구현했습니다. 이를 통해 오류 발생 시 원인을 빠르게 파악하고 실패한 사용자에 대해서만 작업을 재시도할 수 있는 안정적인 운영 환경을 구축했습니다.
유저코드 아래 결과 이미지를 저장
유저코드 아래 결과 이미지를 저장

3. 성과

  • 역대 최고 수준의 사용자 참여: 연말결산 캠페인 론칭 후 DAU(일일 활성 사용자) 35%, MAU(월간 활성 사용자) 17.9%가 상승하며 역대 최고치를 경신했습니다.
  • 신규/복귀 유저 유입 및 이탈률 방어: 생성된 이미지가 SNS를 통해 활발히 공유되면서 신규 사용자의 자연 유입을 이끌었고, 기존 사용자들의 재참여를 유도하여 이탈률 감소에 실질적으로 기여했습니다.
  • 압도적인 기능 사용률: 전체 참여자 46,332명 중 27%(약 12,500명)가 이미지 저장 기능을 직접 사용했으며, 저장 버튼에 대한 전체 클릭률은 78%에 달했습니다. 이는 사용자들이 자신의 결과물을 소유하고 공유하려는 니즈가 매우 강했음을 증명합니다.
notion image
notion image
notion image
 

4. 한계: Web Share API

저는 웹 환경뿐만 아니라 앱 내의 웹뷰까지 담당했기 때문에, 모바일 앱 사용자의 경험도 함께 챙겨야 했습니다. 이 과정에서 Web Share API의 기술적 한계에 부딪혔습니다.
가장 큰 문제는 브라우저 및 웹뷰의 호환성이었습니다. Web Share API는 환경에 따라 동작이 달랐습니다. 특히 구형 모바일 브라우저나 특정 앱의 내장 웹뷰에서는 API가 존재하지 않거나 예상과 다르게 작동했습니다.
이 문제를 해결하기 위해 navigator.share API의 존재 여부를 확인하고, 호출이 불가능한 환경에서는 '이미지를 먼저 저장해주세요'라는 안내 문구를 표시하는 분기 처리를 추가했습니다. 그러나 이 방식으로는 모든 사용자에게 완벽하게 매끄러운 공유 경험을 제공하지 못했다는 아쉬움이 남았고, 향후에는 각 환경에 더 최적화된 공유 방식을 개발하는 것이 과제로 남았습니다.
 

5. 회고

기존 연말결산에선 이미지 저장 기능이 들어가지 않았습니다. 그래서 이미지 저장 기능 없이 출시하는 것이 쉬운 길이었지만 더 좋은 서비스를 만들기 위해 제가 할 수 있는 모든 걸 해주고 싶었습니다. 그래서 기술적으로 이미지 저장 기능이 들어갈 수 있는지 검토하고 기획 회의 때 적극적으로 의견을 내 채택된 기능이었습니다.
저는 더 나은 제품을 만들기 위한 아이디어를 적극적으로 제시하고, 기술을 통해 비즈니스 가치를 창출하는 걸 좋아한다는 걸 다시 한 번 느꼈습니다.
 
Video preview
번외로 작업하면서 깨달은 것을 영상으로 만들었어요.