서버 비용 0원, Astro 블로그 마크다운 기반의 초경량 임베딩 RAG 검색 엔진 구현하기

개인 블로그를 운영하면서 ’내가 쓴 글들을 기반으로 답변해 주는 AI 챗봇이 있으면 좋겠다’는 생각을 한 번쯤 해보셨을 겁니다. 하지만 RAG(검색 증강 생성) 시스템을 실제로 구축하려고 하면 머리가 아파옵니다. Vector DB(Pinecone, Milvus 등)를 호스팅해야 하고, 임베딩 값을 연산하고 저장할 서버가 필요하며, 매달 고정적인 인프라 비용이 발생하기 때문입니다. 방문자가 거의 없는 개인 블로그에 배보다 배꼽이 더 큰 비용을 지불할 수는 없는 노릇입니다.

이 글에서는 static site generator인 Astro 프레임워크의 장점을 극대화하여, 서버 유지 비용 0원(Free Tier)으로 구동되는 초경량 마크다운 기반 RAG 검색 엔진 및 챗봇을 구축하는 아키텍처와 구현 방법을 깊이 있게 다룹니다.


왜 ‘Astro + 마크다운’ 조합인가? 기존 RAG와의 비교

일반적인 RAG 파이프라인은 사용자의 질문이 들어오면 실시간으로 데이터베이스에서 관련 문서를 검색(Retrieval)하고, 이를 LLM에 프롬프트와 함께 전달하여 답변을 생성합니다.

하지만 수백 개 내외의 포스트를 가진 개인 블로그에서 굳이 무겁고 비싼 분산형 Vector DB와 API 서버를 상시 가동할 필요는 없습니다. Astro 블로그 환경에서는 다음과 같은 초경량 우회로를 설계할 수 있습니다.

비교 항목 전통적인 RAG 아키텍처 Astro 초경량 서버리스 RAG
인프라 구성 API 서버 + Vector DB + LLM 오케스트레이터 정적 파일(JSON) + Edge 함수 + Client-side 연산
월 고정 비용 최소 $10 ~ $50 이상 (DB 및 서버 호스팅) 0원 (Cloudflare Pages / Vercel Free Tier)
임베딩 생성 시점 사용자가 글을 등록할 때마다 실시간 생성 **블로그 빌드 시점(Build-time)**에 일괄 생성
벡터 검색 위치 원격 Vector DB 서버 사용자 브라우저(Client-side) 또는 Edge 단
적합한 데이터 규모 수만 개 이상의 대용량 실시간 변경 문서 수천 개 이하의 정적 마크다운 포스트 (블로그 최적화)

0원 RAG 파이프라인의 핵심 설계도

이 시스템의 핵심 아이디어는 **“마크다운 콘텐츠는 빌드할 때 이미 확정되어 있다”**는 점을 이용하는 것입니다. 굳이 런타임에 임베딩을 요청할 필요가 없습니다.

[빌드 단계 (Build-time)]
Markdown 파일 읽기 -> 텍스트 청킹(Chunking) -> 임베딩 API 호출 -> vector-database.json 저장

[런타임 단계 (Runtime)]
사용자 질문 입력 -> 질문 임베딩 생성 (클라이언트 or 엣지) -> vector-database.json 로드 후 코사인 유사도 계산 -> 상위 N개 문맥 추출 -> LLM API 호출 및 답변

1단계: 빌드 타임에 정적 벡터 데이터베이스 구축하기

Astro의 astro:content API를 사용하면 빌드 시점에 모든 마크다운 파일의 텍스트에 접근할 수 있습니다. 이 단계를 활용해 빌드할 때 임베딩 벡터가 포함된 JSON 파일을 정적으로 생성합니다.

// scripts/generate-embeddings.mjs (예시 개념 코드)
import fs from 'fs';
import { globby } from 'globby';
import matter from 'gray-matter';
import { getEmbedding } from './openai-helper.js'; // OpenAI 또는 무료 HuggingFace API 사용

async function buildVectorDB() {
  const posts = await globby(['src/content/blog/**/*.md']);
  const vectorDb = [];

  for (const post of posts) {
    const file = fs.readFileSync(post, 'utf-8');
    const { data, content } = matter(file);
    
    // 텍스트를 의미 있는 단위(Chunk)로 쪼개기
    const chunks = chunkText(content, 500); // 500자 단위 초경량 청킹
    
    for (const chunk of chunks) {
      const embedding = await getEmbedding(chunk); // text-embedding-3-small 모델 추천
      vectorDb.push({
        title: data.title,
        slug: post.replace('src/content/blog/', '').replace('.md', ''),
        content: chunk,
        embedding: embedding
      });
    }
  }

  fs.writeFileSync('./public/vector-db.json', JSON.stringify(vectorDb));
}

주의할 점: 글 개수가 늘어날수록 빌드 시 임베딩 API 호출 비용이 걱정될 수 있습니다. 이를 방지하기 위해 파일의 수정 여부를 체크(Hash 비교 등)하여 변경된 파일만 부분적으로 임베딩을 업데이트하는 캐싱 로직을 추가하는 것이 필수적입니다.

