파트너 이미지 처리 및 비용 통제 검토
ADR-340 파트너 이미지 처리 및 비용 통제 검토
1. Metadata
- ADR ID: ADR-340
- Status: draft
- Date: 2026-03-19
- Owner: YSY
- Related ADRs: ADR-150, ADR-230, ADR-240, ADR-250
2. Context
- 현재 파트너 매장 이미지는
Supabase Storage의 public bucket(partner-photos)에 저장한다. - 현재 구조는
최대 4장이며, 첫 번째 이미지는thumb(320px) + detail(1080px)를 함께 만들고, 나머지는 detail 이미지만 만든다. - 리스트/관심매장 화면은
primary_thumb_url을 사용하고, 상세 화면은photo_urls전체를 가로 스트립으로 로드한다. - 현재 비용 구조에서 먼저 커질 가능성이 높은 항목은
Supabase MAU보다이미지 egress다. - 검토 대상은
4장 유지,2장 축소,외부 CDN/이미지 플랫폼,외부 사이트 이미지/WebView 대체다.
2.1 Current Baseline
이미 완료된 항목:
- 이미지 variant 분리는 이미 적용되어 있다.
- 대표 이미지는
thumb(320px) + detail(1080px)를 분리 저장하고, DB에는primary_thumb_url,primary_detail_url을 별도 보관한다. - 이미지 변경 시
partners.image_version을 증가시키는 계약이 이미 들어가 있다. - 앱은 상세 화면과 파트너 편집 화면에서
?v={image_version}를 붙여 URL 버저닝을 적용한다. - 따라서
내용 변경 시 URL 변경이라는 캐시 무효화의 기본 축은 이미 확보되어 있다.
아직 남아 있는 항목:
- 업로드 시
cacheControl을 명시하지 않아긴 캐시 헤더전략은 코드 기준으로 아직 완성되지 않았다. - 즉, 현재 구조는
thumb/detail 분리 + URL 버저닝까지는 적용됐지만,강한 CDN 캐시 정책까지 확정된 상태는 아니다. - Cloudflare 계층을 붙이더라도 이미지 URL이 실제로 Cloudflare를 거쳐야 캐시 절감 효과를 온전히 얻을 수 있다.
2.2 Versioning and Cache Direction
- 이미지 캐시 전략의 기본 원칙은
같은 URL은 오래 캐시하고, 내용이 바뀌면 URL을 바꾼다이다. - URL 변경은 파일명 변경보다
image_version기반 쿼리 파라미터 방식(?v={image_version})을 기본으로 사용한다. - 따라서 캐시 무효화의 SoT는 스토리지 객체 자체가 아니라
partners.image_version이다. - 이 구조에서는 긴 캐시 헤더를 붙여도 이미지 변경 시 stale URL을 강제로 재사용하지 않는다.
- 반대로
cacheControl없이 URL 버저닝만 두면 정합성은 확보되지만 CDN 절감 폭은 제한될 수 있다.
3. Option Review
3.1 옵션 A. 현재 구조 유지 (Supabase + 최대 4장)
가정:
- 대표 thumb
120KB - detail 이미지
650KB - 파트너 1개 전체 세트는
2.72MB - 상세 화면에서 4장을 모두 보면 약
2.60MB
장점:
- 이미 구현되어 있고 즉시 추가 작업이 거의 없다.
- 매장 분위기, 좌석, 메뉴, 외관 등 다양한 컷을 보여주기 쉽다.
단점:
- 리스트 화면 비용은 크게 변하지 않지만, 상세 화면 진입당 detail payload가 크다.
- 같은 구조에서
2장대비 저장 용량과 detail egress가 약1.9배크다. - Supabase Free
1GB기준 저장 한계는 약376개 파트너다. - Supabase Pro
250GB기준 detail full-load 한계는 약98,461회/월수준이다.
판단:
- 비용 통제 우선순위 기준에서는 MVP 기본안으로 유지하지 않는다.
3.2 옵션 B. 축소 구조 (Supabase + 최대 2장)
가정:
- 첫 번째 이미지는
thumb + detail - 두 번째 이미지는
detail - 파트너 1개 전체 세트는 약
1.42MB - 상세 화면에서 2장을 모두 보면 약
1.30MB
장점:
- 현재 구조를 거의 유지한 채 저장 용량과 detail egress를 약
47.8%줄일 수 있다. - Supabase Free
1GB기준 저장 한계가 약721개 파트너로 늘어난다. - Supabase Pro
250GB기준 detail full-load 한계가 약196,923회/월로 늘어난다. - 파트너 입력 피로도도 줄어 초기 온보딩에 유리하다.
단점:
- 매장 소개 표현력이 줄어든다.
- 외관/실내/대표 메뉴/분위기 등 4컷 구성이 필요한 업종에는 부족할 수 있다.
판단:
- 비용과 운영 단순성을 함께 만족하는 MVP 기본안으로 채택한다.
3.3 옵션 C. Cloudflare Images 사용
전제:
Cloudflare Images는 저장(Images Stored), 전달(Images Delivered), 변환(Unique Transformations) 기준으로 과금한다.- 현재
2장 정책기준으로 파트너 1개는 보통대표 thumb,대표 detail,보조 detail의3개 variant를 가진다고 본다.
장점:
- 이미지 전달 비용을
요청 기반또는 별도 미디어 계정으로 분리할 수 있다. - 포맷 변환, 리사이징, 캐시 제어, 커스텀 도메인 운용이 쉬워질 수 있다.
- Supabase DB/Auth와 미디어 비용을 분리 관찰하기 좋다.
단점:
- 업로드, 삭제, 권한, URL 생성, 캐시 무효화, 장애 대응이 모두 추가된다.
- 현재 DB와 앱은
파일 식별자보다public URL자체에 더 강하게 결합돼 있어, URL 체계가 바뀌면 저장/조회/캐시 정책을 함께 바꿔야 한다. - cache purge 실패, 원본-variant 불일치, DB URL-실제 자산 불일치 같은 정합성 문제가 새로 생긴다.
- signed URL, hotlink 방지, 공개 범위, custom domain 같은 전달 정책 결정을 별도로 가져가야 한다.
- 비용 관측도
DB/Auth 비용과미디어 비용이 분리되어 운영 대시보드와 알람 체계가 복잡해진다. - 현재 단계에서는
2장 축소만으로도 상당한 비용 절감 효과를 얻을 수 있어 즉시 이전 명분이 약하다.
비용 감각:
- 공식 가격 기준
Unique Transformations 5,000건/월 무료, 초과 시1,000건당 $0.50이다. - 저장은
100,000장당 월 $5, 전달은100,000건당 $1수준이다. 2장 정책에서 detail gallery full-load 1회는image deliveries 2건으로 계산한다.2장 정책기준1,666개 파트너까지는 월3 variant x 파트너 수가정에서도 transformation 무료 구간 안에 머문다.
판단:
- 장기 대안으로 유지하되, 지금 즉시 기본안으로 채택하지 않는다.
- 순수 전달비만 보면
Supabase egress overage보다 유리해질 수 있지만, 저성장 구간에서는Supabase 포함량을 버리고 별도 비용을 추가하는 셈이 된다.
3.4 옵션 D. Cloudflare R2 + 현재 방식 유지
전제:
- 현재 앱은 클라이언트에서
thumb/detail을 미리 생성하므로, R2에완성된 variant 파일만 저장하는 구조가 가능하다. - 이 경우
Cloudflare Images의 변환 과금 없이R2 Standard storage + Class B reads만 주로 본다.
장점:
- 현재
미리 생성한 썸네일/상세 파일 업로드구조와 가장 가깝다. - 공식 가격 기준
egress 무료,10GB storage 무료,10M Class B reads 무료라서 초기부터 중기까지 매우 싸다. asset id관리만 추가하면 이미지 비용을 DB/Auth 비용과 깔끔하게 분리할 수 있다.
단점:
- 업로드 인증, 파일 경로 설계, 삭제/정리, 캐시 제어를 직접 관리해야 한다.
Cloudflare Images처럼 자동 포맷 변환과 variant 관리가 기본 제공되지는 않는다.- 현재 Supabase public URL을 그대로 쓰는 코드에서 URL 체계를 추상화해야 한다.
- 이미지 삭제/교체 시 orphan file 정리, stale cache 회피, DB 메타데이터 동기화 책임이 모두 애플리케이션 쪽으로 이동한다.
- 비용은 싸더라도 운영 난이도는 지금보다 낮아지지 않는다.
비용 감각:
- 공식 가격 기준
storage $0.015/GB-month,Class B reads $0.36/million,egress 무료다. 2장 정책기준 파트너 1개 저장량1.42MB로 보면 약7,211개 파트너까지 storage 무료 구간 안에 머문다.- detail gallery full-load 기준
5,000,000회/월까지는10M reads무료 구간 안에 머문다.
판단:
- 외부 미디어 계층을 도입해야 한다면
비용 최적화관점의 1순위 후보는Cloudflare Images보다R2 + 현행 variant 업로드다. - 다만 현재 앱은 Supabase Storage에 이미 결합돼 있으므로, 이전 타이밍은
비용뿐 아니라마이그레이션 공수와 함께 판단한다.
3.5 옵션 E. Supabase Storage Image Transformations
장점:
- 저장소를 바꾸지 않고 리사이징/포맷 최적화를 붙일 수 있다.
- WebP 자동 최적화 등으로 egress를 일부 줄일 수 있다.
단점:
- egress 문제를 근본적으로 없애지 못한다.
- Pro 기준
origin image 100개만 무료이고, 초과 시1,000 origin images당 $5가 붙는다. - 현재는 이미 클라이언트에서 공용 variant를 만들고 있어, on-demand transform을 붙여도 구조 이점이 제한적일 수 있다.
판단:
Supabase 안에서 해결하려는 보조 대안으로는 가능하지만, 현재 구조의 1차 대안으로 채택하지 않는다.
3.6 옵션 F. 외부 사이트 이미지 또는 WebView 대체
예시:
- 파트너 홈페이지 이미지를 직접 참조
- 인스타그램/블로그/플레이스 페이지를 앱 안
WebView로 노출
장점:
- 우리 저장 비용을 줄이는 것처럼 보일 수 있다.
단점:
- 이미지 소유권, hotlink 허용 여부, robots, 추적 스크립트, CORS, 접속 차단 정책을 통제할 수 없다.
- 원본 해상도, 비율, 로딩 속도, 만료 정책이 제각각이라 앱 일관성이 깨진다.
- 앱 상세 화면의 핵심은
가볍고 안정적인 매장 미디어인데, WebView는 페이지 전체 비용과 불안정성을 함께 들여온다. - 외부 페이지 변경이나 삭제가 곧바로 앱 품질 저하로 이어진다.
- 캐시/오프라인/오류 복구/심사 대응 측면에서도 불리하다.
판단:
- 파트너 대표 미디어의 기본안으로 채택하지 않는다.
- 외부 사이트는 보조 링크(
홈페이지 보기)로만 허용 검토한다.
3.7 시나리오별 비용 비교 (2장 정책 기준)
전제:
- 이 비교는
이미지 때문에 추가로 붙는 증분 비용만 비교한다. Supabase Pro 기본요금과 compute는 Auth/DB 때문에 계속 필요하다고 보고, 이미지 overage만 계산한다.Cloudflare Images는 저장 + 전달 + transformation overage를 계산한다.Cloudflare R2는 현재와 같은사전 생성 variant 업로드를 가정하고 storage + Class B reads만 계산한다.- detail gallery full-load 1회는 약
1.30MB,image deliveries 2건,R2 reads 2건으로 본다.
| 시나리오 | 파트너 수 | 월 이미지 트래픽 | Supabase direct | Cloudflare Images | Cloudflare R2 |
|---|---|---|---|---|---|
| B. 1개 생활권 라이브 | 100 | 75GB | $0 | 약 $1.19 | 약 $0 |
| C. 생활권 PMF 확인 | 300 | 300GB | 약 $4.50 | 약 $4.76 | 약 $0 |
| D. 도시 확장 직전 | 1,000 | 900GB | 약 $58.50 | 약 $14.28 | 약 $0 |
| E. MAU 100k 돌파 | 2,000 | 1.8TB | 약 $139.50 | 약 $29.06 | 약 $0 |
해석:
Supabase direct는250GB포함량 안에서는 가장 단순하고 싸다.Cloudflare Images는 저성장 구간에서는 오히려 별도 비용을 추가하지만,Supabase egress overage가 보이기 시작하는 구간부터 유리해진다.Cloudflare R2는 현재처럼thumb/detail을 미리 만들어 올리는 구조라면 비용만 놓고는 가장 강하다.- 다만
R2와Cloudflare Images모두 현재 Supabase URL/권한 구조를 바꾸는 공수만 있는 것이 아니라, 이후 운영 모델 자체를 더 복잡하게 만든다. - 따라서 현재 권장 순서는
2장 축소 -> 실제 egress 계측 -> 필요 시 R2 우선 검토 -> 이미지 변환 요구가 커지면 Cloudflare Images 검토다.
4. Domain Decision
- 파트너 대표 미디어의 SoT는 플랫폼이 직접 관리하는 이미지 자산으로 고정한다.
- 파트너 대표 미디어는 외부 웹페이지나 제3자 웹 콘텐츠에 의존하지 않는다.
- MVP 기본 이미지 정책은
대표 1장 + 보조 1장, 총최대 2장으로 고정한다. - 첫 번째 이미지는 리스트/관심매장용
thumb와 상세용detail을 함께 가지는 대표 자산으로 정의한다. - 두 번째 이미지는 상세 화면에서만 사용하는 보조 자산으로 정의한다.
5. Product Decision
- 기본 플랜 기준 파트너 이미지 허용 수는
최대 2장으로 운영한다. - 제품 가치는
가볍고 신뢰 가능한 매장 신호를 우선하고, 초기부터 풍부한 갤러리를 기본 제공하지 않는다. - 파트너가 더 많은 사진을 보여주고 싶다면 앱 기본 갤러리 확장 대신
외부 홈페이지/소셜 링크를 보조 경로로 제공하는 방식을 우선 검토한다. - 기존에
3장 이상저장된 파트너는 정책 전환 후에도 즉시 깨지지 않도록 읽기 경로에서 우선앞 2장만 사용한다. 3번째/4번째 이미지는 향후 별도 상위 플랜, 수동 승인, 또는 특수 업종 정책이 필요할 때만 재검토한다.
6. UX Decision
- 파트너 편집 화면의 이미지 슬롯은
2개만 노출한다. - 첫 번째 슬롯은
대표 이미지, 두 번째 슬롯은보조 이미지로 명확히 구분한다. - 상세 화면은 최대
2장만 보여주며, WebView로 외부 페이지를 대체 렌더링하지 않는다. 사진 더 보기수요는 앱 내부 갤러리 확장보다외부 링크 열기같은 보조 액션으로 대응한다.- 기존에 3장 이상이 있는 파트너라도 사용자에게는 우선
앞 2장만 노출해 레이아웃을 단순하게 유지한다.
7. Tech Decision
partners.photo_urls의 최대 cardinality는4에서2로 축소한다.PHOTO_SLOTS는4에서2로 축소한다.- 첫 번째 이미지에만
primary_thumb_url과primary_detail_url를 유지하고, 두 번째 이미지는 detail URL만 유지한다. - 리스트/관심매장/최근 본 화면은 계속
primary_thumb_url만 사용한다. - 상세 화면은
photo_urls와primary_detail_url을 조합하되 최대2장까지만 구성한다. - 이미지 캐시 무효화는 계속
image_version 기반 URL 버저닝을 표준으로 사용한다. - 업로드 경로는 variant 이미지에
Cache-Control: public, max-age=31536000, immutable또는 동등한 긴 캐시 헤더를 명시하는 것을 기본 정책으로 한다. - 이 긴 캐시 헤더 정책은
image_version이 URL에 항상 반영된다는 전제에서만 유효하다. - 외부 CDN 이전은 현재 즉시 진행하지 않고,
플랫폼 관리 업로드 -> 플랫폼 관리 전달구조를 유지한다. - 외부 CDN 검토는 다음 조건 중 하나가 충족될 때 시작한다.
월 image egress 150GB 이상이 2개월 연속 발생한다.파트너 1,000개 이상단계에서 detail 이미지 트래픽이 반복적으로 Pro 포함량 근처까지 올라간다.- 리사이징, 서명 URL, 이미지 정책 분리 등 미디어 전용 계층의 운영 이점이 비용보다 커진다.
- 외부 웹페이지/WebView는 대표 미디어 전달 계층으로 사용하지 않는다.
- 외부 미디어 계층으로 이전할 때 비용 최적화 1순위 후보는
Cloudflare R2 + 사전 생성 variant 유지다. - 외부 미디어 계층으로 이전할 때 자동 최적화와 variant 관리 가치가 커지면
Cloudflare Images를 다음 후보로 검토한다.
8. Ops Decision
- 운영 지표에
월 image egress,thumb 평균 바이트,detail 평균 바이트,파트너당 이미지 수,상세 화면 이미지 로드 수를 포함한다. 2장 정책전환 후 기존3번째/4번째이미지는 즉시 삭제하지 않고, 읽기 중단과 저장 중단을 먼저 적용한다.- 실제 스토리지 정리는 정책 전환 안정화 이후 별도 배치로 수행한다.
- 외부 CDN 이전 여부는 월간 비용 리뷰에서
Supabase egress,파트너 수,detail screen load를 함께 보고 판단한다. - 외부 사이트/WebView를 대표 미디어 예외로 허용하지 않는다.
- 외부 CDN으로 이전하더라도 비용 외 운영 리스크 항목(
cache purge,orphan file,broken URL,signed/public 정책)을 함께 점검한다.
9. Implementation Contract (Optional)
9.1 Data Contract
partners.photo_urls는 최대2개까지만 저장한다.partners.primary_thumb_url은 첫 번째 이미지의 썸네일 URL을 저장한다.partners.primary_detail_url은 첫 번째 이미지의 상세 URL을 저장한다.image_version은 기존과 동일하게 이미지 변경 시 증가시킨다.- 이미지 내용이 바뀌면 클라이언트 렌더링 URL도
?v={image_version}기준으로 새 URL을 사용해야 한다. - 쿼리 파라미터 버저닝 형식은
v={image_version}으로 통일한다.
9.2 UI Contract
- 파트너 편집 화면의 이미지 안내 문구는
최대 2장기준으로 고정한다. - 첫 번째 슬롯은
대표 (thumb/detail), 두 번째 슬롯은보조 이미지로 표시한다. - 사용자는 세 번째 이미지 슬롯을 보지 않아야 한다.
9.3 Runtime Contract
- 리스트 계층은
primary_thumb_url만으로 카드를 렌더링해야 한다. - 상세 계층의 gallery 구성 함수는 최대
2장까지만 반환해야 한다. - 기존 데이터에
3장 이상이 있더라도 렌더링 계층은앞 2장만 사용해야 한다. - 상세/편집 미리보기 계층은
image_version을 URL에 반영해야 한다. - URL이 이미 쿼리스트링을 갖고 있더라도 버저닝은
&v=형식으로 추가할 수 있어야 한다. - 업로드 계층은 variant 이미지 저장 시 긴 캐시 헤더를 일관되게 명시해야 한다.
9.4 Delivery Contract
- 공개 이미지 variant는
Cache-Control: public, max-age=31536000, immutable또는 동등한 장기 캐시 정책을 사용한다. - 긴 캐시 정책은
image_version증가 없는 overwrite를 전제로 사용하지 않는다. - CDN을 붙이더라도 URL 버저닝 규칙은 그대로 유지한다.
9.5 Test/Acceptance Contract
- 파트너 저장 요청은
photo_urls3개 이상을 허용하지 않아야 한다. - 파트너 편집 UI는 슬롯 2개만 노출해야 한다.
- 상세 화면은 최대 2장의 이미지 카드만 렌더링해야 한다.
- 기존 4장 데이터가 있어도 앱은 오류 없이 앞 2장만 노출해야 한다.
- 이미지 정책 전환 후에도 리스트 썸네일 노출은 깨지지 않아야 한다.
- 이미지 변경 후
image_version이 증가하고 새 렌더링 URL이 달라져야 한다. - 업로드된 variant 응답은 긴 캐시 헤더를 가져야 한다.
10. Consequences / Impact
긍정 영향:
- 현재 구조 기준 저장 용량과 detail egress를 거의 절반 수준으로 줄일 수 있다.
- 파트너 온보딩 UI와 검수 기준이 단순해진다.
- Supabase direct public URL 구조를 그대로 유지하면서도 비용 민감도를 낮출 수 있다.
- 이미 적용된
URL 버저닝을 유지함으로써 긴 캐시 헤더나 CDN 캐시를 붙일 준비가 되어 있다. 긴 캐시 헤더 + 버저닝조합으로 썸네일 재방문 비용을 크게 줄일 수 있다.
부정 영향:
- 일부 업종은 매장 표현력이 부족해질 수 있다.
- 기존 4장 설계를 전제로 한 문구, 제약, 테스트를 함께 수정해야 한다.
- 외부 CDN을 미루는 동안 미디어 계층 분리 이점은 당장 얻지 못한다.
- 반대로 외부 CDN을 조기 도입하면 비용은 줄어도 정합성, purge, 권한, 관측성 복잡도가 증가한다.
cacheControl이 비어 있으면URL 버저닝이 있어도 CDN 비용 절감 폭이 기대보다 작을 수 있다.image_version증가 없이 같은 URL을 overwrite하면 장기 캐시 정책과 충돌할 수 있다.
11. Rollout / Migration
- 1차로 읽기/쓰기 정책을
최대 2장으로 전환한다. - 기존
3장 이상데이터는 렌더링에서앞 2장만 사용한다. - 편집 UI, DB 제약, 저장 함수, 상세 gallery 구성을 같은 배포 묶음으로 맞춘다.
- 안정화 후 미사용
3번째/4번째이미지 정리 배치를 별도로 검토한다.
12. Validation
- Domain/Product/UX/Tech/Ops 결정이 서로 충돌하지 않는다.
-
App/db/03_store_domain.sql의 이미지 제약과 문서 결정이 일치한다. - 파트너 편집 UI와 상세 화면이
최대 2장정책으로 정렬된다. -
image_version기반 URL 버저닝 계약이 렌더링 계층과 문서에서 일치한다. - 업로드 계층의 긴 캐시 헤더 정책이 문서와 구현에서 일치한다.
-
TECH_COST_SCENARIOS.md의 이미지 비용 판단과 모순되지 않는다. - 외부 CDN 재검토 트리거가 운영 지표로 측정 가능하다.