Captain's Log
← Observatory
Observatory·

같은 뼈대, 반대의 선택 — F1과 콘텐츠, 두 데이터 파이프라인 비교

F1 대시보드와 문화 콘텐츠 프로젝트는 같은 '수집→전처리→분석→시각화' 뼈대를 쓰지만, 저장·전처리·분석·시각화가 단계마다 반대로 갈렸다. 데이터의 모양과 프로젝트의 목표가 어떻게 기술 선택을 결정하는지, 이론과 실무 양쪽에서 복기한다.

TrainingDataArchitecture

왜 이 비교를 남기나

두 개인 프로젝트를 나란히 놓고 보니, 같은 "수집 → 전처리 → 분석 → 시각화" 뼈대를 쓰면서도 단계마다 반대의 기술을 선택했다. 우연이 아니다. 데이터의 모양과 프로젝트의 목표가 다르면 정답도 달라진다. 그 이유를 이론과 실무 양쪽에서 복기해 둔다.

  • F1: 레이스 결과·텔레메트리·규정 데이터로 분석·ML 예측·실시간 해설까지, Cloud Run에 배포된 라이브 서비스
  • contents: 영화(→음악·책 확장 예정) 데이터로 추천·트렌드·시대별·인물별 분석, 로컬 검증 단계

1. 저장 — 파일(CSV/Parquet) vs 데이터베이스(PostgreSQL)

이론. 선택 기준은 "데이터의 모양"과 "어떤 질문을 던질 것인가"다.

파일 기반 (F1)관계형 DB (contents)
데이터 모양평평한 표 — 한 행이 독립적 관측치그물망 — 여러 표가 서로를 참조
어울리는 질문"시즌 전체를 훑어 통계 내줘""봉준호와 3번 이상 작업한 배우는?"
핵심 개념분석 지향(OLAP스러운) 접근정규화(normalization)

F1의 한 행은 "2021 시즌 5라운드, 페르스타펜, 그리드 1번, 최종 1위" — 그 자체로 완결된 사실이라 시즌별 CSV로 두고 pandas로 통째로 읽는 게 가장 단순하고 빠르다. contents는 작품↔인물↔장르의 다대다 관계가 핵심이다. 한 표에 다 넣으면 갱신 이상(같은 인물을 여러 행에서 고쳐야 함)과 집계 오류(표기가 다르면 다른 사람으로 세어짐)가 생기므로, person(인물은 한 번만) ↔ credit(관계만) ↔ work(작품은 한 번만)로 쪼갠다. 교과서적 정규화 패턴이다.

실무. F1은 CSV 44개(1.2MB)가 전부라 DB 서버가 필요 없고, 그 덕에 CSV를 Docker 이미지에 그대로 넣어 배포가 극도로 단순해졌다. 무거운 랩 데이터만 Parquet(컬럼 압축 포맷)를 썼다. contents는 PostgreSQL을 상시 띄우는 운영 비용을 지불하는 대신, 데이터가 수천 편으로 늘고 질의가 복잡해질 미래에 대비했다. 반대로 F1이 DB를 안 쓴 것도 오버엔지니어링 회피라는 실무 판단 — "수요 확인 시 확장"을 명시해 뒀다.

2. 전처리 — 싸우는 적이 다르다: 누수 방지 vs 엔티티 해소

F1의 적은 데이터 누수(leakage)다. 예측 시점에 알 수 없는 미래 정보가 학습에 섞이면, 검증 점수는 높은데 실전에서 무용지물이 된다. ML 실무에서 가장 흔하고 치명적인 실수다. 그래서 드라이버 폼은 shift(1)로 직전 경기까지만, 서킷 혼전지수는 expanding()+shift(1)로 직전 시즌까지만 반영했고, 검증도 무작위 분할이 아닌 시계열 분할(20162023 학습 / 20242026 검증)로 미래→과거 역전을 차단했다.

contents의 적은 엔티티 해소(entity resolution)다. "봉준호" / "Bong Joon-ho" / "Joon-ho Bong"을 한 명으로 합치지 못하면 필모그래피·협업 횟수·추천까지 그 위의 모든 집계가 틀린다. person에 표준 인물 한 명만 저장하고 person_alias에 출처별 표기를 따로 보관해, 나중에 Spotify를 붙여도 같은 인물을 이어 붙일 수 있게 했다.

왜 적이 다른가. F1의 API는 driverId라는 고유 ID를 보장해 "같은 사람 문제"가 없고, 대신 시간 순서가 있는 예측을 하니 누수가 문제다. contents는 예측이 아니라 인물 중심 집계라 누수 걱정이 없고, 여러 표기를 합치는 게 문제다. 데이터의 성질이 전처리의 난제를 결정한다.

3. 분석 — 지도학습 예측 vs 비지도 유사도

