오늘도 공부
Cerebras는 어떻게 사내 지식 검색 시스템을 구축했을까? 본문
PostgreSQL 하나로 구현한 엔터프라이즈 RAG 아키텍처 분석
최근 생성형 AI가 기업 업무에 빠르게 도입되면서, 사내 문서와 대화 기록을 AI가 검색하고 활용할 수 있도록 만드는 엔터프라이즈 지식 검색 시스템이 주목받고 있다.
그러나 실제 기업 환경에서 RAG 시스템을 운영하는 일은 생각보다 단순하지 않다.
기업의 지식은 한곳에 정리되어 있지 않기 때문이다.
- 업무 대화는 Slack에서 이루어진다.
- 소스 코드는 GitHub에 저장된다.
- 프로젝트 일정과 이슈는 Jira에서 관리된다.
- 공식 문서는 Google Drive나 Notion에 존재한다.
- 중요한 결정은 회의록이나 이메일에 남는다.
이처럼 데이터가 여러 서비스에 분산된 상황에서, AI가 사용자의 질문에 정확한 답을 제공하려면 단순한 벡터 검색 이상의 설계가 필요하다.
이번 글에서는 AI 반도체 기업 Cerebras가 공개한 사내 지식 검색 시스템인 Cerebras Knowledge의 구조를 살펴본다.
특히 다음 네 가지를 중심으로 분석한다.
- 전용 벡터 데이터베이스 없이 PostgreSQL을 선택한 이유
- Slack 데이터를 검색 가능한 지식으로 변환하는 방법
- 키워드·벡터·희소성·신선도를 결합한 하이브리드 검색
- 인간용 UI와 AI 에이전트용 MCP 도구를 분리한 설계
1. Cerebras Knowledge란 무엇인가?
Cerebras Knowledge는 Cerebras 내부의 여러 데이터 소스를 통합 검색하기 위해 구축된 사내 지식 시스템이다.
제공된 기사에 따르면 이 시스템은 출시 후 약 3개월 만에 하루 15,000건 이상의 질문을 처리하는 수준으로 사용되고 있다.
질문을 보내는 주체도 사람만이 아니다.
- 일반 직원
- 개발자
- 사내 자동화 시스템
- AI 에이전트
- 코딩 에이전트
즉, Cerebras Knowledge는 단순한 사내 챗봇이 아니라 인간과 AI가 함께 사용하는 조직 지식 인프라에 가깝다.
일반적인 사내 RAG 시스템은 다음과 같은 흐름을 따른다.
Slack · GitHub · Jira · 문서
↓
데이터 수집 및 청크 분할
↓
임베딩 생성
↓
벡터 데이터베이스 저장
↓
유사도 검색
↓
LLM 답변 생성
Cerebras의 구조도 큰 흐름은 비슷하다.
다만 다음 두 가지에서 차별점이 있다.
첫째, Pinecone이나 전용 검색 클러스터 대신 PostgreSQL을 중심으로 시스템을 구성했다.
둘째, Slack 메시지를 그대로 임베딩하지 않고, 검색에 적합한 형태로 정규화한 후 저장했다.
2. 왜 사내 지식 검색은 어려운가?
2.1 지식은 작성하기 편한 장소에서 만들어진다
많은 기업이 지식 관리 문제를 해결하기 위해 다음과 같은 목표를 세운다.
모든 정보를 하나의 플랫폼에 모으자.
이를 흔히 Single Source of Truth, 즉 단일 진실 공급원이라고 부른다.
이론적으로는 합리적인 접근이다. 하지만 실제 조직에서는 제대로 실현되기 어렵다.
사람들은 정보를 관리하기 좋은 장소보다, 업무를 처리하기 편한 장소에 기록하기 때문이다.
예를 들어 개발팀은 Slack에서 기술적인 결정을 내리고, GitHub Pull Request에서 구현 방향을 토론하며, Jira 이슈에 진행 상태를 남긴다.
이 정보를 모두 특정 지식 관리 플랫폼으로 다시 옮기는 작업은 지속되기 어렵다.
따라서 현실적인 사내 지식 검색 시스템은 모든 사용자의 작성 습관을 바꾸기보다, 기존 도구에 흩어진 정보를 수집하고 검색할 수 있어야 한다.
2.2 Slack 메시지는 일반 문서와 다르다
Slack 데이터는 RAG에서 다루기 까다로운 대표적인 비정형 데이터다.
일반 문서는 하나의 문단이나 페이지에 비교적 완전한 의미가 담겨 있다. 반면 Slack 메시지는 대화의 전후 맥락에 의존한다.
예를 들어 다음과 같은 메시지가 있다고 가정해 보자.
A: 이번 장애 원인이 뭐였나요?
B: 캐시 만료 정책 때문입니다.
C: 확인했습니다.
B: 정확히는 Redis TTL이 배포 과정에서 기본값으로 변경됐습니다.
각 메시지를 개별 청크로 저장하면 문제가 발생한다.
캐시 만료 정책 때문입니다라는 메시지는 질문과 분리될 경우 무엇을 설명하는지 알 수 없다.
확인했습니다와 같은 짧은 메시지는 검색 가치가 거의 없지만, 임베딩 유사도에 따라 검색 결과 상단에 나타날 수 있다.
또한 가장 중요한 기술적 설명이 스레드 중간이나 마지막에 위치할 수도 있다.
즉, Slack 데이터를 메시지 단위로 단순 벡터화하면 다음과 같은 문제가 발생한다.
- 전후 맥락 손실
- 짧은 메시지의 과도한 노출
- 검색 가치가 낮은 응답 포함
- 중요한 장문 설명 누락
- 질문과 답변 관계 분리
Cerebras는 이를 해결하기 위해 Slack 데이터를 검색 전에 가공하는 파이프라인을 구축했다.
3. PostgreSQL 하나로 검색 시스템 구성하기
Cerebras Knowledge에서 가장 눈에 띄는 선택은 전용 벡터 데이터베이스를 사용하지 않았다는 점이다.
제공된 기사에 따르면 검색 인프라는 PostgreSQL을 중심으로 구성되어 있다.
- 키워드 검색: PostgreSQL Full Text Search와 GIN 인덱스
- 벡터 검색: pgvector
- 벡터 인덱스: HNSW
- 구조화 데이터 및 메타데이터: 일반 PostgreSQL 컬럼
- 원문 데이터: 동일 데이터베이스에 저장
일반적인 RAG 서비스에서는 다음과 같이 여러 저장소를 분리하는 경우가 많다.
PostgreSQL
- 사용자
- 권한
- 문서 메타데이터
Vector DB
- 임베딩
- 벡터 검색
Elasticsearch
- 키워드 검색
- 로그 검색
반면 Cerebras 방식은 이를 하나의 PostgreSQL 시스템으로 단순화한다.
PostgreSQL
- 원문
- 정규화된 문서
- 임베딩
- 키워드 인덱스
- 메타데이터
- 시간 정보
- 데이터 출처
3.1 PostgreSQL 중심 설계의 장점
운영 복잡도 감소
데이터베이스 종류가 늘어나면 배포, 백업, 모니터링, 장애 대응 체계도 복잡해진다.
PostgreSQL 하나를 사용하면 다음 항목을 일관되게 관리할 수 있다.
- 백업
- 복구
- 마이그레이션
- 트랜잭션
- 접근 권한
- 모니터링
- 데이터 정합성
데이터 동기화가 단순하다
문서 원문은 PostgreSQL에 있고 임베딩은 별도 벡터 DB에 저장되어 있다면, 문서 수정 시 두 시스템을 모두 갱신해야 한다.
이때 다음과 같은 오류가 발생할 수 있다.
- 원문은 최신인데 벡터는 이전 버전인 상태
- 삭제된 문서의 벡터가 남아 있는 상태
- 문서는 존재하지만 임베딩 생성에 실패한 상태
- 재처리 중 중복 벡터가 생성된 상태
동일 데이터베이스에 원문과 임베딩을 저장하면 이러한 동기화 문제를 줄일 수 있다.
개발자의 학습 비용이 낮다
PostgreSQL은 대부분의 백엔드 엔지니어가 사용 경험을 가지고 있다.
새로운 벡터 데이터베이스의 쿼리 문법, 운영 방식, 백업 정책, 클러스터 구조를 추가로 학습하지 않아도 된다는 장점이 있다.
3.2 PostgreSQL만으로 충분한가?
모든 RAG 시스템에 PostgreSQL이 적합한 것은 아니다.
다음과 같은 상황에서는 전용 검색 시스템이 더 적절할 수 있다.
- 수억 개 이상의 임베딩을 검색해야 하는 경우
- 초당 검색 요청이 매우 높은 경우
- 복잡한 언어별 형태소 분석이 필요한 경우
- 여러 지역에 걸친 분산 검색이 필요한 경우
- 벡터 인덱스를 독립적으로 확장해야 하는 경우
- 고급 필터링과 랭킹 기능이 필요한 경우
그러나 사내 지식 검색처럼 데이터 규모가 통제 가능하고, 메타데이터 필터링과 정합성이 중요한 환경에서는 PostgreSQL이 실용적인 선택이 될 수 있다.
핵심은 “전용 벡터 DB가 항상 필요하다”가 아니라, 시스템 규모와 운영 조건에 따라 저장소를 선택해야 한다는 점이다.
4. Slack 스레드를 검색 가능한 문서로 변환하기
Cerebras는 Slack 메시지를 그대로 임베딩하지 않는다.
먼저 스레드 전체를 LLM에 입력하고, 검색에 적합한 구조화 문서로 변환한다.
개념적으로는 다음과 같은 처리 과정이다.
Slack 원본 스레드
↓
대화 분석
↓
핵심 주제 추출
↓
질문과 답변 관계 복원
↓
기술적 결론 요약
↓
예상 검색 질문 생성
↓
정규화 문서 저장
↓
임베딩 생성
예를 들어 원본 Slack 스레드가 다음과 같다고 하자.
A: 배포 이후 API 응답이 느려진 이유가 뭔가요?
B: DB 문제는 아닌 것 같습니다.
C: 로그를 보니 캐시 적중률이 20%대로 내려갔습니다.
B: 배포 스크립트에서 Redis TTL 환경 변수가 누락됐네요.
A: 기본값이 적용된 건가요?
B: 네. 수정 후 캐시 적중률이 정상화됐습니다.
이를 검색용 문서로 변환하면 다음과 같이 정리할 수 있다.
제목:
배포 이후 API 응답 지연과 Redis TTL 환경 변수 누락
요약:
배포 스크립트에서 Redis TTL 환경 변수가 누락되면서
기본 만료 시간이 적용되었고, 캐시 적중률이 약 20%대로 감소했다.
환경 변수를 복원한 후 캐시 적중률과 API 응답 속도가 정상화되었다.
예상 검색 질문:
- 배포 이후 API 응답이 느려진 이유는 무엇인가?
- Redis 캐시 적중률이 감소한 원인은 무엇인가?
- TTL 환경 변수 누락으로 발생한 장애 사례가 있는가?
관련 키워드:
Redis, TTL, 캐시 적중률, 배포 스크립트, API 지연
이렇게 변환하면 사용자가 원문의 정확한 표현을 기억하지 못하더라도 관련 내용을 찾기 쉬워진다.
4.1 예상 질문을 생성하는 이유
임베딩 검색은 질문과 문서가 의미적으로 유사할수록 잘 동작한다.
하지만 원본 문서는 사실을 설명하는 서술형 문장이고, 사용자는 의문형으로 검색하는 경우가 많다.
예를 들어 문서에는 다음과 같이 기록되어 있을 수 있다.
Redis TTL 환경 변수가 누락되어 기본값이 적용됐다.
사용자는 다음과 같이 질문할 수 있다.
지난번 배포 후 API가 느려진 이유가 뭐였지?
두 문장은 동일한 사건을 가리키지만 사용하는 단어와 문장 구조가 다르다.
검색용 예상 질문을 미리 생성하면 사용자 질문과 임베딩 공간에서 가까운 표현을 확보할 수 있다.
이는 흔히 다음과 같은 이름으로 불린다.
- Question Generation
- Hypothetical Question Indexing
- Query-oriented Chunk Enrichment
- Document Expansion
4.2 원문도 별도로 보존한다
정규화된 요약만 저장하면 LLM이 생략한 세부 정보가 손실될 수 있다.
따라서 원본 Slack 텍스트도 함께 저장해야 한다.
권장 구조는 다음과 같다.
knowledge_chunks
├── source_type
├── source_id
├── thread_id
├── raw_content
├── normalized_content
├── summary
├── hypothetical_questions
├── keywords
├── embedding
├── author_ids
├── channel_id
├── created_at
├── updated_at
└── metadata
검색 단계에서는 정규화된 문서와 예상 질문을 벡터 검색에 활용하고, 원문은 키워드 검색과 최종 근거 제공에 활용할 수 있다.
5. 긴 Slack 스레드를 처리하는 방법
스레드 전체를 하나의 청크로 만드는 방법도 완전하지 않다.
스레드가 지나치게 길면 중간에 있는 중요한 정보가 임베딩에서 희석될 수 있기 때문이다.
예를 들어 하나의 스레드에 다음 내용이 모두 포함되어 있을 수 있다.
- 장애 발생 보고
- 원인 분석
- 임시 대응
- 코드 수정
- 재발 방지 대책
- 배포 결과
- 후속 작업
이 스레드를 하나의 벡터로 표현하면 개별 주제의 의미가 약해질 수 있다.
Cerebras는 긴 스레드에서 특정 조건을 충족하는 연속 발언을 별도 청크로 분리하는 방식을 사용한 것으로 소개된다.
특히 한 사용자가 연속해서 작성한 장문의 기술 설명은 독립적인 지식 단위일 가능성이 높다.
개념적인 분할 기준은 다음과 같이 설계할 수 있다.
- 동일 작성자의 연속 메시지
- 일정 글자 수 이상의 메시지 묶음
- 코드 블록을 포함한 설명
- URL이나 문서 링크를 포함한 설명
- 특정 기술 용어가 반복되는 구간
- 질문에 대한 직접적인 원인 또는 해결책
- 최종 결정이나 합의를 나타내는 문장
이를 기반으로 스레드에서 여러 종류의 청크를 만들 수 있다.
Slack Thread
├── 스레드 전체 요약 청크
├── 원인 분석 청크
├── 기술 설명 청크
├── 해결 방법 청크
└── 최종 결정 청크
이 구조는 한 가지 청킹 방식만 사용하는 것보다 검색 recall을 높이는 데 유리하다.
6. 검색할 때 네 가지 신호를 함께 사용한다
Cerebras Knowledge는 사용자의 질문이 들어왔을 때 하나의 검색 방식만 사용하지 않는다.
서로 다른 검색 신호를 병렬로 계산한 후 결과를 결합한다.
기사에서 소개된 주요 신호는 다음 네 가지다.
- 키워드 검색
- 벡터 검색
- IDF 기반 희귀 단어 가중치
- 정보 신선도
6.1 키워드 검색
키워드 검색은 사용자가 입력한 단어가 문서에 직접 포함되어 있는지를 확인한다.
예를 들어 다음과 같은 검색에 강하다.
- 정확한 클래스 이름
- 오류 코드
- 티켓 번호
- 프로젝트 이름
- 사람 이름
- API 엔드포인트
- 라이브러리 이름
- 커밋 해시
ERR_CONNECTION_RESET
JIRA-1842
payment-webhook
RedisClusterManager
이런 값은 의미 기반 임베딩보다 정확한 문자열 매칭이 중요하다.
PostgreSQL에서는 tsvector, tsquery, GIN 인덱스를 사용해 전체 텍스트 검색을 구현할 수 있다.
개념적인 SQL은 다음과 같다.
SELECT
id,
normalized_content,
ts_rank(search_vector, query) AS keyword_score
FROM knowledge_chunks,
plainto_tsquery('simple', :query) AS query
WHERE search_vector @@ query
ORDER BY keyword_score DESC
LIMIT 50;
6.2 벡터 검색
벡터 검색은 표현이 다르더라도 의미가 비슷한 문서를 찾는 데 사용된다.
예를 들어 사용자가 다음과 같이 질문한다고 하자.
서비스 배포 후 갑자기 느려졌던 사건
문서에는 다음 표현만 존재할 수 있다.
릴리스 이후 캐시 적중률 저하로 API latency가 증가했다.
정확히 일치하는 단어는 적지만 의미는 유사하다.
pgvector를 사용하면 다음과 같은 형태로 검색할 수 있다.
SELECT
id,
normalized_content,
1 - (embedding <=> :query_embedding) AS vector_score
FROM knowledge_chunks
ORDER BY embedding <=> :query_embedding
LIMIT 50;
6.3 IDF 기반 희귀 단어 가중치
IDF는 Inverse Document Frequency, 즉 역문서 빈도다.
많은 문서에 반복해서 등장하는 단어는 검색 가치가 낮고, 일부 문서에만 등장하는 희귀 단어는 검색 가치가 높다고 판단한다.
예를 들어 Slack에는 다음과 같은 표현이 매우 자주 나타난다.
- 확인했습니다
- 감사합니다
- 알겠습니다
- 가능합니다
- 좋습니다
- 진행하겠습니다
이런 단어가 포함된 메시지는 검색 결과에서 높은 우선순위를 받을 필요가 없다.
반면 다음 표현은 특정 문제와 직접 연결될 가능성이 높다.
- NCCL timeout
- HNSW index
- memory fragmentation
- TTL override
- compiler regression
IDF 신호를 사용하면 검색 가치가 낮은 상투적 표현을 억제하고, 기술적으로 구체적인 문장을 우대할 수 있다.
6.4 정보 신선도
사내 지식은 시간이 지나면서 변경된다.
6개월 전의 배포 절차보다 지난주에 개정된 배포 절차가 더 정확할 가능성이 높다.
따라서 검색 결과에 시간 가중치를 적용할 수 있다.
예를 들어 다음과 같은 감쇠 함수를 사용할 수 있다.
freshness_score = exp(-λ × document_age)
여기서 document_age는 문서 생성 또는 갱신 후 경과 시간이며, λ는 오래된 정보의 점수를 얼마나 빠르게 낮출지를 결정한다.
다만 최신 문서가 항상 정답인 것은 아니다.
오래된 장애 회고, 기술적 결정 기록, 설계 배경은 시간이 지나도 가치가 유지될 수 있다.
따라서 데이터 유형에 따라 신선도 가중치를 다르게 적용하는 것이 안전하다.
운영 절차 → 신선도 가중치 높음
현재 담당자 정보 → 신선도 가중치 높음
장애 회고 → 신선도 가중치 중간
아키텍처 결정 → 신선도 가중치 중간
역사적 기록 → 신선도 가중치 낮음
7. RRF로 검색 결과를 통합한다
키워드 검색과 벡터 검색은 각각 다른 점수 체계를 사용한다.
예를 들어 키워드 검색 점수는 0.72, 벡터 유사도는 0.84, 신선도 점수는 0.56처럼 계산될 수 있다.
이 점수들을 단순히 더하면 각 점수의 범위와 분포가 달라 결과가 왜곡될 수 있다.
Cerebras는 여러 랭킹을 결합하기 위해 RRF, Reciprocal Rank Fusion을 사용하는 것으로 소개된다.
RRF는 검색 결과의 절대 점수보다 각 검색 방식에서 몇 위에 올랐는지를 사용한다.
기본적인 식은 다음과 같다.
RRF Score(d) = Σ 1 / (k + rankᵢ(d))
- d: 문서
- rankᵢ(d): i번째 검색 방식에서 해당 문서의 순위
- k: 상위 결과의 점수 차이를 완화하는 상수
예를 들어 어떤 문서가 다음 순위를 기록했다고 하자.
키워드 검색: 2위
벡터 검색: 5위
신선도 랭킹: 3위
k = 60이면 다음과 같이 계산할 수 있다.
RRF = 1 / 62 + 1 / 65 + 1 / 63
RRF의 장점은 다음과 같다.
- 검색 점수 정규화가 필요하지 않다.
- 서로 다른 검색 엔진의 결과를 쉽게 결합할 수 있다.
- 특정 검색 방식 하나에 과도하게 의존하지 않는다.
- 구현이 단순하다.
- 하이브리드 검색의 안정성이 높다.
8. 소형 리랭커로 최종 문서를 선별한다
RRF로 검색 결과를 합친 뒤에도 상위 문서가 모두 질문에 적합한 것은 아니다.
따라서 상위 후보에 대해 리랭커를 적용한다.
일반적인 검색 파이프라인은 다음과 같다.
사용자 질문
↓
키워드 검색 Top 50
벡터 검색 Top 50
IDF 랭킹 Top 50
신선도 랭킹 Top 50
↓
RRF 통합
↓
상위 30~50개 후보
↓
소형 리랭커
↓
최종 Top 10
↓
LLM 컨텍스트 구성
리랭커는 질문과 각 문서를 함께 입력받아 관련성을 직접 판단한다.
벡터 검색이 질문과 문서를 각각 독립적으로 임베딩하는 bi-encoder 방식이라면, 리랭커는 질문과 문서를 동시에 비교하는 cross-encoder 방식으로 구현되는 경우가 많다.
리랭커는 일반적으로 벡터 검색보다 정확하지만 계산 비용이 높다.
따라서 전체 문서를 리랭킹하지 않고, 1차 검색으로 좁힌 후보에만 적용한다.
9. 검색된 청크에 주변 문맥을 다시 붙인다
검색용 청크는 정확한 검색을 위해 작게 분할해야 한다.
그러나 LLM에 제공하는 컨텍스트는 충분한 문맥을 포함해야 한다.
이 때문에 검색에 사용한 청크와 생성 모델에 제공하는 청크를 동일하게 구성할 필요는 없다.
예를 들어 검색 결과가 Slack 스레드 중간의 기술 설명이라면, 최종적으로 LLM에 전달할 때는 다음 내용을 추가할 수 있다.
- 최초 질문
- 검색된 핵심 메시지
- 바로 앞 메시지
- 바로 뒤 메시지
- 스레드 최종 결론
- 연결된 문서나 코드 링크
- 작성자와 작성 시점
이를 context expansion, neighbor expansion, parent-child retrieval 등의 방식으로 구현할 수 있다.
작은 청크로 검색
↓
관련 청크 선택
↓
부모 문서 또는 주변 청크 로드
↓
확장된 컨텍스트를 LLM에 전달
이 방식은 검색 정확도와 답변 완성도를 동시에 확보하는 데 유리하다.
10. MCP에는 완성형 답변이 아니라 검색 도구를 공개한다
Cerebras Knowledge는 인간이 사용하는 웹 인터페이스뿐 아니라 AI 에이전트가 호출할 수 있는 MCP 인터페이스도 제공한다.
여기서 중요한 설계 원칙은 AI 에이전트에게 다음과 같은 단일 도구를 제공하지 않는다는 점이다.
answer_company_question(question)
대신 보다 작은 검색 도구를 제공한다.
search_slack(query)
search_code(query)
search_documents(query)
search_jira(query)
이러한 도구를 흔히 primitive tool, 즉 기본 단위 도구라고 부를 수 있다.
AI 에이전트는 사용자의 요청을 분석하고 필요한 도구를 스스로 선택한다.
예를 들어 다음 질문이 들어왔다고 하자.
결제 웹훅 중복 처리 문제가 왜 발생했고 현재 코드는 어떻게 수정됐나요?
에이전트는 다음과 같은 순서로 동작할 수 있다.
1. search_slack
- 장애 당시 논의와 원인 검색
2. search_jira
- 관련 이슈와 후속 작업 검색
3. search_code
- 현재 구현 코드와 커밋 검색
4. 결과 비교
- 과거 원인과 현재 수정 상태를 연결
5. 최종 답변 생성
검색 시스템이 모든 질문을 한 번에 해결하려 하기보다, 에이전트가 조합할 수 있는 작은 기능을 제공하는 구조다.
10.1 인간용 인터페이스와 AI용 인터페이스의 차이
인간 사용자는 일반적으로 다음과 같은 경험을 원한다.
- 질문 입력
- 답변 확인
- 근거 문서 열기
- 추가 질문
- 검색 결과 필터링
반면 AI 에이전트는 다음을 필요로 한다.
- 구조화된 입력과 출력
- 데이터 출처 구분
- 검색 범위 지정
- 페이지네이션
- 메타데이터 필터
- 재현 가능한 결과
- 근거 식별자
- 원문 조회 기능
따라서 인간용 챗봇 API와 AI 에이전트용 검색 API를 분리하는 것은 합리적인 접근이다.
11. 전체 아키텍처 예시
Cerebras 사례를 참고해 사내 지식 검색 시스템을 구성하면 다음과 같은 구조를 설계할 수 있다.
┌─────────────────────────────────────┐
│ 데이터 소스 │
│ Slack · GitHub · Jira · Drive │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 수집 및 동기화 계층 │
│ Webhook · Polling · Incremental Sync│
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 전처리 파이프라인 │
│ 정규화 · 중복 제거 · 권한 추출 │
│ 스레드 요약 · 질문 생성 · 청크 분할 │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ PostgreSQL │
│ 원문 · 요약 · 메타데이터 · 임베딩 │
│ Full Text Search · pgvector · HNSW │
└──────────────────┬──────────────────┘
│
▼
┌─────────────────────────────────────┐
│ 검색 계층 │
│ Keyword · Vector · IDF · Freshness │
│ RRF · Reranker · Context Expansion │
└──────────────────┬──────────────────┘
│
┌────────┴────────┐
▼ ▼
┌─────────────────┐ ┌─────────────────┐
│ 인간용 Web UI │ │ AI Agent · MCP │
│ 검색 · 채팅 │ │ search_slack │
│ 출처 · 피드백 │ │ search_code │
└─────────────────┘ └─────────────────┘
12. 실제 구현 시 가장 어려운 문제: 권한 관리
기사에서도 한계로 지적된 부분이 권한 관리다.
사내 데이터는 모든 직원에게 동일하게 공개되지 않는다.
예를 들어 다음과 같은 접근 제한이 존재할 수 있다.
- 비공개 Slack 채널
- 경영진 전용 문서
- 인사 평가 자료
- 법무 관련 문서
- 특정 프로젝트 참여자 전용 GitHub 저장소
- 고객 계약 자료
- 보안 사고 보고서
원본 서비스의 검색 API를 직접 사용하는 경우에는 해당 서비스의 권한 체계를 활용할 수 있다.
예를 들어 사용자의 Slack 토큰으로 Slack 검색 API를 호출하면, 사용자가 접근 가능한 채널만 검색된다.
하지만 데이터를 별도 PostgreSQL에 복제하면 권한 모델도 함께 복제해야 한다.
원본 데이터 복제
+
권한 정보 복제
+
사용자 그룹 동기화
+
검색 시 권한 필터링
이 중 하나라도 누락되면 민감한 정보가 노출될 수 있다.
12.1 권한 필터링 기본 구조
각 지식 청크에 접근 제어 정보를 저장할 수 있다.
knowledge_chunks
├── allowed_user_ids
├── allowed_group_ids
├── source_channel_id
├── source_repository_id
├── visibility
└── classification_level
검색할 때는 벡터 유사도 계산만 수행하는 것이 아니라, 현재 사용자의 권한 조건을 함께 적용해야 한다.
SELECT
id,
normalized_content,
1 - (embedding <=> :query_embedding) AS score
FROM knowledge_chunks
WHERE
visibility = 'public'
OR :user_id = ANY(allowed_user_ids)
OR allowed_group_ids && :user_group_ids
ORDER BY embedding <=> :query_embedding
LIMIT 20;
중요한 점은 검색 후 결과를 제거하는 것이 아니라, 검색 단계부터 권한 조건을 적용해야 한다는 것이다.
검색 후 필터링 방식은 다음과 같은 문제를 일으킬 수 있다.
- 권한 없는 문서가 상위 결과를 차지함
- 권한 필터링 후 결과가 부족함
- 검색 점수나 로그를 통해 정보가 간접 노출됨
- 리랭커나 LLM에 민감한 문서가 전달될 가능성
12.2 삭제와 권한 변경도 동기화해야 한다
데이터 수집보다 더 어려운 것은 삭제와 권한 변경이다.
예를 들어 다음 상황을 처리해야 한다.
- 공개 채널이 비공개 채널로 변경됨
- 사용자가 프로젝트에서 제외됨
- GitHub 저장소 접근 권한이 제거됨
- 문서가 삭제됨
- Slack 메시지가 수정됨
- 민감 정보가 포함되어 원문이 삭제됨
RAG 시스템에 이전 데이터가 남아 있다면 원본에서 권한이 제거되더라도 계속 검색될 수 있다.
따라서 동기화 시스템은 생성 이벤트뿐 아니라 다음 이벤트를 추적해야 한다.
Create
Update
Delete
Permission Change
Membership Change
Visibility Change
13. 하나의 거대한 지식 기반은 계속 유지될 수 있을까?
초기에는 모든 데이터를 하나의 지식 기반에 저장하는 방식이 단순하고 효율적일 수 있다.
그러나 데이터와 사용 사례가 증가하면 하나의 검색 랭킹으로 모든 질문을 처리하기 어려워질 수 있다.
예를 들어 다음 질문은 서로 다른 검색 전략이 필요하다.
코드 오류 원인을 찾아줘.
지난 분기 영업 전략을 알려줘.
휴가 신청 절차가 어떻게 되나요?
고객 A와 체결한 계약 조건을 찾아줘.
코드 검색은 함수명, 경로, 심볼, 커밋 관계가 중요하다.
인사 규정 검색은 최신 공식 문서와 정확한 조항이 중요하다.
영업 자료 검색은 고객, 산업, 시점, 담당 조직이 중요하다.
따라서 시스템이 성장하면 도메인별 검색 구성이 필요할 수 있다.
Engineering Knowledge
- GitHub
- Jira
- 기술 Slack
- 설계 문서
Sales Knowledge
- CRM
- 제안서
- 고객 미팅 기록
- 영업 Slack
HR Knowledge
- 사내 규정
- 복지 문서
- 인사 공지
Legal Knowledge
- 계약서
- 정책
- 컴플라이언스 문서
이때 상위 에이전트가 질문을 분류한 후 적절한 검색 도구를 선택하도록 구성할 수 있다.
사용자 질문
↓
도메인 라우터
↓
Engineering / Sales / HR / Legal
↓
도메인별 검색
↓
통합 답변
다만 도메인을 분리할수록 권한 관리와 운영 복잡도는 증가한다.
따라서 처음부터 과도하게 분리하기보다, 검색 품질과 데이터 규모를 관찰하면서 단계적으로 분리하는 것이 현실적이다.
14. 실무에 적용할 수 있는 핵심 교훈
14.1 벡터 데이터베이스부터 선택하지 않는다
RAG 시스템을 구축할 때 기술 스택부터 결정하는 경우가 많다.
Pinecone을 사용할까?
Weaviate를 사용할까?
Milvus를 사용할까?
Elasticsearch를 사용할까?
그러나 검색 품질을 결정하는 핵심은 데이터베이스 제품보다 다음 요소일 가능성이 높다.
- 원본 데이터의 품질
- 청크 분할 방식
- 메타데이터 설계
- 질문과 문서의 표현 차이
- 권한 필터링
- 최신 정보 반영
- 검색 결과 결합
- 리랭킹
- 근거 문맥 확장
소규모 또는 중간 규모의 사내 지식 시스템이라면 PostgreSQL과 pgvector로 먼저 검증하는 접근도 가능하다.
14.2 채팅 데이터는 반드시 대화 단위로 처리한다
Slack이나 Teams 메시지를 개별 메시지 단위로 임베딩하는 방식은 피하는 것이 좋다.
최소한 다음 처리가 필요하다.
- 스레드 단위 병합
- 질문과 답변 관계 복원
- 짧은 응답 제거
- 장문 설명 별도 추출
- 결론과 결정 사항 요약
- 예상 검색 질문 생성
- 원문과 정규화 문서 병행 저장
14.3 검색은 하나의 방식으로 해결하지 않는다
벡터 검색만으로는 다음 항목을 정확히 찾기 어렵다.
- 오류 코드
- 프로젝트명
- 함수명
- 티켓 번호
- 파일 경로
- 사람 이름
- 버전 번호
반대로 키워드 검색만으로는 표현이 다른 질문을 찾기 어렵다.
따라서 실무에서는 하이브리드 검색이 필요하다.
Keyword
+ Vector
+ Metadata
+ Freshness
+ RRF
+ Reranker
14.4 검색용 청크와 답변용 컨텍스트를 분리한다
검색에는 작은 청크가 유리하지만, 답변에는 충분한 문맥이 필요하다.
따라서 다음 구조가 효과적이다.
작은 청크로 검색
→ 부모 문서 확인
→ 주변 문맥 확장
→ 최종 컨텍스트 구성
14.5 AI 에이전트에는 작고 명확한 도구를 제공한다
MCP 도구는 하나의 거대한 답변 API보다 기능별 검색 도구로 제공하는 것이 활용성이 높다.
search_slack
search_code
search_jira
fetch_document
get_thread
get_commit
AI 에이전트가 목적에 맞게 도구를 조합할 수 있기 때문이다.
14.6 권한 관리는 나중에 추가할 기능이 아니다
기업용 RAG에서 권한 관리는 부가 기능이 아니라 핵심 아키텍처다.
데이터 모델을 설계할 때부터 다음 항목이 포함되어야 한다.
- 사용자 ID
- 그룹 ID
- 조직 ID
- 원본 서비스 권한
- 문서 공개 범위
- 검색 시 ACL 필터
- 권한 변경 동기화
- 삭제 전파
- 검색 및 열람 감사 로그
15. 단계별 구축 전략
Cerebras 사례를 참고하되 처음부터 모든 기능을 구현할 필요는 없다.
다음과 같이 단계적으로 구축할 수 있다.
1단계: 기본 검색 MVP
- PostgreSQL
- pgvector
- 문서 수집
- 기본 청킹
- 벡터 검색
- 출처 표시
2단계: 하이브리드 검색
- PostgreSQL Full Text Search
- 키워드와 벡터 검색 병렬 실행
- RRF 통합
- 메타데이터 필터
3단계: Slack 최적화
- 스레드 단위 수집
- LLM 요약
- 예상 질문 생성
- 장문 메시지 별도 청킹
- 원문 보존
4단계: 검색 품질 향상
- 리랭커
- 신선도 점수
- IDF 기반 노이즈 억제
- 컨텍스트 확장
- 사용자 피드백 수집
5단계: 엔터프라이즈 권한
- 사용자 및 그룹 동기화
- 문서 ACL 저장
- 검색 단계 권한 필터링
- 삭제와 권한 변경 동기화
- 감사 로그
6단계: AI 에이전트 연동
- MCP 서버
- search_slack
- search_code
- search_documents
- 원문 조회 도구
- 도메인별 검색 라우팅
16. 결론
Cerebras의 사내 지식 검색 사례에서 가장 중요한 점은 특정한 벡터 데이터베이스나 최신 AI 모델을 사용했다는 것이 아니다.
오히려 다음과 같은 기본 원칙을 충실히 적용했다는 점이 중요하다.
- 데이터를 작성되는 장소에서 수집한다.
- Slack 대화를 검색 가능한 문서로 정규화한다.
- 키워드 검색과 벡터 검색을 함께 사용한다.
- 희귀 단어와 최신 정보를 랭킹에 반영한다.
- RRF로 여러 검색 결과를 결합한다.
- 소형 리랭커로 최종 관련성을 평가한다.
- 검색 후 주변 문맥을 다시 확장한다.
- 인간용 인터페이스와 AI 에이전트용 도구를 구분한다.
- 운영 복잡도를 낮추기 위해 PostgreSQL을 중심으로 구성한다.
이 사례는 RAG 시스템의 성능이 단순히 임베딩 모델이나 벡터 데이터베이스 성능으로 결정되지 않는다는 사실을 보여준다.
좋은 사내 지식 시스템을 만들기 위해서는 검색 기술뿐 아니라 데이터 전처리, 동기화, 권한 관리, 사용자 경험, 에이전트 인터페이스를 하나의 시스템으로 설계해야 한다.
특히 초기 단계에서는 복잡한 분산 아키텍처보다 다음과 같은 단순한 구성으로 시작하는 것이 현실적일 수 있다.
PostgreSQL
+ pgvector
+ Full Text Search
+ RRF
+ Reranker
+ 권한 필터
그 위에 Slack 정규화, 코드 검색, MCP, 도메인 라우팅을 단계적으로 추가하면 운영 가능한 엔터프라이즈 지식 기반으로 발전시킬 수 있다.
참고 개념
- Retrieval-Augmented Generation, RAG
- PostgreSQL Full Text Search
- GIN Index
- pgvector
- HNSW
- Inverse Document Frequency, IDF
- Reciprocal Rank Fusion, RRF
- Cross-Encoder Reranker
- Hypothetical Question Generation
- Context Expansion
- Model Context Protocol, MCP
- Access Control List, ACL
'개발상식' 카테고리의 다른 글
| 메인은 GPT-5.6, 서브에이전트는 DeepSeek V4 Flash로 설정하기 (0) | 2026.08.06 |
|---|---|
| # 🚀 한눈에 정리하는 23가지 핵심 디자인 패턴 (Design Patterns for Humans) (0) | 2026.08.05 |
| Codex에서 GPT-5.6 Luna를 서브에이전트로 사용하는 방법 (0) | 2026.08.01 |
| AI가 코드를 쓰는 시대, 개발자는 무엇을 설계해야 할까 (0) | 2026.07.18 |
| 이제는 “뭘 만들까?”보다 “누구의 어떤 문제를 풀까?”가 더 중요하다 (1) | 2026.03.24 |
