Rust로 RML 엔진 만들기 ep2

아키텍처5분English

English version: Building an RML Engine in Rust, Ep. 2

요약

  • RML-Core 공식 테스트 76/76 통과 (JSON 소스, 매핑 파서, 엔진, N-Quads 출력)
  • 스트리밍 실행과 128비트 해시값으로 중복을 지워 100만 행 메모리 3,747MB → 275MB
  • Python 휠과 WebAssembly 패키지
  • SQL 소스: PostgreSQL, MySQL, SQL Server
  • RML-IO: 논리 타깃, 압축(gzip·zip·tar.gz·tar.xz), XML(XPath)
  • KROWN 실행 프레임워크로 Morph-KGC와 비교 측정
  • 공식 테스트 365개 중 89개 → 214개 통과

작업 내역

매핑 mapping.ttl 데이터 JSON·CSV·SQL·XML parser Turtle → RML 모델 source 레코드를 하나씩 engine 트리플 생성·조인·중복 제거 writer N-Quads·논리 타깃 파일 · 표준 출력 Python · 브라우저 압축 4종은 흘려 읽기
매핑은 파서가, 데이터는 소스가 읽고, 엔진이 레코드마다 트리플을 만들어 바로 라이터로 넘긴다.

공식 테스트는 KG-Construct가 공개한 테스트 케이스를 고치지 않고 그대로 돌린다.
결과는 공식 구현 보고서와 같은 형식으로 남기고, 전에 통과하던 테스트가 하나라도 실패하면 CI가 커밋을 막는다.

특이사항 - 1

첫 측정에서 Morph-KGC보다 메모리를 3배 넘게 썼다

CSV 100만 행(출력 600만 개)을 Morph-KGC와 나란히 돌렸더니, 시간은 비슷했지만 메모리를 3배 넘게 썼다.
소스 전체를 메모리에 올리고, 중복 문장을 지우려고 출력 600만 개를 모두 들고 있었기 때문이다.

AS-IS CSV 전체를 메모리에 출력 600만 개를 모음 중복 제거용 정렬 집합 한 번에 씀 3,747MB최대 메모리 TO-BE CSV 한 줄씩 문장을 만들자마자 씀 해시값만 남김 이미 본 해시값이면 쓰지 않음 275MB최대 메모리
바뀐 점: CSV를 한 줄씩 읽음, 문장을 만들자마자 씀, 중복 확인용으로 해시값만 남김.

해시값은 문장 하나를 일정한 길이(여기서는 128비트, 16바이트)의 숫자로 줄인 값이다. 문장 대신 이 값만 저장해 두고, 같은 값이 다시 나오면 이미 쓴 문장으로 보고 건너뛴다.

100만 행시간최대 메모리
RMLrs AS-IS10.98초3,747MB
Morph-KGC 2.10.09.53초1,165MB
RMLrs TO-BE (스트리밍 + 출력 서식 최적화)5.70초275MB

맥북(Apple M4, 16GB)에서 직접 돌린 3회 중앙값이다.

특이사항 - 2

빠른 해시로 바꿨다가 출력 10만 개를 잃을 뻔했다

프로파일러를 보니 해시값 계산(SipHash)이 눈에 띄어서, 직접 짠 더 빠른 해시로 바꿨다.
5.0초로 빨라졌는데 출력 파일의 해시값이 이전과 달랐다.
Morph-KGC 출력과 맞대 보니 한 자리만 다른 IRI들이 같은 해시값을 받아, 약 99,000개가 중복으로 잘못 버려져 있었다.

AS-IS · 직접 짠 빠른 해시 …/person/100008 …/person/100009 해시값 a1f3… 두 번째는 "이미 본 문장" → 버려짐 (약 99,000개) TO-BE · SipHash로 되돌림 …/person/100008 …/person/100009 해시값 7c02… 해시값 e95b… 둘 다 남음
바뀐 점: 검증된 SipHash로 되돌려 서로 다른 문장이 서로 다른 해시값을 받게 했다. 해시값은 예시다.

검증된 SipHash로 되돌리고, 한 자리만 다른 IRI 30만 개를 넣는 테스트를 만들었다.
그 테스트가 문제의 해시로는 실제로 실패하는 것(30만 개 중 28만 개만 남음)까지 확인했다.
시간과 메모리만 봤다면 그대로 커밋했을 것이다.

KROWN 실행 프레임워크로 재기

KROWN의 실행 프레임워크(exectool)에 RMLrs를 붙여 Morph-KGC와 같은 조건으로 쟀다.
이 프레임워크는 엔진을 Docker 안에서 돌리며 시간·CPU·RAM을 기록한다.

테스트 기기의 메모리가 8GB로 작아서, Morph-KGC는 프로세스 수를 1개와 4개로 줄여서 쟀다.

시간 (초) RMLrs 19.9 Morph-KGC ×1 59.7 Morph-KGC ×4 40.3 CPU 시간 (초) RMLrs 17.4 Morph-KGC ×1 55.6 Morph-KGC ×4 109.3 RAM 증가 (MB) RMLrs 358 Morph-KGC ×1 1,412 Morph-KGC ×4 2,956
KROWN raw 시나리오 100만 행(20열), 3회 중앙값. ×1·×4는 Morph-KGC의 프로세스 수다.
행 수엔진·설정시간CPU 시간RAM 증가
10만RMLrs2.44초2.3초3~20MB
10만Morph-KGC 프로세스 17.23초7.3초184~328MB
10만Morph-KGC 프로세스 45.41초9.8초474~586MB
100만RMLrs19.88초17.4초358MB
100만Morph-KGC 프로세스 159.74초55.6초1,412MB
100만Morph-KGC 프로세스 440.29초109.3초2,956MB

100만 행에서 RMLrs는 Morph-KGC보다 2~3배 빨랐고, RAM 증가분은 4분의 1에서 8분의 1 수준이었다.
모든 실행에서 두 엔진의 출력은 정렬 후 같았다.

측정은 맥의 Docker VM(10 CPU, 8GB)에서 3회 중앙값으로 했다.
시간에는 컨테이너 시작이 들어 있고, RAM은 VM 전체 사용량의 증가분이다.
사양을 높인 환경에서 다시 잴 예정이다.

정리

공식 테스트 365개 중 214개를 통과했다.
스트리밍과 해시값 중복 제거로 100만 행 메모리를 3.7GB에서 275MB로 줄였고, 해시나 키를 바꾼 뒤에는 출력이 같은지와 벤치를 다시 본다.

다음 작업

  • KROWN의 나머지 시나리오(조인, DB)와 다른 엔진(CARML, SDM-RDFizer) 비교
  • 확장 모듈: FNML, CC, LV, star
  • 메모리: 출력이 아주 많을 때의 해시값 집합, 문서 크기에 비례하는 XML

참고