Neo4j - chapter 7.2 - Getting More Out of Neo4j

5 분 소요

About this module.

  • 이전의 chapter들에서는 Neo4j를 개발하기 위한 개발 환경은 어떻게 설정되어야 하고, 기본적인 Cypher 쿼리문은 어떻게 작성하며, 그리고, 쿼리를 통해 그래프를 어떻게 수정하는지 등에 대해서 배웠다.
  • 이번 챕터에서는 그 외로 다음의 다양한 것들을 배우게 된다.
    • Use parameters in your Cypher statements.
    • Analyze Cypher execution.
    • Monitor queries.
    • Manage constraints and node keys for the graph.
    • Import data into a graph from CSV files.
    • Manage indexes for the graph.
    • Access Neo4j resources.

Cypher parameters

  • 자주 사용되는 값이나, 실수로 잘못 입력하면 안되는 중요한 값들은 상수화하여, parameter로 설정하여, 쿼리에 넘겨준다.

Using Cypher parameters

  • parameter 설정은 다음과 같이 화살표로 어싸인을 해준다. 다만, R과 같은 언어에서 해주는 어싸인과 방향이 다름을 유의해야 함.
:param actorName => 'Tom Hanks'
  • 파라미터를 쿼리 문제 집어넣어서 실행하는 것은 다음과 같다. $actorName와 같이 dollar sign과 함께 변수명을 넣어준다.
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = $actorName
RETURN m.released, m.title ORDER BY m.released DESC
  • 다음과 같이, json형식으로 param을 함께 넘기는 것 또한 가능합니다.
:params {actorName: 'Tom Cruise', movieName: 'Top Gun'}

Analyzing Cypher execution

  • 사실, neo4j를 실행해서 예제로 주어주는 Movie Graph의 경우는 매우 작은 그래프이며, 실제로 가령 페이스북이나 인스타그램의 데이터들이 거대한 데이터셋으로 구축되어 있다고 할 경우, 적합한 index들을 그래프에 추가하는 것(to add appropriate indexes)이 필요할 뿐만 아니라, Cypher statement를 효율적으로 쓰는 것 또한 매우 중요하다(write Cypher statements that execute as efficiently as possible).
  • 현재 Query의 효율성을 분석하기 위해서 Cypher statement 앞에 붙이는 두 개의 접두사(prefix)가 있다.
    • EXPLAIN: 실제로 Cypher를 수행하지는 않고, 그래프 엔진에서 프로세싱할 때를 대략 가늠하여 알려준다.
    • PROFILE: 실제로 그래프 엔진에서 Cypher를 수행하여 그 결과를 프로파일링하여 제공한다.
  • 가령, 다음과 같이, 우리가 그냥 쓰는 명령어에 EXPLAIN만 붙여서 사용하면 되며 이렇게 사용할 경우, 일종의 파이프라인으로 쿼리를 시각화하여, 어떤 단계에서 부하가 어느 정도 소요되는지를 명확하게 보여준다.
EXPLAIN 
MATCH (p:Person)-[:ACTED_IN]->(m:Movie)
WHERE p.name = $actorName AND
      m.released <  $year
RETURN p.name, m.title, m.released

Monitoring queries

  • 또한, 만약 어플리케이션을 만들어서, 그래프에 여러 쿼리가 동시에 질의된다면, 브라우저에 과부하가 걸려서 응답에 오랜 시간이 소요될 수 있다. 이는, 보통, 해당 쿼리가 너무 많은 데이터를 반환해야 하거나, 응답 시간에 오래 걸리는 경우가 모두 포함되죠. 그러한 예로 해당 링크에서는 다음과 같은 두 가지의 쿼리를 예로 들었습니다.
  • 너무 많은 데이터를 반환하는 경우
    MATCH (a)--(b)--(c)--(d)--(e)--(f) RETURN a
    
  • 응답 시간이 오래 걸리는 경우
    MATCH (a), (b), (c), (d), (e) RETURN count(id(a))
    
  • 이럴 때, 현재 수행되고 있는 쿼리가 무엇무엇이 있는지를 확인하고, 필요할 때 이를 제거하는 경우가 필요하겠죠. 이렇게 현재 수행되는 쿼리들을 모니터링하기 위해서 사용되는 명령어가 :queries입니다. 또한 이 명령어는 dbms.listQueries와 동일하죠.

