-
Qdrant 벡터 DB, 임베디드 모드에서 Docker 서버로 전환한 이유 — 로컬 RAG 시스템 구축 삽질기IT 2026. 4. 10. 21:00
벡터 DB가 왜 필요했나
나는 Obsidian으로 지식을 관리하고 있다. 공부 노트, 읽은 글 정리, 음성 메모 전사본까지 모두 마크다운 파일로 모인다. 그런데 파일이 1,300개를 넘어가면서 "이거 예전에 정리했는데 어디 있더라?"가 일상이 됐다.
Obsidian의 내장 검색은 키워드 매칭이라, "GPU 메모리 관리 방법"을 검색하면 "GPU"와 "메모리"가 들어간 모든 문서가 나온다. 내가 원하는 건 의미적으로 비슷한 내용을 찾는 것인데, 키워드 검색으로는 한계가 있었다.
그래서 벡터 DB 기반의 RAG(Retrieval-Augmented Generation) 시스템을 만들었다. 모든 노트를 임베딩해서 벡터로 변환하고, 질문을 던지면 의미적으로 가까운 문서 조각을 찾아주는 시스템이다.
처음에는 임베디드 모드로 시작했다
벡터 DB로 Qdrant를 선택했다. Rust로 만들어져서 빠르고, Python 클라이언트가 잘 되어 있고, 무엇보다 임베디드 모드를 지원한다.
임베디드 모드란, 별도의 서버 프로세스 없이 Python 코드 안에서 직접 DB를 열고 쓰는 방식이다. SQLite처럼 파일 기반으로 동작한다.
from qdrant_client import QdrantClient # 임베디드 모드: 로컬 디렉토리를 바로 열기 client = QdrantClient(path="./qdrant_data")설치도 간단하고 (
pip install qdrant-client하나로 끝), 별도 데몬을 관리할 필요가 없으니 처음에는 완벽해 보였다.문제는 "동시에 두 놈이 접근할 때" 시작됐다
RAG 시스템이 커지면서, Qdrant에 접근하는 경로가 하나가 아니게 됐다.
- 검색 — Claude Code에서
/vault-search명령으로 수시로 검색 - 증분 인덱싱 — Git commit 할 때마다 변경된 노트를 자동으로 인덱싱하는 post-commit hook
- 야간 배치 — 매일 새벽 2시에 전체 정합성 검사 + 누락 파일 인덱싱
- 수동 전체 인덱싱 — 가끔 전체 재인덱싱이 필요할 때
이 중 둘 이상이 동시에 실행되면? 임베디드 모드에서는 파일 락 충돌이 발생한다.
# 프로세스 A: 검색 중 client_a = QdrantClient(path="./qdrant_data") # 파일 락 획득 # 프로세스 B: 인덱싱 시도 client_b = QdrantClient(path="./qdrant_data") # 💥 락 충돌!임베디드 모드는 기본적으로 단일 프로세스 전용이다. 하나의 Python 프로세스 안에서 여러 스레드가 접근하는 건 괜찮지만, 서로 다른 프로세스가 같은 데이터 디렉토리를 동시에 열면 데이터 손상 위험이 있다.
실제로 야간 배치가 돌고 있는데 검색 요청이 들어오면, 둘 중 하나가 실패하거나 "이미 다른 프로세스가 사용 중"이라는 에러가 났다.
서버 모드로 전환하기로 했다
해결책은 명확했다. Qdrant를 독립 서버로 띄우고, 모든 클라이언트가 HTTP API로 접근하게 만드는 것이다.
from qdrant_client import QdrantClient # 서버 모드: HTTP API로 접속 client = QdrantClient(url="http://localhost:6333")코드 변경은 딱 한 줄이다.
path=를url=로 바꾸면 끝. 나머지 검색, 인덱싱, 컬렉션 관리 코드는 전혀 수정할 필요가 없다. Qdrant 클라이언트가 임베디드와 서버 모드를 같은 인터페이스로 추상화해두었기 때문이다.문제는 "서버를 어떻게 띄우느냐"였다.
왜 Docker를 선택했나
내 서버는 NVIDIA DGX Spark인데, CPU가 ARM64(aarch64) 아키텍처다. 일반적인 x86 서버가 아니라서 소프트웨어 설치에 제약이 많다.
방법 1: apt로 설치 — 불가
Qdrant는 Ubuntu 공식 패키지 저장소에 없다. Qdrant 자체 저장소도 ARM64 바이너리를 제공하지 않는다.
방법 2: 바이너리 직접 다운로드 — 불편
GitHub Releases에서 ARM64 바이너리를 받을 수는 있다. 하지만 systemd 서비스 파일을 직접 작성해야 하고, 업데이트도 수동이다. 설정 파일 경로, 데이터 디렉토리 매핑, 로그 관리까지 전부 직접 해야 한다.
방법 3: Docker — 딱 맞았다
Qdrant 공식 Docker 이미지(
qdrant/qdrant)는 ARM64를 지원하는 멀티 아키텍처 이미지다.docker pull한 번이면 알아서 ARM64 버전을 받아준다.docker run -d \ --name qdrant \ --restart unless-stopped \ -p 6333:6333 -p 6334:6334 \ -v ~/knowledge-vault-rag/qdrant_data:/qdrant/storage \ qdrant/qdrant:v1.13.6이 한 줄이면 끝이다. 분해해보면:
-d— 백그라운드 실행--restart unless-stopped— 서버 재부팅해도 자동 시작, 수동으로 멈추지 않는 한 계속 실행-p 6333:6333— REST API 포트 매핑-v ~/qdrant_data:/qdrant/storage— 데이터를 호스트 디렉토리에 저장 (컨테이너 삭제해도 데이터 유지)
Docker로 얻은 것들
1. 자동 재시작
--restart unless-stopped옵션 덕분에 서버가 재부팅되거나 Qdrant가 크래시해도 자동으로 다시 뜬다. systemd 서비스 파일을 작성할 필요가 없다.2. 버전 관리와 롤백
이미지 태그로 버전을 고정하니까, 업데이트가 간단하다.
# 업그레이드 docker stop qdrant docker rm qdrant docker run -d ... qdrant/qdrant:v1.14.0 # 새 버전 # 문제 생기면 롤백 docker stop qdrant docker rm qdrant docker run -d ... qdrant/qdrant:v1.13.6 # 이전 버전데이터는 호스트 볼륨에 있으니 컨테이너를 지워도 안전하다.
3. 격리된 환경
Qdrant의 의존성이 호스트 시스템의 다른 프로그램과 충돌할 일이 없다. ARM64 환경에서 특히 중요한데, 라이브러리 호환성 문제가 x86보다 자주 발생하기 때문이다.
4. 동시 접근 해결
원래 목적이었던 동시 접근 문제가 깔끔하게 해결됐다. Qdrant 서버가 내부적으로 동시 요청을 처리하니, 검색과 인덱싱이 동시에 돌아도 문제없다.
Docker의 단점도 있다
1. 메모리 오버헤드
컨테이너 런타임(containerd, runc 등)이 차지하는 메모리가 있다. Qdrant 자체가 약 300MB를 쓰는데, Docker 오버헤드까지 합치면 조금 더 나간다. 개인 서버에서는 신경 쓸 정도는 아니지만, 메모리가 아주 빠듯한 환경이라면 고려할 사항이다.
2. 네트워크 레이턴시
임베디드 모드는 메모리에서 직접 읽기 때문에 레이턴시가 거의 0이다. 서버 모드는 HTTP 요청/응답 과정이 추가된다. 다만 localhost 통신이라 실측 차이는 1ms 미만이라 체감되지 않는다.
3. Docker 자체의 학습 곡선
Docker를 처음 접하는 사람에게는 볼륨 매핑, 포트 매핑, 네트워크 개념이 진입 장벽이 될 수 있다. 하지만 위의
docker run명령 하나로 충분하니, 사실 알아야 할 게 많지는 않다.현재 구조
전환 후 시스템 구조를 정리하면 이렇다.
핵심 포인트는 Qdrant 서버(Docker)는 항상 떠있고, Ollama(GPU)는 필요할 때만 뜬다는 것이다. 벡터 검색 자체는 CPU만 쓰기 때문에, GPU가 다른 작업에 쓰이고 있어도 이미 인덱싱된 데이터에 대한 검색은 문제없이 동작한다. GPU가 필요한 건 쿼리를 벡터로 변환하는 임베딩 단계뿐이다.
전환하면서 배운 것
처음부터 서버 모드로 시작할 필요는 없다. 개인 프로젝트에서 벡터 DB를 처음 써본다면, 임베디드 모드로 빠르게 프로토타이핑하는 게 맞다. 설정이 없고, 설치가 쉽고, 디버깅이 편하다.
동시 접근이 필요해지면 그때 전환하면 된다. Qdrant는 임베디드↔서버 전환이 코드 한 줄 차이다. 데이터 디렉토리도 그대로 쓸 수 있다. "나중에 서버로 바꿔야 하면 어쩌지" 걱정은 안 해도 된다.
ARM64에서는 Docker가 사실상 필수다. x86 환경이라면 바이너리 다운로드로도 충분하지만, ARM64에서는 공식 패키지가 없는 소프트웨어가 많다. Docker 멀티 아키텍처 이미지가 이 격차를 깔끔하게 메워준다.
벡터 DB 도입을 고민하고 있다면, 임베디드로 시작해서 필요할 때 서버로 올리는 경로를 추천한다. 생각보다 전환 비용이 낮다.
이 글은 생성형 AI의 도움을 받아 작성되었습니다. 원본 자료를 기반으로 AI가 초안을 생성하고, 작성자가 검토·편집하였습니다.
'IT' 카테고리의 다른 글
아카라이브 알파카 채널과 AI 모델 이름의 계보 (0) 2026.04.14 Gemma 4 로컬 AI 스택 완전 정복 — DGX Spark에서 돌려본 솔직한 후기 (1) 2026.04.13 GPU에서 LLM까지, 추론 스택 완전 해부 (0) 2026.04.13 GitHub 오픈소스 PR 리뷰, 놓치지 않는 자동화 시스템 만들기 (0) 2026.04.12 AI 코딩 에이전트의 자기 진화 학습 시스템 — 실수를 기억하고 성장하는 에이전트 만들기 (1) 2026.04.11 캘린더 싱크의 중복 지옥, event_id로 탈출하기 — Google Calendar → Obsidian 자동화 삽질기 (1) 2026.04.09 Claude Code가 플랜을 짜는 방법 - Plan Mode 내부 동작 원리 (0) 2026.04.08 axios에 악성코드가 심어졌다 - 북한 해커의 npm 공급망 공격 분석 (2) 2026.04.07 Claude Code Hooks로 AI 에이전트의 다단계 파이프라인을 결정적으로 만들기 (0) 2026.04.06 Qwen3.5-122B 양자화 비교: Q4_K_M vs Unsloth UD-Q3_K_XL 실측 (1) 2026.04.05 - 검색 — Claude Code에서