스냅샷으로 Qdrant 백업·복원하기

스냅샷으로 Qdrant 백업·복원하기 (tutorials-operations-create-snapshot)

소요 시간: 20분 난이도: 입문(Beginner)

Qdrant에서 데이터 저장의 기본 단위는 컬렉션(collection)이에요. 컬렉션에는 벡터와 벡터의 ID, 그리고 페이로드가 담겨 있죠. 그런데 검색을 빠르게 유지하려면 데이터 위에 추가적인 자료 구조를 더 구축해야 해요. 특히 컬렉션이 커질수록 이 구조를 만드는 데 꽤 오래 걸릴 수 있어요. 그래서 Qdrant 컬렉션을 내보내고 가져올 때는 스냅샷(snapshot)을 쓰는 게 가장 좋은 방법이에요. 스냅샷에는 컬렉션 전체를 효율적으로 복원하는 데 필요한 모든 요소가 들어 있거든요.

이 튜토리얼에서는 컬렉션의 스냅샷을 만들고, 그 스냅샷을 복원하는 방법을 보여드릴게요. 분산 환경에서 스냅샷을 다루는 건 조금 더 복잡할 수 있으니, 여기서는 3-노드 Qdrant 클러스터를 사용할 거예요. 다만 같은 방식이 단일 노드 구성에도 그대로 적용돼요.

이 페이지에 나온 기법을 사용하면 클러스터를 마이그레이션할 수도 있어요. 아래 튜토리얼을 따라 스냅샷을 만들고 다운로드한 다음, 스냅샷에서 복원하기 절차로 새 클러스터에 데이터를 복원하면 됩니다.

출처: Qdrant 공식문서 - Snapshots

사전 준비 (Prerequisites)

이미 실행 중인 Qdrant 인스턴스나 클러스터가 있다고 가정할게요. 아직 없다면 설치 가이드를 따라 로컬 Qdrant 인스턴스를 준비하거나, Qdrant Cloud에서 몇 번의 클릭으로 클러스터를 만들 수 있어요.

클러스터가 실행되면 필요한 의존성을 설치합니다:

pip install qdrant-client datasets

Qdrant에 연결 설정하기

Python SDK와 raw HTTP 호출을 함께 사용해서 Qdrant와 통신할 거예요. 3-노드 클러스터를 쓰기 때문에 모든 노드의 URL을 알아야 해요. 단순하게 하기 위해 API 키와 함께 모든 URL을 상수로 저장해 두고 나중에 참조할게요:

QDRANT_MAIN_URL = "https://my-cluster.com:6333"
QDRANT_NODES = (
    "https://node-0.my-cluster.com:6333",
    "https://node-1.my-cluster.com:6333",
    "https://node-2.my-cluster.com:6333",
)
QDRANT_API_KEY = "my-api-key"

이제 클라이언트 인스턴스를 만들 수 있어요:

from qdrant_client import QdrantClient

client = QdrantClient(QDRANT_MAIN_URL, api_key=QDRANT_API_KEY)

먼저 미리 계산된 데이터셋으로 컬렉션을 하나 만들 거예요. 이미 컬렉션이 있다면 이 단계를 건너뛰고 바로 스냅샷 만들기부터 시작해도 돼요.

(선택) 컬렉션 생성 및 데이터 임포트

데이터셋 불러오기

미리 계산된 임베딩이 들어 있는 데이터셋을 사용할 건데, Hugging Face Hub에서 받을 수 있어요. 데이터셋 이름은 Qdrant/arxiv-titles-instructorxl-embeddings이고, InstructorXL 모델로 만들었어요. arXiv 데이터셋의 논문 제목 225만 개에 대한 임베딩이 담겨 있어요.

데이터셋을 불러오는 건 아주 간단해요:

from datasets import load_dataset

dataset = load_dataset(
    "Qdrant/arxiv-titles-instructorxl-embeddings",
    split="train",
    streaming=True,
)

스트리밍 모드를 사용했기 때문에 데이터셋이 메모리에 통째로 로드되지는 않아요. 대신 반복하면서 id와 벡터 임베딩을 하나씩 꺼낼 수 있죠:

