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

SELECT 결과를 안전하게 그래프로 교체하는 방법(10편)

DB의 타입과 기본키를 그래프 정체성으로 사용하고, RDF staging 교체와 복합 커서로 전체·증분 색인의 무결성을 지킵니다.

SELECT 결과를 안전하게 그래프로 교체하는 방법(10편) — 커버 일러스트Series · 온톨로지 개발기시리즈 10번째 글 · 전체 10편 보기

지식그래프 설계 · 10/10

데이터베이스 연결 등록과 읽기 전용 SELECT 실행 기능은 이미 있었습니다. 그러나 조회 결과를 온톨로지로 만들려면 CSV로 내보낸 뒤 파일 빌드 경로를 다시 거쳐야 했습니다. 이 과정에서는 컬럼 타입과 기본키·외래키 정보가 약해지고, 운영 데이터가 바뀔 때마다 파일 스냅샷을 다시 관리해야 했습니다.

DB가 이미 제공하는 타입과 키를 이용하면 행마다 LLM 추출을 할 이유는 없습니다. 대신 같은 행을 다시 읽었을 때 기존 노드와 정확히 연결하고, 여러 페이지를 읽는 동안 원본이 바뀌거나 중간 실패가 나도 기존 그래프를 반쯤 교체하지 않는 적재 계약이 필요했습니다.

그래서 DB 연계의 초점을 SQL 실행에서 행의 정체성과 RDF 스냅샷 교체로 옮겼습니다. 기본키(PK)로 URI를 결정하고, 수정 시각이나 단조 증가 컬럼인 watermark와 PK를 묶은 복합 커서로 증분 범위를 고정하며, 임시 그래프가 완성된 뒤에만 기존 RDF 스냅샷을 교체하도록 설계했습니다.

라벨과 정체성을 분리했습니다

사람 이름이나 상품명을 URI에 사용하면 화면은 읽기 쉽습니다. 그러나 같은 이름을 가진 두 행이 하나의 노드로 합쳐지고, 이름이 바뀌면 같은 행이 새 노드가 됩니다. 데이터베이스에서 행의 정체성은 표시 라벨이 아니라 기본키입니다.

DB 행 URI는 connection, table, PK 컬럼, 원본 PK 값을 조합해 결정적으로 만듭니다. 라벨은 별도 속성으로 저장합니다.

identity = connection + table + pk_column + pk_value
label    = 사용자가 고른 표시 컬럼

동일 PK의 라벨이 바뀌어도 URI는 유지되고, 동명이인은 서로 다른 URI를 사용합니다. NULL PK와 중복 PK는 적재 전에 거부합니다. 빈 문자열과 공백은 원본 값으로 구분하고, FK는 NULL일 때만 관계 생성을 생략합니다.

예를 들어 PK가 42인 상품 행은 이름이 바뀌어도 같은 URI를 사용합니다. 증분 조회에서 이 행을 다시 읽으면 URI는 유지한 채 그 행이 소유한 값과 outgoing 관계를 교체합니다. 이후 전체 스냅샷에서 PK 42가 사라졌다면 해당 출처의 행과 그 행을 가리키는 관계를 함께 제거합니다. 라벨, 증분 갱신, 삭제가 서로 다른 규칙을 써도 정체성은 PK 하나로 유지됩니다.

FK도 대상 테이블의 표시 이름이 아니라 명시된 PK 컬럼을 참조할 때만 관계로 만듭니다. 참조 대상이 아직 없을 때는 임시 노드를 만들 수 있지만 실제 DB 행처럼 sourceId를 붙이지 않습니다. 그래야 이후 스냅샷 교체에서 존재하지 않았던 행으로 오인하지 않습니다.

미리보기는 편의 기능이 아니라 스키마 계약입니다

사용자는 등록된 DB 연결과 읽기 전용 SELECT를 선택하고, 미리보기에서 컬럼 타입과 샘플 행, PK 후보를 확인합니다. 이 단계에서 PK, 라벨 컬럼, 외래키, 증분 키를 정합니다.

SELECT만 허용하고 행 수와 실행 시간은 운영 설정으로 제한합니다. 쿼리를 문자열 치환해 증분 조건을 넣지 않고 원 쿼리를 서브쿼리로 감쌉니다. 값은 바인딩하고, 컬럼명은 미리보기에서 확인한 식별자만 허용합니다.

행을 모델로 해석하지는 않습니다. Python 값과 DB 타입을 XML Schema(XSD) 타입으로 매핑하고, PK와 FK로 인스턴스와 관계를 만듭니다. 온톨로지 스키마(OWL)의 라벨 번역과 요약에는 소량의 모델 호출이 남을 수 있지만 호출 수는 행 수가 아니라 클래스와 속성 수에 비례합니다.

RDF 대상 그래프는 임시 그래프에서 완성한 뒤 교체합니다

전체 SELECT를 여러 페이지로 바로 운영 그래프에 넣으면 마지막 페이지 실패 시 새 데이터와 이전 데이터가 섞입니다. 모든 페이지를 출처별 임시 그래프에 먼저 적재했습니다.

적재 전에 전체 결과의 PK NULL과 중복 여부를 DB에서 집계합니다. 각 페이지를 임시 그래프에 쌓고 모든 페이지가 끝나면 그래프 갱신 언어인 SPARQL Update 한 번으로 다음 작업을 수행합니다.

  1. 같은 출처가 이전에 만든 행과 관계를 제거합니다.
  2. 임시 그래프의 새 스냅샷을 운영 그래프에 삽입합니다.
  3. 완료 표식(marker)을 기록합니다.
  4. 임시 그래프를 정리합니다.

