
임베딩 비용을 줄이는 법이 궁금해서 검색하셨다면, 답은 순서에 있습니다. 무작정 더 저렴한 모델로 바꾸기 전에 중복 제거, 캐시, 차원 축소 순서로 손보면 같은 파이프라인에서도 서로 다른 지점의 비용이 줄어듭니다. 중복 제거와 캐시는 API 호출 자체(토큰 사용량)를 줄이고, 차원 축소는 벡터를 저장·검색하는 비용을 줄여서 손대는 대상이 다르기 때문입니다.
임베딩 비용은 왜 생각보다 빨리 늘어나나요?
임베딩 API 비용은 입력 토큰 수와 호출 횟수의 곱으로 결정됩니다. RAG 파이프라인을 운영하다 보면 같은 FAQ 문장, 같은 상품 설명, 같은 사용자 질문이 하루에도 몇 번씩 다시 임베딩되는 경우가 흔합니다. 문서 업데이트 없이 배치 작업을 재실행하거나, 챗봇에서 인기 질문이 반복 유입되는 상황이 대표적입니다.
여기에 벡터DB 쪽 비용도 별도로 쌓입니다. 벡터 차원 수가 클수록 저장 용량과 유사도 검색 연산량이 늘어나는데, 이 비용은 임베딩 API 호출과는 무관하게 인덱스 규모에 비례해서 커집니다. 그래서 “임베딩 비용”이라고 뭉뚱그려 말하지만 실제로는 호출 비용과 저장·검색 비용을 따로 봐야 정확한 처방이 나옵니다.
1단계, 중복 제거로 호출 횟수부터 줄이기
가장 먼저 손댈 곳은 완전히 동일한 텍스트를 두 번 임베딩하지 않는 것입니다. 텍스트를 정규화(공백 정리, 앞뒤 trim, 소문자 변환)한 뒤 SHA-256 같은 해시로 지문을 만들고, 이미 처리한 지문이면 API를 아예 호출하지 않는 방식입니다. 구현 난이도가 낮은데도 효과는 즉시 나타나서, 순서상 제일 먼저 두는 게 맞습니다.
다만 이 방식은 문자 그대로 같은 문장만 걸러냅니다. “환불 정책이 궁금해요”와 “환불 정책 알려주세요”처럼 뜻은 같지만 표현이 다른 문장은 다른 해시값을 갖기 때문에 중복으로 잡히지 않습니다. 의미 기반 중복까지 잡으려면 이미 임베딩된 벡터끼리 유사도를 비교해야 하는데, 이 비교 자체가 또 다른 계산 비용이므로 우선순위는 뒤로 미루는 편이 합리적입니다.

import hashlib
def normalize(text: str) -> str:
return " ".join(text.strip().lower().split())
def content_hash(text: str) -> str:
return hashlib.sha256(normalize(text).encode("utf-8")).hexdigest()
seen = set()
to_embed = []
for doc in raw_documents:
h = content_hash(doc)
if h not in seen:
seen.add(h)
to_embed.append(doc)
print(f"원본 {len(raw_documents)}건 -> 중복 제거 후 {len(to_embed)}건")
2단계, 캐시 계층이 재호출을 막아줍니다
배치 안에서의 중복 제거를 마쳤다면, 다음은 배치와 배치 사이, 즉 시간이 지나도 같은 요청이 다시 들어오는 상황을 막는 캐시 차례입니다. 개발 초기에는 파이썬 딕셔너리 같은 인메모리 캐시로도 충분하지만, 여러 서버가 요청을 나눠 받는 환경이라면 Redis 같은 외부 캐시로 옮겨야 인스턴스 간에 캐시가 공유됩니다.
여기서 실수하기 쉬운 지점이 하나 있습니다. 캐시 키를 텍스트 해시값만으로 잡으면, 나중에 임베딩 모델이나 dimensions 값을 바꿨을 때 예전 모델로 만든 벡터가 그대로 재사용되는 버그가 생깁니다. 캐시 키는 반드시 텍스트 해시 + 모델명 + 차원 값 조합으로 만들어야 모델을 바꾼 뒤에도 안전합니다.
import hashlib
from openai import OpenAI
client = OpenAI()
cache = {} # 프로덕션에서는 Redis 등으로 교체
def cache_key(text: str, model: str, dimensions: int) -> str:
raw = f"{model}:{dimensions}:{normalize(text)}"
return hashlib.sha256(raw.encode("utf-8")).hexdigest()
def get_embedding(text: str, model="text-embedding-3-small", dimensions=256):
key = cache_key(text, model, dimensions)
if key in cache:
return cache[key] # 캐시 적중 시 API 호출 없이 반환
response = client.embeddings.create(input=text, model=model, dimensions=dimensions)
vector = response.data[0].embedding
cache[key] = vector
return vector
v1 = get_embedding("환불 정책이 궁금합니다")
v2 = get_embedding("환불 정책이 궁금합니다") # 동일 질의 재요청 -> API 호출 없음
print(len(v1), v1 is v2)
# 256 True
3단계, dimensions 파라미터로 벡터 차원 줄이기
앞의 두 단계는 호출 횟수를 줄이는 방법이었다면, 마지막은 이미 만든 벡터 자체의 크기를 줄이는 단계입니다. OpenAI의 text-embedding-3-small, text-embedding-3-large 모델은 API 호출 시 dimensions 파라미터를 넘겨서 기본 차원(각각 1536, 3072)보다 짧은 벡터를 바로 받을 수 있습니다. 이 기능은 OpenAI 임베딩 가이드에 공식적으로 설명되어 있으며, 벡터 뒷부분을 잘라내는 방식이라 재학습 없이 바로 적용됩니다.
여기서 짚어야 할 부분은, dimensions를 줄인다고 해서 임베딩 API 호출 비용(토큰당 과금)이 줄어들지는 않는다는 점입니다. 줄어드는 건 벡터DB에 저장하는 용량과 유사도 검색에 드는 연산량입니다. 차원 축소는 원래 정보 손실과 계산량 사이의 절충인데, 이 모델들은 학습 단계에서부터 이런 절삭을 고려해 설계되었기 때문에 무작정 PCA를 돌리는 것보다 품질 손실이 작다는 차이가 있습니다.