2단계: 클라이언트 사이드 임베딩 비교 (코사인 유사도 연산)

보통 코사인 유사도 계산을 위해 서버가 필요하다고 생각하지만, 벡터 차원이 낮고 데이터가 적다면 JavaScript 단에서 단 몇 줄로 해결할 수 있습니다. text-embedding-3-small 기준 1536 차원의 벡터 연산은 최신 브라우저 엔진에서 눈 깜짝할 사이에 처리됩니다.

벡터 데이터 파일의 크기는 포스트 수와 청킹 단위, 임베딩 차원에 비례해 커집니다. 예를 들어 text-embedding-3-small을 기본 1536차원으로 사용하고 포스트당 여러 개의 청크를 생성한다면 포스트 수가 수백 개만 되어도 JSON 파일이 수 MB 단위로 불어날 수 있으므로, 실제 도입 전 자신의 포스트 수와 청킹 전략을 기준으로 파일 크기를 직접 측정해 보는 것이 좋습니다. 클라이언트 사이드에서 코사인 유사도를 계산하는 방식 자체는 벡터 차원이 낮고 문서 수가 적을수록 체감 지연이 거의 없지만, 정확한 응답 속도 역시 브라우저 성능과 데이터 규모에 따라 달라지므로 실제 배포 환경에서 측정해 보는 것을 권장합니다.

// 코사인 유사도 연산 함수
function cosineSimilarity(vecA, vecB) {
  let dotProduct = 0;
  let normA = 0;
  let normB = 0;
  for (let i = 0; i < vecA.length; i++) {
    dotProduct += vecA[i] * vecB[i];
    normA += vecA[i] * vecA[i];
    normB += vecB[i] * vecB[i];
  }
  return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
}

구현 시 맞닥뜨릴 실제적인 한계와 해결법

가장 매력적인 아키텍처처럼 보이지만, 실제로 배포해 보면 몇 가지 디테일한 장벽에 부딪히게 됩니다. 이를 우회하는 팁을 정리했습니다.

문제 1: 정적 JSON 파일 크기 폭발 문제

임베딩 데이터(Float32 배열)는 생각보다 용량을 많이 차지합니다. 마크다운 포스트가 100개가 넘어가면 vector-db.json 파일 크기가 수십 메가바이트(MB)에 달해, 방문자가 페이지에 접속할 때 엄청난 네트워크 트래픽을 유발할 수 있습니다.

문제 2: API 키 노출 위험

클라이언트 사이드에서 직접 OpenAI API나 Gemini API를 호출해 질문을 임베딩화하면 브라우저 개발자 도구에 API 키가 그대로 노출됩니다.


내 블로그에 도입할 때 의사결정 체크리스트

이 가벼운 RAG 시스템이 당신의 블로그 환경에도 최적의 선택일까요? 아래 체크리스트를 통해 판단해 보세요.

💡 판정: 위 항목 중 4개 이상에 해당한다면, 본문에 소개된 ‘Astro 빌드타임 임베딩 RAG’ 아키텍처가 최적의 솔루션입니다. 반면, 매시간 수많은 유저가 글을 쓰고 실시간 댓글까지 검색 대상에 포함되어야 한다면 기존의 무거운 실시간 DB 아키텍처로 선회해야 합니다.


챗봇 도입 후 실제 비용 시뮬레이션

가장 중요한 것은 실질적인 유지비입니다. 이 아키텍처를 도입했을 때 발생하는 청구 비용은 오직 사용자의 질문 임베딩 생성과 LLM 답변 생성 시 사용하는 API 토큰 비용뿐입니다. 빌드 타임 임베딩은 신규·수정된 포스트에 대해서만 한 번씩 발생하고, 런타임에는 질문 한 건당 짧은 임베딩 요청과 답변 생성 요청만 추가되는 구조이므로 요청당 토큰 소모량 자체는 매우 작습니다. 다만 실제 청구액은 방문자 수와 질문 빈도에 따라 달라지므로, 무료 크레딧 소진 여부와 실제 청구액은 도입 후 자신의 OpenAI 대시보드에서 직접 확인하는 것이 정확합니다.

OpenAI의 GPT-5.4-nano 모델과 text-embedding-3-small 모델을 조합하여 사용하면 요청당 토큰 단가가 매우 낮아, 개인 블로그 수준의 트래픽에서는 월 비용 부담이 크지 않은 편입니다.

단순히 키워드가 완벽히 일치해야만 결과를 뱉어내던 기존의 거친 텍스트 검색창에서 벗어나, “이 블로그에서 서버리스 아키텍처 다룬 글들 요약해 줘” 같은 맥락 기반의 자연스러운 대화형 인터페이스를 내 블로그에 직접 이식해 보시기 바랍니다. static 블로그의 한계를 넘어선 새로운 사용자 경험을 제공할 수 있을 것입니다.