XGEN 15일 무료 체험 — 설치 없이 브라우저에서 바로 시작하세요체험 신청
PlateerAI Labs
·Reading Time | 2 min
작성 | 김진수

몇 시간 걸리는 온톨로지 빌드를 작업으로 다루기(3편)

진행 상태를 그래프에서 분리하고, 입력 판별과 정형·비정형 처리 경로를 명시해 긴 빌드를 제어 가능한 작업으로 바꿉니다.

몇 시간 걸리는 온톨로지 빌드를 작업으로 다루기(3편) — 커버 일러스트Series · 온톨로지 개발기시리즈 3번째 글 · 전체 10편 보기

지식그래프 설계 · 3/10

문서가 적을 때 온톨로지 빌드는 하나의 API 요청으로도 충분했습니다. 입력을 읽고 모델을 호출한 뒤 그래프를 저장하면 끝났습니다. 입력이 수만 개 청크와 CSV로 늘자 빌드는 몇 분에서 몇 시간 동안 이어졌고, 어느 단계까지 처리됐는지 묻는 요청도 함께 늘었습니다.

이 글에서는 혼합 컬렉션 한 건이 등록, 분류, 처리, 적재, 완료 상태로 이어지는 흐름을 따라가며 실행 상태와 지식 결과를 왜 분리했는지 설명합니다.

기존 방식에서는 작업 상태가 프로세스 메모리에 있어 다른 API 인스턴스가 조회하면 작업을 찾지 못했고, 재시작하면 이력도 사라졌습니다. 진행 상황을 그래프의 처리 마커로 복원하는 방식은 대량 쓰기와 상태 조회가 같은 저장소를 두고 경쟁하게 했습니다. 한글 문장과 표, 숫자가 많은 입력을 영문 공백 기준으로 걸러 실제 문서가 추출 전에 버려지는 문제도 있었습니다.

빌드가 길어지면서 필요한 것은 더 빠른 모델 호출만이 아니었습니다. 상태·취소·재시작을 담당하는 영속 작업 레코드와, 입력의 실제 구조에 따라 정형·비정형 경로를 나누는 빌드 서비스가 필요했습니다. 그래프는 지식의 최종 상태를 맡고 작업 저장소는 실행의 현재 상태를 맡도록 책임을 분리했습니다.

작업 상태의 기준 저장소를 PostgreSQL로 정했습니다

첫 작업 상태는 API 프로세스 메모리에 있었습니다. 여러 인스턴스가 요청을 나눠 받자 작업을 만든 인스턴스와 상태를 조회한 인스턴스가 달라져 404가 발생했습니다. 공유 캐시인 Redis에 상태를 복제하면 인스턴스 간 공유는 해결할 수 있었지만, 작업 데이터베이스인 PostgreSQL에도 빌드 이력이 남아 있어 어느 쪽이 최신인지 다시 정해야 했습니다. 캐시 만료나 Redis 재시작 뒤 취소·재개 정보를 어떻게 복원할지도 남았습니다.

빌드 작업은 잠깐 재사용할 캐시가 아니라 완료 뒤에도 원인과 재시작 지점을 확인할 운영 기록이었습니다. 그래서 ontology_build_jobs를 PostgreSQL에 영속화하고 Redis 작업 캐시는 제거했습니다. 상태, 단조 증가 카운터, 취소 요청을 한 레코드에서 갱신하면 다른 인스턴스와 재시작 뒤에도 같은 값을 읽을 수 있습니다.

PostgreSQL과 RDF 그래프 저장소인 Fuseki를 하나로 합치지는 않았습니다. 둘 다 데이터를 오래 저장하지만 조회 형태가 다르기 때문입니다. 진행률은 작업 ID로 한 행을 자주 읽고 갱신하는 반면, 그래프는 클래스와 관계를 SPARQL로 탐색합니다. 작업 상태를 PostgreSQL에, 지식 결과를 Fuseki에 둔 것은 저장 기술을 두 개 쓰기 위한 구성이 아니라 서로 다른 접근 패턴과 수명주기를 분리한 것입니다.

진행률은 그래프를 다시 세지 않고 기록합니다

작업 레코드에는 전체 입력 수, 처리한 입력 수, 현재 단계, 취소 요청, 완료 상태를 저장합니다. 워커는 청크 그룹을 끝낼 때 처리 수를 단조 증가시키고, 화면은 이 값을 읽습니다. 진행률 조회는 그래프의 트리플 수와 관계없이 일정한 비용으로 끝납니다.

상태 전이는 pending → running → completed | failed | cancelled로 제한했습니다. 작업 레코드는 재시작 뒤에도 복원되지만, 중단된 워커가 자동으로 이어서 실행된다는 뜻은 아닙니다. 재개 정책과 실제 처리 단위의 멱등성은 별도 계약이 필요합니다.

