Haru Film은 일상의 소중한 순간을 기록하고 공유하는 영상 일기 스트리밍 플랫폼입니다.
바쁜 현대 사회 속에서 '진짜 나'를 돌아보는 시간은 점점 줄어들고 있습니다. 텍스트 기반의 일기는 감정과 분위기를 온전히 담아내기 어렵다는 한계가 있습니다. 저희는 아티스트 빌리 아일리시(Billie Eilish)의 인터뷰 영상에서 영감을 받아, 누구나 자신만의 영상 인터뷰를 통해 현재를 기록하고 과거를 생생하게 회고할 수 있는 서비스를 기획하게 되었습니다.
| 기능 | 설명 |
|---|---|
| 🎥 영상 일기 기록 | 매일 제공되는 3개의 질문에 답하며 하루를 영상으로 기록합니다. |
| 🎞️ Resumable Upload | 네트워크 단절 상황에서도 중단된 지점부터 업로드 재개 가능합니다. |
| ☁️ 비동기 인코딩 | 업로드된 영상을 360p/720p/1080p HLS 포맷으로 자동 변환합니다. 다양한 해상도로 변환하는 CPU 부하가 큰 인코딩 작업은 RabbitMQ를 통해 비동기적으로 처리됩니다. |
| 👨🏻💻 적응형 스트리밍 | 네트워크 상황에 따라 최적의 화질을 자동 선택하여 끊김 없는 재생이 가능합니다. |
| 📅 타임스탬프 & 회고 | 캘린더를 통해 과거 영상을 조회하고, 질문별 타임스탬프를 통해 원하는 답변 구간으로 즉시 이동합니다. |
| 🔔 푸시 알림 | 영상 업로드 완료, 인코딩 완료, 일기 작성 리마인드 등을 FCM 푸시 알림으로 제공합니다. |
Challenge 1: 네트워크 단절을 극복하는 Resumable Upload
문제 상황 (Problem) : 모바일 환경 특성상 지하철 등에서 네트워크가 끊기면, 고용량 영상 업로드가 처음부터 다시 시작되어야 하는 문제가 있었습니다.
해결 방안 (Solution) :
- Chunk 분할: 클라이언트에서 파일을 일정 크기의 조각(Chunk)으로 분할합니다.
- 멀티파트 업로드: 대용량 파일도 안정적으로 업로드합니다.
- Presigned URL: 각 조각별로 보안 서명된 URL을 발급받아 S3로 직접 전송합니다. (서버 부하 감소)
- 상태 추적: localStorage에 업로드 성공한 조각 정보를 기록합니다.
- 재개(Resume): 네트워크 재연결 시, 실패하거나 전송되지 않은 조각부터 업로드를 이어갑니다.
- 업로드 실패 시 자동 롤백: 부분 데이터 자동 정리합니다.
영상 업로드 Flow Diagram
sequenceDiagram
participant Client as 클라이언트
participant Browser as Browser<br/>(localStorage)
participant APIServer as API Server
participant S3 as AWS S3
Client->>Client: 1. 파일을 5MB 청크로 분할
Client->>APIServer: 2. POST /uploads/multi-parts/initiate
APIServer->>S3: 3. CreateMultipartUpload
APIServer-->>Client: 4. uploadId 반환
Client->>Browser: 5. 진행 상태 저장
loop 각 청크별 업로드
Client->>APIServer: 6. GET /uploads/multi-parts/part
APIServer-->>Client: 7. Presigned URL
Client->>S3: 8. PUT (직접 업로드)
alt 성공
S3-->>Client: 9. 200 OK
Client->>Browser: 10. 완료 청크 기록
else 네트워크 단절
Note over Client: 재연결 대기
Client->>Client: 11. 미완료 청크부터 재개
end
end
Client->>APIServer: 12. POST /uploads/multi-parts/complete
APIServer->>S3: 13. CompleteMultipartUpload
APIServer->>DB: 14. Video 레코드 생성
APIServer->>RabbitMQ: 15. 인코딩 작업 발행
Challenge 2: 스트리밍 및 인코딩 서비스
문제 상황 (Problem) : 업로드한 대용량의 영상을 단순 다운로드 방식으로 재생할 경우, 초기 로딩이 느리고 구간 이동이 불가능하며 서버 자원 소모가 큰 비효율적인 구조가 발생합니다. 또한 영상 스트리밍 중, 네트워크 환경에 따라 심각한 버퍼링이 발생해 사용자 경험을 저하하는 문제가 일어날 수 있습니다.
해결 방안 (Solution) : 사용자가 업로드한 원본 영상을 FFmpeg 기반 HLS 스트리밍 포맷으로 자동 변환하여 모든 네트워크 환경에서 끊김 없이 재생 가능한 구조를 설계했습니다.
- 인코딩 파이프라인 자동화
- S3에 업로드된 원본 영상 다운로드
- FFmpeg로 .ts 조각과 master.m3u8 파일 생성 (HLS 표준 방식)
- 360p / 720p / 1080p 등 여러 해상도로 자동 변환(ABR)
- 변환된 파일을 S3 업로드
- 네트워크 환경에 따른 최적 화질 제공(Adaptive Bitrate Streaming)
- HLS 기반으로 사용자의 인터넷 속도에 따라 재생 화질이 실시간으로 자동 전환되어 느린 네트워크에서도 끊김 없이 빠른 로딩 가능
- 초기 로딩 속도 대폭 개선
- 모든 기기와 브라우저에서 동일하게 재생되는 표준 스트리밍 구조
- HLS는 iOS / Android / PC / 모든 브라우저에서 표준적으로 지원
- 사용자 경험 개선 경과
- Buffering Time 100% 감소(395.8ms -> 0ms)
- Seek Latency 58.6% 단축 (6,525ms -> 2,686ms)
- Data Usage 42.5% 절감
스트리밍 flow diagram
sequenceDiagram
participant User as 사용자
participant Browser as 브라우저<br/>(HLS.js)
participant S3 as AWS S3<br/>(Cloud Front CDN)
participant EncodingServer as 인코딩 서버
Note over User,EncodingServer: 비디오 업로드 → 인코딩 → 배포
EncodingServer->>S3: 다양한 화질로 변환<br/>(360p, 720p, 1080p)
EncodingServer->>S3: playlist.m3u8 배포<br/>(각 화질별 세그먼트 목록)
Note over User,EncodingServer: 사용자 재생
User->>Browser: 재생 요청
Browser->>S3: GET playlist.m3u8<br/>(네트워크에 맞는 화질 선택)
S3-->>Browser: 메타데이터 반환
Browser->>S3: Range Request로<br/>첫 세그먼트만 요청<br/>bytes=0-1999999
S3-->>Browser: 첫 세그먼트 전송
Browser-->>User: 즉시 재생 시작
par 재생 중 진행
Browser->>Browser: 다음 세그먼트 미리 로드
User->>Browser: 3분 지점으로 Seek
Browser->>S3: 해당 시간의 세그먼트<br/>Range Request
S3-->>Browser: 해당 부분만 전송
end
Note over Browser: 네트워크 상황에 따라<br/>화질 자동 조정
Challenge 3: Message Queue를 활용한 비동기 인코딩 처리
문제 상황 (Problem) : 고화질 영상을 다양한 해상도(ABR 적용 등)로 변환하는 인코딩 작업은 CPU 부하가 매우 큽니다. API 서버가 이 작업을 직접 처리할 경우, 요청이 몰리면 서버가 멈추거나 타임아웃이 발생할 위험이 있었습니다.
해결 방안 (Solution) : RabbitMQ를 도입하여 API 서버와 인코딩 서버를 물리적으로 분리하여 인코딩 서버에서 비동기적으로 처리됩니다.
- 요청 폭주에도 유실 없는 작업 처리 가능
- 백프레셔 구조로 안정성 확보
- api-server CPU 사용률 90% -> 20%
- 추후 인코딩 서버를 독립적으로 스케일 아웃할 수 있는 구조
- 추후 인코딩 실패 시 Dead Letter Queue를 통해 인코딩 실패 시 재시도 로직 도입 용이
최종 영상 인코딩 Flow Diagram
sequenceDiagram
participant Client as 클라이언트
participant API as API Server
participant MQ as RabbitMQ
participant Encoder as Encoding Server
participant S3 as AWS S3
participant DB as MySQL
participant FCM as Firebase FCM
Client->>API: 1. 업로드 완료 요청
API->>DB: 2. Video 레코드 생성 (PENDING)
API->>MQ: 3. 인코딩 작업 발행
API->>FCM: 4. 업로드 완료 알림
API-->>Client: 5. 즉시 응답
MQ->>Encoder: 6. 메시지 소비
Encoder->>S3: 7. 원본 영상 다운로드
Encoder->>Encoder: 8. FFmpeg 인코딩<br/>(360p/720p/1080p)
Encoder->>S3: 9. HLS 파일 업로드
Encoder->>Encoder: 10. 임시 파일 삭제
Encoder->>MQ: 11. 완료 메시지 발행
MQ->>API: 12. 완료 메시지 소비
API->>DB: 13. Video 상태 변경 (COMPLETE)
API->>FCM: 14. 인코딩 완료 알림
FCM-->>Client: 15. 푸시 알림 수신
Challenge 4: 자막 합성 전략
문제 상황 (Problem) : 초기에는 영상 파일 자체에 질문 자막을 합성(Burning)하는 방식을 고려했습니다.
<video>프레임을<canvas>로 복사- 텍스트 합성 후
canvas.captureStream()으로 녹화
하지만 이 방식은 브라우저가 GPU 가속(VPU)을 사용하지 못하고 CPU 기반 소프트웨어 인코딩을 강제하여, 심각한 프레임 저하가 발생했습니다.
해결 방안 (Solution) : DOM Overlay 방식을 도입하여 녹화 파이프라인과 렌더링 파이프라인을 분리했습니다.
- Stream 녹화: 자막 합성 없이 카메라 원본 스트림을 하드웨어 가속을 받아 녹화합니다.
- 메타데이터 동기화: 질문별 타임스탬프를 별도로 저장합니다.
- 실시간 렌더링: 재생 시
ontimeupdate이벤트를 통해 영상 위에 HTML로 자막을 동적으로 렌더링합니다.
성능 개선 결과 (Benchmark)
| 측정 지표 | DOM Overlay | Canvas (Legacy) | 개선율 |
|---|---|---|---|
| FPS (Frame Rate) | 119.7 ± 0.2 | 30.8 ± 2.5 | +288% (약 3.8배) |
| 효율성 (FPS/CPU) | 23.47 | 6.04 | +288% |
- 렌더링 병목을 제거하여 저사양 기기에서도 고주사율 녹화를 가능하게 했습니다.
- 자막 On/Off 기능 및 추후 STT(Speech to Text) 등 확장성을 확보했습니다.
Challenge 5: 전역 커스텀 에러 핸들링
모든 API에서 동일한 에러 응답 형식 제공하고, 도메인별 커스텀 에러를 정의합니다.
에러 처리 컨벤션
- 모든 에러는 CustomError 사용
import { UserNotFoundError } from '../errors/CustomError.js'; throw new UserNotFoundError('사용자를 찾을 수 없습니다.');
- Controller는 next(error) 호출
try { const result = await this.business.doSomething(); res.status(200).json(result); } catch (error) { next(error); }
- 도메인별 에러 클래스 우선 사용
- ✅
UserNotFoundError(자동으로 에러 코드USER_NOT_FOUND설정) - ❌
NotFoundError('message', 'USER_NOT_FOUND')(수동 입력)
- ✅
- 구체적인 에러 메시지
- ✅
'uploadId, parts, uploadDate가 필요합니다.' - ❌
'파라미터 누락'
- ✅
Challenge 6: Token 기반의 FCM 푸시 알림
하루필름 서비스는 다음과 같은 상황에 사용자에게 푸시 알림을 전송합니다.
- 사용자에게 매일 20시 영상 업로드 리마인드 알림
- 당일 영상을 촬영하지 않은 사용자 대상으로 전송
- 오늘의 질문 리스트 제공
- 영상 업로드 성공/실패 알림
- 영상 인코딩 작업 성공/실패 알림
하루 필름 푸시 알림 특징
- 알림 오류가 메인 로직에 영향이 없습니다. API 응답과의 관계없는 비동기로 진행됩니다.
- 인코딩 및 업로드 성공/실패 모두 알림을 제공하기에, 사용자가 항상 결과 인지 가능합니다.
- 500개 토큰 배치 처리를 통해 알림을 나눠서 전송하기에, 사용자 수나 디바이스 수가 많아도 알림 기능을 지원 가능합니다.
- 모든 동작 추적하기 위해, 상세하게 로깅을 진행했습니다.
- 모든 등록 기기에 푸시 알림이 전송되며, 무효화된 토큰을 자동으로 삭제하여 관리합니다.
| 이름 | 학과 | 이메일 | 역할 |
|---|---|---|---|
| 김휘래(@rlagnlfo1004) | 소프트웨어학과 | [email protected] | BackEnd Developer |
| 박윤서(@Meon-ji) | 사이버보안학과 | [email protected] | BackEnd Developer |
| 전민규(@Mango-Juice) | 소프트웨어학과 | [email protected] | FrontEnd Developer |
효율적인 협업을 위해 GitLab을 활용한 명확한 컨벤션을 수립하여 진행했습니다.
1. Issue 규칙
컨벤션
| 구분 | 규칙 |
|---|---|
| Issue 유형 | feature, bug, refactor 등 라벨 지정 |
| Title 형식 | [Feat] 한 일, [Bug] 어떤 문제, [Refactor] 리팩토링 내용 |
| Template | 각 유형(feature / bug / refactor)에 맞는 템플릿 형식 유지 |
| Task 작성 | 작업 상세 내용은 체크박스로 작성하여 추적 가능하게 유지 |
예시
- Feature Issue 예시
Title: [Feat] 영상 업로드 API 구현 ## 💡 설명 > 추가하려는 기능에 대해 간결하게 설명해주세요 ## ✅ 작업 상세 내용 - [ ] presigned URL 발급 API 구현 - [ ] 업로드 검증 로직 추가 - [ ] 파일 확장자 필터링 추가 ## 📝 추가 설명 없음 ## 📚 참고 자료 없음 - Bug Issue 예시
Title: [Bug] 키스톤 토큰 관련 버그 발견 ## 💡 설명 > 어떤 버그인지 간단하게 설명해주세요 ## 🚨 상황 로그인 후 토큰이 정상적으로 발급되지 않음 ## 💥 실제 결과 서버에서 401 Unauthorized 반환 ## 🖥️ 예상 결과 정상적으로 토큰 발급 및 인증 가능해야 함 ## 📚 참고 자료 없음
2. Branch 규칙
컨벤션
| 구분 | 규칙 |
|---|---|
| 브랜치 구조 | feat/#이슈번호/작업내용 |
| Prefix | feat, fix, refactor, docs 등 issue type과 일치 |
| Issue 연동 | 브랜치명에 GitLab issue number 반드시 포함 |
예시
feat/#75/createProjectForm
fix/#102/loginFailure
refactor/#88/encodingServiceLogic
3. Commit 규칙
컨벤션
| 구분 | 규칙 |
|---|---|
| 형식 | {type}: {message} |
| Type | feat, fix, docs, style, refactor, test, chore, design |
| Message | 한글로 작성, 파일명/디렉토리명 명시 금지 |
| 공백 규칙 | : 뒤에만 공백 존재 |
예시
feat: 예약 기능 validation 추가
fix: 로그인 실패 처리 로직 수정
refactor: 인코딩 서비스 코드 구조 재정비
docs: README API 문서 업데이트
4. Merge Request 규칙
컨벤션
| 구분 | 규칙 |
|---|---|
| Title | [Feat] 작업 내용 #이슈번호 |
| MR Template | default 템플릿 형식 유지 |
| 관련 이슈 | #이슈번호 반드시 연결 |
| 설명 | 작업 요약 + 변경 사항 명확히 기재 |
예시
Title: [Feat] Vercel 배포 스크립트 및 Git Action 추가 #64
## 🔗 관련 이슈
> #64
## 💡 작업 내용
- Vercel 배포 스크립트 추가
- main 브랜치 push 시 자동 배포되는 GitHub Action 설정
## 📝 추가 설명
없음
## 📚 참고 자료
없음


