GS리테일 AX 블로그

AI와 데이터로 고객의 생활 가치를 혁신하는 GS리테일 AX 이야기

Data&AI

[사내 지식 AI 만들기 ⑤·수집] 좋은 답변의 90%는 검색 전에 결정된다 — Notion 회사와는 차원이 다른 20년치 수집

DarionKim 2026. 7. 12. 09:59

GIGO, 그런데 우리 건 차원이 다르다

RAG에는 오래된 격언이 있다. "쓰레기를 넣으면 쓰레기가 나온다(Garbage In, Garbage Out)."

그런데 사내 지식은 — 쓰레기인지 아닌지조차 애매했다. 작년에 폐기된 규정인데 멀쩡해 보이는 문서, 부서마다 다르게 부르는 용어, 같은 내용이 세 군데에 흩어진 중복, 그리고 절대 새어 나가면 안 되는 개인정보.

1탄~4탄에서 검색·권한·대화·채널을 아무리 잘 만들어도, 들어온 지식이 엉망이면 답도 엉망이다. 좋은 검색의 대부분은 검색 알고리즘이 아니라 _수집 단계_에서 결정된다. 그리고 개인정보 한 건이 새어나가면 그건 "틀린 답변"이 아니라 보안 사고다. 사내 지식은 "얼마나 정확한가"와 "얼마나 위험한가"를 동시에 조심해야 하는, 일반 GIGO보다 한 단계 무거운 문제였다.

Notion 하나 쓰는 회사 vs 20년 Excel·PPT 쓴 회사

다른 회사 이야기를 들으면 부러울 때가 있다. 처음부터 지식을 Notion 하나 같은 단일 도구에 모아 쓰는 회사는, 도서관을 신축 설계도 한 장으로 짓는 것과 같다. 소스가 하나고, 게다가 Notion은 태생이 마크다운 — 텍스트 구조가 이미 깔끔해서 파싱이 거의 공짜다. 서가 규칙이 처음부터 하나다.

우리는 달랐다. 20년 넘게 Excel·PowerPoint·사내 게시판·메일로 지식을 쌓아온 회사였다. 여기서 진짜 난이도는 "소스가 많다"가 아니라 "20년치 이질적 포맷" 자체다.

  • Excel: 표, 병합 셀, 여러 시트, 수식 결과값, 셀 안에 줄바꿈으로 욱여넣은 문단.
  • PowerPoint: 텍스트가 도형·표·이미지 안에 흩어져 있고, 발표 흐름은 사람 머릿속에만 있다.
  • 게다가 표를 이미지로 캡처해 붙인 문서도 흔하다 — 그 안의 글자는 텍스트가 아니라 그림이다.

Notion 회사가 "책을 그대로 서가에 꽂는" 일이라면, 우리는 책을 펼쳐 표·도형·이미지에서 글자를 발라내는 일부터 해야 했다. 통째로 다시 지을 순 없었다 — 이미 그 안에 20년치 지식이 살고 있었으니까. 그래서 만든 건 "하나의 표준 서가"가 아니라, 소스마다 맞춘 전용 손수레(connector) 였다. Confluence, Jira, SharePoint, 게시판, Teams… 소스마다 다른 문·다른 포맷에 맞춰 각각의 connector를 만들고, 그 손수레들이 정해진 시간에 각 소스를 도는 순회 일정(scheduler) 을 짰다.

검수대(Ingestion Gate)

포맷을 발라 텍스트를 얻었다고 끝이 아니다. 도서관에 책을 들이기 전, 검수대를 뒀다. 모든 문서는 색인되기 전에 이 게이트를 통과한다.

[소스] → 파싱 → 한국어 전처리 → 청킹 → (검수대: ingestion gate)
                                          ├─ 품질: 너무 짧거나 깨진 문서?
                                          ├─ 보안: PII/기밀? → 격리(quarantine)
                                          └─ 메타: 권한 태깅(tenant/kb/tier/acl)
        → 중복 제거 → 임베딩 → 저장(Qdrant)

