Apache AGE를 활용한 그래프 기반 비디오 관계 쿼리 구축하기
가장 비용이 많이 들었던 쿼리는 단순한 "관련 비디오 표시" 패널이었습니다.
ViralVidVault에서 저희는 비디오 트렌드를 추적합니다. 공유된 채널이나 공동 시청 세션을 통해 관련 비디오를 찾는 과정이 데이터베이스 성능을 심각하게 저하시킨다는 것을 발견했습니다. SQLite와 재귀 조인(recursive joins)을 사용해 보았지만, 1단계(one hop)까지는 작동했습니다. 하지만 2단계(two hops)로 넘어가자 데이터가 폭발적으로 늘어났습니다. 쿼리 하나가 수십만 개의 행을 생성했고, 워커(worker)들은 타임아웃이 발생하기 시작했습니다.
데이터는 그래프 형태입니다. 저희는 이를 억지로 테이블에 맞추려 했고, 그 대가를 치르고 있었습니다.
저희는 관계 레이어를 Apache AGE로 옮겼습니다. 이는 PostgreSQL을 위한 openCypher 확장 기능입니다. PHP 8.4 앱과 SQLite 저장소는 그대로 유지했습니다.
결과: • 관련 패널의 지연 시간(latency)이 900ms에서 40ms 미만으로 줄었습니다. • 복잡한 2단계 탐색(two-hop traversals)도 이제 한 자릿수 밀리초(ms) 내에 완료됩니다. • AGE가 Postgres 내부에서 실행되므로 운영 워크로드는 동일하게 유지되었습니다.
왜 독립형 그래프 데이터베이스 대신 Apache AGE를 사용해야 할까요?
운영의 단순성 백업이나 보안을 위해 새로운 데이터베이스를 구축할 필요가 없습니다. AGE는 기존의 Postgres 설정, 커넥션 풀(connection pools), 보안 규칙을 그대로 사용합니다.
네이티브 그래프 쿼리 SQL에서는 가변 길이 경로(variable-length paths)를 처리하기 위해 복잡한 재귀가 필요합니다. 반면 Cypher에서는 이를 단순한 패턴으로 작성할 수 있습니다. 40줄짜리 재귀 SQL 블록이 6줄의 Cypher 쿼리로 바뀌었습니다.
더 나은 성능 그래프 엔진은 인접성(adjacency)을 인덱싱합니다. 일치하지 않는 경로는 확장을 중단하므로, 이전 시스템을 마비시켰던 데이터 팬아웃(fan-out) 현상을 방지합니다.
마이그레이션을 통해 얻은 핵심 교훈: 항상 진입점 속성(entrypoint properties)에 인덱스를 생성하세요. 탐색을 시작하는 데 사용하는 ID에 인덱스가 없다면, AGE는 전체 스캔(full scan)을 수행하게 됩니다. 이는 아무리 뛰어난 그래프 쿼리라도 느리게 만듭니다.
저희는 그래프를 읽기 모델(read model)로 사용합니다. 원본 데이터는 SQLite에 그대로 둡니다. Python 스크립트를 사용하여 두 데이터를 동기화합니다. 이를 통해 그래프를 빠르고 가볍게 유지하며, 재구축도 쉽게 할 수 있습니다.
재귀 SQL 쿼리가 너무 복잡해지고 있다면, 관계형 모델과 싸우지 마세요. 현재 저장소 옆에 작은 그래프 프로젝션(graph projection)을 구축하십시오.
