메뉴

문서정보

목차

소개

요즘 그래프데이터베이스를 지겨보고 있다. AWS 넵튠(Neptune)와 Neo4j를 주로 살펴보고 있는데, 오랜 역사와 전통을 자랑하는 Neo4j를 우선 살펴볼 생각이다.

그래프데이터베이스

그래프 데이터베이스(GDB)는 노드(node)와 에지(edge) 형태로 표현 할 수 있는 데이터를 저장하기 위해 특화된 데이터베이스다. 이 시스템의 핵심 개념은 데이터 항목을 노드로 표현하고 노드와 노드의 관계를 에지로 표현하는데 있다. 에지는 관계(relationship)이라고 부르기도 한다. 소셜 서비스는 그래프 데이터페이스를 사용하는 대표적인 서비스다.

그래프 데이터베이스는 관계형 데이터베이스의 한계를 해결하기 위해서 만들어진 NoSQL 데이터베이스의 한 유형이다. 관계형 데이터베이스에 "관계"가 있으니 일정부분 관계를 표현 할 수 있기는 하다. 하지만 관계는 firstclass 속성이 아니라서 조금만 관계가 복잡해져도 이를 표현하기가 극도로 어려워진다. 그래프 데이터베이스에서 "관계"는 firstclass다. 데이터베이스 관리자는 관계에 레이블(label)을 지정하고 방향을 지정하며 속서을 부여 할 수 있다.

SQL를 표준으로 사용하고 있는 관계형 데이터베이스와는 달리 아직 그래프 데이터베이스에 대한 표준질의어는 채택되지 않았다. 하지만 표준화에 대한 노력이 진행되고 있으며 Gremlin, SPARQL, Cypher과 같은 여러 질의언어들을 사용 할 수 있다.

 graph database

Gartner는 다섯 가지의 그래프 범주를 제안하고 있다.

Neo4j

Noe4j는 Neo4j, Inc. 에서 개발한 그래프 데이터베이스 관리 시스템이다. 개발사는 그래프 데이터 저장 및 처리 능력을 가진 ACID 호환 트랜잭션 데이터베이스로 설명하고 있다. Neo4j는 GPL3 라이센스를 가지고 있다. 오픈소스 커뮤니케이션 에디션, 온라인 및 고 가용성 확장을 지원하는 상용 라이센서를 가지고 있다.

Neo4j는 Java로 구현되어 있으며 HTTP 엔드포인트 또는 볼트(bolt) 프로토콜을 통해 Cypher 쿼리 언어로 질의 할 수 있다. Bolt는 연결지향의 클라이언트-서버 통신을 위한 연결지향 프로토콜이며 TCP 또는 WebSocket를 통해 작동한다. Bolt는 Neo4j를 위해서 개발했다.

Neo4j 설치

우분투 리눅스 운영체제에서 설치했다.

Cypher shell 설치

Neo4j 데이터베이스에 연결해서 질의 작업을 수행 할 수 있는 클라이언트 프로그램이다. neo4j 다운로드센터에서 다운로드 할 수 있다. deb 패키지를 다운로드 해서 설치했다.
dpkg -i cypher-shell_4.1.3_all.deb

Noe4j Server 설치 - Docker

간편한 Docker로 설치하기로 했다. 2021년 2월 현재 설치 할 수 있는 버전은 Community Edition 4.2.3 이다.
# mkdir $HOME/neo4j
# cd $HOME/neo4j
# docker run \
    --name testneo4j \
    -p7474:7474 -p7687:7687 \
    -d \
    -v $HOME/neo4j/data:/data \
    -v $HOME/neo4j/logs:/logs \
    -v $HOME/neo4j/import:/var/lib/neo4j/import \
    -v $HOME/neo4j/plugins:/plugins \
    --env NEO4J_AUTH=neo4j/test \
    neo4j:latest
