DynamoDB 벡터 검색
DynamoDB는 2026년 8월 5일 네이티브 벡터 검색을 도입했습니다. 임베딩을 항목의 평범한 Number 값 List로 저장하고, 벡터 인덱스를 추가한 뒤, 새로운 SearchVectors API로 근사 최근접 이웃(ANN) 쿼리를 실행합니다.
지금까지 유사도 검색을 하려면 테이블을 OpenSearch나 별도의 벡터 데이터베이스로 복제하고 둘을 동기화 상태로 유지해야 했습니다. 그 파이프라인은 이제 사라졌습니다. 다만 그것을 대체하는 과금 모델은 DynamoDB의 다른 어떤 것과도 닮지 않았습니다.
DynamoDB는 벡터 검색을 지원하나요?
예 — 2026년 8월 5일부터 네이티브로 지원합니다. 임베딩을 평범한 Number 값 List로
저장하고, 벡터 인덱스를 추가한 뒤, SearchVectors API로 근사 최근접 이웃 쿼리를
실행합니다 — OpenSearch 복제본도, 별도의 벡터 데이터베이스도 없습니다. 온디맨드
테이블에서만 동작하고, 최대 4,096차원까지이며, 쓰고 검색하고 저장한 바이트 단위로
과금됩니다.
- 세 번째 인덱스 계열: 벡터 인덱스는 와 LSI 옆에 나란히 자리합니다. 새 읽기 API 하나(
SearchVectors), ANN 전용, 온디맨드 테이블 전용, 최대 4,096차원. - 빠릅니다, 측정했습니다: us-east-1의 라이브 1024차원 인덱스를 상대로
SearchVectors는 같은 클라이언트에서 보낸GetItem만큼 빠르게 응답했고(p50 39 ms 대 44 ms), 갓 쓴 항목은 약 136 ms 만에 검색 가능해졌습니다. - 계량은 바이트 단위입니다: 벡터 쓰기 GB당 $0.52, 검색이 검사하는 벡터 데이터 GB당 $0.002, 스토리지 GB-월당 $0.25(us-east-1). 임베딩을 인덱싱하면 베이스 테이블 복사본이 차원당 정확히 4바이트로 과금되고, 같은 리스트를 인덱싱하지 않으면 약 1.9배로 과금됩니다.
- 대량 저장소는 여전히 S3 Vectors입니다: 저장 비용은 약 8배 저렴하고 배치 로드는 훨씬 더 저렴합니다. DynamoDB는 밀리초 단위 읽기, 스트리밍 쓰기, 그리고 벡터가 자신이 설명하는 항목 바로 옆에 살아야 할 때 유리합니다.
벡터 인덱스의 작동 방식
새로운 속성 유형은 없습니다. 임베딩은 항목에 있는 평범한 숫자 리스트로, 와이어 상에서는 {"L": [{"N": "0.0132"}, {"N": "-0.0475"}, …]}이며, 이미 사용 중인 그 PutItem과 UpdateItem으로 그대로 씁니다.
인덱스는 별도의 구조입니다. DynamoDB는 벡터를 32비트 부동소수점 정밀도로, 프로젝션하거나 필터링하는 속성들과 함께 인덱스에 비동기적으로 복제합니다. 검색 결과는 읽기처럼 최종 일관성을 가집니다.
실제로 지연은 작습니다. 저희 라이브 테스트 인덱스에서는 갓 쓴 벡터가 PutItem이 반환된 뒤 약 136 ms 만에 검색 결과에 나타났습니다. 그래도 자신이 쓴 것을 바로 읽는(read-your-own-write) 흐름은 절대 이 위에 만들지 마세요.
익숙한 인덱스 유형들 옆에 놓고 보면 다음과 같습니다:
| 벡터 인덱스 | GSI | LSI | |
|---|---|---|---|
| 테이블당 최대 개수 | 5 | 20 | 5 |
| 읽기 API | SearchVectors | Query, Scan | Query, Scan |
| PartiQL | 아니요 | 예 | 예 |
| 용량 모드 | 온디맨드 전용 | 둘 다 | 둘 다 |
| 일관성 | 최종 일관성 | 최종 일관성 | 강력한 일관성 가능 |
| 테이블 생성 후 추가 | 예 | 예 | 아니요 |
각 인덱스는 생성 시 차원 수(최대 4,096)와 세 가지 거리 함수 중 하나를 고정합니다. COSINE과 EUCLIDEAN은 점수가 낮을수록 더 유사하고, DOT_PRODUCT는 높을수록 더 유사하며 음수가 될 수 있습니다. 어느 것도 나중에 변경할 수 없습니다.
무엇이든 벤치마크하기 전에 정밀도에 관한 참고 사항 하나. 인덱스는 벡터를 f32로 보관하며, 더 높은 정밀도의 값도 수락되지만 들어가는 길에 정밀도를 잃습니다. float64 임베딩을 가지고 왔다면 모든 거리는 f32 복사본을 기준으로 계산되므로, 재현율(recall)은 원본이 아니라 f32를 기준으로 측정하세요.
인덱스 생성과 검색
지원 티켓에 대한 시맨틱 검색을 운영해서 상담원이 키워드 일치 없이도 "이전에 같은 문제를 겪은 고객"을 찾을 수 있게 한다고 합시다. 각 티켓 항목은 제목과 본문의 임베딩을 담고 있으며, 어떤 모델로 생성해도 됩니다(Bedrock에서 Titan Text Embeddings V2는 입력 토큰 100만 개당 $0.02입니다).
기존 테이블에 인덱스를 추가합니다. HASH 요소는 모든 검색을 하나의 product 값으로 한정하고, INLINE_FILTER 속성(최대 18개)은 검색 시점의 동등 필터를 허용합니다:
aws dynamodb update-table \
--table-name SupportTickets \
--attribute-definitions AttributeName=product,AttributeType=S \
AttributeName=severity,AttributeType=S \
--vector-index-updates '[{"Create": {
"IndexName": "TicketEmbeddings",
"VectorAttribute": {"AttributeName": "embedding"},
"SearchSchema": [
{"AttributeName": "product", "SearchSchemaElementType": "HASH"},
{"AttributeName": "severity", "SearchSchemaElementType": "INLINE_FILTER"}
],
"Projection": {"ProjectionType": "KEYS_ONLY"},
"Dimensions": 1024,
"DistanceFunction": "COSINE"
}}]'빌드는 GSI 백필처럼 동작하지만 모서리가 더 날카롭습니다. SearchVectors는 빌드 전체에 대해 부분 결과 없이 ValidationException을 반환합니다.
AWS는 DescribeTable이 ACTIVE라고 답한 뒤에도 검색 엔드포인트가 한동안 계속 요청을 거부할 수 있다고 경고합니다. 웨이터(waiter)는 없으므로 재시도 루프 안에서 실제 검색으로 확인하세요. 저희가 빈 테이블과 함께 인덱스를 생성했을 때는 26초 만에 ACTIVE가 되었고 0.6초 뒤부터 검색을 수락했습니다.
검색은 쿼리 임베딩을 {"N": …} 값들의 순수 JSON 배열로 받습니다. DynamoDB의 L로 감싸지 마세요. 저장된 속성은 리스트 유형을 쓰지만 요청 파라미터는 그렇지 않으며, 이 둘을 혼동하는 것이 흔한 첫 실수입니다:
aws dynamodb search-vectors \
--table-name SupportTickets \
--index-name TicketEmbeddings \
--search-vector file://query-embedding.json \
--top-k 5 \
--search-condition-expression "product = :p AND severity = :sev" \
--expression-attribute-values '{":p": {"S": "checkout"}, ":sev": {"S": "high"}}'가장 유사한 것부터 정렬된 최대 TopK개의 항목이 각각 Score와 함께 반환되고, 요청하면 ConsumedCapacity도 함께 옵니다. TopK의 상한은 100이고, 페이지네이션은 없으며, 응답은 16 MB로 제한됩니다.
임베딩 자체는 프로젝션하고 명시적으로 요청하지 않는 한 결과에서 제외됩니다. 이 기본값은 의도된 것입니다. 벡터를 반환하면 응답 크기와 검색 요금이 함께 부풀어 오르기 때문입니다.
필터 표현식은 동등 조건만 허용하며 BETWEEN, IN, begins_with는 쓸 수 없습니다. 인덱스가 HASH 속성을 정의하면 모든 검색은 그 속성에 정확히 하나의 값을 고정해야 합니다. 범위 연산자에 대한 AWS의 표현이 "not yet available"이므로 이 제약은 완화될 수 있습니다.
첫 배포 전에 알아 둘 만한 운영상의 돌발 상황이 두 가지 있습니다. SearchVectors에는 새 dynamodb:SearchVectors IAM 작업이 필요한데, 기존 읽기 정책 어디에도 포함되어 있지 않습니다.
또한 별도의 엔드포인트인 search-dynamodb.{region}.amazonaws.com과 통신합니다. dynamodb.{region}만 허용하는 이그레스 허용 목록과 VPC 엔드포인트 구성에서는 벡터 검색만 끊기며, 이유를 절대 말해 주지 않는 연결 오류만 남습니다.
벡터 인덱스의 과금 방식
일반 테이블 요금 위에, 쓰기와 검색 요청마다 최소 1 KB가 적용되고 모두 바이트 단위로 계량되는 세 가지 새 미터가 추가됩니다(us-east-1, AWS 요금 API 기준, 2026-08-15):
| 미터 | Standard | Standard-IA |
|---|---|---|
| 벡터 쓰기 | $0.52/GB | $0.65/GB |
| 검색당 검사되는 벡터 데이터 | $0.002/GB | $0.0025/GB |
| 스토리지(테이블과 인덱스) | $0.25/GB-mo | $0.10/GB-mo |
문서는 List 안에 십진수 문자열로 저장되는 임베딩의 베이스 테이블 복사본이 인덱스의 f32 복사본보다 "considerably larger"일 수 있다고 경고합니다. 저희가 us-east-1의 라이브 테이블을 상대로 쓰기 단위 과금을 측정해 보니, 진실은 더 기묘했습니다.
벡터 인덱스가 없는 속성의 임베딩은 문서화된 십진수 규칙대로, f32 크기의 약 1.9배로 과금됩니다. 같은 속성에 벡터 인덱스를 붙이면 베이스 테이블 과금이 차원당 정확히 4바이트로 떨어집니다:
| 차원 수 | 인덱스 없는 List 속성(과금) | 같은 속성, 벡터 인덱스 적용(과금) |
|---|---|---|
| 256 | 1,914 B | 1,024 B |
| 768 | 5,760 B | 3,072 B |
| 1,024 | 7,653 B | 4,096 B |
| 1,536 | 11,501 B | 6,144 B |
| 3,072 | 22,957 B | 12,288 B |
패딩 속성으로 쓰기 단위 경계를 이진 탐색하고, 쓰기마다 새 항목 키를 쓰고, 바이트 단위까지 보정해 측정했습니다. 저희의 완전한 1024차원 티켓 항목은 5 쓰기 단위로 과금되었고, embedding에 인덱스가 없는 동일한 항목은 8 쓰기 단위로 과금됩니다.
같은 실행에서 벡터 쓰기 미터는 f32 크기를 근접하게 추적했습니다. VectorWriteRequestBytes는 맨(bare) 인덱스에서는 차원당 4바이트에 키 오버헤드 11 B를 더한 값으로, 저희의 속성 두 개짜리 검색 스키마에서는 65 B를 더한 값으로 돌아왔습니다.
검색 과금은 미리 계산할 수 없는 미터입니다. VectorSearchRequestBytes는 ANN 탐색이 검사한 벡터 데이터의 양을 추적하며, AWS 자체 지침도 차원 수로 추정하지 말고 ReturnConsumedCapacity로 측정하라는 것입니다.
저희 프로브가 첫 데이터 포인트를 제공합니다. 벡터 50개짜리 파티션에 대한 TopK=10 검색은 검색당 22.2-22.4 KB를 검사했고, 벡터 1개짜리 파티션에 대한 같은 검색도 여전히 21.4 KB를 검사했으므로, 소규모에서는 쿼리당 약 21 KB(약 $0.00000004)의 바닥이 존재합니다. AWS의 튜토리얼은 자체 50개 벡터 예제에 대해 31,449바이트를 보고합니다.
테이블 쪽 비용은 요금 계산기로 모델링하세요. 벡터 미터는 계산기가 이미 계산하는 쓰기 단위 위에 쌓입니다.
DynamoDB 벡터 검색 vs S3 Vectors
AWS는 이제 두 가지 서버리스 벡터 저장소를 판매하는데, 둘은 정반대의 액세스 패턴을 위해 만들어졌습니다. S3 Vectors(2025년 12월 GA)는 인덱스당 최대 20억 개의 벡터를 GB-월당 $0.06에 보관하고, 100 ms에서 1초 사이에 응답하며, 모든 쿼리에 대해 인덱스 전체 크기를 기준으로 과금합니다.
DynamoDB는 밀리초 단위로 응답하고, 인덱스가 보관하는 양이 아니라 검색이 검사하는 양을 기준으로 과금합니다.
| DynamoDB 벡터 검색 | S3 Vectors | |
|---|---|---|
| GA | 2026년 8월 | 2025년 12월 |
| 지연 시간 등급 | 한 자릿수 ms(AWS 주장) | 자주 쓰면 ~100 ms, 드물게 쓰면 1초 미만(AWS 주장) |
| 규모 상한 | 명시된 벡터 상한 없음; 인덱스 생성 시 600 GB 테이블 상한(소프트) | 인덱스당 벡터 20억 개 |
| 최대 차원 수 | 4,096 | 4,096 |
| 거리 함수 | 코사인, 유클리드, 내적 | 코사인, 유클리드 |
| 인덱스 쓰기 | 테이블에서 비동기(최종 일관성) | 강력한 일관성 |
| 필터링 | 동등 조건만, 속성 ≤18개 + 파티션 키 1개 | 풍부한 메타데이터 필터, 벡터당 필터링 가능 2 KB 상한 |
| TopK | 100, 페이지네이션 없음 | 10,000, 페이지네이션 지원 |
| 스토리지 | $0.25/GB-mo, 2회(테이블 + 인덱스) | $0.06/GB-mo, 1회 |
| 쓰기 | $0.52/GB, 요청당 최소 1 KB | $0.20/GB, PUT당 최소 128 KB |
| 쿼리 | 검사된 데이터 $0.002/GB | 요청 $2.50/M + 전체 인덱스 기준 처리 바이트 요금 |
스트리밍 사례를 결정짓는 것은 쓰기 최소 단위이며, 이는 스토리지 요율과 정반대 방향을 가리킵니다. 1024차원 벡터를 한 번에 하나씩 쓸 때, 쓰기 100만 건당 비용은 다음과 같습니다(검증된 요율과 저희가 측정한 항목당 5 쓰기 단위 + 벡터 쓰기 4,161바이트로 계산):
| 쓰기 패턴 | DynamoDB | S3 Vectors |
|---|---|---|
| 단일 벡터 쓰기 | ~$5.14/M | ~$24.41/M |
배치(PutVectors당 500개) | 해당 없음(쓰기는 항목 단위) | ~$0.78/M |
PUT당 최소 128 KB라는 S3의 조건은, 사람들이 저렴하리라 짐작하는 바로 그 워크로드에서 S3를 비싼 선택지로 만듭니다. 벡터를 한 번에 하나씩 S3 Vectors로 스트리밍하면 DynamoDB 요율의 거의 5배를 내고, 배치 로드하면 대략 7배 더 적게 냅니다.
월간 스토리지와 쿼리 총액은 1024차원 코퍼스에 월 100만 쿼리를 가정하고 검증된 요율로 계산했습니다(쓰기 비용은 위의 100만 건당 표에 있습니다). S3 Vectors의 쿼리 요금은 공개된 공식, 즉 전체 인덱스 크기 곱하기 계층별 요율을 따릅니다.
DynamoDB의 쿼리 요금은 검사된 바이트에 따라 달라지므로, 여러분의 탐색량을 아는 척하는 대신 민감도 범위로 보여 드립니다:
| 코퍼스 | DynamoDB 스토리지 | DynamoDB 쿼리(4 / 40 / 400 MB 검사) | S3 Vectors 스토리지 | S3 Vectors 쿼리 |
|---|---|---|---|---|
| 벡터 100만 개 | ~$1.95 | $8 / $80 / $800 | ~$0.23 | ~$11 |
| 벡터 1000만 개 | ~$19.50 | $8 / $80 / $800 | ~$2.35 | ~$80 |
| 벡터 1억 개 | ~$195 | $8 / $80 / $800 | ~$23.50 | ~$217 |
이 표에서 두 가지가 드러납니다. DynamoDB의 쿼리당 비용은 코퍼스 크기에 따라 늘어나지 않습니다 — ANN 검색은 인덱스 전체가 아니라 이웃 영역을 검사하며, 파티션 키로 범위를 좁히면 더 줄어듭니다.
S3 Vectors의 스토리지 이점(DynamoDB가 4배 요율로 f32 복사본 두 개를 저장하므로 약 8배)은 누가 쿼리하든 하지 않든 영원히 누적됩니다.
언제 무엇을 사용해야 하나요
- 이미 DynamoDB에 보관 중인 살아 있는 항목을 설명하는 벡터(티켓, 상품, 사용자 세션, 에이전트 메모리): 벡터 인덱스를 사용하세요. 쓰기 경로 하나, 항목 하나, 어긋날 동기화 파이프라인 없음.
- 수백만 개의 임베딩을 가끔 쿼리(문서에 대한 RAG, 아카이브, 야간 작업): S3 Vectors를 사용하세요. 저렴하게 배치 로드하고, 저장에 GB당 $0.06을 내고, 수백 ms를 감내하세요.
- 하이브리드 랭킹이 필요한 높은 QPS(텍스트 관련성 + 벡터, 패싯, 집계): 여전히 OpenSearch가 답이며, 클래식 서버리스 컬렉션 기준 인프라 최저선은 월 약 $350입니다.
- 관계형 데이터와 조인되는 벡터: pgvector를 쓰는 Aurora PostgreSQL이 답입니다. 0까지 스케일 다운되고, 소규모 RAG 워크로드라면 월 $50 이하로 들어옵니다.
DynamoDB를 쓰는 조직의 정직한 기본값은 둘 다입니다. 뜨겁고 필터링 가능한 벡터는 쓰기가 항목과 원자적으로 이루어지는 테이블에 두고, 롱테일은 강력한 일관성의 배치 쓰기 덕분에 깔끔한 싱크가 되어 주는 S3 Vectors에 보관하세요.
함정
- 조용한 인덱스 누락: 인덱스의
HASH속성이 없는 항목은 테이블에는 정상적으로 쓰이지만 벡터 인덱스에는 절대 들어가지 않습니다. 저희가 라이브로 재현했습니다:PutItem은 성공했고, 15초가 지나도 그 벡터는 어떤 파티션에도 없었습니다. 오류도, 결과도, 응답에서 알려 주는 것도 없습니다. - 잘못된 차원의 쓰기는 거부됩니다: 인덱스가 차원 수를 영원히 고정하므로, 마이그레이션 없이 임베딩 모델을 바꾸면 모든 쓰기가 속성 이름과 두 크기를 함께 알려 주는
ValidationException으로 실패합니다(Invalid size for parameter, 별도 페이지에 원문 그대로 기록해 두었습니다). - 오래된 임베딩: DynamoDB는 벡터를 다시 계산해 주지 않습니다.
embedding을 다시 쓰지 않고 티켓의 텍스트만 수정하면 검색은 옛 내용과 조용히 일치합니다. Streams에 재생성 컨슈머를 붙이는 것이 표준적인 해법입니다. TopK는 항상 K개의 항목을 반환합니다: 좋은 일치가 세 개뿐이어도--top-k 10이면 10개를 받습니다. 관련성은 결과 개수가 아니라Score로 판단하고, 거리 함수에 따라 점수의 방향이 뒤집힌다는 점을 기억하세요.- 최소 1 KB: 저차원 벡터라고 해서 쓰기든 검색이든 그에 비례해 저렴하게 계량되지 않습니다.
- 모든 것이 불변입니다: 차원 수, 거리 함수,
INCLUDE프로젝션의 속성 집합 모두 바꾸려면 삭제 후 재생성해야 합니다. 인덱스 스토리지는 쿼리 여부와 무관하게 인덱스의 전체 수명 동안 과금됩니다.
직접 테이블에 시도해 보기
벡터 검색은 DynamoDB의 다른 부분이 가르쳐 준 비용 규율을 그대로 물려받습니다. 항목 크기 제한은 여전히 적용되고 3072차원 임베딩은 두 미터 모두에서 모든 항목 쓰기에 12 KB를 더하므로, 임베딩 크기는 확정하기 전에 재어 보세요.
GSI가 비동기적으로 복제되는 방식과 GSI와 LSI 중 무엇을 고를지를 안다면 인덱스 메커니즘이 익숙하게 느껴질 것입니다. 같은 데이터에 대한 어휘 기반 검색이 필요하다면, DynamoDB에는 여전히 전문(full-text) 검색 엔진이 없습니다 — 벡터 검색은 철자가 아니라 의미를 매칭합니다.
항목 크기 계산기에서 임베딩의 실제 바이트 비용을 확인한 뒤, DynoTable을 사용해 벡터 인덱스 뒤에 있는 항목들을 살펴보세요 — 임베딩은 필터링에 쓰는 필드 바로 옆에 평범한 리스트 속성으로 렌더링됩니다.