AWS 기술 블로그
Amazon Aurora PostgreSQL에서 pgvector를 프로덕션 환경으로 운영하기
이 글은 AWS Database Blog에 게시된 Running pgvector in production on Amazon Aurora PostgreSQL by Stefan Aichholzer 을 한국어 번역 및 편집하였습니다.
Amazon Aurora PostgreSQL-Compatible Edition에서 pgvector를 실행하면 이미 익숙한 데이터베이스 위에 프로덕션 수준의 벡터 스토어를 구축할 수 있으며, Amazon Aurora의 운영 도구, 고가용성, 확장 기능이 이를 뒷받침합니다. 이러한 조합 덕분에 pgvector는 개념 증명(PoC)에서 서비스 수준 계약(SLA)이 적용되는 프로덕션 환경으로 전환하는 검색 증강 생성(RAG) 워크로드에서 널리 선택되고 있습니다. 프로덕션 트래픽이 유입되면 예측 가능한 운영 고려 사항이 발생합니다. 코퍼스가 커짐에 따른 쿼리 지연 시간, 필터링된 벡터 검색에서의 재현율(recall), 인덱스 빌드 시 메모리 여유 공간, 부하 상태에서의 연결 동작 등이 그것입니다. 이 게시물은 RAG 검색 계층을 건강하게 유지하는 데이터베이스 운영에 초점을 맞추고 있습니다. 파인 튜닝이나 지속적 사전 훈련을 통한 모델 커스터마이징은 이 게시물의 범위에 포함되지 않습니다.
이 게시물에서는 pgvector 워크로드를 본격적으로 운영할 때 건강하게 유지하는 데 필요한 운영 실무를 다룹니다. 올바른 인덱스와 거리 함수 선택, 양자화와 파티셔닝을 통한 확장, Hierarchical Navigable Small World(HNSW) 변동(churn) 관리, 메모리 상주 운영을 위한 사이징, 그리고 문제를 조기에 포착하는 관측성 신호에 대해 설명합니다.
이 게시물의 구성
먼저, 데이터셋과 쓰기 패턴에 맞는 인덱스 유형(HNSW 또는 Inverted File with Flat Compression)을 선택합니다. 둘째, AWS 권장 파라미터로 기본 스키마와 쿼리를 설정합니다. 셋째, 임베딩 모델에 맞는 거리 연산자를 선택합니다. 넷째, 양자화, 파라미터 튜닝, 파티셔닝을 통해 목표 데이터셋 크기에 맞게 인덱스를 확장합니다. 다섯째, 트래픽이 유입되기 전에 변동(churn), 용량, 관측성에 대한 계획을 수립합니다. 각 섹션은 이전 섹션을 기반으로 구성됩니다.
전체 예제에서는 Aurora PostgreSQL-Compatible에서 멀티 테넌트 문서 저장소 스키마를 사용하며, 임베딩 모델로 Amazon Bedrock 파운데이션 모델인 Amazon Titan Text Embeddings V2를 활용합니다. 각 SQL 예제는 vector 확장이 활성화된 Aurora PostgreSQL-Compatible 클러스터에서 바로 실행할 수 있습니다.
이 게시물은 벡터 스토어와 검색 파이프라인을 직접 관리하는 셀프 매니지드 방식을 다룹니다. 수집, 임베딩, 검색을 모두 처리해 주는 완전 관리형 RAG 기능을 원한다면 Amazon Bedrock Knowledge Bases를 사용하세요. Amazon Bedrock Knowledge Bases는 벡터 스토어 옵션 중 하나로 pgvector가 적용된 Amazon Aurora PostgreSQL을 지원합니다.
사전 요구 사항
이 게시물의 예제를 따라 하려면 다음이 필요합니다.
- PostgreSQL 버전 17.4+, 16.8+, 15.12+, 14.17+ 또는 13.20+을 실행하는 Aurora PostgreSQL-Compatible 클러스터(pgvector 0.8.0 지원). pgvector 0.8.0은 AWS 중국 리전을 제외한 모든 AWS 상용 리전 및 AWS GovCloud(US) 리전에서 사용할 수 있습니다. 출시 세부 정보는 pgvector 0.8.0 Aurora 발표를 참조하세요. 현재 리전 목록은 Aurora PostgreSQL 확장 버전 페이지를 참조하세요.
- 데이터베이스에서 vector 확장이 활성화되어 있어야 합니다.
- 임베딩 모델. 예제에서는 Amazon Bedrock을 통해 제공되는 파운데이션 모델인 Amazon Titan Text Embeddings V2를 사용하며, 기본적으로 1024차원 임베딩을 생성합니다.
올바른 인덱스 전략 선택
pgvector는 두 가지 근사 최근접 이웃(ANN) 인덱스 유형인 HNSW와 IVFFlat을 제공합니다. 두 유형 모두 완벽한 정확도를 속도와 교환하는데, 이는 수백만 개의 벡터에 대한 무차별 대입 정확 검색이 온라인 쿼리에는 너무 느리기 때문입니다. AWS Prescriptive Guidance의 벡터 데이터베이스 옵션 가이드에서 AWS 벡터 스토어 중 pgvector의 위치를 확인할 수 있습니다. 용어에 대한 복습이 필요하다면 Amazon Aurora PostgreSQL을 활용한 셀프 매니지드 멀티 테넌트 벡터 검색 게시물에서 pgvector 기본 사항을 확인하세요.
Aurora PostgreSQL에서의 프로덕션 RAG에서는 대부분의 워크로드에 HNSW가 기본 선택입니다. 하지만 인덱스를 사용하지 않는 것이 올바른 답인 두 가지 경우가 있습니다. 바로 소규모 데이터셋과 100% 재현율이 필요한 파티셔닝된 데이터셋입니다. 이 섹션의 나머지 부분에서는 일반적인 경우에 HNSW가 우수한 이유, IVFFlat이 여전히 적합한 경우, 그리고 인덱스를 완전히 생략하는 것이 올바른 프로덕션 결정인 경우를 설명합니다.
HNSW의 작동 원리와 기본 선택인 이유
HNSW는 Hierarchical Navigable Small World의 약자로, 다층 근접 그래프를 구축합니다. 레이어 0에는 모든 벡터가 포함되어 있으며 가장 가까운 이웃들과 밀집하게 연결되어 있습니다. 상위 레이어로 갈수록 더 적은 수의 벡터 부분집합이 샘플링되며 더 먼 거리의 연결을 갖습니다. 하나의 벡터가 진입점(entry point)으로서 모든 레이어에 존재합니다. 여기서의 요약을 넘어 HNSW와 IVFFlat 내부 구조에 대한 자세한 설명은 IVFFlat 및 HNSW 기법 심층 분석을 참조하세요.
검색은 최상위 레이어의 진입점에서 시작됩니다. 레이어 내에서 pgvector는 그래프 엣지를 따라 탐욕적 최선 우선 탐색(greedy best-first walk)을 수행하며, 쿼리 벡터에 가장 가까운 연결된 이웃으로 이동합니다. 더 이상 가까워질 수 없으면 아래 레이어의 동일한 노드로 하강하여 그곳에서 탐색을 계속합니다. 레이어 0에서는 탐색이 hnsw.ef_search 파라미터로 제어되는 빔 검색(beam search)으로 확장되며, 상위 후보들이 반환됩니다.
다음 다이어그램은 최상위 레이어의 진입점에서 레이어 0의 빔 검색까지의 하강 과정을 보여줍니다.

