개발기 RAG Cloudflare AI Search Qdrant Vector Search 마이그레이션 회고

Qdrant를 걷어내고 Cloudflare AI Search로: 블로그 RAG 운영 단순화 마이그레이션

···

블로그에 RAG 기반 챗봇을 만들면서 Qdrant를 도입해, 직접 청킹과 임베딩부터 인프라 관리까지 전부 제 손으로 굴리고 있었습니다. 그런데 실제로 몇 달간 운영해보니 솔직히 좀 피곤했습니다 😩 새 글을 쓸 때마다 미리 만들어 둔 스크립트 돌려서 임베딩 새로 뽑고, 컬렉션 상태 확인하고, 인스턴스가 잠들지 않도록 keepalive까지 챙기는게.. 블로그를 위해서 이렇게까지 해야하나..?

“그리고 꼭 Qdrant여야 할 이유가 있나?”

주변에 물음표를 띄우며 어리둥절해하는 페페 밈

마침 제 블로그는 이미 Cloudflare 생태계(Workers) 위에서 배포되고 서빙되고 있었습니다. 그러던 중 Cloudflare에 AI Search라는 기능이 있더라고요? 나온지 좀 된거 같기도!

살펴보니 제 블로그 환경에는 수동 파이프라인을 덕지덕지 달고 가는 Qdrant보다, 크롤링부터 하이브리드 검색까지 한 번에 묶어주는 관리형 AI Search가 훨씬 적합하겠다는 판단이 섰습니다. 사실 귀찮음이 컸습니다..크흠.. 물론 기술에 대한 궁금증도 생기더라고요 !

  • 도대체 내부적으로는 어떤 원리와 파이프라인으로 돌아갈까?
  • 기존에 직접 깎아둔 Dense + Sparse 하이브리드 검색 대비 효율과 운영 비용은 얼마나 체감될까?

단순히 “귀찮으니까 편한 걸로 바꾸자”가 아니라, 내부 아키텍처와 효율을 이전 방식과 면밀히 비교·검토해본 뒤 마이그레이션을 단행하기로 결정했습니다.

Qdrant 기반 블로그 RAG를 Cloudflare AI Search로 옮기면서 무엇을 비교했고, 무엇이 사라졌으며, 대신 어떤 제약을 받아들였는지 그 마이그레이션 과정을 정리해둡니다 🫡


Before: 검색은 잘 됐지만 운영은 사람이 했습니다

기존 블로그 RAG는 Qdrant에 세 개의 컬렉션을 두고 있었습니다.

컬렉션 구분역할검색 방식
포스트 본문 컬렉션블로그 글 검색Dense + Sparse Hybrid, RRF
프로필 컬렉션소개·프로젝트·활동 검색Dense + Sparse Hybrid, RRF
Q&A 캐시 컬렉션업보트된 답변 재사용질문 임베딩 코사인 유사도 0.87 이상

Dense Vector가 질문의 의미를 잡고, Sparse Vector가 고유명사와 정확한 키워드를 보완한 다음, RRF로 두 결과를 합쳤습니다. 직접 구현해두었던 Dense + Sparse Hybrid Search 방식을 블로그 챗봇에도 그대로 가져와 쓰고 있었습니다.

문제는 콘텐츠가 바뀌는 순간부터였습니다.

글 작성
→ bun run suggest
→ bun run embed
→ bun run upload-contexts
→ Qdrant 반영 확인
→ 배포

글 본문을 한 줄 고쳐도 임베딩 대상인지 판단하고, 스크립트가 새 청크만 골라 올렸는지 확인해야 했습니다. API 키도 로컬과 배포 환경에서 각각 관리해야 했고요. 거기에 Qdrant Cloud 인스턴스가 잠들지 않도록 10분마다 요청을 보내는 GitHub Actions 워크플로까지 있었습니다 😗 검색 인프라의 생명주기를 제가 관리하고 있었습니다


왜 Vectorize가 아니라 AI Search였나

처음에는 Qdrant를 Cloudflare Vectorize로 그대로 옮기는 방법부터 검토했습니다. Cloudflare 네트워크 안으로 벡터 DB만 옮기면 되는 것처럼 보였거든요?

