웹소설 트렌드 분석 워크플로우 (2): 분석 파이프라인 최적화와 ELTV 아키텍처 도입


어제의 작업은 비용 대비 효율성을 극대화하면서도, 파이프라인의 확장성을 확보하는 데 집중했습니다. 매주 수백 개씩 긁어오는 랭킹 데이터를 어떻게 저비용으로 처리할 것인가, 그리고 이 파편화된 데이터 더미들을 어떻게 구조화된 블로그 포스트로 자동 변환할 것인가에 대한 고민의 결과물입니다.

특히 이번 작업의 핵심은 **분석 단계(Analysis Stage)**의 기틀을 잡는 것이었습니다. 분석 단계를 정의하려면 결국 ‘어떤 정보를 도출할 것인가’라는 목적부터 명확히 해야 했는데, 당장 기획된 최우선 산출물은 ‘플랫폼별 주간 결산’이었습니다. 그래서 주간 순위에 진입한 신작의 분포와 그 신작들을 견인하는 태그(Keyword) 트렌드를 분석하는 것을 기본 골조로 삼았습니다.

하지만 현재 시점에서 향후 추가될 분석 항목이나 리포트의 형태가 모두 완벽하게 정의된 것은 아닙니다. 그래서 특정 분석 로직에 종속되는 구조를 피하고, 큰 틀에서 데이터 파이프라인의 규격을 먼저 정의한 뒤 세부 분석 로직은 필요할 때마다 붙이고 뗄 수 있는 플러그인 방식을 채택하여 불확실성에 대응했습니다.

솔직히 말하면, 여기에는 소프트웨어 공학을 업으로 삼는 사람이라면 누구나 가지고 있는 고질병이 한 스푼 더 섞여 있기도 합니다. 바로 확장성(Extensibility)에 대한 편집증적인 집착이죠. “당장 필요한 건 이것 하나인데… 근데 나중에 기능이 추가되면?” — 이 생각이 드는 순간, 하드코딩 된 분석 로직 파일 하나로 끝낼 수 있었던 작업이 인터페이스 정의와 플러그인 레지스트리를 갖춘 미니 프레임워크로 번지는 거거든요. 돌이켜보면 이 편집증이 오버엔지니어링의 씨앗이 되기도 하지만, 이번만큼은 분석 요구사항이 실제로 미정이었기 때문에 합리적인 선택이었다고 스스로를 납득시켰습니다.

무엇보다, 요즘은 제가 직접 코딩하는 시대가 아니잖아요? AI 에이전트한테 “이 인터페이스 규격에 맞춰서 새 Analyzer 플러그인 하나 짜줘”라고 딸깍 한 방이면 끝나는 세상이니, 확장성을 위해 미리 깔아둔 추상화 레이어가 오버엔지니어링이 될 걱정 자체가 없어진 거죠. 오히려 이런 시대에는 설계를 안 깔아두는 쪽이 더 손해입니다.


• 목표 산출물: ‘주간 결산 포스트’는 이런 구조입니다