for payload in dataset:
    id_ = payload.pop("id")
    vector = payload.pop("vector")
    print(id_, vector, payload)

개별 페이로드 하나는 이렇게 생겼어요:

{
  'title': 'Dynamics of partially localized brane systems',
  'DOI': '1109.1415'
}

컬렉션 생성하기

가장 먼저 컬렉션을 만들어야 해요. 컬렉션 구성을 자세히 만지작거리진 않을 거지만, 지금 만들어 두는 게 좋아요. 컬렉션 구성도 스냅샷의 일부로 함께 저장되거든요.

from qdrant_client import models

if not client.collection_exists("test_collection"):
    client.create_collection(
        collection_name="test_collection",
        vectors_config=models.VectorParams(
            size=768,  # InstructorXL 모델이 만드는 임베딩 벡터의 크기
            distance=models.Distance.COSINE,
        ),
    )

데이터셋 업로드하기

임베딩을 계산하는 건 보통 벡터 검색 파이프라인의 병목 지점이 되곤 해요. 하지만 우리는 이미 임베딩이 준비돼 있어서 다행이에요. 이 튜토리얼의 목적은 스냅샷을 만드는 방법을 보여주는 거니, 데이터셋의 아주 일부만 업로드할 거예요.

ids, vectors, payloads = [], [], []

for payload in dataset:
    id_ = payload.pop("id")
    vector = payload.pop("vector")

    ids.append(id_)
    vectors.append(vector)
    payloads.append(payload)

    # 벡터 1000개만 업로드할 거예요
    if len(ids) == 1000:
        break

client.upsert(
    collection_name="test_collection",
    points=models.Batch(
        ids=ids,
        vectors=vectors,
        payloads=payloads,
    ),
)

이제 컬렉션이 검색에 사용할 준비가 됐어요. 이 컬렉션의 스냅샷을 만들어 볼게요.

이미 컬렉션이 있다면 앞 단계를 건너뛰고 바로 스냅샷 만들기부터 시작해도 돼요.

스냅샷 생성 및 다운로드

Qdrant는 스냅샷 생성을 요청하는 HTTP 엔드포인트를 제공하는데, Python SDK로도 호출할 수 있어요. 우리 구성은 3개 노드로 이루어져 있으니 각 노드별로 엔드포인트를 호출해서 각 노드에 스냅샷을 만들어야 해요. Python SDK를 쓴다면 노드마다 별도의 클라이언트 인스턴스를 만들어야 한다는 뜻이에요.

snapshot_urls = []
for node_url in QDRANT_NODES:
    node_client = QdrantClient(node_url, api_key=QDRANT_API_KEY)
    snapshot_info = node_client.create_snapshot(collection_name="test_collection")
    snapshot_url = f"{node_url}/collections/test_collection/snapshots/{snapshot_info.name}"
    snapshot_urls.append(snapshot_url)
// for `https://node-0.my-cluster.com:6333`
POST /collections/test_collection/snapshots
// for `https://node-1.my-cluster.com:6333`
POST /collections/test_collection/snapshots
// for `https://node-2.my-cluster.com:6333`
POST /collections/test_collection/snapshots

응답(Response):

{
  "result": {
    "name": "test_collection-559032209313046-2024-01-03-13-20-11.snapshot",
    "creation_time": "2024-01-03T13:20:11",
    "size": 18956800
  },
  "status": "ok",
  "time": 0.307644965
}

스냅샷 URL을 얻었으니 이제 다운로드할 수 있어요. 요청 헤더에 API 키를 반드시 포함해야 합니다. 스냅샷 다운로드는 HTTP API로만 가능하기 때문에 requests 라이브러리를 사용할 거예요.

import requests
import os

# 스냅샷을 저장할 디렉토리 만들기
os.makedirs("snapshots", exist_ok=True)

local_snapshot_paths = []
for snapshot_url in snapshot_urls:
    snapshot_name = os.path.basename(snapshot_url)
    local_snapshot_path = os.path.join("snapshots", snapshot_name)

    response = requests.get(
        snapshot_url,
        headers={"api-key": QDRANT_API_KEY},
    )
    with open(local_snapshot_path, "wb") as f:
        response.raise_for_status()
        f.write(response.content)

    local_snapshot_paths.append(local_snapshot_path)