HNSW 계층적 하강. 쿼리는 최상위 레이어의 진입점에서 시작하여 각 레이어 내에서 탐욕적 탐색을 수행해 로컬 최근접 이웃을 찾고, 아래 레이어의 동일한 노드로 하강하며, 레이어 0에서 빔 검색으로 확장하여 top-k 결과를 반환합니다.
HNSW 계층적 하강. 쿼리는 최상위 레이어의 진입점에서 시작하여 각 레이어 내에서 탐욕적 탐색을 수행해 로컬 최근접 이웃을 찾고, 아래 레이어의 동일한 노드로 하강하며, 레이어 0에서 빔 검색으로 확장하여 top-k 결과를 반환합니다.
이러한 구조 덕분에 HNSW는 쿼리 속도가 빠르며 점진적으로 삽입을 수용할 수 있습니다. 일시 중지나 배치 처리 없이 라이브 인덱스에 새로운 벡터를 직접 쓸 수 있습니다. 재현율과 지연 시간은 쿼리 시점에 hnsw.ef_search를 통해 제어되므로, 인덱스를 재구축하지 않고도 워크로드별로 튜닝할 수 있습니다.
트레이드오프는 빌드 비용입니다. HNSW 인덱스는 그래프 자체가 벡터와 함께 저장되어야 하므로 IVFFlat보다 빌드 시간이 더 오래 걸리고 메모리를 더 많이 사용합니다. 이 게시물이 대상으로 하는 프로덕션 RAG 패턴에서는 이 트레이드오프가 그만한 가치가 있습니다. 인덱스를 한 번 빌드하면 변동(churn)을 관리하는 한 쿼리가 빠르게 유지됩니다(이후 인덱스 변동 관리 섹션에서 다룹니다).
IVFFlat이 여전히 적합한 경우
IVFFlat은 Inverted File with Flat compression의 약자입니다. 그래프 대신 k-means를 사용하여 벡터를 클러스터로 그룹화합니다. 각 클러스터는 중심에 센트로이드(centroid)라고 불리는 대표 점을 가지고 있습니다. 쿼리 시점에 pgvector는 쿼리 벡터를 센트로이드와 비교하여 가장 가까운 클러스터를 선택하고, 해당 클러스터 내부에서만 검색을 수행합니다.
다음 다이어그램은 클러스터로 분할된 공간에서 쿼리가 먼저 센트로이드와 비교된 후 가장 가까운 클러스터 내부의 벡터와 비교되는 과정을 보여줍니다.