세 단계를 순서대로 적용했을 때 무엇이 줄어드는지 표로 정리해 보세요
세 방법은 줄이는 대상이 서로 겹치지 않기 때문에, 하나만 적용하고 끝내면 나머지 비용은 그대로 남습니다. 어떤 상황에 어떤 단계가 더 유효한지 표로 정리했습니다.
| 단계 | 줄이는 대상 | 구현 난이도 | 특히 효과가 큰 경우 |
|---|---|---|---|
| 중복 제거 | API 호출 횟수(토큰 사용량) | 낮음 | 배치 재실행, 같은 문서가 반복 유입될 때 |
| 캐시 | API 호출 횟수(반복 요청) | 낮음~중간 | 인기 질문·챗봇처럼 같은 질의가 시간차를 두고 재요청될 때 |
| 차원 축소 | 벡터 저장 용량·검색 연산량 | 중간(재색인 필요) | 벡터DB 규모가 크거나 검색 지연이 문제가 될 때 |
순서를 이렇게 잡은 이유는 단순합니다. 중복 제거와 캐시는 코드 몇 줄로 바로 적용되고 되돌리기도 쉬운 반면, 차원 축소는 기존 인덱스를 다시 만들어야 하는 재색인 작업이 필요합니다. 손이 덜 가는 것부터 처리하고 나서 구조 변경이 필요한 작업으로 넘어가는 편이 리스크가 적습니다.
캐시가 오히려 부담이 되는 경우, 그리고 지금 무엇부터 적용할지
캐시가 항상 이득인 것은 아닙니다. 같은 질의가 거의 겹치지 않는 워크로드, 예를 들어 사용자마다 완전히 다른 긴 문서를 한 번씩만 임베딩하는 배치라면 캐시 적중률이 낮아서 Redis 운영 비용과 관리 부담만 늘어날 수 있습니다. 이런 경우엔 캐시 단계를 건너뛰고 중복 제거와 차원 축소만 적용하는 게 나을 수 있습니다.
차원 축소도 무조건 낮출수록 좋은 건 아닙니다. dimensions 값을 지나치게 작게 잡으면 검색 재현율(recall)이 떨어져서, 비용은 줄었는데 검색 품질이 눈에 띄게 나빠지는 경우가 생깁니다. 특정 값이 항상 안전하다고 단정하기는 어려우니, 실제 검색 결과를 기존 벡터와 비교해 품질 저하가 감당할 수준인지 먼저 확인하는 과정이 필요합니다.
지금 파이프라인에 아무것도 적용하지 않은 상태라면, 순서는 이렇습니다. 먼저 해시 기반 중복 제거를 배치 로직에 넣고 로그로 몇 건이 걸러지는지 확인합니다. 그다음 반복 요청이 많은 구간에만 캐시를 붙이고 적중률을 지켜보다가, 벡터DB 저장·검색 비용이 눈에 띄게 부담될 때 dimensions 값을 낮춰 재색인하는 순서가 리스크 대비 효과가 가장 큽니다.