Managing constraints and node keys

  • 이전에 설명한 것처럼, 기본적인 GraphDB의 경우 자유도가 매우 높아서, 중복되는 노드들에 대해서 허용됩니다. 이를 방지하려면 CREATE가 아닌, MERGE를 사용하는데, 그래도, 음, 일관적으로 데이터를 업데이트한다고 보기는 어려워요.
  • 그리고, 노드나 엣지에 대해서도 label, property에 대해서 어떤 제한사항을 걸 수 있습니다. 그리고, 그 외로도 마치 RDB에서의 Primary key와 같은 제한사항들도 걸 수 있죠. 사실 이런 것은 매우 일반적이고, 당연한 데이터 일관성 관리라고 보이는데요, Neo4j는 다음의 것들을 지원합니다.
    • uniqueness constraint: node의 property를 unique하게
    • existence constrain: node, edge가 생성되었을 때, 이 들이 확정된 property set를 가질 수 있도록
  • 다만, Existence constraints의 경우는 Enterprise Edition of Neo4j에서만 유효합니다.

Ensuring that a property value for a node is unique

  • 노드의 특정한 property가 unique함을 assert하는 constraint를 넣어줄 수 있습니다.
  • 다음과 같이, CONSTRAINT를 정의해줍니다.
CREATE CONSTRAINT 
ON (m:Movie) 
ASSERT m.title IS UNIQUE
  • 위 CONSTRAINT가 존재하는 상황에서, 아래와 같은 쿼리를 통해, 중복되는 노드를 생성하려고 하면, 에러가 발생합니다.
CREATE (:Movie {title: 'The Matrix'})
Neo.ClientError.Schema.ConstraintValidationFailed
Neo.ClientError.Schema.ConstraintValidationFailed: Node(0) already exists with label `Movie` and property `title` = 'The Matrix'

Ensuring that properties exist

  • property가 존재하도록 강제하는 것. 즉, uniqueness뿐만 아니라, 해당 property를 가지고 있도록 강제할 수 있습니다.
  • 다음의 형식으로 노드에 특정 property가 없을 때, 에러를 발생시키도록 할 수 있으며
CREATE CONSTRAINT 
ON (m:Movie) 
ASSERT exists(m.tagline)
  • 마찬가지로 edge에 대해서도 같은 제한사항을 적용할 수 있습니다.
CREATE CONSTRAINT 
ON ()-[rel:REVIEWED]-() 
ASSERT exists(rel.rating)

Retrieving constraints defined for the graph

  • 이렇게, 만들어진 constraint들은 다음의 명령어를 통해서 확인할 수 있습니다.
CALL db.constraints()

Dropping constraints

  • constraint를 폐기하기 위해서는 DROP를 사용하여 다음과 같은 쿼리를 전송합니다.
DROP CONSTRAINT 
ON ()-[rel:REVIEWED]-() 
ASSERT exists(rel.rating)

Creating node keys

  • node key는 그냥 RDB에서 primary key라고 생각하시면 됩니다. 현재 property의 조합만으로 해당 노드의 독창성(uniquess)가 보장되어야 한다는 것이죠.
  • 다음과 같이 표현합니다.
CREATE CONSTRAINT 
ON (p:Person) 
ASSERT (p.name, p.born) IS NODE KEY

