본문으로 건너뛰기
sanguk
Projects로 돌아가기

Project

Mealio

보유 재료·대화 맥락 기반 AI 레시피 추천 서비스

개인 풀스택 프로젝트로, 공공데이터 레시피를 ETL·임베딩한 뒤 NestJS·Kafka·Redis Stream 기반 챗봇으로 추천합니다. 검색 품질, Optimistic UI, 이미지 LCP, 인프라 비용, 캐시 TTL 트레이드오프를 직접 측정·개선했습니다.

형태
개인 프로젝트 · Full-Stack
주요 기술
Next.jsNestJSRedisKafkaPostgreSQLMongoDB

Outcomes

성과

  • 공공데이터 레시피를 ETL·임베딩해 검색·추천에 올렸습니다. 라이브 published는 약 510건이며, ingestion id 상한·fetch limit(1000) 기준으로 대량 수집·가공 파이프라인을 운영했습니다.

Case studies

문제와 근거로 풀어낸 이야기

이력서 불릿을 코드베이스와 맞춰 구체화했습니다. 다이어그램·근거 경로는 각 케이스 아래에 있습니다.

  1. 01

    검색 파이프라인 Semantic-first

    Filter-first → Semantic-first로 Precision@K 약 2배

    초기 레시피 검색은 필터를 먼저 걸고 의미를 나중에 보는 구조라, 재료·분위기 같은 고차원 질의에서 품질이 떨어졌습니다.

    검색을 Semantic-first로 재배치해 재료명 해석(exact → embedding ANN)과 의미 검색을 앞에 두고, 가중치·top-K 정책으로 순위를 조정합니다. 이력서의 Precision@K 약 2배 개선은 별도 평가셋 로그가 코드에 없어, 포트폴리오에서는 정책 파일·핸들러 단계로 품질 통제를 설명합니다.

    챗봇은 Tool Call로 Vector DB를 조회하고 Redis Stream·SSE로 토큰을 스트리밍해 체감 응답을 유지합니다.

    Mermaid 다이어그램 (포트폴리오용 내보내기)
    • Semantic-first + IngredientSemanticResolver(exact → ANN)
    • recipe-search.policy 가중치·top-K로 튜닝 가능
    • Precision@K 2×는 이력서 측정치(코드 내 평가 스크립트 없음)
    • server/... SearchRecipesHandler / IngredientSemanticResolverService (공개 mealio)
    • recipe-search.policy.ts 가중치·top-K
  2. 02

    Optimistic UI — mutation과 조회 반영 지연

    이벤트 기반 command 지연을 UI에서 흡수

    재료 추가·좋아요 같은 mutation은 이벤트 기반 비동기 command로 처리되어, refetch 직후에도 조회가 lagged 할 수 있었습니다.

    예측 가능한 삭제·토글 등은 onMutate에서 setQueryData로 Optimistic update를 넣고 onError에서 롤백합니다. 서버 정합은 이후 invalidate/refetch로 맞춥니다.

    • TanStack Query onMutate optimistic + onError rollback
    • 비동기 command 지연을 UI에서 흡수
  3. 03

    ETL 이미지 압축과 LCP

    원본 ~5MB → WebP ~10KB, 상세 LCP 약 3배 개선

    상세 LCP는 고해상도 원본 이미지가 지배합니다. ingestion persist의 recipe-image-rehost가 식약처 이미지를 받아 sharp로 480×270 cover · WebP quality 80으로 압축한 뒤 S3/CDN에 재호스팅합니다(soft-fail).

    라이브 카드가 .webp CDN URL을 쓰는 점이 결과 증거입니다. 이력서의 평균 5MB→10KB·LCP 15초→5초는 Lighthouse before/after가 코드에 없어, 고정 리사이즈·WebP로 LCP를 개선했다는 서술로 포트폴리오에 맞춰 두었습니다.

    Mermaid 다이어그램 (포트폴리오용 내보내기)
    • MAX_WIDTH=480, MAX_HEIGHT=270, WEBP_QUALITY=80
    • soft-fail로 ETL 전체 실패 방지
    • 용량·LCP 배수 개선은 이력서 측정치
    • recipe-image-rehost.policy.ts / optimizeRecipeImage (sharp)
  4. 04

    Batch API · NAT Instance 비용 절감

    ETL API 비용 약 50%, Public IPv4 2→1

    개인 프로젝트에서 동기 LLM 호출과 EC2 Public IP가 부담이었습니다. OpenAIBatchService가 JSONL을 Files API에 올리고 batches.create로 parse·embed를 비동기화합니다(약 50%는 Batch 공식 할인·설계 추정치).

    배포 문서는 APP(public) 1대 + private INFRA가 APP NAT(ip_forward/MASQUERADE)로 나가도록 해 Public IPv4를 하나로 묶는 비용 설계를 명시합니다.

    • OpenAI Batch(/v1/responses·embeddings, 24h window)
    • NAT Instance로 Public IPv4 1개 유지(문서)
    • openai-batch.service.ts
    • agent/common/deployment_strategy.md
  5. 05

    통계 캐시 TTL 트레이드오프

    조회수·좋아요 — 캐시 효율 vs 정합성

    viewCount/likeCount는 RecipeStats로 본문과 분리되고, 읽기 API는 Cache-Aside를 탑니다.

    상세 recipe:{id} TTL 900초(15분), 목록·검색 TTL 300초(5분)입니다. 코드 TODO에 Write Behind 검토가 있어, 실시간 카운터 정확도보다 응답 속도를 우선한 현재 선택임을 드러냅니다.

    • CACHE_TTL_RECIPE_DETAIL_SECONDS=900
    • CACHE_TTL_RECIPE_LIST_SECONDS=300
    • 통계 분리 + Cache-Aside
    • cache.policy.ts
    • recipes.service.ts (TTL/Write Behind TODO)
  6. 06

    이벤트 기반 백엔드 아키텍처

    API · Kafka · Consumer · Redis Stream · ETL

    실시간 HTTP와 긴 LLM·ETL 작업을 분리하기 위해 NestJS API와 Kafka consumer, Redis Stream SSE, ETL 워커로 역할을 나눴습니다.

    클라이언트가 챗봇에 요청하면 API가 Kafka 메시지를 만들고, Consumer가 OpenAI·Vector DB Tool Call 후 Redis Stream으로 청크를 흘리면 API가 SSE로 전달합니다.

    Mermaid 다이어그램 (포트폴리오용 내보내기)
    • 요청 경로와 비동기 처리 경로 분리
    • 스트리밍 응답으로 체감 지연 완화

Next

Mealio처럼, 제품 단위로 끝까지 책임집니다

설계·구현·운영까지 한 흐름으로 다루는 풀스택 협업이 필요하면 편하게 문의해 주세요.