개발일지

RAG 시스템에서 문서와 Embedding 데이터의 동기화 전략

기미직 2026. 6. 10. 16:35

 

사내에서 문서관리 서비스를 만들고 운영하고 있다.

처음에는 사내 매뉴얼을 등록하고 필요한 내용을 검색할 수 있는 정도의 서비스였다.

매뉴얼을 작성하고 수정하고 승인하는 흐름이 있고 사용자는 필요한 매뉴얼을 검색해서 확인하는 구조였다.

 

그러다 자연스럽게 RAG를 붙여보자는 이야기가 나왔다.

문서를 직접 하나씩 찾아보는 것도 좋지만 사용자가 질문을 던졌을 때 관련 매뉴얼을 찾아서 답변까지 해주면 훨씬 편할 것 같았다. 그래서 문서 데이터를 기반으로 Embedding을 만들고 RAG에서 사용할 수 있도록 구성하기 시작했다.

그런데 막상 붙여보려고 하니 단순히 “문서를 Embedding해서 저장하면 끝”은 아니었다.

 

문서는 계속 바뀐다

처음에는 등록된 매뉴얼을 기준으로 Embedding을 만들면 된다고 생각했다.

하지만 실제 운영 중인 문서관리 서비스에서는 매뉴얼이 계속 수정된다.
오타가 수정되기도 하고 내용이 추가되기도 한다.

잘못된 절차가 바뀌기도 하고 기존 문서가 삭제되거나 새로운 버전으로 올라가기도 한다.

이때 원본 문서만 수정되고 RAG에 사용되는 Embedding 데이터가 그대로 남아 있다면 문제가 생긴다.

사용자는 최신 매뉴얼을 기준으로 질문했다고 생각하지만 RAG는 예전 문서 내용을 기준으로 답변할 수도 있다.

검색 엔진도 마찬가지다.

DB에는 최신 문서가 있는데 검색 결과에는 이전 내용이 남아 있다면 사용자 입장에서는 꽤 혼란스러울 수 있다.

그래서 RAG를 붙이면서 가장 먼저 고민하게 된 부분이 있었다.

 

매뉴얼이 수정되었을 때 검색 엔진과 Embedding 데이터는 어떻게 같이 업데이트해야 할까?

 

업데이트해야 하는 대상이 생각보다 많았다

 

문서가 하나 수정되면 DB만 바뀌는 것이 아니다.

검색 엔진에 들어가 있는 문서 데이터도 바뀌어야 한다.
RAG에서 사용하는 Embedding 데이터도 다시 만들어야 한다.
문서를 chunk 단위로 나누고 있다면 chunk도 다시 생성해야 한다.

대략 흐름을 적어보면 이런 식이다.

매뉴얼 수정
→ DB 업데이트
→ 검색 엔진 인덱스 업데이트
→ 기존 Embedding 데이터 정리
→ 수정된 문서를 기준으로 chunk 재생성
→ Embedding 재생성
→ Vector Store 업데이트

 

처음에는 이걸 문서 저장 API 안에서 한 번에 처리하면 되지 않을까 생각했다.

사용자가 매뉴얼을 수정하고 저장하면 그 시점에 검색 엔진도 업데이트하고 Embedding도 다시 만들면 된다.

흐름 자체는 단순하다.

 

하지만 조금만 생각해보면 이 방식은 부담이 있었다.

Embedding 생성은 생각보다 무거운 작업이다. 문서가 길어지면 chunk도 많아지고 처리 시간도 늘어난다.

외부 API를 사용한다면 네트워크 지연이나 실패 가능성도 있다.

 

사용자는 단순히 문서 저장 버튼을 눌렀을 뿐인데 뒤에서 Embedding까지 모두 처리하느라 응답이 늦어질 수 있다.

문서 저장은 성공했는데 Embedding 생성이 실패하는 경우도 생길 수 있다.

그래서 문서 저장과 동기화 작업은 분리하는 게 맞겠다고 생각했다.

 

문서 저장과 동기화 작업을 분리하기

지금 생각하고 있는 방향은 Queue를 사용하는 방식이다.

사용자가 문서를 수정하면 API 서버는 우선 DB에 문서를 저장한다.

그리고 “이 문서는 동기화가 필요하다”는 작업을 Queue에 넣는다.

이후 실제 검색 엔진 업데이트와 Embedding 재생성은 worker가 처리한다.

 

흐름은 이런 식이다.

사용자가 매뉴얼 수정
→ API 서버가 DB 저장
→ 동기화 작업을 Queue에 등록
→ 사용자에게 저장 완료 응답
→ Worker가 Queue 작업 처리
→ 검색 엔진 업데이트
→ Embedding 재생성
→ Vector Store 업데이트

 

이렇게 하면 사용자는 문서 저장 결과를 빠르게 받을 수 있다.

 

무거운 작업은 뒤에서 따로 처리할 수 있다.

그리고 동기화 작업이 실패했을 때도 재시도하기가 편하다.

API 요청 안에서 모든 것을 처리하면 어느 단계에서 실패했는지 관리하기가 애매해진다.

반면 Queue 기반으로 분리하면 작업 상태를 남기고 실패한 작업만 다시 처리할 수 있다.

 

기존 Embedding은 어떻게 처리할까