100G를 통째로 나를 수는 없다 — 분할과 델타

소스 하나가 워낙 커서(대용량), 손수레로 한 번에 다 나르려면 트럭 몇 대로도 부족했다. 두 가지를 했다.

  • 분할(partition): 큰 자료 뭉치를 통째로 나르지 않고, 감당할 수 있는 단위로 쪼개서 나른다.
  • 델타(delta): 매번 소스 전체를 다시 뒤지지 않는다. fingerprint로 지난번과 달라진 부분만 골라 옮긴다 — 안 그러면 순회할 때마다 도서관 하나를 통째로 다시 옮기는 셈이 된다.

여러 소스를 돌다 보면 같은 문서가 이름만 바꾼 채 여러 곳에 꽂혀 있는 것도 발견한다 — 그대로 다 꽂으면 사용자는 같은 답을 세 번 받거나, 서로 다른 버전의 답을 받는다. 그래서 서가에 꽂기 전, 마지막으로 한 번 더 골라내는 절차(중복 제거)를 거친다.

수집 스택:

소스 connector : Confluence / Jira / SharePoint / 사내 게시판 / Teams — 소스별 어댑터
포맷 파싱      : Excel / PPT / PDF / 문서 — 표·도형·이미지에서 텍스트 추출
증분 크롤      : fingerprint 기반 변경분만 재수집
워크플로       : Temporal(스케줄·재시도·크롤 상태 추적)
PII 스크럽     : 정규 포맷 + 우회 케이스(하이픈 없는 번호 등) 규칙 엔진
중복 제거      : 해시 → 근사중복 → 의미중복 다단계 파이프라인
임베딩/저장    : 청킹 → 임베딩(OpenAI) → Qdrant(1탄의 통합 컬렉션, 권한 태그 포함)

심화 — 검수대는 무엇을 막고, 우리는 어디서 데였나

이 절은 깊다. 검수대가 실제로 어떻게 판정하고, 대용량 수집이 어디서 터졌는지 궁금한 분을 위한 detail이다. 바쁘면 체크리스트로 건너뛰어도 좋다.

① 검수대 판정 — 무엇을 막고, 무엇을 통과시키나

게이트는 결과를 네 갈래로 나눈다.

if verdict in {보안 위반: PII·기밀}:     action = QUARANTINE   # 색인 안 함, 격리
elif core_fail >= 2:                     action = REJECT       # 핵심 품질 다중 실패
elif core_fail == 1:                     action = HOLD         # 재작업 여지
elif core_warn:                          action = PROCEED      # 로그만, 색인은 진행 (아래 ②)

핵심은 보안 위반(PII·기밀)만 색인을 막는 하드 블록(격리) 이고, 나머지 품질 문제는 정도에 따라 REJECT/HOLD로 나뉜다는 것. "정확도"와 "위험도"를 같은 잣대로 다루지 않는다.

② 영리한 선택 — 경미한 경고(WARN)는 색인을 막지 않기로

처음엔 품질 경고(WARN)도 색인을 막았다. 그랬더니 문제가 생겼다 — 사소한 경고 하나 때문에 멀쩡한 문서가 0청크로 저장돼 검색에 안 잡히는 일이 벌어졌다. 그래서 정책을 뒤집었다: Core WARN은 PROCEED(로그만 남기고 색인은 진행). 막아서 얻는 안전보다, 막아서 잃는 "있는데 안 나오는 지식"이 더 컸기 때문이다. 게이트의 목적은 "완벽한 것만 통과"가 아니라 "위험한 것만 차단"이었다.

③ 숫자, 정직하게

여기도 자랑할 실측 %는 없다. 아래 ④의 사고를 고친 뒤 "0청크 → 정상 청크 + 한글 근거 복원"을 확인한 정성적 결과가 전부다. 없는 숫자는 안 만든다.