그런데 기존 검색은 단순한 Dense Vector 검색이 아니었습니다.

  • Dense + Sparse 하이브리드 검색
  • RRF 결과 융합
  • 한국어 키워드 검색
  • 문서 청킹과 임베딩
  • 사이트 변경 감지와 재색인

Raw Vectorize로 가면 Dense 검색은 옮길 수 있지만, Sparse 검색과 RRF, 크롤링, 청킹, 임베딩 파이프라인은 다시 직접 만들어야 하더라고요. Qdrant 클라이언트를 Vectorize API로 바꾸는 데 성공해도 운영할 코드의 총량은 크게 줄지 않는 구조였죠.

반면 AI Search는 사이트 URL을 데이터 원본으로 지정하면 크롤링부터 파싱, 청킹, 임베딩, 하이브리드 검색까지 한 번에 관리해줍니다.

머리 뚜껑이 열리며 뇌정지가 오는 애니메이션 GIF
기준Qdrant 직접 운영Raw VectorizeCloudflare AI Search
Dense 검색지원지원지원
키워드 검색Sparse Vector로 구성별도 구현 필요내장
RRF직접 쿼리 구성직접 구현 필요내장
청킹·임베딩로컬 스크립트직접 구현관리형
사이트 재색인수동 실행직접 구현예약 동기화
운영 자유도높음높음상대적으로 낮음

이번 목표는 검색 엔진을 더 세밀하게 다루는 게 아니라, 블로그를 운영할 때 검색 엔진을 신경 쓰지 않는 것이었습니다. 그래서 Vectorize가 아니라 AI Search를 선택했습니다. 오버 엔지니어링 멈춰 ✋

Qdrant가 문제였던 건 아닙니다

Qdrant는 Hybrid Search를 세밀하게 실험하고 직접 제어하기에 여전히 좋은 도구입니다. 다만 100여 페이지 규모의 개인 블로그에서는 그 자유도를 유지하기 위해 들이는 운영 비용이 더 크게 느껴졌습니다.


After: 사이트맵만 바뀌면 검색도 따라옵니다

이관 후 구조는 크게 배포 및 백그라운드 색인과 런타임 질의응답 두 흐름으로 깔끔하게 정리되었습니다.

1. 배포 및 백그라운드 자동 색인

글을 쓰고 Git Push만 하면, 배포 완료 후 AI Search가 사이트맵을 보고 크롤링부터 파싱, 임베딩 색인까지 전자동으로 처리합니다.

2. 런타임 검색 및 질의응답

사용자가 질문을 던졌을 때 챗봇이 동작하는 런타임 흐름도 Worker 내부에서 단번에 해결됩니다.

현재 제 블로그에 구성한 AI Search 핵심 설정은 이렇습니다.

핵심 항목설정값이유 / 특징
데이터 수집사이트맵 크롤러 (6시간 주기)정적 HTML 배포본 자동 수집 및 동기화
임베딩 모델@cf/qwen/qwen3-embedding-0.6bWorkers AI 관리형 1,024차원 모델
검색 방식Hybrid Search (RRF 융합)의미 기반 벡터 검색 + 키워드 정확 일치
토크나이저문자 트라이그램 (Tri-gram)한국어 고유명사 및 기술 용어 부분 일치 대응

초기 동기화 결과는 107페이지, 271벡터, 내장 스토리지 5.92MB, 오류 0건이었습니다. 전체 작업도 20초 안팎으로 끝났습니다 👍

특히 한국어 검색 때문에 키워드 토크나이저를 문자 트라이그램으로 설정했습니다. 영어용 stemming을 그대로 쓰는 것보다, 세 글자 단위 부분 일치로 한국어 고유명사와 기술 용어를 잡는 쪽이 이 블로그에는 더 잘 맞겠다고 판단했거든요.

