내 지식 그래프(Knowledge Graph)를 두 차례 감사하며 발견한 서로 관련 없는 두 가지 침묵의 실패(Silent Failures)
요약
자율 지식 그래프 시스템 구축 과정에서 발생한 두 가지 '침묵의 실패' 사례를 다룹니다. 중복 노드로 인한 검색 편향 문제와 텍스트 추출 실패로 인한 데이터 누락 문제를 해결하는 과정을 공유합니다.
핵심 포인트
- 중복 노드 제거를 위해 문서 유사도 기반의 중복 제거 단계가 필수적임
- 단순 텍妹 길이 기반의 무결성 검사는 데이터 누락을 감지하지 못할 수 있음
- 스캔된 PDF 처리를 위해 OCR 등 추가적인 텍스트 추출 전략이 필요함
- 에러가 발생하지 않더라도 데이터 무결성을 정기적으로 감사해야 함
저는 도미니카 공화국의 시스템 관리자이며, 지난 몇 달 동안 부업으로 ANIMUS를 구축해 왔습니다. 이는 실제 규제 코퍼스(도미니카 은행 규제 PDF, Superintendencia de Bancos의 800개 이상의 문서) 위에서 실행되는 Rust 기반의 자율 지식 그래프(Autonomous Knowledge Graph) 시스템입니다. 팀도 없고, 자금 지원도 없으며, 애플리케이션 지원 업무를 마친 뒤 저녁 시간과 주말을 이용해 작업하고 있습니다.
두 번이나 제 파이프라인을 감사하면서 제가 예상하지 못했던 실패 모드(Failure mode)를 발견했습니다. 두 경우 모두 에러를 발생시키지 않았습니다. 둘 다 문제가 없어야 할 항목들을 실제로 직접 세어보기 전까지는 침묵 속에서 "성공" 상태를 유지했습니다.
개별 버그 자체보다 그 패턴이 더 흥미롭다고 생각하기에, 두 가지 사례를 모두 소개합니다.
감사 #1 — 그래프의 52%가 중복 노드(Duplicate nodes)였음
저는 고유성 검사(Uniqueness check) 장치가 없는 상태에서 수정을 거친 후 인제스션 파이프라인(Ingestion pipeline)을 재실행해 왔습니다. 동일한 규제 회람이 약간씩 다른 라벨로 그래프에 여러 번 추가되었습니다. 처음 인제스션 시 한 번, 재실행 후 다시 한 번, 때로는 부분적인 재처리(Reprocess) 후에 세 번째로 추가되기도 했습니다.
노드 수(Node count)는 아주 좋아 보였습니다. 하지만 그 절반 이상이 중복된 것이었습니다. 그래프는 실제보다 더 크고 유능해 보였으며, 검색(Retrieval)은 특정 쿼리에 우연히 일치하는 중복 항목 쪽으로 조용히 편향되었습니다.
해결책은 단순히 정확한 파일 이름 일치뿐만 아니라 문서 유사도(Document similarity)를 기준으로 한 중복 제거(Deduplication) 단계였습니다. 중복된 항목들이 항상 같은 이름을 공유하는 것은 아니었기 때문입니다. 저는 이를 발견했을 때 전체 디버깅 이야기를 작성했습니다: 내 지식 그래프의 52%가 중복임을 어떻게 알게 되었나.
감사 #2 — 소스 코퍼스(Source corpus)의 32%가 그래프에 전혀 포함되지 않았음
이 사례는 더 심각했습니다. 왜냐하면 실패가 설계상(By design) 보이지 않았기 때문입니다.
저의 PDF 인제스션 (Ingestion) 스크립트는 추출된 텍스트가 100자보다 길면 해당 문서를 성공적으로 처리된 것으로 표시합니다. 이는 합리적으로 보이는 무결성 검사 (Sanity check)입니다. 텍스트가 비어 있거나 거의 없는 추출 결과는 보통 무언가 잘못되었음을 의미하기 때문입니다. 하지만 다음과 같은 문제가 있었습니다:
- 해당 임계값 미만의 모든 데이터는 그래프에 통합되지 못한 채 조용히 누락됩니다.
- 성공 여부와 관계없이 소스 파일은 여전히 "처리됨 (processed)" 폴더로 이동됩니다.
결과적으로 폴더는 모든 파일에 대해 "완료"라고 말하고 있었습니다. 그래프에는 817개 문서 중 262개 — 코퍼스 (Corpus)의 32% — 가 누락되어 있었지만, 파이프라인 (Pipeline) 입장에서는 아무것도 실패하지 않았기 때문에 그 이유를 설명하는 로그 엔트리 (Log entry)조차 없었습니다.
근본 원인: 저의 텍스트 추출 도구인 PyMuPDF는 PDF에 이미 임베디드 (Embedded)된 텍스트만 읽습니다. 코퍼스의 상당 부분이 실제 텍스트 레이어가 없는 스캔된 문서나 디지털 서명된 문서임이 밝혀졌습니다. 즉, 이미지로 디지털화된 오래된 회보나, 서명 과정에서 페이지가 스캔본처럼 평탄화 (Flattening)된 서명된 PDF들이었습니다. 이러한 문서들은 거의 빈 상태로 반환되어 100자 검사를 통과하지 못하고 흔적도 없이 사라졌습니다.
OCR 폴백 (Fallback) (pytesseract + Tesseract, 스페인어 언어 팩)을 추가하여 262개 중 259개를 복구했습니다. 첫 번째 시도에서는 낮은 렌더링 해상도를 사용하여 노이즈가 많은 텍스트가 생성되었습니다. DPI를 높이고 OCR 전에 기본적인 이미지 전처리 (Preprocessing)를 추가하는 것이 도움이 되었지만, 노이즈 문제를 완전히 해결하지는 못했습니다. 이에 대해서는 아래에서 더 자세히 다루겠습니다.
아직 해결하지 못한 후속 문제
100자 검사를 "통과"한 — 즉, 저의 파이프라인이 정상이라고 간주한 — 일부 문서들이 결과적으로 손상된 네이티브 텍스트 (Native text)를 포함하고 있음이 드러났습니다. 제 최선의 추측은, 원래 PDF가 수년 전 최초 스캔 작업자가 저품질 OCR로 디지털화하면서 손상된 텍스트가 파일에 영구적으로 구워졌다는 것입니다 (Baked into). PyMuPDF는 이를 충실하게 추출합니다. 추출 과정이 고장 난 것이 아니라, 소스 자체가 이미 손상되어 있었던 것입니다.
코퍼스(Corpus)에서 발견한 실제 사례 하나를 들자면, 금융정보분석국(Financial Intelligence Unit)에 의심 거래를 보고하는 내용의 회보(circular)에서 "Entidades"는 "Entídodes"로, "financiero"는 "finonciero"로 추출되었습니다. 사람은 이를 무시하고 읽을 수 있습니다. 하지만 키워드 기반 검색(Keyword-based retrieval)은 불가능합니다. 질문에 포함된 단어가 문서 내의 손상된 단어와 일치하지 않기 때문에, 시스템은 그래프에 바로 놓여 있는 문서에 대해 "정보를 찾을 수 없음"이라고 보고합니다.
"텍스트가 존재하지만 쓰레기(garbage)인 경우"를 탐지하는 것은 "텍스트가 존재하지 않는 경우"를 탐지하는 것과는 의미상으로 완전히 다른 문제임이 드러났습니다. 저는 아직 이에 대한 깔끔한 검증 방법을 가지고 있지 않습니다. 저의 첫 번째 시도(흔한 OCR 아티팩트인 반복되는 연속 단어 찾기)는 수백 건 중 단 한 건만을 잡아냈습니다. 스캔된 문서나 역사적 문서 코퍼스(Corpus)를 대상으로 검색(Retrieval) 작업을 수행하는 다른 분들이 이 문제에 어떻게 접근했는지 진심으로 듣고 싶습니다. 왜냐하면 이 문제는 어딘가에서 이미 해결된 문제여야 할 것 같은데, 제가 검색할 적절한 키워드를 아직 찾지 못한 것 같기 때문입니다.
이것이 실제 벤치마크 수치에 미친 영향
저는 코퍼스를 대상으로 36개의 질문 세트를 사용하여 평가를 진행했습니다. 이 세트는 단일 문서로 답할 수 있는 사실적 질문, 두 개 이상의 관련 문서가 필요한 다중 소스(Multi-source) 질문, 그리고 환각(Hallucination)을 잡아내기 위해 설계된 "이 정보는 코퍼스에 없습니다"가 정답인 적대적 "함정(trap)" 질문으로 구성되었습니다.
OCR 복구 단계를 거친 후, 코퍼스 커버리지(Corpus coverage)는 소스 문서의 68.5%에서 99.6%로 증가했습니다. 전체 벤치마크 점수는 58.3%에서 63.9%로 상승했습니다. 함정 질문에 대한 정직도(Honesty)는 100%를 기록하며, 환각 답변이 전혀 발생하지 않았습니다.
흥미롭게도, 다중 소스 질문(Multi-source questions)의 총합은 크게 변하지 않았습니다. 개별적으로는 몇몇 질문이 완전히 실패하던 상태에서 부분적으로 성공하는 상태(필요한 두 개의 문서 중 하나를 찾는 식)로 나아갔지만, 이는 별개의 관련 없는 문제로 인해 발생한 몇몇 퇴보(regressions)에 의해 상쇄되었습니다. 그 문제는 바로 가치가 낮은 "성찰(reflection)" 노드들이었습니다. 즉, "아무것도 찾을 수 없습니다"와 같은 시스템의 과거 답변들이 최근 노드에 편향된 검색(retrieval) 단계에서 실제 소스 문서들을 밀어내고 있었습니다. 이는 이 포스트에서 다룬 문제와는 다른 또 다른 문제(rabbit hole)이지만, 하나의 침묵의 실패(silent failure)를 해결하면 바로 뒤에 숨어 있던 또 다른 실패가 드러날 수 있다는 점을 잘 상기시켜 줍니다.
공개 사항 (Disclosure)
이 포스트는 저의 디버깅 노트와 로그를 바탕으로 AI 어시스턴트의 도움을 받아 작성되었습니다. 조사 내용, 수정 사항 및 수치는 모두 저의 것입니다. 저는 영어가 모국어가 아니며, 영어로 이 모든 내용을 처음부터 완전히 작성하는 것은 빠르게 할 수 있는 일이 아니기에, 작성 과정 자체에 AI의 도움을 받았습니다.
논문 및 데이터셋: Zenodo
코드: github.com/ernestoariasdiaz/animus-ai
만약 여러분도 이러한 문제들 — 즉, 침묵의 수집 실패(silent ingestion failures)나 "검사는 통과했지만 내용은 쓰레기인 경우" — 를 겪으셨다면, 댓글로 알려주시기 바랍니다.
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기