④ 우리가 처음엔 틀렸다 — chunks=0의 진범은 파일명 길이였다

대용량 재적재 중, 어떤 소스의 문서들이 인제스트했는데 청크가 0개로 나왔다. 원인을 다섯 번 오진했다: PostgreSQL 갭 → S3 키 불일치 → 버킷 IAM 권한 → boto3 버전 → 인코딩(ASCII rekey). 다 아니었다.

진범은 어이없게도 파일명 길이였다. 리눅스 파일시스템의 파일명 한계는 255바이트인데, 그 소스의 한글 파일명은 인코딩하면 283바이트로 그 한계를 넘었다. 그래서 다운로드가 로컬에 저장되는 순간 ENAMETOOLONG으로 실패했는데 — 이걸 넓은 except: continue가 삼키면서 겉으로는 "S3 object not found" 로 위장됐다. 있는 파일을, 없다고 보고하고 있었던 것이다.

고친 방식: 다운로드 로컬 파일명을 원본 키 대신 짧은 해시(sha256 앞 16자) 로 쓰고, 사용자에게 보일 제목(한글 citation)은 원본 키에서 따로 유도했다. 그리고 그 broad except의 마스킹을 걷어내 진짜 에러가 드러나게 했다.

# S3 키 basename이 로컬 255바이트 한계를 넘으면 ENAMETOOLONG →
# 이전엔 "S3 object not found"로 위장됐다. 짧은 해시명으로 우회.
safe_name = hashlib.sha256(key.encode("utf-8")).hexdigest()[:16] + Path(key).suffix

교훈 두 개. 하나의 증상(chunks=0)이 다섯 개의 서로 다른 원인을 가릴 수 있다. 그리고 넓은 except는 사고를 고치는 게 아니라 숨긴다 — 3탄에서 본 "같은 증상, 다른 원인"이 수집 파이프라인에서 다시 나타난 셈이다.

들이기 전에 고민한 것들 (체크리스트)

  • PII redaction — 주민번호·카드·전화·이메일을 들어오기 전에 스크럽한다. 표준 포맷뿐 아니라 우회 케이스(하이픈 없는 번호, 공백 구분, 국제 포맷)까지. 한 번 새면 영속 저장소에 남는다.
  • 중복(dedup) — 같은 문서가 여러 소스에 → 다단계 파이프라인으로 거른다(해시 → 근사 중복 → 의미 중복 → 충돌 탐지).
  • 신선도(freshness) — 오래된 규정이 최신처럼 답하면 그게 사고다. 소스별 신선도 SLA를 두고, 늦으면 경보·재수집.
  • 권한 메타수집 시점에 출입증(2탄의 tenant/kb/tier)을 박아야 한다. 검색 시점에 뒤늦게 권한을 붙일 수 없다.

한 줄로

좋은 답변은 좋은 검색에서, 좋은 검색은 좋은 저장에서, 좋은 저장은 — 좋은 수집에서 시작한다.

시리즈를 마치며

저장(1) → 권한(2) → 대화(3) → 채널(4) → 그리고 그 모든 것의 출발점, 수집(번외). 한 바퀴를 돌았다.

우리가 내내 붙잡은 원칙 하나로 닫는다: 구조를 만드는 것은 시작이지 끝이 아니다. "저장이 잘 됐나?"가 아니라 "각 사용자의 질문에 근거 있게 답했나?" 를 — 실제 사용자가, 측정된 품질로, 운영 경로에서 증거와 함께 — 확인할 때, 비로소 완료다.


시리즈: 1탄 저장 · 2탄 권한 · 3탄-1부 대화 · 3탄-2부 대화 · 4탄 채널 · 번외 수집 (이 글)

김헌기 Darion · AX본부 > AI데이터부문 > AI혁신지원팀

AI 및 공통 Tech 기반의 기술 표준화 업무를 수행하고 있습니다. 동료들과 함께 기술 문화 만들기와 낯선 기술자와의 인연의 시작에 관심이 많습니다.