Playground에서 Tableau DuckDB 파이프라인 최적화를 검색했을 때 해당 글이 점수 1.0으로 1위에 올라오는 것까지 확인했습니다. 다만 이 결과 하나로 Qdrant보다 검색 품질이 좋아졌다고 말할 수는 없습니다. 별도의 평가셋으로 NDCG나 MRR을 다시 측정한 건 아니니까요. 이번 마이그레이션의 성공 기준은 기존에 잘 찾던 콘텐츠를 계속 찾으면서 운영 단계를 줄이는 것으로 잡았습니다.


실제로 얼마나 줄었나

마이그레이션 PR은 22개 파일, 442줄 추가, 778줄 삭제였습니다. 문서 수정까지 포함한 수치지만, 전체적으로 336줄이 순감소했습니다.

사라진 것BeforeAfter
본문 임베딩 스크립트scripts/embed-posts.ts삭제
Qdrant keepalive10분 주기 GitHub Actions + Worker cron삭제
Qdrant 런타임 코드검색·임베딩·upsert 클라이언트BLOG_SEARCH.search() 호출
Qdrant 시크릿URL + API Key삭제
임베딩 API 키색인 작업에 필요색인에는 불필요
발행 시 본문 색인bun run embed 수동 실행6시간마다 자동 동기화
벡터 저장소외부 QdrantAI Search 내장 저장소

런타임 검색 코드도 꽤 단순해졌습니다.

const result = await env.BLOG_SEARCH.search({
  query,
  ai_search_options: {
    retrieval: {
      retrieval_type: 'hybrid',
      fusion_method: 'rrf',
      keyword_match_mode: 'or',
      max_num_results: 30,
      match_threshold: 0.4,
    },
  },
})

예전에는 질문 임베딩 생성, Dense와 Sparse 요청 조립, Qdrant 응답 변환을 각각 처리했습니다. 지금은 검색 방식과 임계값을 지정해 호출하고, 결과 청크를 블로그 포스트 형태로 바꾸는 일만 남았습니다.

가장 체감되는 변화는 발행 절차입니다.

Before
글 수정 → suggest → embed → upload-contexts → 배포 → Qdrant 확인

After
글 수정 → suggest → upload-contexts → Git Push
                           ↓
                AI Search가 사이트맵을 보고 자동 재색인

수동 단계 하나를 없앤 것처럼 보이지만, 그 한 단계 뒤에 달려 있던 API 키, 실패 재시도, 컬렉션 상태 확인, keepalive까지 함께 사라졌습니다. 코드보다 체크리스트가 줄어든 게 더 큰 변화였습니다 😮‍💨


공짜로 얻은 건 아니었습니다

마이그레이션이 무조건 정답이냐는 질문에 손을 씻는 밈

관리형 서비스로 옮겼으니 당연히 잃은 것도 있습니다.

1. 포스트별 서버 필터를 바로 걸 수 없었습니다

기존 Qdrant에서는 payload의 slug로 특정 포스트만 검색할 수 있었습니다. 그런데 Web Crawler 기반 AI Search에서 제가 사용할 수 있는 서버측 메타데이터 필터는 제한적이었습니다.

그래서 현재 보고 있는 글 안에서만 답해야 하는 경우에는 후보를 40개까지 넉넉히 받은 뒤, item.key의 URL을 보고 해당 slug만 남기는 방식으로 처리했습니다 💪

const chunks = await aiSearch(env, `${post.title} ${question}`, {
  candidates: 40,
})

const matched = chunks.filter(
  (chunk) => postSlugFromChunk(chunk) === post.slug,
)

지금처럼 100여 페이지 규모에서는 충분하지만, 글이 수천 개로 늘어나면 후보 40개 안에 원하는 포스트 청크가 들어오지 않을 수 있습니다. 그때는 메타데이터 구조나 데이터 원본 방식을 다시 설계해야 할 것 같습니다.

2. 의미 기반 Q&A 캐시를 포기했습니다

기존 Qdrant의 Q&A 캐시 컬렉션은 질문을 임베딩해 코사인 유사도 0.87 이상이면 이전 답변을 재사용했습니다. 표현이 조금 달라도 같은 질문으로 판단할 수 있었죠.

