Published on

Pinecone·Milvus 임베딩 차원 불일치 해결 가이드

Authors

서로 다른 임베딩 모델을 섞어 쓰거나, 인덱스 스키마를 바꾼 뒤 재색인을 누락하면 Pinecone·Milvus에서 dimension mismatch 류의 오류가 바로 터집니다. 문제는 이게 단순한 개발 실수로 끝나지 않고, RAG 검색 품질 저하(침묵 오류)나 운영 장애(삽입 실패)로 이어진다는 점입니다.

이 글에서는 Pinecone와 Milvus에서 차원 불일치가 발생하는 전형적인 패턴을 분류하고, “지금 당장 장애를 멈추는 응급처치”와 “재발 방지 설계”까지 한 번에 정리합니다.

차원 불일치가 왜 치명적인가

임베딩 벡터는 고정 길이 배열입니다. 인덱스는 그 길이를 전제로 내부 자료구조(HNSW, IVF, PQ 등)를 구성합니다. 따라서 아래 중 하나라도 어긋나면 삽입 혹은 검색이 실패합니다.

  • 인덱스가 기대하는 차원 수와 실제 벡터 길이가 다름
  • 컬렉션(또는 인덱스) 스키마는 맞는데, 일부 데이터가 다른 모델로 생성됨
  • 쿼리 벡터만 다른 모델로 생성됨(삽입은 성공했는데 검색이 실패)

특히 “침묵 오류”가 더 위험합니다. 예를 들어 Milvus에서 필드 스키마는 1536인데, 애플리케이션에서 1536으로 패딩해서 억지로 맞추면 에러는 안 나지만 검색 품질은 급락합니다.

Pinecone에서 흔한 증상과 원인

대표 증상

  • upsert 시 차원 불일치 에러
  • query 시 차원 불일치 에러

Pinecone은 인덱스 생성 시 dimension 이 고정입니다. 이후에는 바꿀 수 없고, 다른 차원의 벡터는 들어갈 수 없습니다.

가장 흔한 원인 5가지

  1. 모델 교체 후 인덱스 재생성 없이 그대로 upsert
  2. 환경별로 모델이 다름(로컬은 768, 운영은 1536)
  3. 배치 파이프라인과 실시간 API가 서로 다른 모델을 사용
  4. 임베딩 생성 라이브러리 버전 업으로 출력 차원이 바뀜
  5. 멀티테넌트에서 테넌트별 인덱스를 공유하면서 모델이 섞임

Milvus에서 흔한 증상과 원인

Milvus는 컬렉션 스키마에서 벡터 필드의 dim 이 고정입니다. 또한 컬렉션과 별도로 인덱스를 생성할 수 있는데, 스키마 dim 자체가 맞아야 insert 가 됩니다.

대표 증상

  • insert 시 벡터 길이가 다르다는 에러
  • search 시 쿼리 벡터 차원 불일치

가장 흔한 원인 5가지

  1. 컬렉션 생성 시 dim 을 잘못 지정
  2. 컬렉션은 맞는데, 검색 쿼리 벡터만 다른 모델로 생성
  3. float32 변환 과정에서 shape 이 깨짐(예: 2차원 배열이 들어감)
  4. 데이터 파이프라인에서 None 혹은 빈 배열이 섞여 길이가 달라짐
  5. 컬렉션 재생성 없이 기존 컬렉션에 다른 모델 데이터를 섞어 넣음

먼저 확인해야 할 체크리스트(장애 대응 순서)

운영에서 차원 불일치가 터졌다면, 아래 순서로 확인하면 원인 범위를 빠르게 줄일 수 있습니다.

  1. “인덱스(컬렉션) 스키마 차원”을 확인
  2. “현재 배포된 모델의 출력 차원”을 확인
  3. “문제 레코드만 다른 모델인지”를 샘플링
  4. “삽입 벡터 vs 검색 벡터가 같은 모델인지”를 확인

이 과정은 보통 로그만 잘 남아 있어도 10분 내로 결론이 납니다. 로그/메트릭 기반 원인 추적은 인프라 장애에서도 똑같이 중요합니다. 운영 장애를 구조적으로 추적하는 관점은 systemd 서비스가 계속 재시작될 때 원인 추적법 글의 접근과도 유사합니다.

Pinecone 해결법: 차원은 바꿀 수 없으니 “새 인덱스”가 정답