실행된 컨테이너 정보를 확인해보자.
docker ps
CONTAINER ID  IMAGE          COMMAND                    PORTS                                                      NAMES
dfa3996e3368  neo4j:latest   "/sbin/tini -g -- /d…"     0.0.0.0:7474->7474/tcp, 7473/tcp, 0.0.0.0:7687->7687/tcp   testneo4j
포트를 정리해야 겠다.
Name 기본 포트
Backup 6362
HTTP 7474
HTTPS 7473
Bolt 7687
Causal Cluster discovery management 5000
Causal Cluster transaction 6000
Causal Cluster RAFT 7000
Causal Cluster routing connector 7688
Graphite monitoring 2003
Prometheus monitoring 2004
JMX monitoring 3637
Remote debuging 5005
cypher-shell 로 접근해보자.
# cypher-shell -a neo4j://172.17.0.4:7687 -u neo4j -p test
Connected to Neo4j using Bolt protocol version 4.2 at neo4j://172.17.0.4:7687 as user neo4j.
Type :help for a list of available commands or :exit to exit the shell.
Note that Cypher queries must end with a semicolon.
neo4j@movie.db> 
잘 설치된 것 같다.

Neo4j Browser 설치

Neo4j는 그래프데이터베이스로 노드간의 관계를 저장하고 표현한다. 이 정보들을 터미널 에서 텍스트로 확인하는 것은 불가능에 가깝다. Neo4j 브라우저를 이용하면 질의 결과를 그래프 이미지로 보여주기 때문에 실행결과를 쉽게 확인 할 수 있다. Neo4j 다운로드 센터 에서 다운로드 할 수 있다. 나는 리눅스용 데스크탑인 Neo4j Desktop(AppImage)를 다운로드했다.
# chmod +x neo4j-desktop-1.4.1-x86_64.AppImage
# ./neo4j-desktop-1.4.1-x86_64.AppImage

실행하면 아래와 같은 프로젝트 관리 화면이 나타난다.

 실행

Add 버튼을 눌러서 새로운 프로젝트를 만들 수 있다. Remote connection을 선택해서 앞서 실행한 neo4j(docker)에 연결하자.

 neo4j 연결

 로그인

로그인에 사용할 username과 password을 설정한다. 기본 유저이름은 neo4j, 패스워드는 test다. 프로젝트가 저장되면,

 연결하기

이제 "connect"를 클릭해서 프로젝트에 연결 한다. 그러면 아래와 같은 neo4j 브라우저가 실행된다.

 neo4j 브라우저

이렇게 기본적인 개발 환경 설정을 끝냈다.

cypher

Cypher는 그래프 데이터베이스에 데이터를 저장하고 검색하기 위한 Neo4j 의 그래프 쿼리 언어다. ASCII ART 형식으로 쿼리를 만들 수 있으며, 누구나 쉽게 사용 할 수 있도록 개발했다고 한다.(라고 말하고 있지만 SQL과 크게 달라서 익숙해지는데 시간이 좀 걸린다.)

이 문서에서는 Cypher을 자세히 다루진 않을 것이다. 지금까지의 과정을 통해서 개발/테스트 환경은 갖추어졌으니, Cypher 쿼리언어-개발자 가이드 문서를 참고하면서 학습해야 한다. SQL은 익숙할 테니, SQL 기준으로 몇 가지만 살펴보기로 했다.

SQL에서 데이터 모델링은 아래와 같을 것이다. 아래 데이터베이스는 소매 응용 프로그램의 데이터 저장소를 모델링 하고 있다.

 SQL에서의 데이터 모델링

그래프 데이터베이스에서는 아래와 같이 모델링 한다.

 그래프 데이터베이스에서의 모델링

노드

Neo4j에서 그래프는 노드와 관계(relationship) 로 이루어진다. 아래는 3개의 노드로 구성된 데이터다. 이 예제에는 Person, Technology, Company 3 개의 노드가 있다.

 Node

Cyper에서는 괄호로 노드를(예: (node)) 표현한다.
()              // 익명(anonymous noe) 노드. 데이터베이스의 모든 노드를 참조한다.
(p:Person)      // 변수 p를 사용하는 Person 레이블이 붙은 노드
(:Technology)   // 변수 없는 Technology 레이블이 붙은 노드
(work:Company)  // work 변수를 사용하는 Company 레이블이 붙은 노드

노드변수
나중에 노드를 참조하기 위해서 변수를 사용 할 수 있다. 노드 변수는 프로그래밍언어에서의 변수와 동일하게 사용 할 수 있으며, 원하는 변수명을 지정하고 나중에 쿼리에서 동일한 이름을 참조 할 수 있다.

