RedisGraph 2.8의 정식 버전(GA) 출시를 발표하게 되어 기쁩니다. 이번 글에서는 새롭게 제공되는 주요 기능들을 자세히 살펴보겠습니다.
RedisGraph란?
RedisGraph는 Redis를 위한 고성능 메모리 우선(memory-first) 그래프 데이터 구조입니다. 그래프 멀티 테넌시(multi-tenancy)를 지원하여 여러 그래프를 동시에 보관할 수 있으며, 다수의 클라이언트가 동시에 그래프에 접근하는 것도 가능합니다. 현재는 Redis Stack의 일부로도 제공됩니다.
RedisGraph 2.8의 주요 신규 기능
- 더욱 풍부해진 그래프 모델
- 멀티 라벨 노드
- 강화된 쿼리 기능
- 향상된 전문 검색(full-text search)
- 더 많은 Cypher 구조, 함수, 연산자 지원
- 성능 개선
- 관계(relationship) 속성에 대한 인덱스
- 델타 행렬(Delta Matrices)
- 노드 생성 버퍼 조절 기능
- 벤치마크 결과
더욱 풍부해진 그래프 모델
멀티 라벨 노드
라벨 속성 그래프(LPG, Labelled Property Graph) 데이터 모델에 대한 여러 정의(예: The Property Graph Database Model – Angles, 2018, ISO/IEC JTC 1/SC 32 – GQL 초안)에 따르면 하나의 노드가 여러 개의 라벨을 가질 수 있습니다. v2.8까지 RedisGraph는 단일 라벨만 지원했지만, 이제 성능 저하나 메모리 사용량 증가 없이 각 노드에 여러 라벨을 추가할 수 있습니다.
여러 라벨을 가진 노드를 생성하려면 콜론(:)으로 구분하여 모든 라벨을 나열하기만 하면 됩니다.
GRAPH.QUERY g "CREATE (e:Employee:BoardMember {Name: 'Vincent Chan', Title: 'Web marketing lead'}) return e"
여러 라벨을 가진 노드를 매칭(AND 조건)할 때도 동일한 콜론 표기법을 사용합니다.
GRAPH.QUERY g "MATCH (e:Employee:BoardMember) return e"
강화된 쿼리 기능
향상된 전문 검색
RedisGraph에는 RediSearch가 내장되어 있으며, 이는 보조 인덱싱뿐만 아니라 고급 인덱싱과 검색에도 활용될 수 있습니다. 예를 들어 지구상 특정 지점으로부터의 지리적 근접성을 기준으로 노드를 찾거나, 관련 항목에 더 높은 점수를 부여하는 작업이 가능합니다.
버전 2.8에는 language와 stopwords 설정 옵션이 추가되었습니다. language는 어간 추출(stemming)에 사용할 언어를 정의합니다. 어간 추출은 단어의 원형을 인덱스에 추가하는 것으로, 예를 들어 "going"을 검색하면 "go", "gone"에 대한 결과도 함께 반환됩니다. stopwords는 검색에 큰 정보를 주지 않으면서 인덱스 공간을 많이 차지하는 매우 흔한 단어들(예: "is, the, an, and...")입니다. 이러한 단어들은 인덱싱되지 않고 검색 시 무시되며, "in Paris"처럼 불용어가 포함된 검색어는 "Paris"로만 처리됩니다.
다음은 Movie 라벨을 가진 모든 노드의 title 속성에 대해 독일어와 커스텀 불용어를 적용한 전문 인덱스를 생성하는 예시입니다.
GRAPH.QUERY DEMO_GRAPH "CALL db.idx.fulltext.createNodeIndex({ label: 'Movie', language: 'German', stopwords: ['a', 'ab'] }, 'title')"
RediSearch는 세 가지 필드 설정 옵션을 추가로 제공합니다.
- weight – 해당 필드 내 텍스트의 중요도
- nostem – 텍스트 인덱싱 시 어간 추출 생략
- phonetic – 텍스트에 대한 음성학적(phonetic) 검색 활성화
다음은 Movie 라벨을 가진 모든 노드의 title 속성에 음성 검색을 적용한 전문 인덱스를 생성하는 예시입니다.
GRAPH.QUERY DEMO_GRAPH "CALL db.idx.fulltext.createNodeIndex('Movie', {field: 'title', phonetic: 'dm:en'})"
더 많은 Cypher 구조, 함수, 연산자 지원
RedisGraph 2.8은 Cypher 지원 범위를 확장했습니다.
- 패턴 컴프리헨션(Pattern comprehensions)
allShortestPaths함수 지원- Cypher 함수:
keys,reduce,replace,none,single SET절에서 노드 속성 집합 복사WHERE절에서 노드 라벨 기반 필터링- Cypher 연산자:
XOR및^
패턴 컴프리헨션(Pattern Comprehensions)
패턴 컴프리헨션은 Cypher에서 사용할 수 있는 문법 구조입니다. 리스트 컴프리헨션이 기존 리스트를 기반으로 새 리스트를 만든다면, 패턴 컴프리헨션은 패턴 매칭 결과로 리스트를 채우는 방식입니다. 일반적인 MATCH 절처럼 지정된 패턴을 매칭하고, 일반적인 WHERE 절처럼 조건을 적용하되, 지정된 프로젝션(projection)을 반환합니다.
예를 들어 다음 쿼리는 남성 직원이 받은 지원금 중 금액이 $1,000를 초과하는 모든 지원금 유형을 리스트로 반환합니다.
GRAPH.QUERY g "CREATE (e:Employee {gender:'Male'})-[:granted]->(g:Grant {type: 'Research', amount: 2000})"
GRAPH.QUERY g "MATCH (e:Employee {gender:'Male'}) RETURN [(e)-[:granted]->(g:Grant) WHERE g.amount > 1000 | g.type] AS grantTypes"
allShortestPaths 함수 지원
allShortestPaths 함수는 모든 조건을 충족하는 두 엔티티 사이의 최단 경로 전체를 반환합니다. 두 엔티티는 반드시 앞선 WITH 절로 구분된 스코프에서 바인딩되어 있어야 합니다.
GRAPH.QUERY DEMO_GRAPH "MATCH (c:Actor {name: 'Charlie Sheen'}), (k:Actor {name: 'Kevin Bacon'}) WITH c, k MATCH p = allShortestPaths((c)-[:PLAYED_WITH*]->(k)) RETURN nodes(p) as actors"
이 쿼리는 Charlie Sheen 배우 노드와 Kevin Bacon 배우 노드를 연결하는 최소 길이의 모든 경로를 생성합니다. 두 배우 사이에는 여러 개의 2홉(hop) 경로가 있으며, 이들이 모두 반환됩니다. 길이가 2보다 큰 경로에는 관심이 없으므로 경로 계산은 여기서 종료됩니다.
검색 시 최소 길이(반드시 1 이상)와 최대 길이(최소 1 이상)를 지정할 수 있습니다. 관계 유형은 0개 이상 지정 가능합니다(예: [:R|Q*1..3]). 단, 패턴에 속성 필터는 사용할 수 없습니다.
keys Cypher 함수 지원
keys 함수는 노드, 관계 또는 맵(map)을 입력으로 받아 입력이 포함하고 있는 모든 키의 배열을 반환합니다.
MATCH (a) RETURN keys(a)
MATCH ()-[e]->() RETURN keys(e)
RETURN keys({a:1, b:2})
reduce Cypher 함수 지원
reduce 함수는 시작 값과 리스트를 받아, 리스트의 각 요소에 대해 표현식을 평가하며 값을 갱신합니다.
GRAPH.QUERY g "RETURN reduce(sum = 0, n IN range(1,10) | sum + n)"
이 함수의 출력은 55로, 1부터 10까지 정수의 합입니다.
GRAPH.QUERY g "RETURN reduce(arr = [], n IN range(1,10) | arr + [n*n])"
이 함수의 출력은 1부터 10까지 정수의 제곱값을 담은 배열입니다.
replace 문자열 함수 지원
replace 함수는 주어진 부분 문자열의 모든 항목을 다른 문자열로 대체합니다. 이 함수는 원본 문자열, 대체할 항목, 대체에 사용할 문자열이라는 세 개의 매개변수를 받습니다.
GRAPH.QUERY g "RETURN replace('abc*efg', '*', 'd')"
반환 값은 'abcdefg'입니다.
이 함수는 부분 문자열을 빈 문자열('')로 대체하여 삭제하는 용도로도 사용할 수 있습니다.
none 및 single Cypher 함수 지원
리스트가 주어졌을 때, none은 어떤 요소에도 술어(predicate)가 참이 아니면 true를 반환하고, single은 주어진 술어가 단 하나의 요소에 대해서만 참이면 true를 반환합니다.
GRAPH.QUERY g "RETURN none(x IN range(1,10) WHERE x>10)"
GRAPH.QUERY g "RETURN single(x IN range(1,10) WHERE x>9)"
이 함수들은 all, any 함수와 유사합니다.
가능한 활용 사례 중 하나는 경로 필터링입니다.
GRAPH.QUERY DEMO_GRAPH "MATCH p = (a {name:'Johnny Depp'})-[*2..5]->(b {name:'Kevin Bacon'}) WHERE none(n IN nodes(p) WHERE n.year > 1970) RETURN p"
이 쿼리는 Johnny Depp에서 Kevin Bacon으로 이어지는 길이 2~5의 경로 중, 1970년 이후 태어난 배우가 포함되지 않은 모든 경로를 반환합니다.
SET 절에서 노드 속성 집합 복사 지원
SET 절을 사용하면 한 노드의 모든 속성 값을 다른 노드의 속성 값으로 대체하거나 추가할 수 있습니다.
다음 쿼리는 두 엔티티를 매칭한 후 a의 모든 속성을 b의 속성으로 대체합니다.
GRAPH.QUERY g "MATCH (a {v: 1}), (b {v: 2}) SET a = b"
다음 쿼리는 두 엔티티를 매칭한 후 a의 속성에 b의 속성을 추가(또는 값 대체)합니다.
GRAPH.QUERY g "MATCH (a {v: 1}), (b {v: 2}) SET a += b"
속성을 변경하지 않고 관계의 유형만 변경할 수도 있습니다.
GRAPH.QUERY g "MATCH (a)-[b]->(c) WHERE ID(b)=0 CREATE (a)-[d:bar]->(c) SET d=b DELETE b RETURN d"
WHERE 절에서 노드 라벨 기반 필터링 지원
이제 WHERE 절에서도 노드 라벨이나 관계 유형으로 필터링할 수 있습니다.
GRAPH.QUERY g "MATCH (a) WHERE a:L RETURN a"
GRAPH.QUERY g "MATCH (a)-[b]-(c) WHERE b:L RETURN b"
Cypher 연산자 XOR 및 ^ 지원
GRAPH.QUERY g "RETURN true XOR true"
GRAPH.QUERY g "RETURN 2 ^ 3"
결과는 각각 false와 8입니다.
성능 개선
관계 속성에 대한 인덱스
노드의 경우 다음 명령으로 인덱스를 생성할 수 있습니다.
GRAPH.QUERY g "CREATE INDEX FOR (n:GRANTS) ON (n.GrantedBy)"
이제 관계에 대해서도 인덱스를 생성할 수 있습니다.
GRAPH.QUERY g "CREATE INDEX FOR ()-[r:R]-() ON (r.prop)"
다음 쿼리를 살펴보겠습니다.
GRAPH.QUERY g "MATCH (a)-[r:R {prop:5}]-(b) return *"
인덱스 생성 전 실행 계획은 다음과 같습니다.
redis:6379> GRAPH.EXPLAIN g "MATCH (a)-[r:R {prop:5}]-(b) return *"
1) "Results" |
그리고 인덱스 생성 후 동일한 쿼리의 실행 계획은 다음과 같습니다.
redis:6379> GRAPH.EXPLAIN g "MATCH (a)-[r:R {prop:5}]-(b) return *"
1) "Results" |
델타 행렬(Delta Matrices)
버전 2.8부터는 그래프 노드와 관계의 추가 및 삭제가 훨씬 빨라졌습니다. 변경 사항이 먼저 작은 델타 행렬에 반영되고, 이후 메인 행렬이 한꺼번에 업데이트되기 때문입니다.
RedisGraph에서 그래프는 인접 행렬(adjacency matrix)로 표현됩니다. 그래프의 모든 노드 라벨과 모든 관계 유형은 각자 자신만의 행렬을 가집니다. 이전에는 새 노드가 그래프에 추가될 때마다 모든 행렬의 크기를 재조정해야 했고, 데이터베이스가 클수록 이 작업에 더 많은 시간이 걸렸습니다.
v2.8부터는 새 노드와 관계 삽입에 소요되는 시간이 크게 줄었으며, 더 이상 그래프 크기에 좌우되지 않습니다. 이 최적화는 그래프의 모든 행렬에 대해 두 개의 델타 행렬, 즉 노드 추가용(D+)과 노드 삭제용(D-)을 도입함으로써 달성되었습니다. 노드 추가와 삭제는 해당 델타 행렬에 반영되며, 델타 행렬이 10,000개 노드 임계값(DELTA_MAX_PENDING_CHANGES 설정 파라미터로 조정 가능)에 도달하면 단일 벌크 연산으로 메인 행렬과 동기화된 후 비워지고, 동일한 주기가 다시 시작됩니다.
노드 생성 버퍼 조절 기능
새로운 로드 시점 설정 파라미터인 NODE_CREATION_BUFFER는 향후 노드 생성을 위해 행렬에 예약할 메모리 양을 제어합니다. 예를 들어 16,384로 설정하면 행렬이 생성될 때 16,384개 노드를 위한 여유 공간이 확보되며, 여유 공간이 소진될 때마다 행렬 크기가 16,384씩 증가합니다.
이 값을 줄이면 메모리 소비량은 감소하지만, 행렬 재할당 빈도가 높아져 성능이 저하됩니다. 반대로 값을 늘리면 쓰기 작업이 많은 워크로드에서 성능이 향상될 수 있지만 메모리 소비량이 증가합니다.
전달된 인자가 2의 거듭제곱이 아닌 경우, 메모리 정렬을 개선하기 위해 다음으로 큰 2의 거듭제곱 값으로 반올림됩니다.
벤치마크
델타 행렬 외에도 많은 성능 개선이 이루어졌습니다. 아래에서 LDBC SNB 벤치마크를 통해 이러한 개선 사항을 확인할 수 있습니다.
LDBC SNB(Linked Data Benchmark Council – Social Network Benchmarks)는 그래프 데이터베이스의 실제 읽기·쓰기 워크로드를 비교하는 업계 표준 벤치마크입니다.
RedisGraph 2.8에서는 전체 데이터 로드가 훨씬 빨라졌습니다.
- LDBC 스케일 팩터 1:
RedisGraph 2.8은 RedisGraph 2.4보다 1.92배 빠릅니다. - LDBC 스케일 팩터 10:
RedisGraph 2.8은 RedisGraph 2.4보다 2.00배 빠릅니다.
LDBC 쿼리(읽기 및 쓰기 모두) 역시 RedisGraph 2.8에서 훨씬 빠르게 실행됩니다.
- 읽기 쿼리:
RedisGraph 2.8은 RedisGraph 2.4보다 2.32배 빠릅니다. - 쓰기 쿼리:
RedisGraph 2.8은 RedisGraph 2.4보다 1.09배 빠릅니다.
데이터 복원 및 동기화(RDB와 AOF) 속도도 크게 향상되었습니다(일부 상황에서는 수 배에서 수십 배까지 빨라졌습니다).
RedisGraph는 이제 Redis Stack의 일부
RedisGraph는 이제 Redis Stack에 포함되어 있습니다. macOS, Ubuntu, Redhat용 최신 Redis Stack Server 바이너리를 다운로드하거나, Docker, Homebrew, Linux로 설치할 수 있습니다.
RedisInsight로 RedisGraph 체험하기
RedisInsight는 개발자를 위한 시각화 도구로, Redis 또는 Redis Stack을 사용하는 개발 과정에서 데이터를 손쉽게 탐색할 수 있는 훌륭한 방법을 제공합니다.
그래픽 사용자 인터페이스에서 직접 그래프 쿼리를 실행하고 결과를 확인할 수 있습니다. 이제 RedisInsight는 RedisGraph 쿼리 결과를 시각화할 수 있습니다.
또한 RedisInsight에는 RedisGraph를 대화형으로 학습할 수 있는 빠른 가이드와 튜토리얼이 포함되어 있습니다.
RedisGraph에 대해 더 자세히 알아보려면 redis.io와 developer.redis.com을 방문하세요.