본격적인 개발 이야기에 앞서, 이 파이프라인이 최종적으로 뱉어낼 ‘주간 결산 포스트’가 어떤 모습인지 먼저 정리해 두겠습니다. 결국 아래의 콘텐츠를 자동으로 생성하기 위해 뒤에서 나올 모든 개발이 진행된 것이니까요.

  1. 📊 이주의 ‘진짜’ 트렌드 키워드 (Micro-Trending Tags): 수백 편의 기성작 데이터를 전부 소거하고, 오직 이번 주에 새롭게 베스트에 진입했거나 살아남은 50편 이하의 유망주들이 달고 있는 태그 빈도수만 추출합니다. “이번 주 신인 구역에서는 #착각, #TS 태그가 갑작스레 치고 올라왔습니다” 같은, 시장 최전선의 극초기 트렌드를 포착하는 코너입니다.

  2. 🔥 루키 오브 더 위크 (Rookie of the Week): 이번 주에 스냅샷에 처음 포착된(DISCOVERED) 작품 중 가장 무서운 기세로 올라온 단일 작품을 집중 해부합니다. 단순 총 조회수가 아닌 VPC(편당 조회수) 메트릭을 앞세워 “단 15편 만에 편당 조회수 X만을 찍으며 돌파 중인 괴물 신인” 형태로 조명하는 거죠.

  3. 💎 코어 팬덤 결집도 랭킹 (Highest RPV): 조회수는 1위가 아닐지라도, 들어온 독자들의 추천 결속력이 가장 높은 작품들을 꼽습니다. “아직 랭킹은 30위권이지만, 조회수 100회당 추천이 N개씩 박히고 있는 충성도 1위 작품” — 곧 상위권으로 치고 올라갈 확률이 높은 숨은 알짜를 찾아내는 코너입니다.

  4. 🎓 이주의 졸업생 (명예의 전당): 살얼음판 같은 50편 이하 경쟁 구간을 무사히 버텨내고 기성작의 반열에 오른 작품들을 짧게 축하합니다. 독자 입장에서는 “이 작품은 이제 초반부 폐사(?) 걱정 없이 정주행하셔도 좋습니다”라는 신뢰도 높은 추천픽이 되는 셈이죠.

  5. 📉 생존 지수 및 총평 (Survival Dynamics): 이번 주에 신규 진입한 유망주 수와 탈락한 유망주 수를 대조하여 시장의 물갈이 온도를 측정합니다. “이번 주는 11개의 유망주가 순위권 밖으로 밀려나고 17개의 새로운 별이 등장하는 등, 물갈이가 몹시 심한 한 주였습니다” 같은 전체적인 인사이트를 제공하는 섹션입니다.

이 다섯 가지 코너를 매주 자동으로 뽑아내려면, 결국 신작/기성작 분류, 상태 추적, 편당 효율 지표 연산, 태그 필터링 같은 분석 모듈들이 필요합니다. 아래는 이 요구사항들을 실현하기 위해 진행된 개발 작업입니다.


• 추적 대상 선별: ‘50편 컷오프’ 규칙

위 코너들을 만들기 위해 가장 먼저 해야 할 일은 ‘유망주’와 ‘기성작’을 칼같이 나누는 것이었습니다. 이전 포스트에서 구축한 크롤러가 매주 쏟아내는 원시 데이터(Raw Data)에는 연재 1,000편을 넘긴 장수 작품부터 이번 주에 갓 올라온 신작까지 전부 뒤섞여 있거든요. 이 숫자 더미를 블로그 포스트에 바로 꽂아 넣을 수 있는 정제된 데이터로 가공하려면, 먼저 명확한 기준선이 필요했습니다.

여기서 적용한 규칙이 **“50편 컷오프”**입니다. 웹소설 시장에는 “50편 안에 못 뜨면 끝이다”라는 암묵적인 생존 법칙이 있습니다. 독자들은 보통 초반 5편에서 10편 안에 작품을 계속 볼지 결정하고, 그 장벽을 넘긴 작품이 입소문을 타며 차트에 올라오기까지 대략 30편에서 50편 정도의 구간이 걸립니다. 다시 말해, 50편 이하의 작품이 베스트에 들어와 있다는 건 지금 이 순간 실시간으로 트렌드를 만들어가고 있는 작품이라는 뜻이고, 50편을 돌파하고도 차트에 남아있다면 이미 검증이 끝난 기성작으로 봐도 무방합니다.

참고로 노벨피아의 경우 ‘신작 베스트’라는 별도 섹션이 존재하긴 합니다. 하지만 거기에만 머물고 주간 베스트 100위권에 진입하지 못하는 작품은 실질적인 시장 트렌드에 크게 기여하지 못한다고 판단하여, 굳이 해당 섹션까지 크롤링 범위를 넓히지는 않았습니다. 주간 100위에 들어올 정도의 돌파력을 가진 신작만이 분석할 가치가 있다는 것이 현재 파이프라인의 설계 철학입니다.