값이 바뀐 행에서 나가는 기존 관계는 교체되고, SELECT에서 사라진 행과 그 행을 가리키는 관계도 제거됩니다. 새 스냅샷에도 남아 있는 행을 향한 다른 출처의 관계와 다른 출처가 소유한 개체는 보존합니다. 빈 SELECT 결과도 성공한 새 스냅샷이므로 이전 출처 데이터가 정상적으로 삭제됩니다.

같은 교체 확정 요청이 응답 유실로 다시 들어와도 완료 표식을 보고 이미 끝난 작업인지 판정합니다. 운영 그래프를 직접 수정하는 시간은 마지막 원자 교체 구간으로 제한됩니다.

여기서 원자적으로 보호되는 범위는 Fuseki 안의 RDF 대상 그래프입니다. 이후 PostgreSQL 스키마 메타데이터를 확정하는 단계는 다른 트랜잭션 도메인입니다. 여러 documents 워커가 동시에 finalizer를 실행하면 메타데이터 행 중복 가능성이 남아 있으므로, 전체 ingest를 하나의 원자적 트랜잭션이라고 부를 수는 없습니다.

증분 실행은 (watermark, PK) 복합 커서를 사용합니다

전체 실행과 증분 실행 가운데 하나만 남기지는 않았습니다. 전체 실행은 현재 SELECT 결과를 기준 스냅샷으로 다시 만들기 때문에 원본에서 삭제된 행과 오래된 관계까지 정리할 수 있습니다. 대신 데이터가 클수록 매번 전부 읽고 임시 그래프를 만드는 비용이 큽니다.

증분 실행은 마지막 커서 이후에 추가되거나 변경된 행만 읽어 평소 반영 비용을 줄입니다. 하지만 DB가 삭제 이력이나 변경 로그를 제공하지 않는다면 조회 결과에 나타나지 않은 삭제 행을 알 수 없습니다. 그래서 증분 경로는 이번 실행에서 읽은 개체만 교체하고, 보지 못한 행을 삭제하지 않습니다. 낮은 비용의 잦은 갱신에는 증분을 사용하고, 삭제와 누적 차이를 다시 맞춰야 할 때는 전체 스냅샷을 실행할 수 있도록 두 경로를 함께 뒀습니다.

수정 시각만 커서로 사용하면 같은 시각 값을 가진 행이 페이지 경계에 걸릴 때 일부가 누락될 수 있습니다. 증분 위치를 watermark와 PK의 튜플로 저장합니다.

WHERE (watermark, pk) > (:last_watermark, :last_pk)
ORDER BY watermark, pk

데이터베이스별 문법 차이는 어댑터가 처리하되 의미는 같습니다. 다음 페이지는 마지막 행의 두 값을 이어받습니다. 이번 증분 실행에 실제로 들어온 DB 개체만 교체하고, 전체 스냅샷에 없는 행을 임의로 삭제하지 않습니다.

작업 소유권에는 만료 시간을 둡니다. 워커는 조건부 UPDATE로 pending 작업을 가져와 주기적으로 만료 시간을 연장하고, 소유권 토큰이 일치할 때만 상태와 커서를 갱신합니다. 만료된 작업은 다른 워커가 복구할 수 있고, 늦게 돌아온 이전 워커는 만료된 토큰이 차단되므로 완료 상태를 덮어쓸 수 없습니다.

검증은 행 수보다 교체 의미를 확인했습니다

격리된 실제 Fuseki 그래프에서 전체 교체, 값 변경, 삭제 행 제거, 다른 출처에서 들어오는 관계 보존, 동일 교체 요청 재시도, 증분 교체, 이전 sourceChunk 형식의 업그레이드, 빈 스냅샷을 확인했습니다. 프론트엔드 빌드와 서비스별 정적·단위 검증도 통과했습니다. 이 변경은 격리 환경의 통합 검증까지 마쳤으며 아직 운영 배포 전입니다.

남은 경계도 있습니다. 전체 SELECT의 여러 페이지를 원본 DB의 하나의 장기 트랜잭션 스냅샷으로 묶지는 않습니다. 실행 중 원본이 바뀌면 행 수 변화는 감지하지만, 같은 행 수를 유지한 값 교체까지 완전히 찾을 수는 없습니다. 운영에서는 일관 읽기 뷰, 변경이 멈춘 시간대, 또는 DB가 제공하는 스냅샷 SELECT가 필요합니다. 일부 DB 어댑터는 실제 서버 대신 공식 구문과 모의 실행까지만 검증했고, 서비스 조합 전체를 한 번에 통과하는 E2E는 아직 수행하지 않았습니다.

이 시리즈에서 구현하고 검증한 범위는 문서·표·DB를 그래프로 만들고, 검색 경로와 근거를 추적하며, 고정 질문 세트에서 검색 구성을 비교하는 데까지입니다. 도메인별 역량 질문 전체 중 몇 퍼센트에 답할 수 있는지를 계산하는 CQ coverage는 아직 구현하거나 검증하지 않았습니다. 질문별로 필요한 개념·관계·연산을 정의하고 빌드 결과와 자동 대조하는 일은 다음 과제로 남아 있습니다.

질문이 빌드의 발견 범위를 제한하지 않게 하면서도 마지막 평가 기준으로 돌아오게 만드는 것. 그것이 문서 중심 그래프에서 시작해 운영 가능한 지식그래프까지 이어진 이번 설계의 다음 단계입니다.


이전 편 → 모델 한도에 맞춰 추출 배치를 조절하는 방법 시리즈 처음 → 질문을 온톨로지 빌드에서 뺀 이유

#온톨로지#RDB#증분 색인
블로그 목록으로