노드 레이블
노드에 노드 레이블을 할당해서 유사한 노드를 그룹으로 묶을 수 있다. 예를 들어서 "Person", "Company", "Techonolgoy"등으로 레이블을 붙일 수 있다. 이렇게 구성한 레이블은 Cypher가 엔터티를 구별하고 쿼리 실행을 최적화하기 위해서 사용 할 수 있으므로 노드 레이블을 사용하는 것이 좋다. 만약 레이블로 노드 범주를 필어링 하지 않을 경우, 전체 노드에 대해서 쿼리를 실행한다. 당연하지만 비효율적으로 작동 할 것이다.

Cypher에서의 Relationship

그래프 데이터베이스가 관계형데이터베이스와 다른 가장 큰 특징은 "관계(relationship)"이 firstclass 요소라는 점이다. 관계는 두 노드 사이에 >, <로 나타낸다. 데이터베이스 관리자는 화살표를 이용한 쿼리를 통해서 시각적인 관계정보를 얻을 수 있다. 이 관계정보는 "어느 노드가 어느 노드와 연결되어 있는지"와 "어느 방향으로 연결되어 있는지"를 포함하고 있다.

아래는 Relationship 구조를 보여주고 있다.

 Relationship

방향이 없이 연결되는 경우에는 화살표 없이 "--"로만 표시된다. 이는 관계가 어느 방향으로든 순회할 수 있음을 의미한다. 아래는 Relationship 예제다. 또한 관계는 관계유형(Relationship Types)를 가지며, 이를 통해서 노드가 어떤 유형의 관계로 연결됐는지를 표시 할 수 있다.
CREATE (p:Person)-[:LIKES]->(t:Technology)
MATCH (p:Person)<-[:LIKES]-(t:Technology)
MATCH (p:Person)-[:LIKES]->(t:Technology)

노드에서와 마찬가지로 질의에서 관계를 참조하기 위해서 변수를 설정 할 수 있다. 예를 들어 -[rel:LIKES]-> 로 질의 하고 나중에 rel 변수를 호출하여 관계의 세부 정보를 참조 할 수 있다.

노드와 관계의 속성
노드와 관계는 속성을 가질 수 있다. 이 속성은 key-value 쌍으로 설정한다. 노드의 속성은 "JSON"과 비슷하게 괄호({})에 설정한다.  속성

Cypher의 패턴

Cypher는 노드와 관계를 이용해서 그래프 패턴을 질의하기 위한 빌딩 블록을 구성한다. 이러한 빌딩 블록을 결합하여 복잡한 패턴들을 만들 수 있다. 이러한 패턴은 neo4j 의 가장 강력한 기능이다.

아래는 Chpher의 패턴 예제다.
(p:Person {name: "jennifer"})-[rel:LIKES]->(g:Technology {type: "Graph"})

Cypher과 SQL

SQL에는 익숙할 테니, SQL과 비교해서 Cypher 질의어의 특징을 살펴보려 한다.

SELECT

SQL에서는 아래와 같이 테이블의 모든 것을 검색한다.
SELECT p.*
FROM products as p;
Cyper에서는 Match, Label, Return으로 표현 할 수 있다.
MATCH (p:Product)
RETURN p;

pruducts를 낮은 가격 순으로 정렬하여 상위 10개를 출력하는 SQL 예제다.
SELECT p.ProductName, p.UnitPrice
FROM products as p
ORDER BY p.UnitPrice DESC
LIMIT 10;
위 SQL의 많은 부분을 복사해서 사용 할 수 있다. Neo4j는 대소문자를 구분한다.
MATCH (p:Product)
RETURN p.productName, p.unitPrice
ORDER BY p.unitPrice DESC
LIMIT 10;

Name으로 Product를 찾는 예제다.
SELECT p.ProductName, p.UnitPrice
FROM products AS p
WHERE p.ProductName = 'Chocolade';

Cypher로 where절을 사용 할 수 있다.
MATCH (p:Product)
WHERE p.productName = 'Chocolade'
RETURN p.productName, p.unitPrice;

혹은 아래와 같이 쿼리를 단순화 할 수 있다.
MATCH (p:Product {productName:"Chocolade"})
RETURN p.productName, p.unitPrice;

색인