AI Search로 옮기면서 임의의 Q&A 벡터를 넣어둘 컬렉션이 없어졌기 때문에, 이 캐시는 KV 기반의 정규화된 질문 완전 일치로 바꿨습니다.

  • 대소문자 제거
  • 문장부호 제거
  • 연속 공백 정리
  • slug + 질문을 SHA-256 키로 변환
  • 30일 TTL로 KV에 저장

비슷한 질문까지 재사용하는 능력은 줄었지만, 캐시 조회는 KV 읽기 한 번으로 끝나고 동작도 훨씬 예측 가능해졌습니다. 실제 캐시 히트 대부분이 추천 질문을 다시 누르는 경우라면 이 정도 타협은 괜찮겠죠..?

3. 콘텐츠 변경이 즉시는 아닙니다

AI Search는 6시간마다 사이트맵을 다시 읽습니다. 새 글이나 수정한 글이 검색에 반영되기까지 최대 6시간이 걸릴 수 있습니다.

급할 때는 대시보드에서 전체 동기화나 항목 동기화를 직접 실행할 수 있습니다. 또 검색 결과 유사성 캐시의 TTL이 48시간이라, 중요한 내용을 고친 직후에는 캐시도 함께 비워야 합니다.

4. 내부 동작은 덜 보입니다

Qdrant 시절에는 Dense와 Sparse 결과를 각각 보고, 점수와 payload를 원하는 방식으로 디버깅할 수 있었습니다. AI Search는 청킹, 임베딩, 키워드 인덱싱의 많은 부분이 관리형 파이프라인 안에 들어갑니다.

제어권을 줄이고 운영 부담을 없앤 선택입니다. 검색 자체를 연구하는 프로젝트라면 Qdrant를 남겼겠지만, 블로그 기능을 안정적으로 유지하는 목적이라면 이쪽이 더 맞다고 생각합니다.


비용은 어떻게 바뀌었나

2026년 9월 기준 AI Search는 오픈 베타라 한도 내에서 무료입니다. 현재 블로그 규모는 107페이지와 271벡터이고, Workers Free 기준 월 20,000회 검색과 하루 500페이지 크롤 한도에 비해 여유가 큽니다.

베타 가격은 바뀔 수 있습니다

AI Search 정식 과금은 아직 시작되지 않았고, Cloudflare는 과금 시작 최소 30일 전에 안내한다고 명시하고 있습니다. 따라서 지금의 무료 상태를 장기 비용으로 가정하면 안 됩니다.

그래도 비용 구조는 예전보다 이해하기 쉬워졌습니다.

  • 저장소, 벡터 색인, 웹 크롤링은 AI Search에 포함
  • 기본 임베딩과 검색 파이프라인도 관리형
  • 실제 답변 생성은 OpenRouter 모델 비용으로 별도 청구
  • Qdrant 인스턴스와 임베딩 API의 별도 운영 비용은 제거

정확한 절감액을 계산하진 않았습니다. Qdrant도 작은 규모에서는 무료 범위로 사용하고 있었기 때문입니다. 이번에 줄인 건 청구서보다 비용이 발생할 수 있는 지점과 관리해야 할 계정의 수였습니다.


마무리

Qdrant에서 직접 Hybrid Search를 구성해본 경험이 있었기 때문에 AI Search 설정도 무작정 기본값으로 두지 않았습니다. Dense와 키워드 검색을 왜 결합하는지, RRF가 왜 필요한지, 한국어 토크나이저가 왜 중요한지를 알고 선택할 수 있었거든요. 직접 만들어본 뒤 관리형 서비스로 옮기니, 무엇을 포기했고 무엇을 위임했는지도 더 선명하게 보였습니다 👀

물론 검색 품질을 정량 비교하지 못한 건 아쉽습니다. 다음에는 기회가 된다면 블로그 전용 질문 평가셋을 따로 만들어, Qdrant 시절의 결과와 AI Search 결과를 NDCG·MRR로 직접 비교해보고 싶습니다. 운영은 단순해졌으니 이제 다시 품질을 만질 차례인 거겠죠?? 🤔