Managing indexes

  • 우선 DB index를 다시 정리합니다. index는 한국말로 ‘색인’이며, 만약 우리에게 엑셀 파일이 있고, 여기에 행(row)이 약 1억 개 있다고 해봅시다. 그리고, 무엇이 어디에 있는지 정확하게 알지 못하여, 매번 처음부터 끝까지 다 찾아야 한다고 하면, 우리는 매 쿼리마다 full-scan을 해야 합니다. 이 얼마나 비효율적인가요?
  • 다시, 여기서 전화번호부를 생각해봅시다. 전화번호부를 보면, 이미 ㄱ부터 ㅎ까지 특정 칼럼에 대해서 어떤 값이 어디에 위치하는지를 구분해 놓았습니다. 따라서, 만약 쿼리에서 필요로 하는 값들이 ㄱ에 위치해 있다면, 그 값들만을 읽어들이면 되는 것이죠. 즉, 일종의 hyperlink들처럼 원하는 값들이 어디에 존재하는지를 정리해서 모든 값들을 확인하지 않고도 바로 읽어들일 수 있는 것을 말합니다.
  • DB에서 보통 Index는 초기의 노드 검색 성능(initial node lookup performance)를 향상시키기 위해서 사용됩니다. 하지만, 당연하지만, 이는 추가의 저장 공간이 필요하고, 그래프의 값들이 변경될 경우, 다 함께 indexing되므로 추가의 부하가 당연히 발생하게 되죠. index는 노드의 값들에 대한 중복되는 데이터를 저장합니다.

  • single-property에 대해서 index가 사용될 때에 대한 간단한 요약을 정리하면 다음과 같습니다.
    • Equality checks =
    • Range comparisons >,>=,<, <=
    • List membership IN
    • String comparisons STARTS WITH, ENDS WITH, CONTAINS
    • Existence checks exists()
    • Spatial distance searches distance()
    • Spatial bounding searches point()
  • single property가 아니라, 복합 키(composite indexes)의 경우는 equality checks 와 list membership에서만 사용될 수 있습니다. index maintenance는 노드가 생성될 때, 추가적인 부하를 발생시킵니다. 따라서, 큰 그래프의 경우, 데이터가 충분히 그래프에 담긴 이후, index를 생성 및 유지할 것으로 추천한다.

Indexes for range searches

  • node의 property에 대해서 index를 추가하게 되면, graph 엔진이 쿼리를 마족하기 위해서, 방문해야 하는 노드의 수가 극도로 감소하게 됩니다. 가령, 아래와 같은 쿼리에서 m.released에 대해 index를 만들어두었다면(즉, 해당 프로퍼티에 대해서 binary search tree와 같은 형태로 위치 값들이 구축되어 있다면), 아래의 쿼리는 모든 노드를 방문하지 않고도 빠르게 실행될 수 있습니다.

Creating indexes

  • 앞서 말한 바와 같이, index는 graph engine의 성능을 향상시키기 위해서 만들어집니다. 다음과 같은 형태로 생성되며,
CREATE INDEX 
ON :Movie(released)
  • composite index에 대해서는 다음과 같이 실행한다.
CREATE INDEX 
ON :Movie(released, videoFormat)

wrap-up

  • 이전의 챕터들에서 배운 것들이 Cypher를 통해서 단지 데이터를 어떻게 접근하고, 생성하고, 성질들을 부여 및 제거하는 등, 기본적인 그래프 데이터 모델 위에서 걸어가는 법을 배웠다면, 여기서는 보다 전통적인 관점에서 DB를 다루는 기술들을 배웠다고 볼 수 있다.
  • 가령, parameter를 통해 필요한 변수들을 상수화하는 것, constraint를 통해 데이터베이스에서 관리하는 데이터의 일관성을 관리하는 것, 쿼리가 얼마나 효율적으로 수행되는지를, 분석하는 것, 필요한 데이터를 빠르게 쿼리하기 위해서 인덱스를 사용하는 것 등이 여기에 속하는 것들이다.
  • 다만, RDB에서는 VIEW와 같은 방법으로 가상의 테이블을 저장하는 방법이 존재하는데(정확히는 가상의 테이블이 존재하는 것이 아니라, 필요에 따라서, 만들어내는 것에 가깝기는 하지만), 본문에서는 Neo4j에서 이와 같은 가상의 뷰를 만드는 방법을 알려주지 않았다. 아마도 있을 것이다, 라고 생각하기는 하지만, 이는 이후에 확인해보아야 할 것이고.

댓글남기기