Pinecone은 인덱스의 dimension 변경이 불가하므로, 모델 차원이 바뀌면 선택지는 사실상 하나입니다.

  • 새 인덱스를 만들고
  • 전량 재임베딩 후 재색인하고
  • 트래픽을 안전하게 스위칭

Pinecone 인덱스 차원 확인

Pinecone 콘솔에서도 확인 가능하지만, 코드로도 확인해두면 배포 실수를 줄일 수 있습니다.

// TypeScript (pseudocode)
// 실제 SDK 메서드명은 버전에 따라 다를 수 있으니 개념 위주로 보세요.

const indexName = process.env.PINECONE_INDEX!;
const indexDescription = await pinecone.describeIndex(indexName);

console.log({
  name: indexDescription.name,
  dimension: indexDescription.dimension,
  metric: indexDescription.metric,
});

업서트 전에 차원 강제 검증(가장 효과적인 재발 방지)

function assertDim(vec: number[], expected: number) {
  if (vec.length !== expected) {
    throw new Error(`Embedding dimension mismatch: got ${vec.length}, expected ${expected}`);
  }
}

const expectedDim = 1536; // 인덱스 생성 시 고정된 값
const embedding = await embed(text);
assertDim(embedding, expectedDim);

await index.upsert([{ id, values: embedding, metadata }]);

이 검증은 “에러를 빨리, 애플리케이션 경계에서” 터뜨립니다. 그래야 Pinecone 호출 실패가 재시도 큐를 오염시키거나, 배치 작업이 수 시간 후반부에 가서야 터지는 일을 막습니다.

무중단 마이그레이션(블루/그린 인덱스)

  • index-v1 : 기존 768
  • index-v2 : 신규 1536

전환 절차 권장안

  1. 신규 인덱스 생성
  2. 백필 작업으로 모든 문서를 신규 모델로 재임베딩해 index-v2 에 upsert
  3. 애플리케이션에서 “쓰기”를 이중화(일정 기간 v1v2 모두 upsert)
  4. “읽기”를 점진적으로 v2 로 전환(트래픽 10퍼센트, 50퍼센트, 100퍼센트)
  5. 품질/지표 확인 후 v1 폐기

이때 Next.js나 Node 런타임에서 배포/번들 변경으로 API 동작이 바뀌는 경우도 있으니, 런타임 호환성 이슈가 의심되면 Bun 1.1에서 Node API 호환이 깨질 때 디버깅 같은 방식으로 “환경 차이”를 먼저 제거하는 게 좋습니다.

Milvus 해결법: 컬렉션 dim 이 틀리면 컬렉션을 다시 만들어야 한다

Milvus에서도 벡터 필드의 dim 은 사실상 불변으로 취급하는 편이 안전합니다. 차원이 바뀌면 새 컬렉션을 만드는 전략이 가장 깔끔합니다.

Milvus 컬렉션 생성 예시(벡터 dim 명시)

아래 예시는 개념 예시입니다. 핵심은 벡터 필드에 dim 을 명확히 두고, 애플리케이션에서도 동일 값으로 검증하는 것입니다.

# Python (pymilvus 개념 예시)
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, auto_id=False, max_length=64),
    FieldSchema(name="embedding", dtype=DataType.FLOAT_VECTOR, dim=dim),
    FieldSchema(name="text", dtype=DataType.VARCHAR, max_length=2048),
]

schema = CollectionSchema(fields, description="docs")
col = Collection(name="docs_v2", schema=schema)

insert 전 차원 검증

def assert_dim(vec, expected):
    if len(vec) != expected:
        raise ValueError(f"dim mismatch: got {len(vec)}, expected {expected}")

expected_dim = 1536
vec = embed(text)
assert_dim(vec, expected_dim)

col.insert([[doc_id], [vec], [text]])

Milvus에서 “컬렉션은 맞는데 검색만 실패”하는 케이스

많이 놓치는 포인트가 쿼리 벡터입니다.

  • 데이터 삽입은 배치 파이프라인이 수행(모델 A)
  • 검색 쿼리는 API 서버가 수행(모델 B)

이 경우 데이터는 정상인데, 검색 요청에서만 차원 불일치가 납니다. 해결은 단순합니다.

  • 임베딩 모델을 서비스 경계에서 단일화
  • 모델 식별자(예: embedding_model=v3)를 설정으로 고정
  • 쿼리 생성 경로에도 동일 검증 로직 적용

