정형 그래프가 너무 커져서 줄이기로 했습니다. 인스턴스도 관계도 없는 클래스는 쓸모없어 보였으니 자동으로 지우게 했습니다. 그렇게 사라진 것 중 약 1,500개가 정상 클래스였습니다. 추출 단계가 아직 관계를 만들지 못한 개념을, 후처리가 불필요한 개념으로 판정한 것입니다. 무엇을 줄일지와 누가 지울지를 나눈 과정입니다.
원본에 충실하게 옮겼더니 사실 기록이 그래프를 덮었습니다
커머스 CSV를 그대로 RDF로 바꿨습니다. 테이블을 클래스로, 컬럼을 속성으로, 외래키를 관계로 옮겼습니다.
결과는 원본에 충실했습니다. 그리고 그래프 대부분을 주문과 판매 이력 행이 차지했습니다.
상품과 분류 같은 기준 개체를 탐색하려면 거대한 사실 기록을 함께 읽어야 했습니다. 변환은 정확했는데 쓸 수가 없었습니다.
4편에서 정한 기준으로 보면 이것은 검색 튜닝 문제가 아니라 모델링 문제였습니다. 무엇을 그래프의 개체로 남길지 정하지 않고 전부 옮긴 것입니다.
테이블 이름 대신 참조 방향을 봤습니다
줄이려면 기준이 필요했습니다. 처음에는 테이블 이름으로 나눠볼까 했지만 이름은 도메인마다 다릅니다.
참조 구조를 봤습니다.
기준 정보 다른 테이블이 참조하는 대상 상품 · 색상 · 분류
→ 각 행을 개체로 남긴다. 관계를 따라갈 수 있어야 한다
사실 기록 여러 기준을 연결하며 참조를 내보낸다 주문 · 판매 이력 · 로그
→ 클래스와 컬럼, 외래키 구조만 남기고 행은 원본 저장소가 맡는다
다만 외래키 방향만으로 판정하지는 않았습니다. 행 수가 작은 테이블은 보수적으로 유지하는 보호 조건을 뒀고, 단일 파일만 들어와 전체 참조 방향을 알 수 없을 때도 행을 유지합니다.
이 행 수 기준은 도메인의 불변식이 아니라 오판 비용을 줄이기 위한 운영 휴리스틱입니다. 원칙처럼 적어두면 나중에 왜 그 숫자인지 설명할 수 없게 되므로 성격을 분명히 해뒀습니다.
같은 데이터에 이 규칙을 적용하자 개체가 13,798개에서 286개로 줄었습니다.
중요한 검증은 감소율이 아니었습니다. 기준 개체가 남아 있는지, 외래키 관계가 올바른 방향으로 연결됐는지, 사실 테이블의 스키마가 보존됐는지를 함께 확인했습니다. 줄어든 것보다 남아야 할 것이 남았는지가 기준입니다.
관계가 없다는 모양을 불필요하다는 뜻으로 읽었습니다
정형 쪽을 줄이는 동안 문서 쪽에서는 반대 작업을 했습니다. 그래프를 정리하려고 인스턴스와 관계가 부족한 클래스를 자동으로 지우게 했습니다.
판정 조건은 합리적으로 보였습니다. 인스턴스가 없고, 속성의 도메인이나 레인지로 쓰이지 않고, 상하위 관계도 없는 클래스입니다. 그런 클래스가 그래프에 있을 이유가 없어 보였습니다.
약 1,500개의 정상 클래스가 사라졌습니다.
관계 추출이 아직 불완전하면 정상 개념도 정확히 같은 모양을 가집니다. 아직 연결되지 않은 것과 연결될 일이 없는 것을 그래프의 모양만으로는 구분할 수 없습니다.
그러니까 저희가 만든 것은 고아 클래스 탐지가 아니었습니다. 앞 단계가 아직 하지 못한 일을 그래프에서 지우는 기능이었습니다. 후처리가 추출의 누락을 데이터 정리로 바꿔놓고 있었습니다.
자동 삭제를 끄고 탐지만 남겼습니다. 같은 대형 그래프에서 1,568개의 후보가 감지됐고, 이전 결과보다 1,547개의 클래스가 보존됐습니다.
자동 삭제 켬 후보를 바로 제거 정상 클래스 약 1,500개 함께 소멸
자동 삭제 끔 후보 1,568개 보고만 클래스 1,547개 보존
기본 동작을 삭제에서 후보 수 보고로 바꿨습니다.
파이프라인의 뒤쪽에서 앞쪽의 결과를 정리하는 기능을 만들 때는, 앞쪽이 완성됐다고 가정하고 있지는 않은지 확인해볼 만합니다. 앞쪽이 미완성인 동안 그 기능은 정리가 아니라 은폐가 됩니다.
지우지 않기로 한 것까지가 확보한 안전선입니다
여기서 저희가 확보한 것을 과장하지 않고 적어두겠습니다.
현재 통계에는 감지된 후보 개수만 남고 후보 식별자와 판정 근거는 저장되지 않습니다. 다음 빌드에서 그 후보가 사라졌는지 비교할 수단이 아직 없다는 뜻입니다.
명시적으로 삭제하는 경로에도 경계가 있습니다. 대상 식별자를 주어로 가진 나가는 트리플은 제거하지만, 그 식별자를 목적으로 가리키는 들어오는 관계까지 원자적으로 정리하지는 않습니다.
일부 참조만 남으면 화면에서는 노드가 사라져도 스키마 안에는 끊어진 관계가 생깁니다. 노드 수가 줄었다는 것이 정리가 끝났다는 뜻이 아닌 이유입니다.
안전한 삭제에는 삭제 대상 식별자를 주어와 목적으로 가진 트리플, 속성의 도메인과 레인지, 스키마 저장소의 레코드를 하나의 삭제 계획으로 묶는 참조 무결성 계약이 필요합니다. 그것을 만들기 전에는 운영에서 자동 삭제를 켜지 않는 편이 안전합니다.
구현으로 확보한 안전선은 자동 삭제를 끈 데까지입니다. 그 뒤는 후속 과제로 남았습니다.
정리 작업이 무거워지자 진행률 API가 같이 멈췄습니다
중복 정리와 품질 검사가 늘면서 3편에 남겨뒀던 실행 문제가 커졌습니다.
문서 청크 스캔, 스키마 전체 조회, CSV 변환을 백그라운드 코루틴에서 동기로 실행하면 하나의 작업이 이벤트 루프를 점유합니다. 3편에서 작업 상태를 별도 저장소로 옮겼는데도, 진행률 API가 응답하지 않으면 사용자는 취소도 완료도 확인할 수 없습니다.
작업 모델을 나눈 것과 실행 자원을 나눈 것은 다른 문제였다는 3편의 예고가 여기서 현실이 됐습니다.
입력 크기에 따라 시간이 늘어나는 벡터 저장소 스캔, 데이터베이스 조회, CSV 변환, 스키마 저장을 워커 스레드로 넘겼습니다. 빌드당 몇 번만 실행되는 짧은 상태 갱신은 이벤트 루프에 남겼습니다.
모든 함수를 기계적으로 옮기지 않고 점유 시간이 데이터 양에 비례하는 구간만 골랐습니다.
26,934개 청크를 스캔하는 로컬 조건에서 25.6초 동안 진행 상태를 50회 조회했고 평균 12ms, 최대 57ms로 응답했습니다. 이 값은 해당 장비의 결과이지 일반적인 성능 보장은 아닙니다. 확인하려던 것은 속도 자체가 아니라 무거운 빌드 단계가 도는 동안 제어 API가 함께 멈추지 않는지였습니다.
모양을 보고 줄이지 않고 역할을 보고 줄였습니다
이 편의 두 작업은 겉으로 보면 둘 다 그래프를 줄이는 일입니다. 판단 기준은 정반대였습니다.
정형 행을 남길지는 테이블의 역할로 정합니다. 고아 클래스를 지울지는 탐지의 신뢰도와 삭제 권한으로 정합니다.
둘을 같은 기준으로 다루면 사고가 납니다. 실제로 저희는 후자에서 그렇게 했고, 데이터가 적거나 연결이 없다는 모양을 근거로 정상 개념을 지웠습니다.
모양만 보고 줄이면 아직 완성되지 않은 것과 필요 없는 것을 같이 지웁니다. 데이터의 역할과, 그 판정에 쓰인 정보가 완성된 범위를 함께 봐야 했습니다.
정형 그래프의 생성 규칙은 고쳤지만 아직 남은 것이 있습니다. 빌드 끝의 의미 기반 중복 정리가 정형 클래스와 문서 개념을 구분하지 못합니다. 이 문제는 반복 빌드에서 서로 다른 테이블 클래스가 합쳐진 뒤에야 확인됐고 8편에서 다룹니다.
이제 그래프 구조를 검색에 쓸 준비가 됐습니다. 다음 편에서는 2편에서 만든 멀티턴 검색을 실제 질문 세트로 측정합니다. 그런데 그 측정에서 먼저 발견한 것은 검색 성능이 아니라, 비교하던 한쪽 그래프가 비어 있었다는 사실이었습니다.

