- Published on
Pinecone·Milvus 차원 불일치 오류 5분 해결
- Authors
- Name
- 스타차일드
- https://x.com/ETFBITX
서론
벡터 DB를 붙인 RAG나 시맨틱 검색을 만들다 보면, 가장 자주 만나고 가장 빨리 고칠 수 있는 오류가 dimension mismatch 입니다. Pinecone에서는 인덱스 생성 시 고정된 dimension 과 업서트 벡터 길이가 다를 때 터지고, Milvus에서는 컬렉션 스키마의 dim 과 넣는 벡터의 길이가 다를 때 실패합니다.
문제는 원인이 단순한데도, 파이프라인이 길어지면 어디서 차원이 바뀌었는지 찾느라 시간을 허비한다는 점입니다. 이 글은 “5분 안에” 끝내기 위해, 원인 패턴을 유형화하고 바로 적용 가능한 진단 코드와 수정 전략을 제공합니다.
차원 불일치 오류가 나는 정확한 조건
Pinecone
- 인덱스 생성 시
dimension을 고정 upsert혹은query에 전달하는 벡터의 길이가 인덱스dimension과 다르면 실패
대표 형태
Vector dimension 1536 does not match the dimension of the index 768
Milvus
- 컬렉션 스키마에서 벡터 필드에
dim을 고정 insert혹은search에 전달하는 벡터 길이가 스키마dim과 다르면 실패
대표 형태
expected dim=768, got 1536
핵심은 하나입니다.
- 저장소 스키마의 차원과, 임베딩 모델이 뱉는 차원이 반드시 같아야 합니다.
5분 해결 체크리스트 (가장 빠른 순서)
1) 지금 사용 중인 임베딩 모델의 차원을 “코드로” 확인
문서나 기억에 의존하지 말고, 실제로 임베딩을 한 번 만들어 길이를 찍어보는 게 제일 빠릅니다.
# 예시: OpenAI 계열/호환 API를 쓰든, 로컬 모델을 쓰든
# 최종 산출물은 list[float] 형태의 벡터라고 가정
def get_dim(vec):
return len(vec)
sample_vector = [0.1, 0.2, 0.3] # 실제론 임베딩 결과
print(get_dim(sample_vector))
실전에서는 아래처럼 “업서트 직전”에 assert를 박아두면, 원인 위치가 즉시 고정됩니다.
def assert_dim(vec, expected_dim, where=""):
d = len(vec)
if d != expected_dim:
raise ValueError(f"dimension mismatch at {where}: got={d}, expected={expected_dim}")
2) Pinecone 인덱스의 dimension 확인
Pinecone은 인덱스 메타데이터로 확인합니다.
from pinecone import Pinecone
pc = Pinecone(api_key="YOUR_KEY")
index_name = "my-index"
desc = pc.describe_index(index_name)
# desc.dimension 형태는 SDK 버전에 따라 다를 수 있음
print(desc)
실무 팁
- 운영 중인 인덱스를 “모델 변경” 없이 계속 쓰려면, 임베딩 모델도 같은 차원을 유지해야 합니다.
- 모델을 바꿨다면 인덱스를 새로 파는 게 정석입니다.
3) Milvus 컬렉션 스키마의 dim 확인
Milvus는 컬렉션 스키마에서 벡터 필드의 dim 을 확인합니다.
from pymilvus import connections, Collection
connections.connect(alias="default", host="localhost", port="19530")
col = Collection("my_collection")
schema = col.schema
print(schema)
스키마 출력에서 벡터 필드의 dim 이 무엇인지 확인하세요.
4) 업서트 직전에 “벡터 길이 검사”를 고정 장치로 넣기
차원 불일치는 대부분 업서트/인서트 단계에서 터지지만, 근본 원인은 “임베딩 생성 단계”에 있습니다. 따라서 임베딩 생성 함수의 반환 직후에 검사를 박는 게 가장 효율적입니다.
EXPECTED_DIM = 1536
def embed(text: str) -> list[float]:
# 실제 임베딩 호출 로직
vec = [0.0] * EXPECTED_DIM
return vec
def embed_checked(text: str) -> list[float]:
vec = embed(text)
assert_dim(vec, EXPECTED_DIM, where="embed_checked")
return vec
원인 패턴 7가지와 즉시 수정법
패턴 1) 임베딩 모델을 바꿨는데 인덱스/컬렉션을 그대로 쓴다
가장 흔합니다.
- 예:
text-embedding-3-small로 바꿨는데 기존 인덱스가 다른 차원
수정
- Pinecone: 새 인덱스를 만들고 재색인
- Milvus: 새 컬렉션을 만들고 재적재
“기존 데이터 유지”가 필요하면, 이관 작업을 배치로 돌리되 신규/구형을 동시에 운영하는 기간을 두는 게 안전합니다.
패턴 2) 동일 모델인데 설정으로 차원이 달라진다
모델/라이브러리에 따라 truncate 나 pooling 방식에 따라 차원이 달라지는 경우가 있습니다.
- 예: 어떤 모델은
mean pooling결과가 히든 사이즈와 같지만, 다른 파이프라인은 concat을 하여 차원이 늘어남
수정
- 임베딩 생성 코드를 단일 함수로 통일
- “임베딩 생성 함수의 출력 dim”을 단위 테스트로 고정
def test_embedding_dim():
vec = embed_checked("hello")
assert len(vec) == 1536
패턴 3) 배치 처리에서 한 샘플만 깨진다
문장 전처리/토크나이저 오류로 빈 입력이 들어가거나, 예외 처리로 빈 벡터를 리턴하는 경우가 있습니다.
수정
- 빈 입력은 업서트 대상에서 제외
- 예외 시 “빈 벡터 반환” 금지, 실패로 처리
def safe_embed(text: str) -> list[float]:
text = (text or "").strip()
if not text:
raise ValueError("empty text")
return embed_checked(text)
패턴 4) float 배열이 아니라 중첩 리스트를 넣는다
차원 불일치처럼 보이지만, 실제로는 데이터 형태가 잘못되어 길이 계산이 어긋나는 케이스가 있습니다.
- 예:
[[...]]형태를 그대로 넣음
수정
- 업서트 전에 타입/중첩 여부를 검사
def assert_vector_1d(vec):
if len(vec) > 0 and isinstance(vec[0], list):
raise TypeError("vector must be 1D list[float], got nested list")
패턴 5) Pinecone에서 인덱스 dimension을 잘못 생성했다
초기 생성 시 실수로 dimension=768 로 만들고, 모델은 1536을 쓰는 상황.
수정
- 인덱스 삭제 후 재생성 혹은 새 인덱스 생성
- 운영이라면 새 인덱스로 마이그레이션
주의
- Pinecone 인덱스 dimension은 생성 후 변경이 사실상 불가한 설계로 보는 게 안전합니다.
패턴 6) Milvus에서 컬렉션 스키마를 잘못 만들었다
Milvus도 벡터 필드 dim 은 스키마의 핵심 제약입니다.
수정
- 새 컬렉션 생성 후 재적재
- 컬렉션명을 버전으로 관리
예: docs_v1_768, docs_v2_1536 처럼 차원까지 이름에 박아두면 사고가 줄어듭니다.
패턴 7) 멀티테넌트/멀티인덱스 환경에서 라우팅이 꼬인다
서비스가 커지면 “요청마다 다른 인덱스”를 쓰게 됩니다. 이때 인덱스 라우팅 테이블이 꼬이면 차원 불일치가 발생합니다.
수정
- 라우팅 키에
embedding_model_version혹은embedding_dim을 포함 - 업서트/쿼리 모두 동일 라우팅 규칙 사용
Pinecone: 재현 가능한 최소 예제와 해결 코드
아래 예제는 “인덱스는 768인데 1536 벡터를 업서트”하는 상황을 의도적으로 만듭니다.
from pinecone import Pinecone
pc = Pinecone(api_key="YOUR_KEY")
index = pc.Index("my-index")
INDEX_DIM = 768
vec_1536 = [0.0] * 1536
# 업서트 전에 차원 확인
assert_dim(vec_1536, INDEX_DIM, where="pinecone_upsert")
index.upsert(vectors=[("id1", vec_1536, {"source": "demo"})])
해결 방법은 둘 중 하나입니다.
- 임베딩 모델을 768 차원으로 맞춘다
- 인덱스를 1536 차원으로 새로 만들고 재색인한다
실무에서는 2번이 더 흔합니다. 모델 변경은 품질과 직결되기 때문입니다.
Milvus: 스키마 생성부터 dim 고정까지 안전하게
Milvus는 컬렉션 생성 시 스키마를 명시하고, 그 스키마가 곧 계약이 됩니다.
from pymilvus import (
connections, FieldSchema, CollectionSchema, DataType, Collection
)
connections.connect(alias="default", host="localhost", port="19530")
DIM = 1536
fields = [
FieldSchema(name="id", dtype=DataType.VARCHAR, is_primary=True, max_length=128),
FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=DIM),
]
schema = CollectionSchema(fields, description="docs embeddings")
col = Collection(name="docs_v2_1536", schema=schema)
# 이후 insert 시에도 DIM을 검사
vec = [0.0] * DIM
assert_dim(vec, DIM, where="milvus_insert")
col.insert([["id1"], [vec]])
운영 팁
- 컬렉션명을 버전/차원과 함께 관리하면, 배포 시 실수로 다른 컬렉션에 넣는 문제를 크게 줄일 수 있습니다.
차원 불일치를 예방하는 운영 설계 4가지
1) “임베딩 계약”을 코드로 고정
EMBEDDING_MODEL_NAMEEMBEDDING_DIM- 전처리 규칙
이 3개를 환경변수와 코드 상수로 동시에 관리하고, 런타임에 로깅하세요.
import os
EMBEDDING_MODEL = os.getenv("EMBEDDING_MODEL", "unknown")
EMBEDDING_DIM = int(os.getenv("EMBEDDING_DIM", "1536"))
def log_embedding_contract(logger):
logger.info(f"embedding_contract model={EMBEDDING_MODEL} dim={EMBEDDING_DIM}")
2) 업서트/인서트 전 차원 검사 미들웨어화
파이프라인이 여러 곳에 흩어지면 결국 누락됩니다. 공통 함수로 강제하세요.
3) 인덱스/컬렉션을 “차원 단위로” 버저닝
- Pinecone 인덱스:
myindex-1536-v2 - Milvus 컬렉션:
docs_v2_1536
이렇게만 해도 라우팅 실수가 확 줄어듭니다.
4) 배포 전에 스모크 테스트로 쿼리까지 확인
업서트만 통과해도, 쿼리 벡터 차원이 다르면 검색이 실패합니다. 따라서 배포 파이프라인에서 아래를 자동화하세요.
- 샘플 텍스트 임베딩 생성
- 업서트 1건
- 동일 벡터로 쿼리 1회
비슷한 “배포 전 체크리스트” 접근은 인프라 쪽에서도 효과가 큽니다. 예를 들어 EKS에서 설정 드리프트로 비용이 폭증하는 문제를 잡는 방식과 결이 같습니다. 자세한 접근은 EKS Karpenter 노드 드리프트로 비용 폭증 잡기도 참고할 만합니다.
자주 묻는 질문
Q1. 차원만 맞추면 품질은 그대로인가요
아닙니다. 차원은 “형식 계약”이고, 검색 품질은 임베딩 모델/전처리/도메인 적합성에 좌우됩니다. 다만 차원이 안 맞으면 시스템이 아예 동작하지 않으니, 먼저 형식을 맞추고 그 다음 품질을 튜닝해야 합니다.
Q2. 임베딩을 강제로 자르거나 패딩해서 맞춰도 되나요
권장하지 않습니다.
- 단순 truncate는 정보 손실
- zero padding은 거리 계산을 왜곡
정말 불가피한 경우가 아니라면, 저장소 스키마와 임베딩 모델을 같은 차원으로 “정상 정렬”하는 게 정답입니다.
Q3. 장애가 간헐적으로만 나요
대부분 라우팅/버전 혼재 문제입니다.
- 일부 요청은 768 인덱스로
- 일부 요청은 1536 인덱스로
- 혹은 워커마다 다른 모델 버전
이때는 embedding_contract 를 요청 로그에 남기고, 어떤 요청이 어떤 계약으로 처리됐는지부터 추적하세요. 네트워크/재시도 관점에서 간헐 오류를 잡는 방법론은 Python httpx ReadTimeout·ConnectError 재시도 설계도 함께 보면 도움이 됩니다.
결론
Pinecone·Milvus의 차원 불일치 오류는 원인이 단순합니다. “저장소 스키마 차원”과 “임베딩 출력 차원”이 다르기 때문입니다. 5분 안에 끝내려면 다음만 지키면 됩니다.
- 임베딩 결과의
len을 코드로 확인 - 인덱스/컬렉션의 dimension 또는
dim을 확인 - 업서트 직전에 assert로 방어
- 모델을 바꿨다면 인덱스/컬렉션도 버전업하고 재색인
이 네 가지를 기본 규칙으로 잡아두면, 차원 불일치로 인한 불필요한 디버깅 시간을 거의 0에 가깝게 만들 수 있습니다.