사내 지식 MCP 서버 구현기의 마지막 편이다. 앞선 글에서는 서버 구조를 설계하고 검색 품질을 개선한 뒤, 평가셋으로 그 결과를 검증했다. 이번에는 서버를 실제로 배포한 뒤에야 발견한 문제와, 사람의 개입을 줄이려고 만든 운영 자동화를 정리한다.
서버의 역할은 검색 요청을 받아 근거를 돌려주는 것으로 비교적 단순하다. 하지만 실제 운영에서는 요청 처리 로직보다 인프라의 동작 방식, 데이터 갱신, 검증 체계 같은 주변 요소가 더 많은 문제를 만들었다. 특히 사용량에 비해 지나치게 높은 Cloud Run 비용이 나온 적이 있는데, 추적해 보니 MCP 프로토콜의 연결 방식과 Cloud Run의 과금 구조가 맞물려 생긴 문제였다.
1. 운영 환경
지금 MCP 서버는 다음 환경에서 돈다.
- GCP Cloud Run 서울 리전
- 1 vCPU / 2GB 컨테이너
- 요청이 없으면 인스턴스 0으로 축소
- 하루 백여 건 수준의 요청
- 인증 토큰은 Secret Manager로 주입
- 활성 검색 인덱스는 환경 변수로 관리
인증 토큰이 설정되지 않으면 컨테이너가 아예 뜨지 않게 했다. 설정 실수로 인증 없는 서버가 외부에 노출되는 상황을 애플리케이션 단계에서 막기 위해서다.
답변 생성은 각 사용자의 코딩 에이전트가 맡고, MCP 서버는 검색과 근거 반환에만 집중한다. 서버 연산량이 크지 않으니 처음에는 운영비도 요청량에 비례할 거라고 예상했다. 실제 청구 내역은 달랐다.
2. 요청은 적은데 과금은 계속됐다
하루 백여 건을 처리하고 한 건에 약 0.5초가 걸린다면, 실제 연산 시간은 하루 1분도 안 돼야 한다. 그런데 Cloud Run 청구 내역에는 주당 약 319K CPU초가 찍혀 있었다. 검색 요청량으로는 설명이 안 되는 숫자였다.
Cloud Run 지표와 서버 요청 기록을 함께 보니, 전체 CPU 사용량의 약 82%가 검색 처리가 아니라 유휴 연결을 유지하는 데 쓰이고 있었다.
원인은 MCP Streamable HTTP 전송의 GET 스트림이었다. MCP 클라이언트는 도구를 호출하는 POST 요청 말고도, 서버가 클라이언트에 알림을 보낼 수 있도록 GET 기반 SSE(Server-Sent Events) 연결을 쓸 수 있다. 일부 에이전트 클라이언트는 이 GET 스트림을 기본으로 연결했다. 연결이 약 5분 뒤 끊기면 곧바로 다시 연결하면서, 결국 하루 종일 서버와 소켓을 붙잡고 있었다.
문제는 Cloud Run이 열려 있는 스트림을 처리 중인 요청으로 본다는 점이다. 검색 요청이 하나도 없어도 SSE 연결이 하나라도 살아 있으면 인스턴스가 줄지 않았고, CPU 할당과 과금도 이어졌다. 요청 건수로는 아주 작은 서비스였지만, 인프라 입장에서는 긴 요청을 쉬지 않고 처리하고 있던 셈이다.
3. 쓰지 않는 GET 스트림 차단
MCP 사양을 다시 보니 GET 스트림은 서버가 클라이언트에 먼저 알림을 보낼 때 쓰는 선택 기능이었다. 이 서버는 요청을 받아 검색 결과를 돌려주는 단방향 요청·응답 구조다. 서버가 먼저 이벤트를 보낼 일이 없으니 GET 스트림을 열어 둘 이유도 없었다.
그래서 /mcp 경로로 들어오는 단독 GET 요청을 HTTP 405(Method Not Allowed)로 즉시 거부하게 고쳤다. 도구 호출에 쓰는 POST는 그대로 뒀고, 클라이언트 동작에도 문제가 없었다.
수정 전에는 연결 하나가 약 301초 유지됐는데, 수정 후에는 약 3밀리초 만에 끝났다. 이후 Cloud Run 사용량은 긴 연결이 아니라 실제 검색 처리 시간에 비례하기 시작했다. 사용자가 늘어난 지금도 서버 비용은 호출량에 따라 월 몇천 원에서 몇만 원 사이다.
프로토콜이 지원하는 기능을 모두 열어 두는 것이 꼭 좋은 설계는 아니다. 운영 환경에서는 실제로 쓰는 기능만 분명히 허용하는 편이 비용과 안정성 모두에서 안전하다.
4. 매일 도는 데이터 갱신 자동화
비용 문제를 푼 뒤에는 지식 데이터를 사람 손 없이 최신으로 유지하는 작업을 했다.
커뮤니티 글 크롤러는 네이버의 데이터센터 IP 차단 때문에 클라우드가 아니라 로컬 Mac에서 돌고, 매일 오후 8시에 launchd 스케줄러가 실행한다. 매번 전체를 다시 처리하지 않고, 문서마다 content_hash를 비교해 새로 쓰였거나 바뀐 글만 증분 처리한다.
- 새로 쓰인 글 수집
- 수정된 글의 변경 여부 확인
- 바뀐 문서만 청크 재생성
- 바뀐 청크만 임베딩 재호출
- 원문에서 삭제된 미참조 청크 정리
- 렉시컬 검색 인덱스 갱신
첫 증분 처리에서는 그동안 쌓인 신규 글 60건이 한 번에 반영됐다. 전체 청크는 12,378개에서 12,545개로 늘었고, 렉시컬 인덱스도 함께 다시 만들었다.
인덱스를 통째로 바꿔야 할 때는 Blue/Green 방식으로 배포한다. 새 후보 인덱스는 3편의 평가 검증을 모두 통과해야 활성 인덱스가 된다. 활성 인덱스는 KB_ACTIVE_INDEX 환경 변수 하나로 제어해서, 문제가 생기면 바로 이전 인덱스로 되돌릴 수 있다.
5. 매주 도는 검증 자동화
데이터를 최신으로 유지하는 것만으로는 부족했다. 운영 중 발견한 주의 사항과 반복될 만한 오류도 따로 지식으로 관리해야 했다. 이를 위해 1편에서 소개한 check_before_change 도구를 쓴다. 특정 작업 전에 관련 주의 사항이 있는지 알려 주는 도구로, 일반 문서에 적기 어려운 운영 경험이나 반복될 수 있는 실수를 별도 항목으로 관리한다.
다만 이런 항목은 일반 검색 결과보다 조심해서 다뤄야 한다. 검색 결과는 사용자가 원문을 읽고 판단할 수 있지만, 주의 사항은 "이 경우에는 이렇게 해야 한다"는 단정적인 형태로 전달된다. 잘못된 내용이 들어가면 비슷한 작업마다 잘못된 안내가 반복된다.
그래서 새 항목은 먼저 초안 상태로 격리하고 운영 검색에서는 뺀다. 매주 월요일 오전 GitHub Actions에서 자동 검증을 돌리고, 모든 조건을 통과한 항목만 정식으로 반영한다. 검증하는 것은 다음과 같다.
- 필수 필드와 참조 정보가 올바른지
- 항목에 연결된 자체 점검 절차가 정상 수행되는지
- 항목을 검증할 테스트 질의가 등록돼 있는지
- API 키나 개인 토큰으로 의심되는 값이 없는지
- 검색 기준선과 단위 테스트가 유지되는지
- 새 항목 반영 후 기존 검증 항목에 회귀가 없는지
하나라도 통과하지 못하면 정식 반영을 멈추고 실패 사유만 남긴다. 항목마다 자동 승격 가능 여부도 따로 관리해서, 사람이 직접 내용을 봐야 하는 항목은 파이프라인이 임의로 반영하지 않는다.
최근 운영에서 발견한 주의 사항 두 건도 같은 방식으로 등록했다. 발견 당일 초안 항목을 쓰고, 같은 상황에서 그 항목이 제대로 검색되는지 확인하는 검증 케이스를 더했다. 그 결과 preflight 검증 케이스는 23건에서 27건으로 늘었다.
한 번 겪은 문제를 개인의 기억에만 두지 않고, 다음 작업에서는 시스템이 먼저 알려 주게 만드는 것이 이 구조의 목적이다.
지금은 필수 데이터 갱신이 실패하면 배포 파이프라인 전체가 즉시 실패하도록 바꿨다. 기능이 조용히 꺼진 채 배포되는 것보다, 배포가 분명히 실패하고 담당자에게 알림이 가는 편이 안전하다.
6. 서비스 운영보다 비쌌던 평가 비용
서버 운영비를 줄인 뒤에도 예상보다 많은 비용이 나오는 곳이 하나 더 있었다. 3편에서 소개한 검색·답변 품질 평가 비용이다.
MCP 서버가 검색 한 건을 처리하는 비용은 아주 작다. 반면 생성된 답변을 평가하는 심판 LLM은 훨씬 많은 토큰과 연산을 쓴다. 생성 평가 풀셋 54문항에 모델과 검색 옵션 조합을 적용해 반복 실행하면, 실험 세션 한 번에도 수만 원이 나왔다.
평가 비용이 높으면 실험을 자주 못 한다. 그러면 결국 숫자가 아니라 감으로 변경을 판단하게 된다. 이를 막으려고 평가 구조를 네 방향으로 고쳤다.
- 검색 전용 평가 분리. 정답 문서의 라벨 ID만 비교한다. 기대한 문서가 결과에 들었는지만 보면 되니 LLM 호출이 필요 없다.
- 경량 심판. 심판 LLM을 더 저렴한 경량 모델로 바꾸고 Thinking 옵션을 껐다.
- 스모크 테스트. 개발 중에는 카테고리별 대표 문항만 돌리고, 전체 평가셋은 주요 마일스톤에서만 돌린다.
- 답변 캐시. 질문, 대화 맥락, 검색 근거, 프롬프트 버전을 묶은 키로 생성 답변을 디스크에 캐싱했다. 검색 로직만 바뀐 경우에는 기존 답변을 재사용해 생성 비용이 들지 않는다.
이후로는 검색 방식과 옵션을 바꿔 가며 비교 실험을 반복하기가 훨씬 쉬워졌다.
다만 경량 심판의 판정을 절대 기준으로 쓰지는 않았다. 정답 문서 포함 여부나 회수율처럼 명확하게 계산되는 지표에는 자동 평가를 쓰고, 문장의 정확성이나 설명 품질처럼 해석이 필요한 부분은 따로 확인했다. 평가 비용을 줄이는 것만큼, 싸진 평가 결과를 어디까지 믿을지 구분하는 일도 중요했다.
7. 지금의 운영 비용과 시리즈 마무리
지금 MCP 서버의 고정 운영비는 사실상 0원에 가깝다. 답변 생성은 각 사용자의 코딩 에이전트가 맡으니 서버에는 답변 생성 비용이 없다. 서버는 검색 질의마다 임베딩을 한 번 호출하고, Firestore에서 관련 청크 약 30개를 조회한다. 데이터 수집과 재색인도 바뀐 분량만 처리한다. 실제 월 청구액은 호출량과 GCP 무료 제공량 범위에 따라 소액으로 오르내린다.
이 시리즈는 에이전트가 E-GENE의 개념을 몰라 엉뚱한 답을 내놓는 문제에서 시작했다. 답변 생성을 각자의 에이전트에 맡기고 서버는 정확한 근거만 돌려주게 바꿨고, 검색 품질을 개선했고, 그 결정이 실제로 효과가 있었는지 평가셋으로 검증했다. 마지막으로 이번 편에서는 예상하지 못한 운영 비용의 원인을 찾고, 데이터 갱신과 검증을 자동화해 사람이 계속 지켜보지 않아도 도는 구조를 만들었다.