내가 증거 기반의 SaaS 기회 파이프라인을 구축한 방법
요약
SaaS 기회 발굴을 위해 다중 소스 데이터를 수집하고 분석하는 증거 기반 조사 파이프라인 구축 사례를 다룹니다. LLM을 활용한 신호 해석과 결정론적 스코어링, 내구성이 있는 워크플로 설계를 통한 아키텍처 결정 사항을 공유합니다.
핵심 포인트
- 플랫폼 인기가 아닌 데이터의 증거를 정규화할 것
- 신호 해석에는 LLM을 쓰되 최종 점수 산정에는 사용하지 말 것
- 기회의 품질과 신뢰도를 분리하여 관리할 것
- 단순 cron 작업 대신 내구성이 있는 워크플로를 사용할 것
- 모든 결론의 근거가 되는 원본 증거를 보존할 것
GripeRadar의 이면에 있는 어댑터(adapters), 증거 모델(evidence model), LLM 분석, 결정론적 스코어링(deterministic scoring), 그리고 내구성이 있는 오케스트레이션(durable orchestration)에 대한 실질적인 고찰.
나는 2026년 6월에 GripeRadar를 만들기 시작했습니다. 왜냐하면 계속해서 똑같은 문제에 부딪혔기 때문입니다. SaaS 아이디어를 생성하는 것은 쉬웠지만, 그것을 만들어야 할 설득력 있는 이유를 찾는 것은 어려웠습니다.
Hacker News의 불만 사항은 진정한 좌절감을 드러낼 수 있습니다. 성장하는 GitHub 리포지토리(repository)는 기술적 모멘텀을 보여줄 수 있습니다. Google Trends는 증가하는 관심을 보여줄 수 있습니다. Product Hunt는 출시 활동을 드러낼 수 있습니다. 매출 데이터는 상업적 행동을 보여줄 수 있습니다.
하지만 이러한 신호(signals) 중 그 어떤 것도 동일한 의미를 갖지는 않습니다.
열 개의 불만 사항이 지불 의사를 증명하는 것은 아닙니다. GitHub 스타(stars)가 충족되지 않은 수요를 증명하는 것은 아닙니다. 검색 성장(Search growth)이 유용한 제품을 만들 수 있다는 것을 증명하지는 않습니다. 매출은 누군가가 돈을 벌고 있다는 것을 증명하지만, 반드시 인근의 기회가 여전히 열려 있다는 것을 의미하지는 않습니다.
그래서 나는 또 다른 아이디어 생성기를 만드는 대신, 더 유용한 질문을 중심으로 다중 소스 조사 파이프라인(multi-source research pipeline)을 구축했습니다.
이 기회를 뒷받침하는 증거는 무엇인가, 그 증거가 실제로 의미하는 바는 무엇인가, 그리고 여전히 불확실한 것은 무엇인가?
이 글에서는 파이프라인이 어떻게 작동하는지, 그 이면의 아키텍처 결정(architectural decisions), 그리고 내가 다시 시작한다면 피할 실수들에 대해 설명합니다.
요약 (TL;DR)
파이프라인은 7가지 제품 단계를 따릅니다:
Source adapters (소스 어댑터)
↓
Raw signal ingestion (원시 신호 수집)
...
가장 중요한 결정 사항은 다음과 같습니다:
- 플랫폼의 인기를 정규화(normalizing)하는 대신 증거를 정규화할 것.
- 신호를 해석하는 데는 LLM을 사용하되, 최종 점수를 할당하는 데는 사용하지 말 것.
- 기회의 품질(quality)을 신뢰도(confidence)와 분리하여 유지할 것.
- 모든 결론에 이의를 제기할 수 있도록 원래의 증거를 보존할 것.
- 스케줄링을 느슨하게 시간 설정된 cron 작업 세트가 아닌, 내구성이 있는 워크플로(durable workflow)로 취급할 것.
- 기술적 접근(technical access)과 소스 사용 권한(permission to use a source)을 별개의 문제로 취급할 것.
진짜 문제: 신호는 투표가 아니다
유혹적인 접근 방식은 많은 데이터를 수집하고, 모든 지표를 점수로 변환하여 결과를 순위 매기는 것입니다.
그것은 숫자를 빠르게 만들어낼 수는 있지만, 반드시 유용한 결론을 도출하는 것은 아닙니다.
| 신호 (Signal) | 시사할 수 있는 점 | 증명하지 못하는 점 |
|---|---|---|
| Hacker News 불만 사항 | 창업자 또는 개발자의 고통 | 시장 규모 또는 지불 의사 |
| ... |
파이프라인은 신호와 그 제한된 의미(bounded meaning)를 모두 저장합니다.
GitHub 리포지토리는 기술적 증거 (technical evidence)로 남습니다. 검색 트렌드는 관심 증거 (attention evidence)로 남습니다. 매출 기록은 상업적 증거 (commercial evidence)로 남습니다. 시스템은 나중에 이들을 결합할 수 있지만, 이들이 서로 교체 가능한 단위인 것처럼 가장하지는 않습니다.
이러한 구분이 아키텍처의 기반이 되었습니다.
1단계: 모든 소스를 어댑터 (adapter) 뒤에 배치하기
각 제공업체(provider)는 서로 다른 인증 (authentication), 페이지네이션 (pagination), 속도 제한 (rate limits), 식별자 (identifiers), 메타데이터 (metadata), 그리고 장애 모드 (failure modes)를 가지고 있습니다. 이러한 세부 사항들이 애플리케이션 전체로 퍼지게 두면, 새로운 소스가 추가될 때마다 파이프라인 전체를 수정해야 하는 상황이 발생할 것입니다.
대신 저는 공통된 어댑터 경계 (adapter boundary)를 정의했습니다. TypeScript 인터페이스는 대략 다음과 같습니다:
interface SignalSourceAdapter<TRaw = unknown> {
descriptor: SignalAdapterDescriptor;
executionPolicy?: SignalAdapterExecutionPolicy;
...
각 어댑터는 다음 네 가지 질문에 답합니다:
- 이 소스를 현재 사용할 수 있는가?
- 어떤 독립적인 스트림 (streams)을 가져와야 하는가?
- 한 페이지를 어떻게 검색해야 하는가?
- 원시 레코드 (raw record)를 어떻게 정규화된 신호 (normalized signal)로 변환해야 하는가?
스트림은 키워드, 계정, 채널, 트렌드 피드, 제품 카테고리 또는 API 쿼리일 수 있습니다.
수집 러너 (ingestion runner)는 다음과 같은 공통 메커니즘을 처리합니다:
- 페이지네이션 (Pagination) 및 재시도 (retries)
- 속도 제한 (Rate-limit) 계산
- 레코드 검증 (Record validation)
- 중복 제거 (Deduplication) 및 콘텐츠 해싱 (content hashing)
- 삽입, 업데이트, 변경 없음 및 실패 횟수 카운트
- 정규화된 증거 및 원시 증거의 영속성 (Persistence)
현재 레지스트리 (registry)에는 서로 다른 성숙도 단계를 가진 13개의 어댑터가 포함되어 있습니다. 레지스트리에 등록되었다고 해서 해당 소스가 자동으로 활성화되거나 운영 스케줄링에 포함되는 것은 아닙니다.
일부 소스는 자격 증명 (credentials)을 요구합니다. 일부는 명시적인 정책 검토 (policy review)를 요구합니다. 일부는 전송 방식이 너무 취약하기 때문에 의도적으로 비활성화되어 있습니다. 이를 통해 저는 다른 다운스트림 파이프라인 (downstream pipeline)을 생성하지 않고도 하나의 소스를 제거하거나 일시 중지할 수 있습니다.
의미가 아닌 증거를 정규화하기
정규화된 계약 (normalized contract)에는 다음과 같은 공유 필드가 포함됩니다:
- 소스 및 외부 식별자 (external identifiers)
- 표준 URL (Canonical URL)
- 제목 및 내용
- 발행 및 발견 타임스탬프 (timestamps)
- 소스 신뢰성 메타데이터 (reliability metadata)
- 참여 또는 트렌드 맥락 (engagement or trend context)
- 콘텐츠 해시 (Content hashes)
- 원본 소스 메타데이터 (Raw source metadata)
하지만 정규화가 소스를 차별화하는 요소를 지워버려서는 안 됩니다.
저는 GitHub 스타 (stars)와 YouTube 조회수 (views)를 모두 참여 메타데이터로 저장할 수 있지만, 이 둘을 합쳐서는 안 됩니다. 이들은 서로 다른 행동, 관객, 그리고 참여 수준을 나타내기 때문입니다.
정규화된 레코드 (normalized record)는 다운스트림 단계에 안정적인 기술적 형태를 제공합니다. 소스를 인식하는 메타데이터 (Source-aware metadata)는 나중에 해석하는 데 필요한 의미를 보존합니다.
2단계: LLM을 판사가 아닌 분석가로 활용하기
가공되지 않은 신호 (Raw signals)는 노이즈가 많습니다. 게시물이 실제 고통을 표현하지 않고도 문제를 언급할 수 있습니다. 저장소 (repository)가 제품 기회를 나타내지 않고도 인기가 있을 수 있습니다. 트렌드는 구매자 수요보다는 뉴스에 의해 주도될 수 있습니다.
2단계에서는 OpenRouter 호환 모델을 사용하여 가공되지 않은 신호를 구조화된 분석 (structured analyses)으로 변환합니다. 이 모델은 다음과 같은 근거 있는 요소들을 찾습니다:
- 사용자 또는 고객 세그먼트 (customer segment)
- 영향을 받는 워크플로 (workflow)
- 문제 또는 충족되지 않은 니즈 (unmet need)
- 기존의 임시방편 (workarounds)
- 도구 요청 및 긴급성
- 상업적 의도 (Commercial intent)
- 경쟁 또는 채택 맥락 (adoption context)
- 해석을 뒷받침하는 직접적인 발췌문
후보들은 모델에 도달하기 전에 순위가 매겨지며, 적응형 소스 할당량 (adaptive source quotas)을 통해 하나의 노이즈가 많은 제공자가 전체 배치 (batch)를 소비하는 것을 방지합니다.
모든 응답은 검증되며 명시적인 상태 (state)가 할당됩니다:
accepted
needs_review
rejected
...
이 상태 모델은 매우 중요함이 증명되었습니다. 성공적으로 파싱된 모든 응답을 신뢰할 수 있는 것으로 취급한다면, 약한 해석들이 클러스터링 (clustering) 단계로 조용히 넘어가는 결과를 초래할 것입니다.
구조화된 출력 (Structured output)이 도움이 되지만, 그것이 마법은 아닙니다. OpenRouter의 구조화된 출력 문서에서는 JSON Schema가 어떻게 호환 가능한 모델들을 제약할 수 있는지 설명합니다. 애플리케이션에는 여전히 검증 (validation), 실패 상태 (failure states), 재시도 제한 (retry limits), 그리고 모델 버전 추적 (model-version tracking)이 필요합니다.
3단계: 증거를 기회로 클러스터링 (Cluster evidence into opportunities)
단 하나의 신호만으로는 기회로 간주하기에 부족한 경우가 많습니다.
여러 게시물이 서로 다른 언어를 사용하여 동일한 워크플로 (workflow) 문제를 설명할 수 있습니다. GitHub 이슈가 Hacker News에서 발견된 불만 사항을 뒷받침할 수도 있습니다. 검색량의 증가가 이미 다른 곳에서 입증된 문제에 타이밍 맥락 (timing context)을 더해줄 수도 있습니다.
클러스터링 (clustering) 단계는 원래의 증거 링크를 유지하면서 호환 가능한 분석들을 그룹화합니다. 현재의 점진적 구성 (incremental configuration)은 두 가지 임계값 (thresholds)을 사용합니다:
const clustering = {
matchThreshold: 0.72,
reviewThreshold: 0.62,
...
강력한 매칭 (strong match)은 기존의 기회를 업데이트할 수 있습니다. 경계선에 있는 매칭 (borderline match)은 클러스터에 조용히 강제로 포함되는 대신, 검토할 가치가 있는 상태 (review-worthy)가 됩니다.
상업적 또는 기술적 맥락은 기회를 강화할 수 있지만, 근본적인 문제를 대체해서는 안 됩니다. 이는 시스템이 인기 있는 기술을 발견한 뒤, 그것을 중심으로 허구의 고객 문제를 역공학 (reverse-engineering)하는 것을 방지합니다.
4단계: 분류 (Classification)를 분리하여 유지하기
분류 (Classification)는 다음과 같은 질문에 답합니다:
- 이것은 B2B, B2C, 또는 개발자 중심인가?
- 어떤 산업이나 워크플로에 속하는가?
- 이것은 새로운 제품인가, 자동화 레이어 (automation layer)인가, 버티컬 도구 (vertical tool)인가, 아니면 인프라 (infrastructure)인가?
- 이 분류가 게시하기에 충분히 확신할 수 있는 수준인가?
분류가 점수 산정 (scoring)과 분리되어 있는 이유는 두 작업의 실패 모드 (failure modes)가 다르기 때문입니다.
증거가 강력하더라도 카테고리가 모호할 수 있습니다. 반대로, 기회는 분류하기 쉽지만 뒷받침되는 근거가 부족할 수도 있습니다. 이 두 가지 결정을 하나의 불투명한 모델 응답으로 결합하면 그러한 차이를 숨기게 될 것입니다.
5단계: 결정론적으로 점수 산정하기 (Score deterministically)
저는 최종적인 기회 점수 (opportunity score)가 LLM에게 "이 아이디어가 1점에서 100점 사이로 얼마나 좋은가요?"라고 묻는 것에 의존하는 것을 원하지 않았습니다.
그러한 답변은 재현(reproduce), 비교(compare) 또는 디버깅(debug)하기 어려울 것입니다.
점수 산정 단계는 결정론적(deterministic)이며 버전이 관리됩니다. 이 단계는 소스 중립적인(source-neutral) 7가지 차원을 평가합니다:
- 시장 견인력 (Market pull)
- 미충족 기회 (Unmet opportunity)
- 상업적 생존 가능성 (Commercial viability)
- 타이밍 및 모멘텀 (Timing and momentum)
- 제품 실현 가능성 (Product feasibility)
- 유통 접근성 (Distribution access)
- 시장 공백 (Market whitespace)
증거가 누락된 경우에는 낙관적인 가정 대신 보수적인 사전 확률 (conservative priors)을 적용합니다.
또한 이 시스템은 세 가지 개념을 분리하여 유지합니다.
기회 품질 (Opportunity quality)
가용한 증거를 바탕으로 해당 기회가 얼마나 매력적으로 보이는가?
신뢰도 (Confidence)
해당 결론이 얼마나 강력하게 뒷받침되는가?
신뢰도는 증거의 독립성 (evidence independence), 차원 커버리지 (dimension coverage), 종단적 깊이 (longitudinal depth), 소스 신뢰성 (source reliability), 분석 일관성 (analysis consistency) 및 완결성 (completeness)을 고려합니다.
따라서 유망한 기회는 품질은 높지만 신뢰도는 낮을 수 있습니다. 이는 더 많은 연구가 필요할 수는 있지만, 아직 구축 (build)을 약속할 단계는 아님을 의미할 수 있습니다.
증거 성숙도 (Proof maturity)
실제로 어떤 종류의 증거가 관찰되었는가?
발견 (discovery) → 유망함 (promising) → 확증됨 (corroborated) → 검증됨 (validated)
단순히 인기나 신선함만으로는 최고 등급을 받을 수 없습니다. 강력한 프로모션(promotion)을 위해서는 여러 근거 있는 차원들이 필요하며, 결정적인 반대 신호 (anti-signal)가 없어야 합니다.
가장 중요한 점은, 이 점수는 조사를 돕는 도구이지 제품-시장 적합성 (product-market fit)을 보장하는 약속이 아니라는 것입니다.
6단계 및 7단계: 방대한 정보가 아닌, 선별된 목록을 공개하라
공개되는 결과물은 순위가 매겨진 소수의 기회를 포함하는 일일 보고서입니다.
각 기회는 이를 뒷받침하는 증거로 추적 가능하게 유지됩니다. 독자는 소스를 열어 해석을 검토하고, 그에 동의하지 않을 수 있습니다.
이것이 중요한 이유는 파이프라인이 불완전한 공개 정보를 바탕으로 가설을 생성하기 때문입니다. 세련된 요약 뒤에 소스를 숨기는 것은 거짓된 권위 (false authority)를 만들어낼 것입니다.
독립적인 cron 타이밍을 교체한 이유
저의 이전 스케줄링 모델은 고정된 간격에 너무 많이 의존했습니다:
08:00 데이터 수집 (ingestion)
08:30 분석 (analysis)
09:15 클러스터링 (clustering)
...
한 단계가 예상보다 오래 걸리기 전까지는 이 과정이 질서 정연해 보입니다.
만약 데이터 수집 (ingestion)이 지연되면, 분석 (analysis)이 불완전한 입력값으로 시작될 수 있습니다. 모델 제공자 (model provider)가 여러 번의 요청을 재시도(retry)한다면, 클러스터링 (clustering) 단계에서 준비된 데이터가 아무것도 발견되지 않을 수 있습니다. 이후의 리포트 작업은 여전히 오래된(stale) 기회들을 사용하여 게시될 수도 있습니다.
크론 스케줄 (cron schedule)은 함수가 언제 시작되는지는 알려주지만, 그 의존성 (dependencies)이 완료되었음을 증명하지는 않습니다.
현재 설계는 하나의 영속화된 코디네이터 (persisted coordinator)를 사용합니다. Supabase의 pg_cron 작업이 pg_net을 통해 10분마다 이를 호출합니다. Supabase는 scheduled functions guide에서 이 조합에 대해 설명하고 있습니다.
각 호출은 최대 하나의 제한된 작업 단위 (bounded unit of work)를 임대(lease)하고 진행합니다. 데이터베이스에는 다음 항목들이 저장됩니다:
- 현재 단계 (Current step)
- 시도 횟수 및 재시도 시간 (Attempts and retry time)
- 커서 및 소스 실행 식별자 (Cursor and source run identifiers)
- 결과 요약 및 에러 상세 정보 (Result summary and error details)
- 임대 만료 (Lease expiration)
충돌(crash)이 발생한 호출은 재개될 수 있으며, 느린 단계는 여러 번의 펄스 (pulses)에 걸쳐 계속 진행될 수 있습니다.
보호된 엔드포인트 (protected endpoint)는 Next.js Route Handler로 구현되었습니다. 이는 Next.js documentation에 설명된 표준 App Router 메커니즘입니다.
이것은 완전한 분산 워크플로 엔진 (distributed workflow engine)은 아닙니다. 제가 겪었던 특정 신뢰성 문제를 해결하기 위해 의도적으로 작게 만든 코디네이터입니다.
설계를 바꾼 교훈들
데이터가 많아질수록 결과가 나빠질 수 있다
소스(sources)를 추가하면 커버리지 (coverage)는 늘어나지만, 중복 데이터, 무관한 트렌드, 플랫폼별 편향 (platform-specific biases), 그리고 모델 비용도 함께 증가합니다. 필터링 (filtering)은 계층적으로 이루어져야 합니다.
플랫폼 지표를 무턱대고 결합할 수는 없다
투표 (vote), 스타 (star), 조회수 (view), 검색 인덱스 (search index), 댓글 (comment), 그리고 달러 (dollar)는 비교 가능한 단위가 아닙니다. 이들은 동일한 기회에 맥락을 제공할 수는 있지만, 그 의미가 정규화 (normalization) 과정에서도 유지되어야 합니다.
건강한 수집 실행이 0개의 레코드를 수락할 수도 있다
때로는 제공자(provider)가 올바르게 응답하더라도 모든 항목이 품질 임계값(quality threshold)을 통과하지 못할 수 있습니다. 이것이 반드시 시스템의 실패를 의미하는 것은 아닙니다. 제공자의 가용성(availability)과 유용한 증거 수확량(yield)은 서로 다른 지표입니다.
LLM 출력에는 운영 상태(operational states)가 필요합니다
"성공적으로 파싱됨(Parsed successfully)"은 "소스에 의해 뒷받침됨(supported by the source)"과 동일하지 않습니다. 수락(accepted), 검토(review), 거부(rejected), 건너뜀(skipped), 실패(failed) 상태를 정의함으로써 파이프라인의 나머지 부분을 더 쉽게 추론할 수 있게 되었습니다.
신뢰도(Confidence)는 품질(quality) 내부에 숨겨져서는 안 됩니다
잠재력은 높지만 근거가 약한 기회는, 방대한 증거로 뒷받침되는 평범한 기회와는 다릅니다. 하나의 점수로는 이 두 가지 사실을 정직하게 전달할 수 없습니다.
기술적 접근이 곧 권한은 아닙니다
공개 페이지, 토큰, 피드 또는 브라우저에서 보이는 엔드포인트(endpoint)가 자동화된 수집이나 상업적 수집을 자동으로 허용하는 것은 아닙니다. 소스 정책(Source policy)은 누군가가 기억하기를 바라는 메모가 아니라, 아키텍처(architecture)의 일부로 포함되어야 합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기