이 분야의 특성 덕분에 기존에 매번 Firestore 전체 히스토리를 스캔하며 신작 여부를 판별해야 했던 과정을 완전히 생략할 수 있었습니다. chapters <= 50 필터 하나로 DB 전수 조회(N+1 문제)를 회피하고, 비교 대상 자체가 극소수로 줄어드니 처리 속도와 비용 모두 극적으로 개선되었습니다.


• 상태 머신(State Machine) 기반 라이프사이클 추적

유망주를 걸러내는 데는 성공했지만, 이것만으로는 부족합니다. ‘졸업생 명예의 전당’이나 ‘생존 지수’를 뽑아내려면 같은 작품이 매주 어떤 상태로 변하는지를 추적해야 하거든요. 그래서 각 유망주의 생애 주기를 기록하는 상태 머신을 도입했습니다.

유망주 리스트를 다음 4단계로 세분화합니다.

  • DISCOVERED: 파이프라인에 최초 포착된 신작. “이번 주에 갑자기 차트에 나타난 새 얼굴”입니다.
  • TRACKING: 베스트 순위를 유지하며 성장세를 그리는 중. 아직 살얼음판 위에 있는 상태죠.
  • GRADUATED: 50편 돌파 성공 및 랭킹 안착. 검증 완료된 기성작으로 취급하며 추적을 종료합니다.
  • DROPPED: 50편 이전에 순위권에서 이탈. 안타깝지만 추적을 종료합니다.

매주 스냅샷을 비교하여 각 작품의 상태를 자동 전이시키면, 위 코너들에서 필요한 데이터가 알아서 분류됩니다. 루키 오브 더 위크는 DISCOVERED 풀에서, 졸업생은 GRADUATED로 전환된 작품에서, 생존 지수는 DISCOVERED vs DROPPED 숫자 비교에서 바로 뽑아낼 수 있는 구조입니다.


• 커스텀 트래킹 지표(Metrics) 설계

상태 추적만으로는 “누가 가장 잘 나가는가”를 판별할 수 없습니다. 총 조회수만 보면 연재 편수가 많은 작품이 당연히 높게 나오니까요.

웹소설 플랫폼이 보여주는 숫자는 조회수, 추천수, 선호수(즐겨찾기) 등 여러 가지입니다. 이 중 ‘선호수’는 독자가 연재 알림을 받기 위해 등록하는 것이라 오래 연재한 작품일수록 유리한 구조이고, ‘조회수’와 ‘추천수’도 단독으로 쓰면 편수가 많을수록 자연히 불어나는 함정이 있습니다. 그래서 이 숫자들을 편수와 교차시켜(나눠서) 편당 효율로 환산한 커스텀 지표를 만들었습니다.

  • VPC (Views Per Chapter): 편당 조회수. 누적 조회수가 높아 보여도 편수가 200편인 작품과 10편인 작품은 전혀 다른 이야기거든요. 이 지표가 높을수록 “적은 편수로 많은 조회를 끌어내는 괴물 신인”으로 볼 수 있습니다.
  • RPV (Recommendations Per View): 조회수 대비 추천수 비율. 들어온 독자 중 몇 명이 추천 버튼을 누르는지를 보여주는 코어 팬덤 결집도 지표입니다. 이 수치가 높다는 건 “아직 대중적 인지도는 낮지만, 읽은 사람들은 거의 다 팬이 된다”는 뜻이죠.

극초기 트렌드 키워드 추출

지표 설계와 함께, 태그 분석 로직도 추가했습니다. 50편 이상의 기존 흥행작이 가진 무거운 태그들을 전부 배제하고, DISCOVEREDTRACKING 그룹의 태그만 실시간 집계하여 노이즈 없는 ‘극초기 트렌드 키워드’ 추출에 성공했습니다. 이것이 ‘이주의 진짜 트렌드 키워드’ 코너의 데이터 소스가 됩니다.

예를 들어, 기성작까지 포함해서 태그를 집계하면 #판타지, #회귀 같은 만년 상위 태그가 항상 1~2위를 차지합니다. 하지만 유망주만 따로 모아서 집계하면 #착각, #TS처럼 이번 주에 갑자기 치고 올라온 마이크로 트렌드가 수면 위로 드러나게 됩니다. 숲이 아니라 새로 자라나는 새싹만 보는 것이죠.

