대부분의 벡터는 조종용(Steerage)입니다. 왜 일등석(First Class)에 두고 계신가요?
요약
RAG 애플리케이션의 벡터 데이터 규모 확장 시 발생하는 급격한 인프라 비용 문제를 다룹니다. 모든 벡터를 고가의 핫 스토리지에 저장하는 대신, 액세스 패턴에 따라 스토리지 계층을 분리하여 비용, 재현율, 지연시간의 트레이드오프를 최적화하는 전략을 제안합니다.
핵심 포인트
- 벡터 데이터의 80% 이상은 낮은 빈도로 호출되는 콜드 데이터일 가능성이 높음
- 비용, 재현율, 지연시간 사이의 트레이드오프를 이해하고 최적화해야 함
- 액세스 패턴에 따라 RAM, 디스크, 오브젝트 스토리지로 계층화하여 저장 권장
- 데이터의 온도(Temperature)에 따른 차등적 스토리지 전략이 비용 절감의 핵심
지난달 저는 막 AWS 청구서를 받은 한 스타트업의 CTO와 통화를 했습니다. 그들은 시맨틱 검색(semantic search), 대화형 AI(conversational AI) 등 모든 기능을 갖춘 멋진 RAG 애플리케이션을 구축했습니다. 그들의 벡터 인덱스(vector index)는 약 5,000만 개의 임베딩(embeddings)과 함께 원활하게 작동하고 있었습니다. 그러다 그들은 제품-시장 적합성(product-market fit)을 달성했습니다.
6주 만에 그들은 5억 개의 벡터로 규모를 확장했습니다. 월간 인프라 비용은 2,000달러에서 20,000달러로 치솟았습니다. 진짜 문제는 무엇이었을까요? 액세스 패턴(access patterns)을 살펴보니, 해당 벡터의 80% 이상이 일주일에 한 번 미만으로 쿼리(query)되었습니다. 그들은 어떤 정직한 기준으로 보더라도 콜드 데이터(cold data)인 데이터에 대해 핫 스토리지(hot-storage) 가격을 지불하고 있었던 것입니다.
여기서 표준적인 조언은 "그저 더 저렴한 벡터 데이터베이스(vector database)를 사용하라"는 것입니다. 하지만 더 흥미로운 질문은 이것입니다. 왜 애초에 모든 벡터를 동일한 온도(temperature)로 저장하고 계신가요?
비용-재현율-지연시간 삼각형 (The Cost-Recall-Latency Triangle)
벡터 검색(Vector search)은 세 가지 측면의 트레이드오프(tradeoff)를 강제합니다. 비용(cost), 재현율(recall), 지연시간(latency)을 최적화할 수 있지만, 오직 두 가지만 선택할 수 있습니다. 높은 재현율과 낮은 지연시간을 원하시나요? 그러면 비용이 많이 듭니다(전정밀도 벡터(full-precision vectors)를 포함한 인메모리(in-memory) HNSW 그래프가 RAM을 대량 소비함). 낮은 비용으로 높은 재현율을 원하시나요? 지연시간이 늘어납니다. 저렴하고 빠르기를 원하시나요? 재현율이 희생됩니다.
대부분의 팀은 이 삼각형 위의 단일 지점을 선택하여 인덱스의 모든 벡터에 일률적으로 적용합니다. 벡터 데이터베이스가 단일 스토리지 계층(storage tier)만을 제공했을 때는 그 결정이 타당했습니다. 하지만 이는 일부 파일이 빠른 액세스를 필요로 한다는 이유로 파일 시스템 전체를 NVMe SSD에 저장하는 것과 같은 논리입니다.
통념은 삼각형 위의 지점을 선택하고 그것을 감수하며 살라는 것입니다. 하지만 그 통념은 벡터 스토리지(vector storage)가 흥미로워지기 전에 작성된 것입니다. 더 나은 접근 방식은 이미 스토리지 계층을 나누는 방식과 동일하게 벡터의 계층(tier)을 나누는 것입니다. 서로 다른 액세스 패턴에는 서로 다른 경제성이 필요합니다. RAM에서 월 0.12달러가 드는 동일한 임베딩이 디스크(disk)에서는 월 0.004달러, 오브젝트 스토리지(object storage)에서는 월 0.0002달러가 들 수도 있습니다. 5억 개의 벡터를 보유하고 있다면, 이러한 소수점 단위의 차이가 매우 중요해집니다.
핫 계층(The Hot Tier): 인메모리 HNSW 및 정확한 k-NN (Exact k-NN)
지속적으로 호출되는 벡터(사용자 대상 검색, 실시간 추천, 페이지 로드 과정의 임계 경로에 있는 모든 것)의 경우, Amazon OpenSearch Service는 HNSW 그래프를 네이티브 메모리(native memory)에 완전히 저장합니다. 1,024 차원의 벡터 1억 1,300만 개를 보유한 단일 r6g.8xlarge 노드(source) 환경에서, 이 구성은 초당 300개 쿼리(queries per second)에서 25ms의 P90 지연 시간(latency)과 0.95의 재현율(recall)을 제공했습니다. 처리량(Throughput)은 복제 샤드(replica shards)에 따라 선형적으로 확장됩니다.
또한 HNSW 문헌에서 흔히 간과하는 사례가 있는데, 바로 정확한 k-NN (Exact k-NN)입니다. 정확한 k-NN은 그래프를 탐색하는 대신 브루트 포스(brute force) 방식으로 모든 후보를 비교합니다. 쿼리에 필터(테넌트 ID, 카테고리, 날짜 범위 등)가 포함되어 후보 집합이 약 100,000개의 벡터 미만으로 줄어드는 경우, 정확한 k-NN이 근사 검색(approximate search)보다 성능이 뛰어납니다. 해당 규모에서는 브루트 포스 스캔이 HNSW 탐색보다 더 빠르게 완료되며, RAM 사용량도 적고(빌드하거나 유지 관리할 그래프가 없으며, ef_construction 튜닝이나 ef_search 파라미터가 필요 없음), 완벽한 재현율(perfect recall)을 반환합니다.
핫 계층(Hot tier)은 쿼리 빈도가 높거나, 지연 시간 요구 사항이 50ms 미만이거나, 혹은 두 가지 모두에 해당하는 벡터를 위한 적절한 장소입니다.
양자화 방법(Quantization methods: FP16, 이진 양자화 (binary quantization), 곱 양자화 (product quantization))을 사용하면 각 계층에서 RAM 대 재현율(recall) 사이의 트레이드오프(tradeoff)를 더욱 정밀하게 제어할 수 있습니다. 이 주제는 별도의 글로 다룰 가치가 있습니다.
웜 계층(The Warm Tier): 디스크 기반 모드 (대부분의 벡터가 머물러야 할 곳)
여기는 현재 받는 것보다 더 많은 관심을 받아야 할 부분입니다.
OpenSearch Service의 디스크 기반 모드(on-disk mode)는 양자화된 탐색 그래프(navigation graph)를 RAM에 유지하고, 전체 정밀도(full-precision) 벡터를 SSD에 저장합니다. 기본값은 32배 압축된 이진 양자화(binary quantization)입니다(각 float 차원이 단일 비트로 축소됨). 하지만 더 높은 재현율을 위해 더 많은 RAM을 사용하는 대신 2배, 4배, 8배 또는 16배 압축을 선택할 수 있습니다. 쿼리가 도착하면, OpenSearch는 메모리 내의 압축된 그래프를 따라 이동하며 후보를 식별한 다음, 디스크에 있는 전체 벡터를 대상으로 재점수화(rescore)를 수행합니다.
동일한 113M-벡터 벤치마크(source)에서, 8배 압축을 적용한 디스크 기반 모드(on-disk mode)는 0.98의 재현율(recall)과 함께 96ms의 P90 지연 시간(latency)을 제공했습니다. 32배 압축 시에는 0.94의 재현율과 104ms의 지연 시간을 기록했습니다. 이를 인메모리 계층(in-memory tier)의 25ms와 비교해 보십시오. 여러분은 메모리 사용량을 약 3분의 2 가까이 줄이는 대신 70~80ms의 지연 시간을 감수하게 됩니다. 즉, 256차원의 벡터 100만 개를 사용할 경우, 내비게이션 그래프(navigation graph)를 위한 메모리가 1.31 GB (FP32)에서 0.18 GB로 감소하며, 여기에 파생 소스(derived source) 최적화를 더하면 복제본(replica) 간의 벡터 저장 공간을 중복 제거함으로써 최대 3분의 2를 추가로 절약할 수 있습니다.
대부분의 워크로드(workload)는 여기에 해당합니다. LLM 생성에 2~3초가 소요되는 RAG 파이프라인(RAG pipeline)에서 벡터 검색에 걸리는 100ms는 체감되지 않는 수준입니다. 25ms의 검색 속도가 반드시 필요한 유일한 워크로드는 엄격한 SLA(Service Level Agreement)가 적용되는 사용자 대면 페이지 로딩의 임계 경로(critical path)에 있는 경우뿐입니다.
디스크 기반 모드를 활성화하려면,
콜드 계층(The Cold Tier): 아카이브 규모를 위한 S3 벡터
접근 빈도가 낮은 벡터의 경우, Amazon S3 Vectors는 S3의 경제적인 비용으로 네이티브 벡터 저장 및 검색을 제공합니다. O-시리즈 인스턴스를 사용하는 OpenSearch 2.19+ 버전의 인덱스 매핑(index mapping)에서 "engine": "s3_vector"를 지정하기만 하면, 나머지는 OpenSearch가 관리합니다. OpenSearch는 벡터 데이터를 S3로 오프로드(offload)하고, 필터링을 위한 메타데이터는 클러스터에 유지하며, 각 쿼리의 k-NN 부분을 투명하게 S3로 라우팅합니다. 애플리케이션 관점에서는 인메모리 및 디스크 기반 인덱스에서 사용하는 것과 동일한 _search API로 쿼리를 수행하면 됩니다.
응답 시간은 1초 미만(일반적으로 500-800ms)이며, 저장 비용은 인메모리 인덱스(in-memory indexes) 대비 최대 70%까지 절감됩니다. S3 Vectors는 쿼리당 과금(pay-per-query) 방식을 사용하므로, 유휴 용량(idle capacity)에 대해 비용을 지불할 필요가 없습니다. 이 모델은 쿼리 트래픽이 낮거나 중간 정도인(하루 최대 수천 건의 쿼리) 대규모 데이터 세트에 이상적입니다. 지속적으로 높은 처리량(throughput)이 필요한 워크로드(초당 수백 건의 쿼리)의 경우, 비용이 용량 기반(capacity-based)인 온디스크(on-disk) 모드가 더 나은 보완책이 됩니다. 이 두 계층은 함께 작동합니다. S3 Vectors는 액세스 빈도가 낮은 대규모 데이터를 처리하고, 온디스크 모드는 꾸준한 쿼리 트래픽이 발생하는 데이터를 처리합니다.
S3 Vectors는 OpenSearch 통합 외부에서도 독립적인 벡터 저장소(vector store)로 작동합니다. S3 Vectors를 독립적으로 사용하는 팀의 경우, 원클릭 내보내기(one-click export)를 통해 데이터를 OpenSearch Serverless로 이동할 수 있으며, 여기서 쿼리는 200ms 미만의 지연 시간(latency)으로 실행됩니다. 이를 통해 팀은 액세스 패턴이 변화하여 콜드 데이터(cold data)를 핫 데이터(hot data)로 전환해야 할 때 OpenSearch Serverless의 처리량을 활용할 수 있습니다.
의사결정 프레임워크 (The Decision Framework)
각 벡터가 어디에 위치할지는 다음 두 가지 질문에 따라 결정되어야 합니다: 벡터가 얼마나 자주 쿼리되는가, 그리고 호출자에게 필요한 지연 시간(latency)은 얼마인가?
지속적으로 호출되며 30ms 미만의 응답이 필요한 벡터는 OpenSearch Service에서 인메모리 HNSW를 사용하여 메모리에 두어야 합니다 (FP16 권장). 쿼리 필터가 후보군을 100,000개 미만으로 좁히는 경우(멀티 테넌트 애플리케이션에서 흔히 발생함)에는 정확한 k-NN(Exact k-NN)을 테스트해 볼 가치가 있습니다. 100ms의 지연 시간이 허용되는 꾸준한 쿼리 트래픽을 가진 벡터는 32배 압축(32× compression)을 적용하여 디스크에 두어야 합니다. 내부 검색 도구, 배치 RAG 파이프라인, 분석, 제품 카탈로그 등을 생각해보십시오. 온디스크 모드는 대규모 환경의 대부분의 프로덕션 워크로드에 대한 기본 계층입니다. 볼륨이 크고 액세스 빈도가 낮거나 중간 정도이며 500-800ms의 지연 시간을 허용할 수 있는 벡터는 "engine": "s3_vector" 통합을 사용하는 S3 Vectors에 속합니다: 역사적 아카이브, 컴플라이언스 보관(compliance retention), 시즌 외의 계절성 카탈로그 등이 이에 해당합니다. 액세스 패턴이 변경되면 원클릭 내보내기를 통해 콜드 데이터를 OpenSearch Serverless로 승격(promote)시킬 수 있습니다.
이러한 계층화 (tiering)의 묘미는 호출하는 애플리케이션이 이를 알 필요가 없다는 점입니다. 모든 계층은 동일한 OpenSearch _search API를 노출합니다. 애플리케이션은 쿼리를 전송합니다. OpenSearch는 대상 인덱스에 따라 쿼리를 적절한 스토리지 계층으로 라우팅합니다. 계층 간의 마이그레이션 (migration)은 운영상의 결정일 뿐, 애플리케이션 코드의 변경 사항이 아닙니다.
20,000달러 청구서에 일어난 일
도입부 이야기의 CTO는 액세스 패턴 (access-pattern) 감사를 실시했습니다. 그들의 5억 개 벡터 중 약 15%는 핫 (hot) 데이터였습니다. 이들은 실시간 검색을 구동하며 인메모리 (in-memory) 속도가 필요했습니다. 약 60%는 웜 (warm) 데이터였습니다. 이는 RAG 파이프라인을 위한 지식 베이스 (knowledge-base) 벡터로, 3초간 지속되는 LLM 호출 과정 내에서는 100ms의 지연이 무시할 수 있는 수준이었습니다. 나머지 25%는 몇 달 전에 종료된 마케팅 캠페인에서 생성된 계절성 제품 임베딩 (embeddings)이었습니다.
그들은 웜 벡터를 온디스크 (on-disk) 모드로 이동시키고, 계절성 벡터를 S3 Vectors로 이동시켰습니다. 애플리케이션 계층의 쿼리를 단 하나도 변경하지 않고도 월간 청구 금액이 약 3분의 2 가량 감소했습니다. 애플리케이션은 여전히 동일한 _search API를 호출했습니다. 계층화는 호출하는 코드에 보이지 않았습니다.
만약 여러분이 오늘 벡터 워크로드 (vector workload)를 실행하고 있다면, 이번 주에 할 가치가 있는 한 가지는 바로 액세스 패턴 메트릭 (access-pattern metrics)을 확인하는 것입니다. 어떤 임베딩이 높은 빈도로 쿼리되는지, 반대로 어떤 것들이 유휴 상태 (idle)로 있는지 살펴보십시오. 그 답은 각 벡터가 어떤 계층에 속해야 하는지를 정확히 알려줄 것입니다. 일단 확인하고 나면 계산 결과는 명확한 경향이 있으며, 모든 벡터가 핫 데이터여야 한다고 가정했던 사람들에게 절감액은 놀라움으로 다가갈 것입니다.
여러분의 벡터 액세스 패턴 분포는 어떤 모습인가요? 다른 분들도 거의 사용되지 않는 임베딩과 활발하게 쿼리되는 임베딩 사이에 동일한 80/20 분할 법칙이 나타나는지 궁금합니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기