F1은 지도학습. 정답(과거 결과)이 있는 데이터로 미래를 맞힌다. 포디움 여부는 분류(로지스틱 회귀), 완주 순위는 회귀(Ridge)로 정식화했다. 교훈 하나: 처음 쓴 GradientBoosting은 과거의 메르세데스/레드불 시대 패턴에 과적합해, 판도가 바뀐 2024~2026에서 "그리드 순서대로 들어온다"는 베이스라인조차 못 넘었다. 일부러 더 단순하고 규제가 강한 모델로 후퇴해서야 공정하게 이겼다. 복잡한 모델 ≠ 좋은 모델. 페널티 분석에서는 "페널티가 없었다면 몇 위였을까"를 재순위하는 반사실적(counterfactual) 추론까지 갔다.

contents는 비지도 유사도. 정답 레이블 없이 작품 속성으로 "비슷함"을 계산한다. 텍스트를 TF-IDF로 벡터화(흔하지 않지만 이 문서에 자주 나오는 단어에 가중치) → 코사인 유사도(0~1) 계산 → 높은 순으로 추천. 추천 이론의 양대 축 중 콘텐츠 기반 필터링이고, 사용자 로그가 필요한 협업 필터링은 다음 단계다. 실무 디테일: 한국어는 형태소 분석기 대신 문자 n-gram으로 우회 — 설치가 무거운 도구 없이 그럭저럭 작동하는 전형적 트레이드오프.

왜 방법이 다른가. F1은 "미래에 일어날 일"을 묻고(→ 정답 있는 과거로 학습), contents는 "이미 있는 것들 사이의 관계"를 묻는다(→ 정답이 필요 없는 유사도). 질문의 종류가 방법론을 결정한다.

4. 시각화 — 탐색형 제품 vs 설명형 리포트

이론. 시각화는 목적에 따라 둘로 나뉜다. 사용자가 직접 만지며 발견하는 **탐색형(exploratory)**과, 이미 발견한 결과를 전달하는 설명형(explanatory).

F1은 탐색형 제품. Plotly 인터랙티브 차트(확대·호버·트랙맵) + Streamlit 4탭 대시보드. 대가도 치렀다 — Streamlit은 상시 WebSocket이 필요해 정적 호스팅이 불가하고, Docker + Cloud Run(--session-affinity)이라는 배포 복잡도를 감수했다.

contents는 설명형 리포트. matplotlib 정적 PNG 3장 + 가벼운 Flask 화면 + 탐색용 Jupyter 노트북. 아직 "남에게 보여주는 단계"가 아니라 "파이프라인이 맞는지 검증하는 단계"라서 이걸로 충분하다. 성숙도의 차이가 시각화 스택에 그대로 반영됐다.

5. 수집 — 다중 소스 계층화 vs 단일 소스 + 픽스처

F1: 소스별 어댑터 패턴. 커버 범위가 다른 소스(Jolpica 2016+, FastF1 2018+, OpenF1 실시간)를 계층으로 분리해 각자 잘하는 걸 맡겼다. 실무 디테일: 페이지네이션 전체 순회, 요청 간 0.3초 + 429 시 지수 백오프, 무거운 텔레메트리는 로컬 캐시, 그리고 2017 시즌에 실제로 필드가 누락된 사례를 방어적 파싱으로 흡수 — 실데이터는 반드시 더럽다.

contents: 픽스처(fixture) 패턴. TMDB 키가 없어 실제 응답과 똑같은 구조의 샘플 12편으로 파이프라인 전체를 먼저 완성했다. 테스트의 모킹 개념을 파이프라인에 적용한 것 — 키가 생기면 수집 명령만 바꾸면 나머지는 코드 수정 없이 그대로 돈다. 외부 의존성 때문에 개발이 멈추지 않게 하는 실용적 기법이다.

한눈에 정리

질문F1의 답contents의 답갈린 이유
데이터 모양?평평한 시계열 표작품↔인물 그물망도메인의 본질적 구조
어디에 저장?CSV/ParquetPostgreSQL 정규화관계 질의 필요 여부
전처리의 적?미래 정보 누수동일 인물 표기 분산예측이냐 집계냐
분석의 질문?"다음 결과는?" (지도학습)"무엇이 비슷한가?" (유사도)정답 레이블의 유무
시각화 목적?탐색하는 제품검증 리포트프로젝트 성숙 단계
어디까지 왔나?클라우드 라이브 + 계측로컬 검증, 실데이터 대기배포할 만큼 데이터가 찼는가

한 줄 요약. F1은 시계열 데이터 위에서 예측 서비스를 완성하는 깊이의 프로젝트, contents는 관계형 데이터 모델 위에서 여러 도메인으로 넓히는 확장의 프로젝트다. 같은 뼈대에서 반대의 선택이 나온 건, 각 단계의 도구가 데이터의 성질과 목표를 따라갔기 때문이다.