소식·굿즈 피드 병렬 수집으로 완성
목차
최근발매 정보를 실시간으로 보여주려면 4개 데이터 소스(뉴스, SNS, 쇼핑몰, 커뮤니티)에서 동시에 정보를 수집해야 했다. 순차 처리로는 30초가 걸리지만, 병렬로 4팀 리서치를 시작하면서 이 작업은 완전히 달라졌다. DB 스키마 확장부터 백엔드 파이프라인, 프론트엔드 렌더링까지 모두 연결한 첫 번째 데이터 피드 완성 이야기다.
병렬 처리는 왜 필요했나
처음 설계 회의에서 나는 "소식 api 콜, 기다렸다가 굿즈 콜, 또 기다렸다가…" 순차 흐름으로만 생각했다. 그런데 각 소스마다 응답 시간이 다르다는 걸 알게 됐다. 한 소스가 5초를 먹으면 전체 파이프라인이 5초 블로킹된다. 네 개를 동시에 요청하면? 가장 느린 놈(보통 2~3초)만 기다리면 되고, 나머지는 병렬이니까 겹친다. 8초 vs 30초의 차이는 운영 관점에서 꽤 크다. 밤 11시에 도는 크론 잡이 30초면 다음 잡과 겹치기 쉽지만, 8초면 충돌 확률이 훨씬 낮아진다.
bot/enrich_seed.py — 씨드 데이터 채우기
"enrich"라는 표현이 재밌는데, 이건 단순히 외부 API를 fetch하는 게 아니라 기존 데이터에 추가 컨텍스트를 덧붙이는 의미다. 뉴스 제목이 들어오면, 우리 시스템이 필요한 형태로 노멀라이즈하고(타임스탬프 포맷, 텍스트 인코딩), 중복을 제거하고, 품질 필터링을 한다.
병렬 처리에서 가장 신경 쓴 부분은 부분 실패 처리다. 4개 소스 중 1개가 500 에러를 뱉으면 어떻게 할까? 내가 원한 건 "나머지 3개는 계속 진행, 1개는 별도 로깅해서 수동 리트라이"였다. 전체 파이프라인을 멈추는 것보다, 팀이 상황을 빠르게 파악하고 조치할 수 있게 하는 게 중요했다.
# 의사 코드
results = []
for source in [news_api, sns_crawler, shop_api, community_feed]:
try:
data = await source.fetch()
results.append(normalize(data))
except Exception as e:
log_error(f"Source {source.name} failed: {e}")
# 나머지는 계속 진행
이렇게 하면 대시보드에 "3/4 sources OK, 1 retry needed"라고 깔끔하게 뜬다.
스키마: 테이블 분리 vs 통합
처음엔 feed 하나로 모두 담으면 어떨까 했다. 근데 테이블 설계를 진행하다 보니 소식과 굿즈가 너무 달랐다.
| 구분 | 뉴스 | 굿즈 |
|---|---|---|
| 주요 필드 | 제목, URL, 요약, 발행사 | 상품명, 가격, 재고, 판매링크 |
| 검색 패턴 | 키워드, 카테고리 | 가격 범위, 재고 여부 |
| 데이터 신선도 | 몇 시간마다 | 매분 |
한 테이블에 강제로 담으면 news.price IS NULL, goods.article_url IS NULL 같은 낭비가 생긴다. 결국 도메인별로 테이블을 분리하고, 프론트에서 UNION ALL로 통합 피드를 만들기로 했다. 스키마 설계는 명확하고(각 테이블이 자신의 의미에만 집중), 쿼리는 유연하게.
src/lib/db.ts — 쿼리 통합
새 테이블에 대한 쿼리 헬퍼를 추가했다. 핵심은 getLatestFeed() 함수다. UNION ALL을 쓴 이유는 뉴스와 굿즈 사이에 중복은 절대 없다(서로 다른 테이블)는 보장이 있기 때문. UNION을 쓰면 불필요한 DISTINCT 연산이 들어가서 느려진다. 작은 최적화지만, 홈페이지가 피크타임에 수천 req/s를 받을 때 이런 것들이 쌓인다.
페이지네이션과 선택적 필터(카테고리, 날짜 범위)도 고려해서 쿼리를 설계했다. SQL에서 계산 부담을 최소화하고, 인덱싱이 잘 먹도록.
프론트엔드: 피드 렌더링
pages/index.astro는 홈 화면이니까 전체 최근발매를 카루셀로 보여주고, pages/groups/[slug].astro는 특정 그룹과 관련된 소식·굿즈만 필터링해서 좌측 사이드바에 표시했다.
데이터 페칭은 서버사이드 렌더링 시점(빌드 또는 요청 시간)에 일어난다. 방문자는 이미 최신 정보가 담긴 HTML을 받으니까 자바스크립트 로딩 후 데이터 fetch를 기다릴 필요가 없다.
팀 협업 관점 회고
1) 병렬 처리는 모니터링이 필수
처음 구현했을 때는 타임로그가 부족했다. "파이프라인이 느려졌어"라는 슈팅이 들어오면, 원인이 뉴스 API인지 SNS 크롤러인지 알 수가 없었다. 지금은 각 소스별로 start/end 타임스탬프를 기록해서, 병목을 정확히 파악할 수 있도록 했다. 이게 다음번 최적화의 출발점이 된다.
2) 스키마 버전 관리
이전에 비슷한 작업에서, 스키마를 확장했는데 일부 구버전 앱이 새 테이블을 모르면서 에러가 난 적이 있다. 이번엔 schema_version 테이블에 현재 버전을 기록하고, 앱 시작 시 최소 요구 버전을 체크하도록 했다. 마이그레이션 중에도 버전 체크로 호환성을 보장한다.
3) 증분 롤아웃
네 개 소스 모두를 한 번에 켜지 않았다. 1개씩 켜서, 데이터 품질과 성능을 검증한 후 다음 소스를 활성화했다. "피드의 10%만 새 소스"에서 시작해서 점진적으로 비율을 높혔다. 문제가 생기면 빠르게 롤백할 수 있는 구조다.
다음
이제 피드에 사용자별 개인화 랭킹을 넣을 차례다. 지금은 모든 사용자가 같은 순서로 피드를 본다. 하지만 클릭, 스크롤, 체류 시간 데이터를 모으면, 각자 관심사에 맞춘 피드로 개선할 수 있다. 그리고 리얼타임 알림도 고려 중이다. "당신이 찜한 브랜드의 새 상품이 나왔어요"라는 느낌으로.
🛒 이 글과 어울리는 추천 상품
*위 링크는 쿠팡파트너스 활동의 일환이며, 일정액의 수수료를 제공받을 수 있습니다.
댓글 0
첫 댓글 달아줘.