본문으로 건너뛰기

© 2026 Molayo

Dev.to헤드라인2026. 06. 30. 08:43

제로 스택 기반, Gemini가 조언하는 패스워드리스 대시보드를 구축하다

요약

AWS와 Vercel의 제로 스택 환경에서 Gemini를 활용해 마이크로 커머스 판매자를 위한 재고 조언 대시보드를 구축한 사례입니다. 서버리스 아키텍처에서 IAM 데이터베이스 인증을 통해 비밀번호 없이 안전하게 데이터베이스에 연결하는 구현 방법을 다룹니다.

핵심 포인트

  • Vercel과 AWS Aurora Serverless v2를 결합한 서버리스 아키텍처 구축
  • Gemini 2.5 Flash를 활용한 데이터 기반 재고 확보 조언 기능 구현
  • IAM 데이터베이스 인증을 통한 패스워드리스(Passwordless) 보안 적용
  • VPC 없이 인터넷 액세스 게이트웨이를 통한 데이터베이스 연결 최적화

_저는 Kajota Pulse를 만들고 이 기사를 AWS × Vercel "H0: Hack the Zero Stack" 해커톤 출품작으로 작성했습니다. (해시태그 #H0Hackathon) 라이브 앱: kajota-pulse.vercel.app · 코드: github.com/KaJota-inc/kajota-pulse

아무도 구축하지 않는 문제점

아프리카의 마이크로 커머스 분야에서, "공동 판매자(co-sellers)"들은 도매업체로부터 재고를 구매하여 자신들의 네트워크에 마진을 붙여 재판매합니다. 상품 목록을 작성하는 데 필요한 도구는 온통 있지만, 공동 판매자가 실제로 돈을 벌지 결정하는 질문, 즉 '이번 주에는 무엇을 재고로 확보해야 할까?' 에 대한 것은 거의 없습니다.

그래서 저희는 Kajota Pulse를 구축했습니다. 이는 마켓플레이스를 모니터링하고, 한 번의 클릭만으로 판매자에게 무엇을 사야 하고 그 이유가 무엇인지 알려주는 블룸버그 터미널 스타일의 대시보드입니다. 이것은 세 가지 앱 스택 중 "모니터" 기둥 역할을 합니다: Coach는 상품 목록 초안을 작성하고, Pulse는 재고 확보할 것을 조언하며, Mesh는 온체인으로 거래를 확정합니다.

해커톤의 제약 조건이 재미있었습니다. 바로 제로 스택(zero stack) 위에서 구축하는 것이었는데, 컴퓨팅에는 Vercel을, 상태 관리를 위한 데이터베이스로는 AWS를 사용하며, 관리할 서버가 전혀 없다는 의미였습니다. 실제로 이것이 어떤 과정을 거쳤는지 알려드리겠습니다.

한 번에 설명하는 아키텍처

Vercel의 Next.js 16 (App Router) → 대시보드의 모든 숫자에 대한 AWS Aurora Serverless v2 (PostgreSQL) → 조언을 위한 Gemini 2.5 Flash → 실제 Kajota 카탈로그를 스트리밍하는 MongoDB Atlas Database Triggers. 다섯 개의 Postgres 테이블, 두 개의 SQL 뷰, 두 개의 Gemini 엔드포인트, 하나의 인제스트 엔드포인트가 사용되었습니다. VPC도 없고, 커넥션 풀러도 없고, 서버도 없습니다.

흥미로운 공학적 부분은 UI가 아니었습니다. 그것은 튜토리얼에서는 보여주지 않는 세 가지였습니다.

까다로움 1 — 서버리스 + Aurora는 패스워드리스 인증을 강제한다 (그리고 이것이 기능이다)

저희는 Vercel이 VPC 설비 없이도 접근할 수 있도록 새로운 인터넷 액세스 게이트웨이 네트워킹 모델을 적용하여 Aurora Serverless v2를 프로비저닝했습니다. 그러자 모든 비밀번호 연결이 PAM authentication failed로 실패했습니다.

새로운 모델은 IAM 데이터베이스 인증 (IAM database authentication)을 강제합니다. 게다가 보너스로, RDS Data API도 지원하지 않습니다. 따라서 저장된 비밀번호 대신, 모든 연결은 수명이 짧은(15분) IAM 인증 토큰을 생성합니다:

const signer = new Signer({ hostname, port, username, region, credentials });
pool = new Pool({
  host, port, user, database,
...

pg는 비동기 password 콜백을 지원하므로 구현이 깔끔합니다. 그리고 보안 특성도 정말 훌륭합니다. 어디에도 장기 유지되는 데이터베이스 비밀번호가 없습니다. Vercel에도, 리포지토리(repo)에도, 시크릿 매니저(secret manager)에도 존재하지 않습니다.

주의사항 2 — Vercel의 Lambda가 AWS 자격 증명을 가립니다

이 문제로 한 시간을 허비했습니다. IAM 서명기(signer)는 토큰에 서명하기 위해 AWS 자격 증명 (AWS credentials)이 필요합니다. Vercel에 AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY를 설정했지만... 여전히 실패했습니다.

Vercel 함수는 Lambda에서 실행되며, Lambda 런타임(runtime)이 자체적인 실행 역할(execution-role) AWS_ACCESS_KEY_ID / AWS_SECRET_ACCESS_KEY를 주입하여 사용자의 설정을 가려버립니다(shadow). 서명기는 잘못된(no-rds-db:connect) 신원(identity)으로 토큰을 생성하고 있었습니다.

해결책: 사용자 정의 환경 변수 이름을 사용하고 이를 서명기에 명시적으로 전달하십시오.

function signerCredentials() {
  const accessKeyId = process.env.PULSE_AWS_ACCESS_KEY_ID;
  const secretAccessKey = process.env.PULSE_AWS_SECRET_ACCESS_KEY;
...

클러스터의 dbuser 리소스에 대해 rds-db:connect 권한만 가진 전용 IAM 사용자를 PULSE_AWS_* 이름으로 노출하면, 가려짐(shadowing) 문제가 사라집니다.

주의사항 3 — 실제 변경 스트림(change-streams)은 시드 데이터보다 복잡합니다

대시보드의 품질은 데이터의 품질에 달려 있습니다. 그래서 우리는 실제 Kajota 컬렉션(products, cosell_products, orders)에 **세 개의 MongoDB Atlas 데이터베이스 트리거 (Database Triggers)**를 연결했습니다. 각 트리거는 변경 이벤트를 /api/ingest로 POST하며, 이는 Aurora에 업서트(upsert)됩니다. 이를 프로덕션(production) 데이터에 연결하자마자, 시드 파일(seed file)로는 절대 발견할 수 없었던 세 가지 버그가 즉시 드러났습니다:

  1. Extended JSON. Atlas는 변경 이벤트(change events)를 EJSON으로 직렬화(serialize)합니다. 따라서 Mongo의 _id{"$oid":"…"}로, 가격(price)은 {"$numberInt":"9500"}로 전달되며, 문자열이나 숫자로 전달되지 않습니다. 디코더(decoder)가 없다면 데이터베이스에 id="[object Object]"price=NaN이 기록됩니다. 두 개의 작은 헬퍼 함수(ejsonId, ejsonNum)로 이 문제를 해결했습니다.
  2. 컬렉션 명명 (Collection naming). 실제 컬렉션 이름은 cosell_products(언더스코어 포함)이지만, 저희 라우터는 cosellproducts와 매칭되었습니다. 이벤트들은 "무시됨(ignored)" 상태로 조용히 누락되었습니다. 이제 라우터가 이름을 정규화(normalize)합니다.
  3. 외래 키(Foreign keys) vs 이벤트 순서(event ordering). 변경 스트림(change-stream) 이벤트는 순서가 뒤바뀐 채(out of order) 도착합니다. 즉, 공동 판매(co-sell) 리스팅이 참조하는 제품보다 먼저 도착할 수 있습니다. 저희의 외래 키(FK) 제약 조건은 이러한 행들을 조용히 삭제했습니다. 해결책은 직관에 어긋나지만 이벤트 스트림 기반의 인제스션(ingestion)에는 올바른 방법입니다: 외래 키(FK)를 제거하고 각 테이블을 독립적인 프로젝션(projection)으로 취급하십시오.

이 중 어느 것도 피스처(fixtures)를 통해서는 재현되지 않습니다. 이 문제들은 실제 운영(production) 쓰기 작업이 파이프라인에 도달할 때만 나타납니다. 이것이 바로 저희가 시드(seed) 데이터로 데모를 하는 대신 실제 데이터에 연결한 정확한 이유입니다.

대시보드가 아닌 어드바이저(advisor)로 만드는 기능

대시보드는 숫자들을 보여주고 사용자가 직접 종합(synthesis)하게 만듭니다. 저희는 Pulse가 질문에 답하기를 원했습니다. 그래서 /api/recommend는 트렌딩 수요, 카테고리 마진, 경쟁사 품절 상태, 가격 위치와 같은 실시간 신호(live signals)를 가져와 **구조화된 출력 스키마(structured-output schema)**와 함께 Gemini 2.5 Flash에 전달합니다:

유기농 시어 버터 (Organic Shea Butter)주말 전 재고를 10~15개 확보하세요.
"즐겨찾기 +27개, 고마진 뷰티 카테고리(18%)에 속하며, 경쟁사가 방금 유사한 크림의 재고를 소진했습니다."

실패해서는 안 되는 데모를 위해 중요한 두 가지 세부 사항은 다음과 같습니다:

  • 구조화된 JSON 출력 (Structured JSON output) (responseMimeType: "application/json" + responseSchema)은 산문(prose)을 파싱하는 것이 아니라 깔끔하게 순위가 매겨진 리스트를 렌더링할 수 있음을 의미합니다.
  • 결정론적 폴백 (A deterministic fallback). 만약 Gemini를 사용할 수 없는 상황이 발생하면, 대신 휴리스틱 순위 산정 방식(수요 × 마진 × 기회)이 실행됩니다. 따라서 심사위원이나 고객 앞에서 카드가 비어 있는 상태로 노출되는 일은 절대 없습니다.

"제로 스택 (zero stack)"이 실제로 가져다준 것들

  • 서버 없음. API를 위한 Vercel functions, 상태 관리를 위한 Aurora를 사용합니다. 패치하거나 확장(scale)할 것이 아무것도 없습니다.
  • 제로 스케일링 (Scale to zero). Aurora Serverless v2는 0 ACU까지 유휴 상태로 내려갑니다. 콜드 스타트 (cold-start, 약 8초)가 유일한 비용이지만, 프리워밍 (pre-warm)하기는 쉽습니다.
  • 단일 명령 검증. node scripts/verify-live.mjs를 실행하면 라이브 랜딩 페이지, Aurora 배지, 두 개의 Gemini 엔드포인트, 인제스트 인증 게이트 (ingest auth gate), 그리고 실제 IAM 인증된 행 수 (row count)를 모두 확인합니다 — 5/5 성공. 누구나 실행할 수 있습니다.

핵심 요약 (Takeaways)

  1. 새로운 Aurora 네트워킹 모델은 패스워드리스 IAM 인증을 강제합니다. 이를 적극 활용하세요. 저장된 비밀번호보다 더 나은 보안 태세 (security posture)를 제공하며, Lambda 자격 증명 섀도잉 (credential-shadowing) 특이사항만 처리하면 단 몇 줄의 코드로 해결됩니다.
  2. 데이터 파이프라인이 제대로 작동하는지 알고 싶다면, 시드 (seed) 데이터가 아닌 실제 데이터를 대상으로 테스트하세요. 우리가 발견한 세 가지 인제스트 (ingestion) 버그는 모두 프로덕션 쓰기 작업이 발생하기 전까지는 보이지 않았습니다.
  3. 라이브 데모에서 LLM 기능을 사용할 때는 구조화된 출력 (structured output)을 사용하고, 항상 결정론적 폴백 (deterministic fallback)을 포함하여 배포하세요. "보통 인상적인 것"보다는 "절대 비어 있지 않은 것"이 훨씬 낫습니다.

라이브: kajota-pulse.vercel.app · 코드: github.com/KaJota-inc/kajota-pulse · Next.js 16 (Vercel) + Aurora Serverless v2 + Gemini 2.5 Flash 기반 구축.

이 포스트 활용 방법

  • Dev.to / Hashnode / Medium: 있는 그대로 게시하세요 (커버 이미지를 추가하세요 — 데모 GIF가 효과적입니다).
  • LinkedIn: "새로운 Aurora 모델은 비밀번호 사용을 허용하지 않습니다 — 이것이 왜 좋은 일인지 알려드립니다"라는 문구로 시작한 뒤, 세 가지 주의사항을 언급하고 링크를 남기세요.
  • X/Bluesky 스레드: 포스트당 하나의 주의사항을 다루세요 (패스워드리스 IAM → Lambda 섀도잉 → EJSON/FK), 마지막에 라이브 링크와 GIF로 마무리하세요.

AI 자동 생성 콘텐츠

본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.

원문 바로가기
0

댓글

0