IVFFlat 클러스터링. 벡터 공간이 클러스터로 분할되며 각 클러스터의 중심에 센트로이드가 위치합니다. 쿼리는 먼저 센트로이드와 비교된 후 가장 가까운 클러스터 내부에서만 검색됩니다.
IVFFlat은 HNSW보다 빌드 비용이 저렴합니다. 클러스터링이 그래프 구축보다 단순하고, 인덱스에 그래프가 저장되지 않으므로 메모리 사용량도 적습니다. 그러나 IVFFlat은 빌드 시점에 센트로이드를 고정합니다. 인덱스가 빌드된 후 데이터가 변경되면 새로운 벡터가 기존 클러스터와 잘 맞지 않을 수 있으며, 재현율이 떨어집니다. 해결 방법은 인덱스를 재구축하여 현재 데이터에 대해 센트로이드를 다시 계산하는 것입니다. 센트로이드 재훈련이란 전체 데이터셋에 대해 k-means를 다시 실행하여 새로운 클러스터 중심 세트를 생성하는 것을 의미합니다.
IVFFlat은 좁은 범위의 워크로드에 적합합니다. 기존 배치 재구축 일정이 있는 대규모의 거의 정적인 코퍼스가 그 대상입니다. 그 외 모든 경우에는 HNSW를 사용해야 합니다.
인덱스를 사용하지 않는 것이 올바른 선택인 경우
인덱스를 사용하지 않는 것이 두 가지 ANN 옵션보다 더 나은 성능을 보이는 두 가지 프로덕션 패턴이 있습니다.
첫 번째는 소규모 데이터셋의 경우입니다. 약 10,000~50,000개의 벡터가 있는 테이블에서는 병렬 순차 스캔만으로도 충분히 빠르며, 인덱스를 생략하면 빌드 시간, 유지 관리 비용, 그리고 근사 검색에 내재된 재현율 손실을 피할 수 있습니다. 이는 pgvector 0.8.0 모범 사례에서 언급된 경우이며, 코퍼스가 커지기 전 새로운 워크로드의 일반적인 시작점입니다.
두 번째는 파티셔닝된 재현율 중시 사례입니다. 스키마가 데이터를 파티셔닝하여 모든 쿼리가 제한된 부분집합만 접근하고(예: 테넌트별 또는 사용자별), 사용 사례에서 100% 재현율이 필요한 경우, 파티션 내 무차별 대입 병렬 스캔이 ANN 인덱스보다 나을 수 있습니다. Ring 엔지니어링 팀이 프로덕션에서 정확히 이 패턴을 운영하고 있습니다. 1,000억~2,000억 개의 임베딩이 각각 약 1GB 크기의 사용자별 파티션에 분산되어 있습니다. 각 파티션은 벡터 인덱스 없이 max_parallel_workers_per_gather = 16으로 스캔됩니다. 인덱스를 제거한 것이 PostgreSQL이 단일 스레드 인덱스 스캔 대신 병렬 순차 스캔을 선택하게 만든 변경점이었으며, 이를 통해 EBS 처리량이 약 50MB/s에서 거의 500MB/s로 향상되었습니다. 자세한 내용은 Ring의 수십억 규모 시맨틱 비디오 검색 게시물을 참조하세요.
이 패턴이 작동하려면 두 가지 조건이 충족되어야 합니다. 첫째, 파티션별 스캔이 지연 시간 예산 내에 맞아야 하며, 실제로는 각 파티션이 버퍼 캐시나 로컬 NVMe에서 대부분 서비스될 수 있을 만큼 충분히 작아야 합니다. 둘째, 워크로드가 진정으로 100% 재현율을 필요로 해야 합니다. HNSW로 달성 가능한 ANN 재현율이 허용 범위 내라면, HNSW가 더 간단하고 빠릅니다. 두 조건이 모두 충족되면 인덱스를 생략하는 것으로 빌드 비용, 변동(churn) 관리, 인덱스 페이지를 위한 메모리 사이징 등 전체 운영 고려 사항 범주를 제거할 수 있습니다.
빠른 의사결정 가이드
다음 플로우차트는 데이터셋 크기에서 시작하여 쓰기 패턴, HNSW 빌드 비용, 파티셔닝을 거쳐 구체적인 인덱스 선택에 이르는 의사결정 과정을 요약합니다.

