이벤트 사진 앱 개발자들은 이제 인파가 몰린 장소에서도 브라우저 업로드를 안정적으로 유지할 수 있는 구체적인 체크리스트를 갖게 되었습니다. 사용자가 Wi-Fi에서 셀룰러 데이터로 전환하거나, 수십 대의 기기가 동일한 핫스팟을 사용하기 위해 경쟁하는 상황에서도 말이죠. 이 가이드는 게스트가 휴대폰을 잠그거나 네트워크에 일시적인 문제가 생기더라도, "업로드 완료" 토스트 메시지가 뜬 직후 사진이 사라지는 것을 방지하는 방법을 보여줍니다.

결혼식과 페스티벌에서 일반적인 업로드가 실패하는 이유

사무실에서는 노트북이 안정적인 이더넷 연결에 연결되어 있고 사용자가 "전송"을 클릭하면 끝납니다. 하지만 결혼식 피로연이나 음악 페스티벌에서는 동일한 동작이 연쇄적인 문제를 일으킬 수 있습니다. 게스트가 예식장에서 주차장으로 이동하거나, 수백 대의 휴대폰 때문에 라우터가 과부하에 걸리거나, 휴대폰이 Wi-Fi 연결을 끊고 셀룰러로 전환될 수 있기 때문입니다. 브라우저가 서버로 모든 바이트를 스트리밍했을지라도, 서버가 아직 파일을 스토리지에 저장(commit)하지 않았을 수 있습니다. 만약 UI가 진행률 표시줄이 100%에 도달하는 즉시 성공을 선언해 버리면, 게스트는 사진을 삭제할 수 있고 주최자는 누락된 파일을 마주하게 됩니다.

"그냥 업로드하면 되지"라는 생각의 숨겨진 비용

단순한 접근 방식은 업로드를 단일 HTTP POST로 처리합니다. 연결이 안정적일 때는 잘 작동하지만, 혼잡한 네트워크에서는 중단이 발생할 때마다 파일 전체를 처음부터 다시 전송해야 합니다. 수십 대의 휴대폰이 동시에 재시도를 하면 사용자는 짜증을 느끼고 대역폭은 급증합니다. 파일을 청크(chunk) 단위로 나누고 각 조각을 추적하는 것은 복잡성을 더하지만, 그 대가로 네트워크 전환 시에도 견딜 수 있는 예측 가능하고 오버헤드가 낮은 전송 방식을 얻을 수 있습니다.

재개 가능한 청크 단위 업로드 시스템 구축하기

아래는 실무에 바로 적용할 수 있는 단계별 가이드입니다.

1. 데이터가 브라우저를 떠나기 전에 업로드 ID를 생성합니다

로컬에서 범용 고유 식별자(UUID)를 생성하고 이를 첫 번째 요청으로 서버에 보냅니다. 서버는 해당 ID 아래에 세션을 기록합니다. 나중에 브라우저가 타임아웃으로 인해 재시도할 때 동일한 UUID를 포함하면, 서버는 해당 세션을 인식하여 중복 항목 생성을 방지할 수 있습니다. 이를 통해 워크플로우에 멱등성(idempotent)을 부여할 수 있습니다. 즉, 동일한 요청을 반복해도 부작용이 발생하지 않습니다.

2. 파일을 5~10MB 크기의 청크로 나눕니다

청크 크리는 트레이드오프(trade-off) 관계에 있습니다. 너무 작은 청크(1MB 미만)는 HTTP 요청 횟수와 그에 따른 헤더 오버헤드를 증가시킵니다. 반대로 너무 큰 청크는 중단 시 클라이언트가 커다란 조각을 다시 보내야 하므로 비용이 많이 듭니다. 일반적인 사진과 짧은 영상의 경우, 5~10MB가 적절한 균형점입니다. 각 요청이 UI 응답성을 유지할 수 있을 만큼 빠르게 완료되면서도, 요청 횟수가 관리 가능한 수준을 유지합니다.

3. 병렬 업로드 수를 제한합니다

모바일 브라우저는 많은 연결을 열 수 있지만, 혼잡한 Wi-Fi 네트워크에서는 추가되는 각 스트림이 제한된 대역폭을 두고 경쟁하게 됩니다. 안정적인 두 개의 스트림이 서로 경쟁하는 여덟 개의 스트림보다 훨씬 낫습니다. navigator.connection API를 사용하여 저대역폭 상태를 감지하고 자동으로 동시성을 줄이십시오.

4. IndexedDB에 업로드 상태를 유지합니다

업로드 ID, 이미 전송된 청크 목록, 서버가 확인한 오프셋(offset) 등을 브라우저의 IndexedDB에 저장합니다. 페이지가 새로고침되거나 사용자가 탭을 닫더라도, 클라이언트는 다음 로드 시 상태를 복구할 수 있습니다. 사용자가 페이지를 다시 열면 동일한 파일을 선택하도록 안내하십시오. 저장된 메타데이터를 통해 처음부터 다시 시작하는 대신 마지막으로 확인된 청크부터 업로드를 재개할 수 있습니다.

5. navigator.onLine뿐만 아니라 실제 네트워크 변화를 감지합니다

navigator.onLine 플래그는 연결을 사용할 수 없는 상태에서도 종종 "online"이라고 보고합니다. 대신 각 청크에 대해 짧은 요청 타임아웃(예: 5초)을 설정하십시오. 타임아웃이 발생하면 네트워크가 끊긴 것으로 간주합니다. 연결이 복구되면 서버에 이미 가지고 있는 청크 목록을 쿼리한 다음, 누락된 조각만 계속 업로드합니다. 이렇게 하면 짧은 중단 이후에 중복 데이터를 보내는 일을 방지할 수 있습니다.

6. 재시도 시 지터(jitter)를 포함한 지수 백오프(exponential backoff)를 적용합니다

많은 게스트의 기기가 네트워크 복구를 감지하는 순간, 모두가 동시에 재시도를 시도하여 서버를 마비시킬 수 있습니다. 지수 백오프는 각 재시도 대기 시간을 이전보다 더 길게 설정하며, 지터는 여기에 무작위의 작은 오프셋을 더합니다. 이 조합은 재시도 트래픽을 몇 초에 걸쳐 분산시켜 갑작스러운 급증(spike)을 방지합니다.

7. 계층적이고 접근 가능한 피드백을 제공합니다

3단계 상태 표시줄을 통해 파일의 실제 상태를 전달합니다:

  • Received (수신됨) – 서버가 모든 청크를 저장하고 파일을 완료로 표시했습니다.
  • Preparing (준비 중) – 서버가 썸네일을 생성하거나 비디오를 트랜스코딩하고 있습니다.
  • Available (사용 가능) – 주최자가 파일을 보거나 다운로드할 수 있습니다.

색상에만 의존하지 마세요. 스크린 리더 사용자가 진행 상황을 이해할 수 있도록 아이콘과 짧은 텍스트를 함께 사용하세요.

여전히 발생할 수 있는 문제는 무엇인가요?

잘 설계된 이어올리기(resumable) 업로드 방식이라도 몇 가지 예외적인 상황(edge cases)에서 문제가 발생할 수 있습니다.

다음에 주의 깊게 살펴볼 점

웹 플랫폼은 계속 진화하고 있습니다.

핵심 정리

각 조각을 추적하고, 상태를 로컬에 저장하며, 지능적으로 재시도하는 이어올리기 방식의 청크(chunked) 업로드는 불안정한 이벤트 네트워크를 게스트 사진을 위한 신뢰할 수 있는 통로로 바꿔줍니다. 위의 체크리스트를 구현해 보세요.