약 4분

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)로 직전 시즌까지만 반영했고, 검증도 무작위 분할이 아닌 시계열 분할(2016~2023 학습 / 2024~2026 검증)로 미래→과거 역전을 차단했다.

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는 관계형 데이터 모델 위에서 여러 도메인으로 넓히는 확장의 프로젝트다. 같은 뼈대에서 반대의 선택이 나온 건, 각 단계의 도구가 데이터의 성질과 목표를 따라갔기 때문이다.