색인을 만들면 빠르게 데이터를 검색 할 수 있다. 제품이름과 가격으로 색인을 걸어보자.
CREATE INDEX ON :Product(productName); 
CREATE INDEX ON :Product(unitPrice);

필터링

여러개의 값으로 필터링 할 수 있다.
SELECT p.ProductName, p.UnitPrice
FROM products as p
WHERE p.ProductName IN ('Chocolade','Chai');
IN 연산자 함수등 많은 부분에서 유사하다. {{[#!plain MATCH (p:Product) WHERE p.productName IN ['Chocolade', 'Chai'] RETURN p.productName, p.unitPrice; }}}

JOIN

누가 Chocolade를 샀는지 확인하고 싶다. SQL의 경우 많은 테이블을 join 해야 한다.
SELECT DISTINCT c.CompanyName
FROM customers AS c
JOIN orders AS o ON (c.CustomerID = o.CustomerID)
JOIN order_details AS od ON (o.OrderID = od.OrderID)
JOIN products AS p ON (od.ProductID = p.ProductID)
WHERE p.ProductName = 'Chocolade';

Cypher는 join이 필요하지 않다. 그래프 패턴으로 간단하게 조회 할 수 있다.
MATCH (p:Product {productName: "Chocolade"}) <-[:PRODUCT]-(:Order)<-[:PURCHASED]-(c:Customer)
RETURN distinct c.companyName;

최고판매 직원

최고 판매 직원을 질의하기 위해서는 sum, count, avg, max 등의 함수를 사용해야 한다. SQL에서는 GROUP BY 절을 이용해서 그룹화를 해야 한다.
SELECT e.EmployeeID, count(*) AS Count
FROM Employee AS e
JOIN Order AS o ON (o.EmployeeID = e.EmployeeID)
GROUP BY e.EmployeeID
ORDER BY Count DESC LIMIT 10;
Cypher에서 집계를 위한 그룹화는 암시적이다. 집계함수를 사용하면 자동으로 그룹화가 된다.
MATCH (:Order)<-[:SOLD]-(e:Employee)
RETURN e.name, count(*) AS cnt
ORDER BY cnt DESC LIMIT 10

제품카테고리

제품카테고리는 여러 계층으로 구성 될 수(여러 층을 가지는 게시판을 생각해보자) 있다. 이 경우 일반적으로 자식에서 부모로의 외래키를 이용해서 join 해야 한다. 다단계 쿼리를 시작하면 join 수가 급증한다. 특히 계층의 깊이가 고정되지 않은 경우 더욱 복잡해질 것이다.
SELECT p.ProductName
FROM Product AS p
JOIN ProductCategory pc ON (p.CategoryID = pc.CategoryID AND pc.CategoryName = "Dairy Products")

JOIN ProductCategory pc1 ON (p.CategoryID = pc1.CategoryID
JOIN ProductCategory pc2 ON (pc2.ParentID = pc2.CategoryID AND pc2.CategoryName = "Dairy Products")

JOIN ProductCategory pc3 ON (p.CategoryID = pc3.CategoryID
JOIN ProductCategory pc4 ON (pc3.ParentID = pc4.CategoryID)
JOIN ProductCategory pc5 ON (pc4.ParentID = pc5.CategoryID AND pc5.CategoryName = "Dairy Products");

Cypher는 적절한 관계만으로 모든 깊이의 계층을 표현할 수 있다. 계층 수준은 여러 개의 경로로 표현할 수 있다.
MATCH (p:Product)-[:CATEGORY]->(l:ProductCategory)-[:PARENT*0..]-(:ProductCategory {name:"Dairy Products"})
RETURN p.name

프로그램 개발

간단한 Neo4j 애플리케이션을 개발하기로 했다. 개발에 사용하는 언어는 go다. Neo4j, Cypher, Noe4j 데스크탑은 모두 설치되어 있는 걸로 가정하겠다.

데이터 모델링

아래와 같은 소셜 네트워크 기반 게임 플랫폼을 모델링 했다. 이 서비스는 게임을 카테고라이징하고, 유저와 유저의 관계를 설정한다. 이 게임 플랫폼은 사용자 관계에 기반하여 게임 컨텐츠를 추천한다.

 데이터 모델링

... 계속

참고