wget 명령을 써도 됩니다:

wget https://node-0.my-cluster.com:6333/collections/test_collection/snapshots/test_collection-559032209313046-2024-01-03-13-20-11.snapshot \
  --header="api-key: ${QDRANT_API_KEY}" \
  -O node-0-snapshot.snapshot
wget https://node-1.my-cluster.com:6333/collections/test_collection/snapshots/test_collection-559032209313047-2024-01-03-13-20-12.snapshot \
  --header="api-key: ${QDRANT_API_KEY}" \
  -O node-1-snapshot.snapshot
wget https://node-2.my-cluster.com:6333/collections/test_collection/snapshots/test_collection-559032209313048-2024-01-03-13-20-13.snapshot \
  --header="api-key: ${QDRANT_API_KEY}" \
  -O node-2-snapshot.snapshot

이제 스냅샷이 로컬에 저장됐어요. 이 스냅샷으로 다른 Qdrant 인스턴스에 컬렉션을 복원하거나, 백업으로 삼을 수 있어요. 이번에는 같은 클러스터에 같은 데이터로 새 컬렉션을 만들어 볼게요.

스냅샷에서 복원하기

새로 만든 스냅샷을 복원할 준비가 됐어요. 보통은 컬렉션을 다른 Qdrant 인스턴스로 옮길 때 쓰지만, 여기서는 같은 클러스터에 새 컬렉션을 만드는 데 사용할 거예요. 이름만 다르게 해서 test_collection_import라고 할게요. 컬렉션은 자동으로 생성되기 때문에 미리 만들 필요가 없어요.

컬렉션 복원도 노드별로 따로 수행해야 해요. 하지만 Python SDK는 아직 이 기능을 지원하지 않아서, requests 라이브러리로 각 노드에 HTTP 요청을 보낼 거예요.

for node_url, snapshot_path in zip(QDRANT_NODES, local_snapshot_paths):
    snapshot_name = os.path.basename(snapshot_path)
    requests.post(
        f"{node_url}/collections/test_collection_import/snapshots/upload?priority=snapshot",
        headers={
            "api-key": QDRANT_API_KEY,
        },
        files={
            "snapshot": (snapshot_name, open(snapshot_path, "rb")),
        },
    )

curl 명령을 써도 됩니다:

curl -X POST 'https://node-0.my-cluster.com:6333/collections/test_collection_import/snapshots/upload?priority=snapshot' \
  -H 'api-key: ${QDRANT_API_KEY}' \
  -H 'Content-Type:multipart/form-data' \
  -F '[email protected]'
curl -X POST 'https://node-1.my-cluster.com:6333/collections/test_collection_import/snapshots/upload?priority=snapshot' \
  -H 'api-key: ${QDRANT_API_KEY}' \
  -H 'Content-Type:multipart/form-data' \
  -F '[email protected]'
curl -X POST 'https://node-2.my-cluster.com:6333/collections/test_collection_import/snapshots/upload?priority=snapshot' \
  -H 'api-key: ${QDRANT_API_KEY}' \
  -H 'Content-Type:multipart/form-data' \
  -F '[email protected]'

중요: priority=snapshot을 선택한 이유는 노드에 저장된 데이터보다 스냅샷을 우선하도록 하기 위해서예요. priority에 대한 더 자세한 내용은 문서에서 확인할 수 있어요.

스냅샷 외에도 Qdrant는 Qdrant Migration Tool을 제공하는데, 여기서 지원하는 기능은 다음과 같아요:

  • Qdrant Cloud 인스턴스 간 마이그레이션
  • 다른 제공업체의 벡터를 Qdrant로 마이그레이션
  • Qdrant OSS에서 Qdrant Cloud로 마이그레이션

Qdrant Migration Tool을 효과적으로 사용하는 방법은 마이그레이션 가이드에서 배울 수 있어요.

더 알아보기 (Learn more)