사진과 단어를 위한 하나의 벡터 공간: Aurora 상의 Bedrock Titan 멀티모달
요약
Amazon Bedrock의 Titan Multimodal Embeddings를 사용하여 텍스트와 이미지를 동일한 1024차원 벡터 공간에 매핑하는 방법을 설명합니다. Aurora PostgreSQL의 pgvector를 활용해 멀티모달 데이터를 효율적으로 검색하고 매칭하는 아키텍처를 다룹니다.
핵심 포인트
- Amazon Titan Multimodal Embeddings를 통한 텍x트-이미지 통합 벡터 공간 구축
- Aurora PostgreSQL의 pgvector를 이용한 고성능 벡터 검색 구현
- HNSW 코사인 유사도 검색을 통한 효율적인 항목 매칭
- Vercel과 AWS 자격 증명 체인을 활용한 운영 환경 구성
지난 포스트에서 저는 OpinLog를 설명했습니다. 이는 Amazon Aurora PostgreSQL의 pgvector를 통해 사용자의 "버거(burger)"와 저의 "버거(burger)"가 동일한 표준 항목(canonical item)으로 해결되는 사용자 간 리뷰 그래프입니다. 이번 포스트는 매칭을 마법처럼 느껴지게 만드는 핵심 요소, 즉 Vercel에 배포되어 Aurora 내부의 매처(matcher)에 데이터를 공급하는 **Amazon Bedrock의 멀티모달 임베딩 (multimodal embeddings)**에 관한 것입니다.
버거 사진은 "버거"라는 단어와 매칭되어야 합니다
사용자는 두 가지 방식으로 항목을 기록합니다: 이름을 입력하거나, 사진을 찍는 방식입니다. 만약 텍스트와 이미지가 서로 다른 벡터 공간 (vector spaces)에 존재한다면, 저는 두 개의 매처와 융합(fusion) 단계가 필요했을 것입니다. 대신 저는 텍스트와 이미지 **모두를 동일한 1024차원 공간 (1024-dimensional space)**으로 매핑하는 Amazon Titan Multimodal Embeddings G1 (amazon.titan-embed-image-v1)을 사용했습니다. 하나의 모델, 하나의 인덱스, 하나의 쿼리로 작동합니다.
임베딩 계약(embedding contract) 전체는 매우 간단합니다:
export async function embed({ text, imageBase64 }: EmbedInput): Promise<number[]> {
const body: Record<string, unknown> = {
embeddingConfig: { outputEmbeddingLength: 1024 },
...
text, imageBase64, 또는 둘 다를 전달합니다. In-N-Out 버거 사진이 "In-N-Out Double-Double"이라는 텍스트 근처에 위치하기 때문에, 사진을 업로드하는 사용자는 이름을 입력한 사용자와 별도의 특수 처리 없이도 매칭됩니다. 이 단일한 설계 선택이 "사진으로 추가하면 → 이것이 무엇인지 이미 알고 있다"는 데모 순간을 가능하게 만듭니다.
몇 가지 운영(production) 참고 사항:
- Bedrock 클라이언트는 명시적인 키가 없을 때 **기본 AWS 자격 증명 체인 (default AWS credential chain)**을 사용하므로, 동일한 코드가 로컬(SSO)과 Vercel(환경 변수의 IAM 사용자 키)에서 모두 실행됩니다.
- 반환된 배열을 리터럴 헬퍼 —
`[${vec.join way(',')}]`— 를 통해pgvector컬럼에 직접 저장하며, 이는 rawsql템플릿 내부에서::vector로 캐스팅됩니다.
임베딩에서 매칭까지
항목을 추가할 때마다, 저는 새 항목을 임베딩하고 표준 카탈로그에 대해 HNSW 코사인 ANN 검색을 실행합니다:
SELECT id, name, photo_url, rating_avg, rating_count,
1 - (embedding <=> :q) AS similarity
FROM canonical_items
...
<=>는 pgvector의 코사인 거리 (cosine distance)입니다. 신뢰도가 높은 일치 항목은 기본값으로 자동 제안되지만 ("이것이 정답인 것 같습니다"), 그렇지 않은 경우 사용자는 상위 후보군과 함께 "이 중 일치하는 항목 없음 — 새로 만들기"라는 탈출구 (escape hatch)를 보게 됩니다. 매칭은 **비차단 방식 (non-blocking)**입니다. 항목은 canonical_item_id = NULL 상태로 즉시 저장되며, 링크는 나중에 채워집니다.
임계값 튜닝: "검색"이 "느낌"을 이기는 지점
가공되지 않은 벡터 검색 (Raw vector search)은 점점 희미해지는 유사도 순으로 카탈로그 전체를 기꺼이 반환할 것입니다. 저는 Titan 텍스트 임베딩 (text embeddings)이 실제로 어디에 위치하는지 측정했습니다:
- 관련 없는 텍스트의 경우 코사인 거리(cosine) ~0.3
- 실제로 주제와 관련된 텍스트의 경우 ~0.5–0.6
...그리고 1 - distance > 0.45에서 결과를 제한했습니다. 이 임계값 미만의 결과는 버려집니다. 이는 단 하나의 상수일 뿐이지만, "의도한 것을 찾음"과 "동전 던지기로 정렬된 모든 것을 보여줌" 사이의 경계선입니다. 매처(matcher)의 자동 제안 (auto-suggest) 임계값의 경우 기준을 훨씬 높게 설정하여, 우리가 확신할 때만 일치 항목을 사전 선택하고 그렇지 않으면 사람이 선택하도록 했습니다.
카탈로그가 스스로 정교해지는 과정
각 정규 항목 (canonical item)의 임베딩은 연결된 구성원들의 **실행 중인 중심점 (running centroid)**입니다. 당신의 버거 로그가 나의 로그와 연결되면, 정규 벡터 (canonical vector)는 두 로그의 평균이 됩니다. 더 많은 사람이 동일한 항목을 기록할수록 해당 벡터는 더 대표성을 갖게 되며, 다음 사람의 로그 (텍스트 또는 사진)가 해당 벡터에 더 안정적으로 스냅(snap)됩니다. 임베딩 모델 (embedding model)이 이해를 담당한다면, Aurora는 비정규화된 평점 집계 (denormalized rating aggregates)를 업데이트하는 동일한 트랜잭션 내에서 기억과 평균화를 담당합니다.
이것이 Aurora에서 실행되는 이유
자격 요건을 갖춘 AWS 데이터베이스는 DynamoDB, Aurora DSQL, 그리고 Aurora PostgreSQL이었습니다. 오직 Aurora PostgreSQL만이 pgvector를 제공하므로, Bedrock에서 생성된 임베딩 (embedding)을 관계형 데이터(평점, 기여자, 리스트 등) 바로 옆에서 인덱싱 (HNSW) 및 쿼리 (<=>) 할 수 있습니다. 의미 (meaning)를 임베딩 (Bedrock)하고 이를 트랜잭션 방식으로 저장 및 제공 (Aurora pgvector)하는 것이 이 애플리케이션의 핵심 엔진이며, 이 둘을 단 하나의 JOIN 거리로 유지함으로써 분산 시스템 (distributed-systems) 프로젝트가 아닌 주말 프로젝트로 완성할 수 있었습니다.
스택: Vercel 상의 Next.js 16 → Amazon Aurora PostgreSQL Serverless v2 (pgvector) → Amazon Bedrock (Titan multimodal). 사진은 S3에 저장되며, 매처 (matcher)는 단 한 줄의 SQL로 구현됩니다.
H0 해커톤 ("Hack the Zero Stack with Vercel and AWS Databases")을 위해 제작되었습니다. 본 콘텐츠는 해당 해커톤 참가를 목적으로 작성되었습니다. #H0Hackathon
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기