🚀 향후 계획 (이상치 통제): 연재 초반(15편 미만)의 작품들은 조회수나 추천수 12개에도 VPC, RPV 지표가 과하게 부풀려지는 케이스가 존재합니다. 향후에는 ‘최소 연재 편수(예: 5편 이상)‘에 도달한 데이터만 유효 지표로 산정하거나 시간에 따른 가중치를 부여하는 필터링 로직을 추가할 계획입니다.


• 플러그인(Plugin) 기반 ELTV 파이프라인 아키텍처 구축

지금까지는 일단 데이터를 축적하는 것이 급선무였기 때문에, 크롤링 코드만 만들어 클라우드에 올려두는 것으로 충분했습니다. 하지만 위에서 만든 상태 추적, 메트릭 연산, 태그 필터링 같은 분석 모듈들이 하나둘 붙기 시작하니, 이 워크플로우 전체를 일관되게 관리하는 상위 영역이 필요해졌습니다. 그래서 파편화된 스크립트들을 하나의 공장 라인으로 통합하기 위한 파이프라인 인터페이스를 제정했습니다.

[아키텍처 결정: 왜 ETL이 아닌 ELTV인가?] 이번 파이프라인은 수집(Extract) ➔ 원본 저장(Load) ➔ 다중 분석(Transform) ➔ 시각화 생성(Visualize)ELTV 스트림으로 확립했습니다. 변환된 데이터를 저장(ETL)하는 대신 원본 스냅샷을 먼저 저장(ELT)한 이유는 플러그인 방식의 확장성 때문입니다. 향후 새로운 종류의 블로그 포스트나 리포트가 필요해질 때마다, 과거의 원본 데이터를 훼손 없이 그대로 불러와 새로운 Analyzer 플러그인에 밀어 넣을 수 있어야 하니까요.

  • 코어 인터페이스 정의: 분석 모듈(IAnalyzer)과 문서 생성 모듈(IGenerator)의 표준 규격을 정의했습니다 (src/core/interfaces.ts).
  • 통합 오케스트레이터 (src/pipeline.ts): 다수 분석 모듈의 병렬 실행(Promise.all)을 제어하고, 산출된 데이터를 엮어 시각화 모듈로 순차적(Sequential)으로 주입합니다.
  • 1호기 안착: 위에서 만든 50편 컷오프, 상태 머신, VPC/RPV 연산, 태그 필터링을 하나로 묶어 TrendCandidateAnalyzer라는 이름의 플러그인으로 리팩터링했으며, E2E 테스트까지 성공적으로 마쳤습니다. 컨베이어 벨트 위에 첫 번째 가공기를 올려놓은 셈이죠.

현재 이 파이프라인은 로컬 스냅샷(snapshots.json) 기반으로 동작하고 있으며, 클라우드 승격 시에는 저장소만 Firestore로 교체하면 되는 구조입니다.


• 다음 여정: 공장에서 첫 번째 완제품 찍어내기

이로써 데이터를 긁어오는 크롤러(1편)에 이어, 그 데이터를 분석하고 가공하는 두 번째 공정 라인까지 조립이 완료되었습니다. 원시 데이터 댐에서 물을 끌어와 정수 처리하는 필터가 얹어진 셈이죠.

다음 포스트에서는 이 ELTV 파이프라인의 마지막 단계인 시각화(Visualize) — 즉, 가공된 데이터를 실제 블로그 포스트 본문으로 변환하는 과정을 다룰 예정입니다. 구체적으로는 클라우드 상에서 Google AI Studio의 Gemini API를 호출하여, 위에서 산출된 JSON 데이터를 프롬프트에 주입하고 주간 결산 포스트의 초안을 자동으로 작성하는 워크플로우를 만들어 볼 계획입니다. 컨베이어 벨트의 끝에서 완제품이 톡 떨어지는 순간, 기대해 주시기 바랍니다.