“패딩/자르기”로 차원을 맞추면 안 되는 이유

운영에서 급하면 이런 유혹이 생깁니다.

  • 768 벡터를 1536으로 0 패딩
  • 1536 벡터를 앞 768만 잘라서 사용

하지만 이건 벡터 공간 자체를 훼손합니다. 코사인 유사도나 내적 기반 검색에서 의미가 크게 변하고, 검색 품질이 체감될 정도로 무너집니다. 차원 불일치는 “스키마 문제”가 아니라 “표현 공간이 달라진 문제”라서, 정석은 재임베딩입니다.

재발 방지 설계: 모델 버전과 차원을 데이터 계약으로 만들기

1) 모델 메타데이터를 문서 단위로 저장

문서 메타데이터에 아래를 함께 저장하세요.

  • embedding_model (예: text-embedding-3-large)
  • embedding_dim (예: 3072)
  • embedding_version (내부 버전)

Pinecone은 metadata를 같이 저장할 수 있고, Milvus도 별도 필드로 저장 가능합니다. 이렇게 하면 “섞인 데이터”를 사후에 필터링하거나, 마이그레이션 범위를 정확히 산정할 수 있습니다.

2) 인덱스/컬렉션 이름에 모델 정보를 포함

예:

  • docs_e5base_dim768_v1
  • docs_openai_dim1536_v2

이 규칙 하나로 운영 실수(잘못된 인덱스로 upsert)를 크게 줄입니다.

3) CI 단계에서 차원 계약 테스트

간단한 테스트로도 충분합니다.

import { strict as assert } from "node:assert";

test("embedding dim contract", async () => {
  const vec = await embed("contract test");
  assert.equal(vec.length, Number(process.env.EMBEDDING_DIM));
});

Node 생태계에서는 모듈 시스템 차이로 런타임에서 임포트가 깨져 테스트 자체가 실패하는 경우도 있으니, ESM 환경이라면 Node.js ESM에서 require is not defined 해결법 같은 이슈도 함께 점검해 두면 좋습니다.

운영에서 자주 쓰는 트러블슈팅 시나리오 4가지

시나리오 A: 갑자기 upsert가 전부 실패

  • 최근 배포에서 임베딩 모델이 바뀌었는지 확인
  • 인덱스/컬렉션의 차원과 모델 차원이 맞는지 확인
  • 즉시 조치: 배포 롤백 또는 임베딩 호출을 이전 모델로 되돌림

시나리오 B: 일부 레코드만 실패

  • 특정 배치 작업만 다른 모델을 쓰는지 확인
  • 실패 레코드의 embedding_model 메타데이터 확인
  • 즉시 조치: 해당 파이프라인만 중단 후 재처리

시나리오 C: 삽입은 되는데 검색 품질이 급락

  • 패딩/자르기 같은 임시 처리 여부 확인
  • 검색 쿼리 모델과 인덱싱 모델이 동일한지 확인
  • 즉시 조치: 임시 처리 제거, 재임베딩 계획 수립

시나리오 D: 개발은 되는데 운영만 실패

  • 운영 환경에서 다른 모델 엔드포인트를 바라보는지 확인
  • 설정값 EMBEDDING_DIM 과 실제 모델 출력의 불일치 확인
  • 즉시 조치: 설정 동기화, 배포 파이프라인에서 계약 테스트 추가

결론: 차원 불일치는 “새 인덱스/컬렉션 + 재임베딩”로 끝내야 한다

Pinecone와 Milvus 모두 차원은 사실상 스키마 계약입니다. 모델을 바꾸는 순간, 기존 인덱스에 억지로 끼워 맞추는 게 아니라 새 인덱스(또는 컬렉션)를 만들고 재임베딩하는 것이 정답입니다.

정리하면 아래 3가지만 지키면 재발 확률이 크게 떨어집니다.

  • upsert/search 직전에 차원 검증을 강제한다
  • 인덱스(컬렉션) 이름과 메타데이터에 모델·차원을 명시한다
  • 모델 변경은 블루/그린 방식으로 마이그레이션한다

이 원칙대로만 운영해도, 임베딩 차원 불일치로 인한 장애는 대부분 “배포 직후 즉시 탐지되고, 짧게 복구되는” 수준으로 관리할 수 있습니다.