인덱스 선택 플로우차트. 데이터셋 크기, 쓰기 패턴, HNSW 빌드 비용, 파티셔닝을 기반으로 인덱스 유형을 선택합니다.
“인덱스 생략” 분기는 이전 섹션에서 다룬 두 가지 경우를 모두 포함합니다. 순차 스캔만으로도 충분히 빠른 소규모 테이블과, 제한된 파티션별 스캔 내에서 100% 재현율이 필요한 파티셔닝된 스키마가 그것입니다. Ring은 고객별 파티션을 사용하여 모든 검색이 해당 고객의 데이터만 읽도록 하는 두 번째 패턴을 프로덕션에서 운영하고 있습니다. 이를 어떻게 설계했는지에 대해서는 앞의 인덱스 미사용 논의와 Ring의 수십억 규모 시맨틱 비디오 검색 게시물을 참조하세요.
실행 가능한 기본 구성
유사도 함수와 확장에 대해 더 깊이 들어가기 전에, 이 게시물의 나머지 부분이 기반으로 하는 기본 스키마, 인덱스, 쿼리를 소개합니다. 이는 pgvector 0.8.0 모범 사례 섹션의 AWS 권장 HNSW 파라미터와 프로덕션에 권장하는 반복 스캔(iterative scan) 기본값을 반영합니다.
이 기본 구성은 HNSW를 사용하며, 이는 일반적인 프로덕션 RAG 사례에 적합합니다. 테넌트별 파티션이 순차 스캔으로는 경쟁력이 없을 만큼 충분히 크고, 기본 설정에서의 ANN 재현율이 허용 범위 내인 경우입니다. 워크로드가 앞서 다룬 인덱스 미사용 패턴(소규모 테이블 또는 제한된 파티션별 스캔 내에서 100% 재현율이 필요한 파티셔닝된 스키마)에 해당한다면, 다음 예제에서 HNSW 인덱스를 생략하고 나머지 스키마는 그대로 유지하세요.
데이터가 채워진 documents 테이블에서 이 쿼리는 코사인 거리 순으로 최대 10개의 행을 반환합니다:
| id | content |
|---|---|
| 42 | Q3 revenue grew 18% year over year, driven by enterprise… |
| 117 | The FY26 capital plan was approved at the November board… |
| 203 | Customer churn declined to 2.1% following the support model… |
| … | … |
| (10 rows) | |
프로덕션 코드에서는 두 개의 SET 문과 SELECT를 트랜잭션으로 감싸고 SET LOCAL을 대신 사용하여, 값이 전체 세션이 아닌 해당 쿼리에만 적용되도록 하세요. 임베딩이 단위 정규화(unit-normalized)되어 있다면 vector_cosine_ops를 vector_ip_ops로, <=> 연산자를 <#>로 변경하세요. 다음 섹션에서 각 선택이 적용되는 경우를 설명합니다.
대규모 환경에서의 유사도 함수
pgvector는 여러 거리 연산자를 지원합니다. 텍스트 및 시맨틱 임베딩에서 중요한 세 가지 연산자가 다음 표에 나열되어 있습니다. 각 연산자는 인덱스 생성 시 특정 HNSW 연산자 클래스와 쌍을 이룹니다. 잘못된 쌍을 사용하면 플래너가 인덱스를 사용할 수 없습니다.
| 연산자 | 의미 | HNSW 연산자 클래스 | 사용 시기 |
|---|---|---|---|
| <=> | 코사인 거리 | vector_cosine_ops |
텍스트 또는 시맨틱 임베딩에 대한 안전한 기본값. 벡터 크기를 무시합니다. |
| <#> | 음의 내적 | vector_ip_ops |
벡터가 이미 단위 정규화되어 있을 때 코사인보다 빠릅니다. |
| <-> | L2(유클리드) 거리 | vector_l2_ops |
시맨틱 검색에는 거의 적합하지 않습니다. |
<#>에 대한 참고 사항: pgvector는 PostgreSQL이 ASC 순서 인덱스 스캔만 사용할 수 있기 때문에 음의 내적을 반환합니다. 이는 ORDER BY embedding <#> query ASC가 가장 유사한 벡터를 먼저 반환한다는 것을 의미합니다. 결과에서 양의 유사도 점수를 원한다면 -1을 곱하세요: SELECT (embedding <#> query) * -1 AS similarity.
코사인과 내적 중 선택하기
Amazon Titan Text Embeddings V2는 normalize API 파라미터를 통해 기본적으로 정규화하며, 이 파라미터의 기본값은 true입니다. 기본 설정의 Amazon Titan Text Embeddings V2 출력에는 내적(<#>)이 올바른 선택입니다. 정규화된 벡터에서는 코사인과 내적이 동일한 순위를 생성하는데, 코사인은 내적을 벡터 노름의 곱으로 나누며 단위 벡터의 경우 그 나눗수가 항상 1이기 때문입니다. 노름 계산과 나눗셈은 비교할 때마다 여전히 수행되지만, 내적은 그 작업을 생략합니다. normalize를 false로 재정의하거나 정규화하지 않는 임베딩 모델(BGE 또는 이전 Sentence-Transformers 모델 등)을 사용하는 경우에는 코사인으로 대체하세요.
연산자를 전환하기 전에 저장된 벡터가 실제로 단위 정규화되어 있는지 확인하세요. 노름은 부동소수점 허용 범위 내에서 1.0이어야 합니다.
하나 이상의 행에서 노름이 1.0과 크게 다른 값을 반환한다면, 데이터가 정규화되지 않은 것이며 <#> 단축 연산자는 잘못된 결과를 생성합니다. greatest(0, …) 가드는 허용 범위 내에서 단위 정규화된 벡터에 대해 아주 작은 음의 부동소수점 잔여값의 sqrt를 방지합니다.
필터링된 쿼리를 위한 반복 스캔
pgvector 0.8.0에서는 과도한 필터링(overfiltering) 문제를 해결하는 반복 인덱스 스캔(iterative index scan)이 도입되었습니다. 0.8.0 이전에는 WHERE 절과 벡터 검색을 결합한 쿼리가 LIMIT에서 요청한 것보다 적은 결과를 반환하는 경우가 많았는데, 이는 인덱스가 필터 적용 전에 top-k 후보를 반환하고 대부분의 후보가 필터링되어 제거되었기 때문입니다.
반복 스캔은 쿼리가 필터를 충족하거나 설정 가능한 한계에 도달할 때까지 인덱스에서 후보를 계속 가져옵니다. 세 가지 모드를 사용할 수 있습니다.
- off: 0.8.0 이전의 동작. 가장 빠르지만 결과가 부족할 수 있습니다.
- strict_order: 정확한 거리 순서를 유지합니다. 더 안전하지만 선택적 필터에서는 더 느립니다.
- relaxed_order: 결과 세트 내에서 근사 순서로 올바른 개수를 반환합니다. 대부분의 프로덕션 사용 사례에 이 모드를 권장합니다.
반복 모드를 활성화하면 두 가지 관련 파라미터가 스캔 범위를 제한합니다. hnsw.max_scan_tuples(기본값 20,000)는 스캔이 인덱스를 탐색하는 범위를 제한하고, hnsw.scan_mem_multiplier(기본값 1)는 스캔이 work_mem의 배수로 사용하는 메모리 양을 제한합니다. max_scan_tuples만 증가시켜도 필터링된 쿼리의 재현율이 개선되지 않는 경우에는 scan_mem_multiplier를 먼저 올리세요.
수백만 벡터로의 확장
데이터셋이 수십만 벡터를 넘어 성장할 때 세 가지 레버가 중요합니다: 양자화, HNSW 파라미터 튜닝, 파티셔닝입니다.
양자화는 재현율에 대한 약간의 비용으로 메모리 사용량을 줄여줍니다. pgvector는 halfvec(16비트 부동소수점)과 이진 양자화를 지원합니다. AWS pgvector 0.7.0 벤치마크에 따르면 halfvec은 최소한의 재현율 손실로 메모리를 약 절반으로 줄이며, 이진 양자화는 재현율 비용을 감수하면서 빌드 속도를 크게 향상시키는데, 이 재현율은 다음 하위 섹션에서 설명하는 재순위화(re-ranking) 과정을 통해 회복할 수 있습니다. 대부분의 워크로드에서는 halfvec으로 시작하세요. 이진 양자화는 재순위화 파이프라인이 이미 구축되어 있는 경우에만 사용하세요.
벤치마크에서는 OpenAI(500만 벡터, 1536차원) 및 Cohere(1,000만 벡터, 768차원) 데이터셋을 사용했습니다. 1024차원의 Amazon Titan Text Embeddings V2는 이 두 데이터셋 사이에 위치하므로, 메모리 및 빌드 시간 트레이드오프가 비례적으로 적용됩니다. 양자화 전략을 확정하기 전에 자체 데이터로 검증하세요.
다음 차트는 해당 벤치마크에서 float32, halfvec, 이진 표현 간의 메모리 및 재현율 트레이드오프를 보여줍니다.

AWS pgvector 0.7.0 벤치마크에서 가져온 float32, halfvec, 이진 벡터 표현의 메모리 사용량과 재현율. halfvec은 최소한의 재현율 손실로 메모리를 절반으로 줄이며, 이진 양자화는 메모리를 더 줄이지만 재현율 회복을 위해 재순위화 과정이 필요합니다.
이진 양자화를 활용한 2단계 검색
HNSW 인덱스와 전체 정밀도 벡터는 성능을 위해 메모리에 상주해야 합니다. 작업 세트가 RAM 용량을 초과하면 메모리 내 표현을 축소(양자화)하거나, Aurora Optimized Reads로 유효 메모리를 확장하거나, 또는 두 가지를 모두 사용할 수 있습니다. 재순위화를 동반한 이진 양자화가 축소를 담당합니다. 거친(coarse) 후보 선택은 쉽게 메모리에 들어가는 작은 이진 벡터에서 실행되고, 코사인 거리로 재순위화할 때만 최종 top-N에 대해 전체 정밀도 벡터를 가져옵니다.
다음 SQL 패턴은 이 2단계 검색을 구현합니다. 거친 패스에는 해밍 거리를, 재순위화에는 코사인 거리를 사용합니다. 재순위화가 양자화로 인해 손실된 재현율의 대부분을 회복하는 핵심입니다.
외부 쿼리는 100개의 거친 후보만 보므로, 비용이 큰 코사인 비교는 작은 세트에서만 실행됩니다. 인라인 binary_quantize() 캐스트는 명확성을 위해 표시한 것입니다. 대규모 프로덕션에서는 이진 벡터를 저장된 컬럼으로 구체화하고 bit_hamming_ops로 인덱싱하여, 거친 패스가 쿼리 시점에 양자화를 계산하는 대신 인덱스를 사용하도록 하세요. pgvector 0.8.0 및 Aurora에서의 이 패턴에 대한 벤치마크는 pgvector 0.8.0을 활용한 Amazon Aurora PostgreSQL 벡터 검색 성능 및 관련성 극대화를 참조하세요.
HNSW는 튜닝할 가치가 있는 세 가지 파라미터를 제공합니다.
m: 레이어당 최대 연결 수. 값이 높을수록 재현율이 향상되지만 메모리 사용량과 빌드 시간이 증가합니다. AWS는m = 16을 시작점으로 권장합니다.ef_construction: 빌드 시 동적 후보 리스트 크기. 값이 높을수록 빌드 시 인덱스 품질이 향상됩니다. AWS는ef_construction = 128을 권장합니다. pgvector 기본값은 64입니다.ef_search: 쿼리 시 동적 후보 리스트 크기. 값이 높을수록 쿼리 지연 시간을 대가로 재현율이 향상됩니다. pgvector 기본값은 40이며, 이는 프로덕션에서 너무 낮은 경우가 많습니다. 워크로드별로 튜닝하세요. 하드코딩하지 마세요.
파티셔닝은 개별 인덱스를 관리 가능한 크기로 유지합니다. 멀티 테넌트 워크로드에서는 테넌트별로, 이벤트나 로그 데이터와 같은 추가 위주 워크로드에서는 시간별로, 쿼리가 알려진 부분집합으로 범위가 지정되는 경우에는 카테고리별로 파티셔닝하세요. 파티셔닝은 병렬 인덱스 빌드와 파티션별 재구축도 지원합니다.
대량 로드의 경우 인덱싱을 지연시키세요. 먼저 데이터를 로드한 다음 마지막에 인덱스를 한 번 빌드하세요. 라이브 HNSW 인덱스에 삽입하는 것은 로드 후 단일 빌드보다 느리며, 결과적으로 생성되는 인덱스의 구조도 더 좋은 경우가 많습니다.
작업 세트가 RAM을 초과할 때
RAM 상주가 첫 번째 목표이지만, 특정 데이터셋 크기에서는 전체 HNSW 그래프를 shared_buffers에 유지하는 것이 경제적이지 않게 됩니다. Aurora Optimized Reads는 로컬 NVMe 스토리지를 계층형 캐시로 사용하여 유효 캐시 용량을 인스턴스 메모리의 최대 5배까지 확장하며, Aurora 스토리지에서 가져와야 하는 쿼리의 읽기 지연 시간을 최대 8배까지 낮춥니다. 기능 문서에서는 수백만 벡터 임베딩에 대한 pgvector 최근접 이웃 검색을 대상 사용 사례로 명시하고 있습니다.
Optimized Reads 계층형 캐시는 NVMe 기반 인스턴스 패밀리(r6gd, r8gd 또는 r6id)의 Aurora I/O-Optimized 클러스터가 필요하며, 해당 인스턴스 클래스에서 자동으로 활성화됩니다. Aurora Standard 클러스터에서는 Optimized Reads가 계층형 캐시가 아닌 임시 객체 가속만 제공합니다. AuroraOptimizedReadsCacheHitRatio Amazon CloudWatch 지표를 BufferCacheHitRatio와 함께 모니터링하여 RAM을 미스하지만 스토리지가 아닌 NVMe에서 처리되는 트래픽이 얼마나 되는지 확인하세요.
계층형 캐시는 메모리 예산의 대체가 아닌 확장으로 취급하세요. RAM은 여전히 NVMe보다 빠르고, NVMe는 여전히 Aurora 스토리지보다 빠릅니다. 핫 작업 세트가 RAM에 유지되도록 인스턴스 크기를 설정하고, Optimized Reads가 롱테일을 흡수하도록 하세요.
인덱스 변동(churn) 관리
HNSW 인덱스는 삭제와 업데이트로 인해 성능이 저하됩니다. 각 변경은 그래프에 무효 항목을 남깁니다. 시간이 지남에 따라 이는 재현율을 떨어뜨리고 인덱스 크기를 부풀립니다. 제자리 압축(in-place compaction)은 지원되지 않습니다.
다음 차트는 재구축 사이에 재현율이 점진적으로 하락하고 예약된 REINDEX CONCURRENTLY 실행 시마다 회복되는 과정을 보여줍니다.

시간에 따른 HNSW 재현율 예시. 예약된 재구축 없이는 무효 그래프 항목이 누적되면서 재현율이 점진적으로 하락합니다. 예약된 REINDEX CONCURRENTLY를 실행하면 각 재구축 시마다 재현율이 회복됩니다. 실제 곡선은 워크로드, 쓰기 비율, 인덱스 파라미터에 따라 달라집니다.
프로덕션에서 효과적인 세 가지 패턴:
REINDEX CONCURRENTLY는 쓰기를 차단하지 않고 인덱스를 재구축하지만, 리소스를 많이 소모합니다. 트래픽이 적은 시간대에 예약하고 maintenance_work_mem 여유 공간을 모니터링하세요. 대규모 인덱스의 경우 분 단위가 아닌 시간 단위로 계획하세요.
파티션 기반 재구축이 더 깔끔한 경우가 많습니다. 시간별로 파티셔닝한 경우, 단일 모놀리식 그래프를 변동시키는 대신 전체 파티션의 인덱스를 한 번의 작업으로 삭제하고 재구축할 수 있습니다. 이는 재구축 실패 시 영향 범위도 제한합니다.
추가 전용(append-only) + 주기적 압축은 오래된 데이터가 더 이상 관련이 없는 워크로드에 적합합니다. 새로운 벡터를 활성 파티션에 쓴 다음, 주기적으로 오래된 파티션을 이동하거나 삭제합니다. 활성 인덱스는 작고 빠른 상태를 유지합니다.
쓰기 패턴에 따라 어떤 방식을 선택할지 결정됩니다. 업데이트가 거의 없이 대부분 삽입으로 이루어진 워크로드는 예약된 REINDEX CONCURRENTLY로 충분합니다. 업데이트나 삭제가 많은 워크로드는 파티셔닝(시간별 또는 배치별)과 파티션 독립 재구축이 더 적합합니다. 관련성이 시간으로 제한되는 워크로드(예: 최근 90일만 검색하는 경우)는 오래된 파티션의 주기적 압축을 동반한 추가 전용 패턴이 적합합니다.
용량 계획
변동(churn) 관리는 그래프를 얼마나 자주 재구축할지를 결정합니다. 용량 계획은 각 재구축과 각 쿼리가 필요한 메모리를 확보할 수 있는지를 결정합니다. 이 두 가지는 함께 진행됩니다.
HNSW 인덱스는 메모리에 상주해야 합니다. 디스크로 스필(spill)되면 검색 지연 시간이 저하되는데, 이는 그래프 탐색 패턴이 랜덤하며 각 미스가 페이지 폴트이기 때문입니다. 동시 쿼리와 유지 관리 작업을 위한 여유 공간을 포함하여 인덱스가 RAM에 들어갈 수 있도록 계획하세요.
HNSW 그래프는 주로 m 파라미터에 의해 원시 벡터 데이터보다 더 많은 메모리를 소비합니다. shared_buffers에 연결 및 작업 메모리를 더한 값이 인스턴스의 사용 가능한 RAM 이하가 되도록 Aurora 인스턴스 크기를 설정하세요.
HNSW 그래프가 RAM에 들어가야 하므로, Aurora PostgreSQL 벡터 워크로드에는 메모리 최적화 인스턴스 클래스를 선택하세요. Amazon Relational Database Service(Amazon RDS) r-시리즈 인스턴스 패밀리는 벡터 검색과 같은 메모리 바운드 워크로드에 맞게 설계되었습니다. 현재 지원되는 인스턴스 클래스 목록은 Aurora DB 인스턴스 클래스 문서를 참조하세요.
다음 PostgreSQL 파라미터를 튜닝하세요.
shared_buffers: Aurora가 합리적인 기본값을 설정하지만, 예상되는 인덱스 작업 세트를 커버하는지 확인하세요.effective_cache_size: 사용 가능한 OS 수준 캐시에 대해 쿼리 플래너에 신호를 보냅니다. Aurora에서는 인스턴스 메모리의 약 75%로 설정하세요.maintenance_work_mem: 인덱스 빌드 시 매우 중요합니다. 너무 낮으면CREATE INDEX또는 REINDEX가 스필되어 느려집니다. pgvector는 HNSW 그래프가 더 이상maintenance_work_mem에 들어가지 않을 때NOTICE를 출력합니다. 빌드 로그에서 이를 발견하면 인스턴스에서 파라미터를 올리고 빌드를 다시 실행하세요.work_mem: 작업별로 적용됩니다. 동시 벡터 쿼리가 많을 때work_mem이 낮으면 스필이 발생합니다. 값이 너무 높으면 부하 상태에서 메모리 압박 위험이 있습니다.max_parallel_maintenance_workers: pgvector 0.7.0에서 병렬 HNSW 빌드가 추가되었습니다. 인덱스 생성 시 더 큰 인스턴스를 활용하려면 이 값을 설정하세요.
요금에 대해서는 Amazon Aurora 요금 페이지를 참조하세요.
관측성
볼 수 없는 것은 튜닝할 수 없습니다. pgvector에서는 네 가지 관측성 계층이 중요합니다: 쿼리 수준 통계, 인스턴스 수준 지표, 대기 이벤트 분석, 그리고 애플리케이션 정의 커스텀 지표입니다. 연결 처리가 이를 완성합니다.
쿼리 수준 통계는 pg_stat_statements와 Aurora 전용 aurora_stat_statements 함수에서 제공됩니다. Aurora 변형은 스토리지 I/O 및 피크 메모리 컬럼을 추가하는데, 이는 벡터 쿼리에서 중요합니다. 스필이 발생하는 검색은 캐시된 검색과 I/O 패턴이 다르게 나타나기 때문입니다. 총 시간과 피크 메모리 기준으로 상위 벡터 쿼리를 찾으려면
total_exec_time은 밀리초 단위이고 max_exec_peakmem은 바이트 단위입니다. 피크 메모리 컬럼은 Aurora PostgreSQL 16.3, 15.7 또는 14.12 이상이 필요합니다. 이전 마이너 버전에서는 SELECT 목록에서 max_exec_peakmem을 제거하세요.
CloudWatch의 인스턴스 수준 지표는 인덱스가 메모리에 들어가 있는지를 알려줍니다. BufferCacheHitRatio(건강한 벡터 워크로드에서는 99% 이상을 유지해야 함), SwapUsage(0이어야 함), ReadIOPS(읽기 중심 벡터 워크로드에서 지속적으로 높은 값은 인덱스가 스필되고 있음을 시사)를 모니터링하세요.
Amazon CloudWatch Database Insights는 대기 이벤트 분석과 함께 느린 쿼리를 표시합니다. I/O 또는 잠금 경합으로 차단된 벡터 쿼리를 찾는 데 활용하세요.
커스텀 지표는 데이터베이스 상태와 애플리케이션 상태 간의 격차를 메워줍니다. 다음 두 가지를 구축할 가치가 있습니다.
- 재현율 추적: 올바른 top-k 결과를 알고 있는 고정된 평가 쿼리 세트를 주기적으로 실행하고, pgvector가 반환하는 결과를 알려진 정답 세트와 비교하여 재현율 백분율을 CloudWatch로 전송합니다. 재현율 하락은 인덱스 드리프트 또는 쿼리 파라미터 회귀를 나타냅니다.
- 쿼리 유형별로 태깅된 벡터 검색의 p99 지연 시간. 벡터 검색의 테일 지연 시간은 CPU나 메모리 지표보다 먼저 움직이는 경우가 많은데, 이는 HNSW 그래프를 캐시에서 제거하는 소수의 쿼리가 평균을 변경하지 않으면서도 재현율과 지연 시간을 저하시킬 수 있기 때문입니다.
연결 처리도 모니터링이 필요합니다. 벡터 쿼리는 메모리를 많이 사용하므로, 과도하게 할당된 연결 풀은 work_mem을 빠르게 소진합니다. Aurora 앞에 Amazon RDS Proxy를 사용하고 DatabaseConnections, ClientConnections, MaxDatabaseConnectionsAllowed를 모니터링하세요.
리소스 정리
예제를 따라 하기 위해 기본 documents 테이블과 인덱스를 생성했다면, 작업이 완료된 후 사용하지 않는 데이터에 대한 지속적인 스토리지 비용이 발생하지 않도록 이를 제거하세요.
vector 확장 자체는 비용이 발생하지 않지만, 이 예제만을 위해 활성화한 경우 DROP EXTENSION IF EXISTS vector;로 제거할 수 있습니다. 클러스터의 다른 데이터베이스에서 이 확장을 사용하고 있다면 삭제하지 마세요.
테스트를 위해 전용 Aurora PostgreSQL-Compatible 클러스터를 프로비저닝한 경우, 작업이 완료되면 AWS Command Line Interface(AWS CLI)를 사용하여 클러스터와 자동 백업을 삭제하세요. Aurora PostgreSQL-Compatible 클러스터는 존재하는 동안 계속 비용이 발생하며, 중지된 클러스터에서도 스토리지 비용이 부과되므로 테스트 후 클러스터를 삭제하여 이러한 비용을 중단하세요.
수동 스냅샷은 클러스터가 삭제된 후에도 유지되며, 명시적으로 삭제할 때까지 스토리지 비용이 계속 발생합니다. 테스트 중에 스냅샷을 생성했다면 제거하세요:
<your-db-instance-id>, <your-cluster-id>, <your-snapshot-id>를 테스트 환경의 값으로 교체하세요. --skip-final-snapshot 플래그는 일회용 테스트 클러스터에 적합합니다. 보존하려는 데이터가 있는 클러스터에는 사용하지 마세요.
결론
운영 환경에 적용하기 전에 다음 다섯 가지를 확인하세요.
- 첫날부터 HNSW 재구축을 계획하세요. 쓰기 패턴에 따라 예약된
REINDEX CONCURRENTLY, 파티션 기반 재구축, 또는 압축을 동반한 추가 전용 방식 중 하나를 선택하세요. hnsw.ef_search를 세션 또는 쿼리 수준에서 명시적으로 설정하세요. 기본값(40)은 프로덕션 재현율에 너무 낮은 경우가 많습니다. 100이 합리적인 시작점입니다.maintenance_work_mem을 가장 큰 인덱스 빌드를 커버할 수 있도록 여유 공간을 포함하여 설정하세요. 디스크로 스필되는 빌드는 품질이 낮은 그래프를 생성합니다.- 연결 할당량을 설정할 때 동시 벡터 검색을 고려하세요. 각 동시 쿼리는
work_mem을 소비합니다. 이를 곱해서 계산하고 클러스터 앞에 Amazon RDS Proxy를 사용하세요. BufferCacheHitRatio를 가장 먼저 확인하는 지표로 취급하세요. 인덱스가 인스턴스 용량을 초과했음을 알려주는 가장 빠른 신호입니다.
프로덕션 규모에서 Amazon Aurora PostgreSQL에 pgvector를 운영하는 것은 워크로드에 맞게 사전에 설계하는 팀에게 보상을 줍니다. 코퍼스 크기, 쓰기 비율, 필터 패턴, 재현율 목표가 올바른 인덱스 선택, 파라미터 설정, 용량 계획을 결정합니다. 이러한 결정을 의도적으로 빨리 내릴수록 개념 증명에서 SLA가 적용되는 프로덕션으로의 전환이 더 원활해집니다.
이 게시물에서는 중요한 운영 실무를 다루었습니다. 올바른 인덱스와 거리 함수 선택, 양자화와 파티셔닝을 통한 확장, HNSW 변동(churn) 관리, 메모리 상주 운영을 위한 사이징, 그리고 문제를 조기에 포착하는 관측성 신호가 그것입니다. 이를 첫 프로덕션 배포를 위한 체크리스트로 활용하고, 트래픽이나 데이터셋 크기가 두 배로 늘어날 때 다시 검토하세요.