문서가 수정되었을 때 기존 Embedding을 일부만 수정할 수도 있지 않을까 생각했다.

예를 들어 문서의 한 문단만 바뀌었다면 해당 chunk만 다시 Embedding하면 될 것 같았다.

 

하지만 실제로는 그렇게 단순하지 않았다.

문장 하나가 추가되면 chunk 경계가 달라질 수 있다.
문단이 합쳐지거나 나뉘면 기존 chunk와 새 chunk를 정확히 비교하기도 애매하다.

그래서 처음부터 너무 복잡하게 부분 업데이트를 하기보다는 문서 단위로 다시 생성하는 쪽이 더 안전하다고 봤다.

기존 Embedding 삭제 또는 비활성화
→ 최신 문서 기준으로 chunk 재생성
→ 새 Embedding 생성
→ Vector Store에 저장

 

물론 문서가 아주 많아지고 비용이 부담되는 시점이 오면 부분 갱신도 고민할 수 있다.

하지만 우선은 정확성과 단순함이 더 중요하다고 생각했다.

오래된 Embedding이 남아서 RAG가 예전 내용을 답변하는 것보다는 문서 단위로 확실하게 갈아끼우는 편이 낫다.

 

삭제된 문서도 신경 써야 한다

수정만큼 중요한 게 삭제다.

문서관리 서비스에서 매뉴얼이 삭제되었는데 Vector Store에는 해당 문서의 Embedding이 그대로 남아 있다면 RAG는 삭제된 문서를 근거로 답변할 수 있다.

이건 생각보다 위험했다.

사용자 입장에서는 더 이상 존재하지 않는 문서이거나 폐기된 매뉴얼인데 RAG가 그 내용을 기준으로 답변할 수 있기 때문이다.

그래서 문서 삭제가 일어나면 검색 엔진에서도 제거해야 하고 Vector Store에서도 해당 document_id를 가진 데이터를 제거하거나 비활성화해야 한다.

soft delete를 사용한다면 RAG 조회 시에도 삭제된 문서는 제외되도록 처리해야 한다.

 

최신 작업만 처리하기

또 하나 고민되는 부분은 중복 작업이다.

사용자가 문서를 수정하고 저장한 뒤 바로 다시 수정할 수 있다.
그럼 같은 문서에 대해 동기화 작업이 여러 번 Queue에 들어갈 수 있다.

예를 들면 이런 상황이다.

10번 문서 수정
10번 문서 다시 수정
10번 문서 또 수정

 

이 경우 세 번 모두 Embedding을 만들 필요는 없다.
결국 필요한 것은 가장 마지막 상태의 문서에 대한 Embedding이다.

그래서 worker가 작업을 처리할 때 Queue에 담긴 데이터만 믿기보다는 DB에서 최신 문서를 다시 조회하는 방식이 필요해 보인다.

작업에 document_id와 version을 같이 넣어두고 현재 DB의 version보다 오래된 작업이면 건너뛰는 방식도 괜찮을 것 같다.

 

Queue 작업의 version < 현재 문서 version
→ 오래된 작업이므로 skip

 

이렇게 하면 불필요한 Embedding 생성을 줄일 수 있고 최종 문서 상태를 기준으로 동기화를 맞출 수 있다.

 

지금 생각하는 구조

현재는 아래 구조가 가장 현실적이라고 보고 있다.

1. 사용자가 매뉴얼을 생성하거나 수정하거나 삭제한다.
2. API 서버는 원본 DB를 먼저 업데이트한다.
3. 동기화 작업을 Queue에 넣는다.
4. Worker가 작업을 가져온다.
5. Worker는 DB에서 문서의 최신 상태를 다시 조회한다.
6. 검색 엔진 인덱스를 업데이트한다.
7. 기존 Embedding 데이터를 삭제하거나 비활성화한다.
8. 최신 문서 기준으로 chunk를 다시 만든다.
9. Embedding을 생성한다.
10. Vector Store에 저장한다.
11. 작업 상태를 완료로 변경한다.

 

핵심은 API 서버가 모든 작업을 직접 처리하지 않는 것이다.

문서 저장은 빠르게 끝내고 검색 엔진과 RAG 동기화는 별도의 작업으로 분리한다.

그리고 worker는 항상 최신 문서를 기준으로 처리한다.

 

정리

RAG를 문서관리 서비스에 붙이면서 느낀 건 Embedding을 만드는 것보다 그 이후의 관리가 더 중요하다는 점이었다.

처음에는 문서를 Embedding해서 Vector Store에 넣으면 어느 정도 끝난다고 생각했다.

하지만 실제 운영 중인 문서는 계속 수정되고 삭제되고 버전이 바뀐다.

그 변화에 맞춰 검색 엔진과 Embedding 데이터도 같이 움직여야 한다.

 

결국 RAG의 답변 품질은 모델이나 프롬프트만으로 결정되지 않는다.
원본 문서와 Embedding 데이터가 얼마나 잘 동기화되어 있는지도 중요하다.

이번에는 우선 문서 변경 이벤트를 Queue에 넣고 worker가 검색 엔진과 Embedding 데이터를 갱신하는 구조로 가보려고 한다.

구현하면서 또 문제가 생기겠지만 적어도 방향은 정리된 것 같다.

문서가 바뀌면 Embedding도 같이 바뀌어야 한다.