빌드 작업 저장소              그래프 저장소
-------------------          ------------------
전체·처리 청크 수             클래스·인스턴스
현재 단계                     관계·속성
취소·실패·완료 상태           원문 출처

취소 요청도 작업 레코드에 남깁니다. 워커는 그룹 경계에서 이를 확인하고 안전하게 멈춥니다. 그래프 업로드 중간에 프로세스를 강제로 끊는 것보다, 어디까지 완료됐는지 확정할 수 있는 경계에서 종료하는 편이 재시작과 복구에 유리합니다.

이 단계에서 실행 상태와 결과 저장소의 책임은 분리됐지만, 상태 API가 항상 빠르게 응답한다고 보장된 것은 아니었습니다. 백그라운드 작업 안의 동기 문서 스캔과 데이터베이스 조회가 이벤트루프를 점유하는 문제는 남아 있었습니다. 작업 모델을 분리하는 것과 실행 자원을 분리하는 것은 다른 문제였고, 후자는 자동 후처리 범위가 커진 뒤 다시 다루게 됩니다.

입력 판별은 언어가 아니라 추출 가능성을 봅니다

긴 빌드에서 모델 호출 전에 쓸모없는 입력을 거르는 것은 중요합니다. 그러나 문자열 길이와 공백 비율 같은 간단한 규칙은 한글 문장, 숫자가 많은 표, 코드가 섞인 문서를 비자연어로 오인하기 쉽습니다.

입력 게이트의 질문을 “자연스러운 영어 문장인가”에서 “추출할 기호와 의미가 있는가”로 바꿨습니다. 한·중·일 문자(CJK)가 있으면 통과시키고, base64는 문자 모양이 아니라 실제 디코딩 가능 여부로 판정합니다. 영숫자가 전혀 없거나 같은 문자가 반복되는 극저다양성 입력만 제외합니다.

게이트는 문서의 품질을 최종 판정하지 않습니다. 모델을 호출해 볼 가치가 명백히 없는 입력만 걷어냅니다. 애매한 입력은 뒤 단계로 보내고, 추출 결과가 비었는지는 별도 상태로 기록합니다. 유효한 문서를 일찍 버리는 비용이 불필요한 한 번의 호출보다 크기 때문입니다.

표와 문서는 같은 컬렉션에서도 다른 경로를 탑니다

파일 절반 이상이 표면 CSV 경로, 아니면 전부 문서 경로로 보내는 식의 컬렉션 단위 분기는 혼합 입력을 다루지 못합니다. 파일 단위로 표와 문서를 나누고 같은 그래프에 합치는 하이브리드 경로를 만들었습니다.

문서는 청크 그룹으로 스트리밍하며 모델이 개념과 관계를 추출합니다. 표는 스키마와 행 구조를 결정적으로 변환합니다. CSV 인스턴스에는 형태소 기반 중복 병합을 적용하지 않습니다. 기본키와 외래키로 만든 식별자를 의미 유사도로 합치면 서로 다른 행이 사라질 수 있기 때문입니다.

대용량 RDF(주어·술어·목적어로 표현한 트리플 데이터)는 한 번에 전송하지 않고 배치로 나눠 병렬 업로드합니다. 로컬 격리 Fuseki에서 60,007개와 120,007개 트리플을 각각 나눠 적재한 뒤 저장소 재조회 수가 일치하는지 확인했습니다.

완료 상태와 품질 검증을 분리했습니다

모델이 JSON을 반환했다고 빌드 전체를 설명할 수는 없습니다. 입력 판별, 추출, 구조 검증, 스키마 저장, RDF 업로드, 출처를 단계별 관측 대상으로 나눴습니다. 어느 단계에서 멈췄는지 알아야 재시도 범위를 결정할 수 있기 때문입니다.

다만 현재 완료 로직은 RDF 트리플이 있는데 PostgreSQL 스키마가 비어 있어도 경고를 남긴 뒤 completed로 전환할 수 있고, built_chunk_ids 저장도 best-effort입니다. 따라서 completed는 워커가 끝났다는 실행 상태이며 모든 스키마·출처 검증이 통과했다는 품질 인증이 아닙니다. 경고와 품질 보고서를 함께 봐야 합니다.

이 구조로 빌드는 오래 걸려도 제어 가능한 작업이 됐습니다. 다만 안정적으로 끝나는 것과 좋은 그래프를 만드는 것은 다른 문제입니다. 다음 편에서는 트리플 수나 완료 상태 대신 그래프의 구조, 출처, 질문 응답을 서로 다른 품질 경계로 나눈 과정을 다룹니다.


이전 편 → 멀티턴 GraphRAG는 왜 필요했고 무엇이 한계였나 다음 편 → 트리플 수로는 지식그래프 품질을 알 수 없었습니다

#온톨로지#빌드 파이프라인#비동기 처리
블로그 목록으로