마이콕 시스템
문서 정보
- 작성일: 2026-02-27
- 최종 업데이트: 2026-04-05
- 버전: v1.2.0
마이콕은 PLUS 멤버십 전용 영구 클립 보관소입니다. 사용자가 콕한 순간을 영원히 보관하기 위해, 원본 영상 만료 전 자동으로 15초 클립을 생성하여 독립 저장합니다. 모든 카메라 앵글의 클립이 하나의 마이콕에 포함됩니다.
목차
- 마이콕이란?
- 핵심 개념
- 영상 상태 전이
- 마이콕 저장 흐름
- 배치 클립 생성 파이프라인
- VM 프로비저닝과 작업 관리
- 멀티카메라 통합
- PLUS 멤버십과 제한
- 원본 삭제와 영구 보존
- 다운로드 구매
- FAQ
마이콕이란?
마이콕(MyKok)은 "내 콕 영상을 영원히 보관" 하는 기능입니다.
체육관에서 촬영된 원본 영상은 일정 기간이 지나면 만료되어 삭제됩니다. 하지만 사용자가 콕을 찍은 순간 중 특별히 보관하고 싶은 것이 있다면, 마이콕으로 저장할 수 있습니다. 마이콕으로 저장된 영상은 원본이 삭제되더라도 독립적인 15초 클립으로 영구 보존됩니다.
| 구분 | 콕(Kok) | 마이콕(MyKok) |
|---|---|---|
| 성격 | 운동 중 특정 순간을 표시한 타임스탬프 | 영구 보관을 위해 선택한 콕 |
| 보존 기간 | 원본 영상 만료 시 함께 접근 불가 | 영구 보존 |
| 영상 파일 | 없음 (원본에서 해당 구간만 재생) | 15초 독립 클립 파일 보유 |
| 멤버십 | 패스 보유자 | PLUS 멤버십 전용 |
| 개수 제한 | 패스별 상이 | 최대 100개 |
콕이 "포스트잇으로 책의 중요한 페이지를 표시"하는 것이라면, 마이콕은 "그 페이지를 복사해서 별도의 파일에 영구 보관"하는 것입니다. 원본 책이 도서관에 반납되더라도 복사본은 남아 있습니다.
핵심 개념
Video와 MyKok의 관계
하나의 Video(10분 원본 영상)에 여러 사용자의 여러 마이콕이 연결될 수 있습니다.
Video (10분 원본, 카메라 4~6대)
│
│ 1 : N 관계
│
├── MyKok A (김철수, 5분 32초 시점)
├── MyKok B (김철수, 8분 10초 시점)
├── MyKok C (이영희, 3분 45초 시점)
└── MyKok D (박민수, 5분 32초 시점)
- 각 마이콕은 정확히 하나의 Video를 참조합니다 (1:1 모델)
- 서로 다른 사용자가 같은 시점을 마이콕으로 저장할 수 있습니다
- 마이콕 1개 = 모든 카메라 앵글의 15초 클립 + 대표 썸네일 1장
클립 구간
콕 시점을 기준으로 앞 10초 + 뒤 5초 = 총 15초 구간을 클립으로 생성합니다.
콕 시점
│
◄──10초──┤──5초──►
─────────┼────────
시작 콕 끝
영상 시작/끝 경계에 걸리면 부족한 만큼 반대쪽으로 늘려서 가능한 한 15초를 확보합니다.
데이터 모델
마이콕 레코드에 저장되는 핵심 데이터입니다.
| 필드 | 설명 |
|---|---|
videoId | 원본 Video FK (1:1 모델) |
kokTimestampId | 콕 타임스탬프 ID (중복 방지) |
kokInfo | 콕 메타데이터 스냅샷 (타임슬롯, 오프셋 등) |
videoState | 영상 보 존 상태 (ORIGINAL / CLIPPING / CLIPPED / FAILED) |
mediaInfo | 클립 영상 정보 (카메라별 URL + 썸네일) — 배치 완료 후 채워짐 |
memo | 사용자 메모 (최대 200자, 선택) |
expiresAt | 만료 시각 (구독 해지 시 설정) |
영상 상태 전이
마이콕의 videoState는 클립 생성 진행 상태를 나타내며, 클라이언트가 어떤 소스로 영상을 재생할지 결정합니다.
상태별 상세
| 상태 | 재생 소스 | 설명 |
|---|---|---|
| ORIGINAL | 원본 Video의 10분 영상에서 해당 구간 재생 | 마이콕 저장 직후. 실제 클립 파일은 아직 없음 |
| CLIPPING | 원본 Video의 10분 영상에서 해당 구간 재생 | 배치 처리 진행 중. ORIGINAL과 동일하게 동작 |
| CLIPPED | 마이콕 자체 클립 파일로 직접 재생 | 독립된 15초 클립 보유. 원본 삭제 후에도 재생 가능 |
| FAILED | 재생 불가 | 클립 생성과 FALLBACK 모두 실패. 운영팀 수동 개입 필요 |
두 상태 모두 원본 영상을 참조하지만, CLIPPING은 "곧 독립 클립이 생성될 예정"이라는 의미입니다. 클라이언트 관점에서는 동일하게 동작합니다.
마이콕 저장 흐름
사용자가 콕 목록에서 마이콕으로 저장하는 과정입니다.
토글 방식 (Redis Debounce)
마이콕 저장/삭제는 토글 버튼으로 동작합니다. 빠르게 여러 번 눌러도 마지막 상태만 반영되도록 Redis Debounce 패턴을 적용합니다.
저장 시 검증 순서
마이콕 저장이 실행되면 다음 순서로 검증합니다.
- PLUS 멤버십 확인 (Controller Guard)
- 마이콕 최대 개 수 확인 (100개 제한)
- 스케줄 존재 여부 + 소유권 + 접근 기간 확인
- 콕 데이터 일치 여부 확인
- 중복 저장 체크
- 해당 타임슬롯의 Video 조회
- 마이콕 레코드 생성 (
videoState = ORIGINAL)
원본 영상이 아직 살아있으므로, 굳이 지금 클립을 만들 필요가 없습니다. 사용자가 마이콕을 추가했다가 삭제하는 경우를 고려하면, 원본 만료 직전에 한꺼번에 처리하는 것이 효율적입니다.
배치 클립 생성 파이프라인
원본 영상이 만료되기 전, 마이콕으로 저장된 구간을 실제 15초 클립으로 생성하는 배치 시스템입니다.
전체 흐름
스케줄러 실행 순서
매일 새벽 두 개의 스케줄러가 순차 실행됩니다.
| 시각 | 스케줄러 | 역할 |
|---|---|---|
| 03:00 KST | ExpiredVideoStrategy | 마이콕이 없는 만료 Video를 삭제 (R2/Stream 리소스 제거 + DB soft delete) |
| 03:30 KST | MyKokGenerationStrategy | 마이콕이 있는 만료 Video에서 클립 생성 배치 시작 |
이 순서가 중요합니다. 03:00에 마이콕이 없는 영상을 먼저 정리하고, 03:30에 마이콕이 필요한 영상만 배치에 포함합니다.
파이프라인 단계별 역할
| 단계 | 담당 | 역할 |
|---|---|---|
| 대상 조회 | MyKokGenerationStrategy | 만료 Video 중 ORIGINAL 마이콕이 있는 것 조회 |
| 그룹핑 | MyKokGenerationStrategy | 체육관별 Video 그룹핑 + R2 인증 정보 복호화 |
| 매니페스트 | ManifestGenerator | 클립 시간 범위, 카메라별 출력 경로 등 작업 지시서 생성 |
| VM 할당 | VmAllocator | 체육관별 데이터 용량(50GB) 기준으로 VM 분할 |
| 작업 저장 | AssignmentWriter | MongoDB vm_assignments 컬렉션에 작업 지시서 저장 |
| 프로비저닝 | VmProvisioner | Vultr VM 생성 + videoState CLIPPING 업데이트 |
VM 프로비저닝과 작업 관리
Vultr VM
배치 클립 생성은 Vultr 클라우드 서버(VM)에서 수행됩니다.
| 항목 | 값 |
|---|---|
| 리전 | 도쿄 (nrt) |
| 플랜 | vx1-g-2c-8g-120s (2 CPU, 8GB RAM, 120GB SSD) |
| OS | Ubuntu 24.04 LTS |
| 실행 방식 | cloud-init 스크립트로 Docker 컨테이너 자동 실행 |
| 수명 | 작업 완료 후 자동 삭제 (사용 시간만큼만 비용 발생) |
VM 생명주기
MongoDB 작업 관리
배치 진행 상황은 MongoDB 두 개 컬렉션으로 추적합니다.
| 컬렉션 | 역할 | 핵심 필드 |
|---|---|---|
| vm_assignments | VM별 작업 지시 + 진행 상태 | status (PENDING → RUNNING → COMPLETED), completedCount / totalMyKoks |
| batch_logs | 이벤트 단위 상세 로그 | event (VM_PROVISIONED, FILE_UPLOADED, BATCH_COMPLETED 등) |
배치 이벤트 흐름
VM과 서버 사이에 발생하는 이벤트 순서입니다.
| 순서 | 이벤트 | 발신자 | 설명 |
|---|---|---|---|
| 1 | VM_PROVISIONED | 스케줄러 | VM 생성 완료 기록 |
| 2 | VM_STARTED | VM Worker | VM 부팅 후 첫 보고 |
| 3 | CLIP_COMPLETED | VM Worker | 마이콕 1건의 클립 생성 완료 |
| 4 | FILE_UPLOADED | CF Worker | R2에 파일 1개 업로드 감지 (per-file) |
| 5 | UPLOAD_COMPLETED | 서버 (내부) | 마이콕 1건의 모든 파일 도착 확인 |
| 6 | BATCH_COMPLETED | 서버 (내부) | VM의 모든 마이콕 처리 완료 |
| 7 | BATCH_CLEANUP_COMPLETED | 스케줄러 | 원본 리소스 삭제 완료 |
완료 판정
마이콕 1건의 완료는 FILE_UPLOADED 카운팅으로 판정합니다.
카메라 6대인 코트의 마이콕 1개:
필요한 파일 = 6개 클립(.mp4) + 1개 썸네일(.webp) = 7개
FILE_UPLOADED x 1 → 1/7 (대기)
FILE_UPLOADED x 2 → 2/7 (대기)
...
FILE_UPLOADED x 7 → 7/7 도달!
→ UPLOAD_COMPLETED 트리거
→ PostgreSQL: videoState → CLIPPED, mediaInfo 저장
→ MongoDB: vm_assignments.completedCount +1
VM의 모든 마이콕이 완료되면(completedCount === totalMyKoks):
- VM 삭제 (Vultr API)
- 원본 리소스 삭제 큐잉 (5분 딜레이 후 실행)
- Discord 리포트 전송
멀티카메라 통합
1개 마이콕 = 모든 앵글
체육관 코트에는 보통 4~6대의 카메라가 설치되어 있습니다. 마이콕은 사용자가 카메라를 선택하는 것이 아니라, 모든 카메라 앵글의 클립을 하나의 마이콕에 자동 포함합니다.
마이콕 1개의 구성:
├── 카메라 1 클립 (15초 .mp4)
├── 카메라 2 클립 (15초 .mp4)
├── 카메라 3 클립 (15초 .mp4)
├── 카메라 4 클립 (15초 .mp4)
├── 카메라 5 클립 (15초 .mp4)
├── 카메라 6 클립 (15초 .mp4)
└── 대표 썸네일 (.webp) ← 기본 카메라에서 추출
카메라별 미디어 타입
클립 생성 결과에 따라 각 카메라에 타입이 부여됩니다.
| 타입 | 의미 | 파일 크기 | 클라이언트 동작 |
|---|---|---|---|
| CLIPPED | 정상적으로 15초 클립 생성 완료 | 수 MB | 파일 그대로 재생 |
| FALLBACK | 클립 실패 후 원본 전체를 복사 | 250~430MB | 15초 구간만 seek+play |
특정 카메라의 클립 생성이 실패하면, VM Worker가 최대 1시간까지 재시도합니다. 그래도 실패하면 해당 카메라의 원본 전체를 my-koks 버킷으로 복사합니다. 용량이 크지만 영상이 아예 없는 것보다 낫기 때문입니다. FALLBACK은 극히 드물어 스토리지 비용 영향은 미미합니다.
R2 버킷 구조
raw-videos 버킷 (원본 저장소, 만료 후 삭제)
└── {gymCode}/{date}/{hour}/{timeSlot}/{cameraCode}.mp4
my-koks 버킷 (마이콕 영구 저장소)
└── {userUuid}/{myKokUuid}/{cameraCode}.mp4
{userUuid}/{myKokUuid}/thumbnail.webp
원본은 체육관 기준으로 정리되고, 마이콕은 사용자 기준으로 정리됩니다.
PLUS 멤버십과 제한
필수 조 건
마이콕은 PLUS 멤버십 전용 기능입니다. 모든 마이콕 API 엔드포인트에 @RequiresPlus 가드가 적용되어 있어, PLUS가 아닌 사용자는 접근할 수 없습니다.
개수 제한
| 항목 | 값 |
|---|---|
| 최대 보관 개수 | 100개 |
| 제한 기준 | PLUS 멤버십의 benefitPolicy.myKokMaxCount |
| 초과 시 | 저장 거부 (MY_KOK_LIMIT_EXCEEDED 에러) |
구독 해지 시 동작
| 단계 | 설명 |
|---|---|
| 해지 즉시 | 마이콕 접근 차단 (API 가드에서 거부) |
| 유예 기간 | 마이콕에 expiresAt 설정 (기본 10일) |
| 유예 종료 후 | 마이콕 데이터 삭제 |
| 재구독 시 | 유예 기간 내라면 데이터 복원 가능 |
원본 삭제와 영구 보존
왜 만료 직전에 클립을 만드는가?
마이콕 저장 시점에 바로 클립을 만들지 않고, 원본 만료 직전까지 미루는 전략을 사용합니다.
| 전략 | 장점 |
|---|---|
| 원본 있는 동안은 원본으로 재생 | 불필요한 클립 생성 방지 (저장 후 삭제 반복 시) |
| 만료 직전 한꺼번에 클립 생성 | VM 효율적 활용 (같은 Video의 여러 마이콕 일괄 처리) |
| 클립 완성 후 원본 삭제 | 원본과 클립 동시 존재 기간 최소화 → 스토리지 절약 |
타임라인
[원본 생성] ─────────────── [만료 시점] ─── [삭제]
│ │ │
│ ORIGINAL 상태 │ CLIPPING │
│ (원본으로 재생) │ (클립 생성) │
│ │ │
│ 마이콕 자유 추가/삭제 │ 배치 처리 │ CLIPPED
│ │ (VM 작업) │ (독립 클립 보유)
│ │ │
▼ ▼ ▼
마이콕 저장 03:30 배치 시작 원본 삭제
+ VM 삭제
삭제 순서
원본 삭제는 해당 VM의 모든 마이콕이 완료된 후 일괄 수행합니다.
마이콕 하나가 완료될 때마다 해당 Video 원본을 삭제하면, 같은 Video의 다른 마이콕이 아직 처리 중일 수 있습니다. 원본이 사라지면 남은 마이콕의 클립 생성이 불가능해집니다. 반드시 모든 마이콕이 완료된 후 일괄 삭제해야 합니다.
다운로드 구매
마이콕 클립은 앱 내 스트리밍으로 시청할 수 있지만, 기기에 저장하려면 톨(Grain)으로 구매해야 합니다. 1회 구매당 10회 다운로드 사이클이 제공되며, 구매 즉시 다운로드 URL이 반환됩니다.
가격 산정
원본 비디오와 동일한 초당 비율로 가격을 산정합니다.
| 콘텐츠 | 길이 | 가격 | 초당 비용 |
|---|---|---|---|
| 원본 비디오 | 10분 (600초) | 2,000 grain | 3.33 grain/초 |
| 마이콕 클립 | 15초 | 50 grain | 3.33 grain/초 |
산정식:
2000 × (15 / 600) = 50 grain
다운로드 사이클
| 항목 | 값 |
|---|---|
| 1회 결제당 다운로드 횟수 | 10회 |
| 만료 기간 | 무기한 |
| 사이클 소진 시 | 동일 비용(50 grain)으로 재결제 → 새 10회 사이클 |
카메라별 개별 구매
마이콕 1개에는 모든 카메라 앵글이 포함되어 있지만, 다운로드는 카메라별로 개별 구매합니다. 각 카메라 앵글마다 별도의 다운로드 사이클이 생성됩니다.
클라이언트 플로우
- 이력/상태 조회 →
canDownload확인 canDownload: true→ 다운로드 URL 요청 (횟수 차감)canDownload: false→ 구매 또는 재구매 (Grain 차감 + URL 즉시 반환)