TL;DR

플러터 웹뷰 위 Next.js 15 서비스에서 2주간 4번의 CPU 사건. "Next/Image의 효과는 인프라가 결정한다"로 수렴. 분산 CDN 전제가 약한 셀프호스팅(Cloud Run)에서는 자산 종류별로 최적화 적용 여부를 분리하는 방향 검토 중. 5/15 Dockerfile 권한 결함 → 5/19 /_next/image 부하(응급 unoptimized) → 5/20~26 Cloud CDN 도입 → 5/28 AVIF 인코딩+업로드 사진 동시 변환이 잔존 원인.


0. 사전 이해 — 어떤 구조 위에서 이야기하나

아키텍처: 플러터 앱 + Next.js 웹뷰

아파트케어2 사용자 앱은 플러터 네이티브 셸 안에서 Next.js 웹앱이 웹뷰로 동작하는 구조입니다. 서버 구성은 GCP asia-northeast3 Cloud Run + 그 앞에 HTTP(S) LB + Cloud CDN(5/28 활성). Cloud Run 1개 인스턴스는 평소 CPU 2 / 메모리 1Gi / 동시 요청 80 한계로 설정되어 있습니다.

Next.js 서버는 어떤 일을 하는가

CRA처럼 브라우저에서만 렌더링하는 SPA는 서버가 정적 파일만 보내 CPU 부담이 거의 없습니다. 반면 Next.js 15 App Router는 같은 요청에 서버가 더 많은 일을 합니다 — SSR, 미들웨어, 이미지 최적화(Sharp 변환), fetch 캐싱. 즉 서버 CPU가 일정 부분 드는 것은 설계에 내재된 특성이고, 줄여야 할 대상은 불필요하게 많이 드는 경우와 한 인스턴스에 과도 적재되는 경우입니다.

이 글의 범위

본 글은 이미지 변환 CPU(/_next/image 경로) 만 다룹니다. SSR 자체의 부하는 별도 트랙(논의 단계)입니다.

단일 인스턴스 — 왜 한 곳에 부하가 몰리나

Cloud Run은 minScale 1로 평소 1개가 상시 동작합니다. 스케일아웃에 지연이 있고, 그 사이 들어온 무거운 요청(AVIF 인코딩·대용량 리사이즈)이 단일 인스턴스 CPU를 포화시킵니다. CDN은 이를 크게 완화하지만 캐시에 없는 신규 요청(첫 변환)은 여전히 origin에 도달합니다.

같은 사내, 다른 프로젝트와 비교

항목 v2front (메인 아파트아이) webapp-aptcare2-user
인프라 GAE Flex (cpu 1 / mem 1.6GB) Cloud Run (cpu 2 / mem 1Gi)
Next.js 14.2.35 15.3.8
라우터 Pages Router App Router
기본 렌더링 CSR 위주 SSR 기본
정적 자산 assetPrefix로 별도 CDN 자체 서버 직접(5/28 CDN 전)
이미지 img 134파일·370회, next/image 0건 Image 42파일·59회 → /_next/image 변환
운영 이력 동일 CPU 이슈 없음 본 시리즈 4건

v2front는 정적 자산을 assetPrefix로 별도 CDN에서 서빙하고 이미지는 img 직접 사용이라 /_next/image Sharp 변환 경로가 아예 없습니다. 본 프로젝트는 App Router 기본 동작에 올라타 있고 CDN도 5/28까지 없어, 정적 자산·이미지·SSR HTML이 모두 단일 인스턴스로 도달했습니다.


사건 타임라인

시점 트리거 진단 처방 사후 평가
5/15 단지 오픈+알림톡 Dockerfile EACCES → 45회 재시도 → 인스턴스 27개 COPY --chown + .next/cache 사전 생성 정확. 일괄 해소
5/19 /bridge 진입 132명 /_next/image 변환을 Cloud Run이 직접 처리 unoptimized: true 전역 응급 효과, 정적 자산 최적화 일시 중단
5/20~26 APTI-CPU 후속 인스턴스 로컬 캐시 한계 Cloud CDN + 30일 TTL + unoptimized 제거 CDN 성공. 변환 비용은 후속 보강
5/28 CDN 적용+리비전 배포 동시 cold+콜드스타트+단일 인스턴스 + 잔존 4건 AVIF 제거(적용 완료) + 업로드 선택적 우회(논의 중) AVIF 제거 후 후속 72.5h 안정 확인

1차 사건 (5/15) — 컨테이너 권한 결함