포스트

그래프 데이터베이스 개발자 가이드

그래프 데이터베이스 개발자 가이드

Cypher (Neo4j) & Turtle (RDF/OWL) 완전 정복


아래 사이퍼 설명해줘. 이와 관련된 사이퍼 문법, 개발자 가이드를 마크다운 문서로 만들어 줄 수 있을까? 
테스트 해볼 수 있는 환경도 알려주고. 
turtle이 무엇인지도 궁금해. 

// 수사관은 자신이 배정된 케이스의 데이터만 볼 수 있음
MATCH (u:User {id: $userId})-[:ASSIGNED_TO]->(case:Case)
     <-[:PART_OF]-(entity)
RETURN entity

# 규칙 1: Suspect는 Person의 하위 클래스다
:Suspect rdfs:subClassOf :Person .

# 규칙 2: Investigator와 Suspect는 겹칠 수 없다
:Investigator owl:disjointWith :Suspect .

# 규칙 3: CriminalOrganization은 Organization의 하위 클래스다
:CriminalOrganization rdfs:subClassOf :Organization .

📌 코드 분석: 무엇이 섞여 있나?

질문에 제시된 코드는 두 가지 완전히 다른 기술이 혼재합니다.

구분언어목적시스템
첫 번째 블록Cypher그래프 DB 데이터 조회Neo4j
두 번째~네 번째 블록Turtle (.ttl)온톨로지/지식그래프 정의RDF/OWL

1부: Cypher — Neo4j 그래프 쿼리 언어

🔍 코드 해설

1
2
3
4
// 수사관은 자신이 배정된 케이스의 데이터만 볼 수 있음
MATCH (u:User {id: $userId})-[:ASSIGNED_TO]->(case:Case)
     <-[:PART_OF]-(entity)
RETURN entity

이 쿼리가 하는 일을 단계별로 보면:

1
2
3
4
[User] --ASSIGNED_TO--> [Case] <--PART_OF-- [entity]
  ↑                       ↑                    ↑
특정 userId를           배정된 케이스       케이스에 속한
가진 수사관             찾기               모든 엔티티 반환

핵심 포인트:

  • (u:User {id: $userId}) → 파라미터 바인딩으로 SQL Injection 방지
  • <-[:PART_OF]- → 화살표 방향: entitycase를 향해 PART_OF 관계를 가짐
  • 이 패턴 자체가 Row-Level Security (행 수준 보안) 역할을 함
    • 조인 조건이 곧 권한 검사 — “배정된 케이스에 속한 것만” 자연스럽게 필터링

📚 Cypher 문법 완전 가이드

1. 노드 (Node) 표현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
// 기본 노드
()

// 레이블이 있는 노드
(:Person)

// 변수 바인딩
(p:Person)

// 속성 필터
(p:Person {name: "Alice", age: 30})

// 여러 레이블 (Neo4j 4.x+)
(p:Person:Employee)

2. 관계 (Relationship) 표현

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 방향 없는 관계
(a)-[:KNOWS]-(b)

// 방향 있는 관계
(a)-[:KNOWS]->(b)   // a가 b를 앎
(a)<-[:KNOWS]-(b)   // b가 a를 앎

// 관계에 변수 바인딩
(a)-[r:KNOWS]->(b)

// 관계 속성
(a)-[r:KNOWS {since: 2020}]->(b)

// 가변 길이 경로 (1~3홉)
(a)-[:KNOWS*1..3]->(b)

// 최단 경로
shortestPath((a)-[:KNOWS*]-(b))

3. MATCH — 패턴 매칭

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
// 단순 매칭
MATCH (p:Person)
RETURN p

// 복합 패턴
MATCH (suspect:Suspect)-[:ASSOCIATED_WITH]->(org:CriminalOrganization)
WHERE org.name = "Cobra"
RETURN suspect.name, org.name

// 선택적 매칭 (LEFT JOIN과 동일)
MATCH (p:Person)
OPTIONAL MATCH (p)-[:HAS_ALIAS]->(alias)
RETURN p.name, alias.value

// 여러 패턴 동시 매칭
MATCH (a:Person)-[:WORKS_AT]->(c:Company),
      (a)-[:LIVES_IN]->(city:City)
RETURN a.name, c.name, city.name

4. WHERE — 조건 필터

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// 기본 조건
WHERE p.age > 30

// AND / OR / NOT
WHERE p.age > 30 AND p.status = "active"
WHERE NOT p.deleted

// IN 리스트
WHERE p.status IN ["active", "suspended"]

// 정규식 (STARTS WITH, ENDS WITH, CONTAINS)
WHERE p.name STARTS WITH "Kim"
WHERE p.email CONTAINS "@"

// NULL 체크
WHERE p.alias IS NULL
WHERE p.alias IS NOT NULL

// 관계 존재 여부
WHERE EXISTS { (p)-[:HAS_RECORD]->() }

5. 데이터 변경 (Write)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 노드 생성
CREATE (p:Person {name: "Alice", id: 1})

// 관계 생성
MATCH (a:Person {id: 1}), (b:Case {id: 100})
CREATE (a)-[:ASSIGNED_TO {date: date()}]->(b)

// MERGE — 없으면 생성, 있으면 매칭 (upsert)
MERGE (p:Person {id: $id})
ON CREATE SET p.created = timestamp()
ON MATCH SET p.lastSeen = timestamp()

// 속성 업데이트
MATCH (p:Person {id: 1})
SET p.status = "inactive"
SET p += {score: 90, updated: true}  // 여러 속성 한번에

// 삭제
MATCH (p:Person {id: 999})
DELETE p                // 관계 없는 노드만 삭제 가능

MATCH (p:Person {id: 999})
DETACH DELETE p         // 관계 포함 강제 삭제

6. 집계 함수

1
2
3
4
5
6
7
8
9
10
11
// 기본 집계
MATCH (c:Case)<-[:PART_OF]-(e)
RETURN c.id, count(e) AS entityCount, collect(e.name) AS entityNames

// 집계 함수 목록
count(*)          // 전체 행 수
count(x)          // null 제외 카운트
sum(x.amount)     // 합계
avg(x.score)      // 평균
min(x), max(x)    // 최소/최대
collect(x)        // 리스트로 수집

7. WITH — 파이프라인 (중간 결과 전달)

1
2
3
4
5
6
7
// SQL의 서브쿼리처럼 중간 처리
MATCH (u:User)-[:ASSIGNED_TO]->(c:Case)
WITH u, count(c) AS caseCount
WHERE caseCount > 5
RETURN u.name, caseCount
ORDER BY caseCount DESC
LIMIT 10

8. UNWIND — 리스트 펼치기

1
2
3
4
5
6
7
8
9
// 리스트를 개별 행으로 전개
WITH ["Alice", "Bob", "Charlie"] AS names
UNWIND names AS name
CREATE (:Person {name: name})

// JSON 배열 처리 패턴
UNWIND $suspects AS suspectData
MERGE (s:Suspect {id: suspectData.id})
SET s += suspectData

9. 전체 CRUD 패턴 예제 (수사 시스템)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
// ===== 케이스 생성 =====
CREATE (c:Case {
  id: "CASE-2024-001",
  title: "Operation Nightfall",
  status: "active",
  created: datetime()
})

// ===== 수사관 배정 =====
MATCH (u:Investigator {badge: $badge}), (c:Case {id: $caseId})
MERGE (u)-[:ASSIGNED_TO {assignedAt: datetime()}]->(c)

// ===== 용의자를 케이스에 연결 =====
MATCH (c:Case {id: $caseId})
MERGE (s:Suspect {name: $name})
MERGE (s)-[:PART_OF]->(c)

// ===== 조직 관계망 탐색 (가변 길이 경로) =====
MATCH path = (s:Suspect {name: "Lee"})-[:MEMBER_OF|ASSOCIATED_WITH*1..4]-(other)
RETURN path

// ===== 케이스 종결 =====
MATCH (c:Case {id: $caseId})
SET c.status = "closed", c.closedAt = datetime()

📊 인덱스와 성능

1
2
3
4
5
6
7
8
9
10
// 인덱스 생성 (Neo4j 4.x+ 구문)
CREATE INDEX user_id_idx FOR (u:User) ON (u.id)
CREATE INDEX case_status_idx FOR (c:Case) ON (c.status)

// 복합 인덱스
CREATE INDEX suspect_name_dob FOR (s:Suspect) ON (s.name, s.dob)

// EXPLAIN / PROFILE로 실행 계획 확인
EXPLAIN MATCH (u:User {id: $id})-[:ASSIGNED_TO]->(c:Case) RETURN c
PROFILE MATCH (u:User {id: $id})-[:ASSIGNED_TO]->(c:Case) RETURN c

2부: Turtle — RDF 온톨로지 언어

🐢 Turtle이란?

Turtle (Terse RDF Triple Language)지식 그래프(Knowledge Graph)의 구조와 규칙을 정의하는 언어입니다.

1
2
RDF = Resource Description Framework
      → 모든 지식을 "주어 - 술어 - 목적어" 트리플로 표현하는 W3C 표준

Neo4j Cypher가 “데이터를 어떻게 조회하는가”라면, Turtle/OWL은 “데이터가 어떤 개념이고 어떤 규칙을 따르는가”를 정의합니다.

 Cypher / Neo4jTurtle / RDF
목적데이터 저장·조회지식 구조·규칙 정의
유형프로퍼티 그래프 DB시맨틱 웹 / 지식그래프
추론없음 (명시적 데이터만)있음 (규칙으로 새 사실 추론)
표준사실상 표준 (Neo4j)W3C 국제 표준
사용처앱 백엔드, 그래프 분석지식베이스, AI, 의미론적 검색

🔍 코드 해설

1
2
3
4
5
6
7
8
# 규칙 1: Suspect는 Person의 하위 클래스다
:Suspect rdfs:subClassOf :Person .

# 규칙 2: Investigator와 Suspect는 겹칠 수 없다
:Investigator owl:disjointWith :Suspect .

# 규칙 3: CriminalOrganization은 Organization의 하위 클래스다
:CriminalOrganization rdfs:subClassOf :Organization .

각 줄은 트리플 (Subject - Predicate - Object) 입니다:

1
2
3
4
:Suspect          rdfs:subClassOf      :Person .
   ↑                    ↑                 ↑
주어(Subject)     술어(Predicate)    목적어(Object)
"용의자 클래스"    "~의 하위 클래스"   "사람 클래스"

추론 효과:

  • rdfs:subClassOf → “Suspect이면 자동으로 Person이기도 함” → SPARQL에서 Person 검색 시 Suspect도 포함됨
  • owl:disjointWith → “한 개인이 동시에 Investigator이면서 Suspect일 수 없음” → 데이터 무결성 보장

📚 Turtle 문법 완전 가이드

1. 기본 구조: 트리플

1
2
3
4
5
6
7
8
9
10
11
12
13
# 기본 형식: 주어 술어 목적어 .
:Alice rdf:type :Investigator .

# 약식 표현 (a = rdf:type)
:Alice a :Investigator .

# 같은 주어, 여러 술어 (세미콜론)
:Alice a :Investigator ;
       :name "Alice Kim" ;
       :badgeNumber "INV-001" .

# 같은 주어+술어, 여러 목적어 (쉼표)
:Alice :investigates :CASE-001, :CASE-002, :CASE-003 .

2. Prefix 선언

1
2
3

# PREFIX 없이 긴 URI 직접 사용도 가능
<http://example.org/crime#Suspect> a <http://www.w3.org/2002/07/owl#Class> .

3. 클래스 계층 (RDFS)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 클래스 정의
:Person a owl:Class ;
        rdfs:label "사람"@ko ;
        rdfs:comment "수사 시스템의 모든 사람 개체"@ko .

# 상속 계층
:Investigator rdfs:subClassOf :Person .
:Suspect      rdfs:subClassOf :Person .
:Witness      rdfs:subClassOf :Person .

:CriminalOrganization rdfs:subClassOf :Organization .

# 다중 상속도 가능
:UndercoverAgent rdfs:subClassOf :Investigator ;
                 rdfs:subClassOf :Suspect .  # 위장 수사관

4. OWL 제약 (Constraints)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# 두 클래스가 겹칠 수 없음
:Investigator owl:disjointWith :Suspect .
:Victim       owl:disjointWith :Suspect .

# 속성 도메인/범위 제약
:investigates rdfs:domain :Investigator ;
              rdfs:range  :Case .

# 카디널리티 (함수형 속성 = 값이 하나만)
:badgeNumber a owl:FunctionalProperty ;
             rdfs:domain :Investigator ;
             rdfs:range  xsd:string .

# 역 속성 정의
:isInvestigatedBy owl:inverseOf :investigates .

5. 데이터 속성 (Datatype Properties)

1
2
3
4
5
6
7
8
9
:Suspect a owl:Class .

# 개인(Individual) 데이터
:suspect_lee a :Suspect ;
             :fullName "이민준"^^xsd:string ;
             :dateOfBirth "1985-03-12"^^xsd:date ;
             :age 39^^xsd:integer ;
             :dangerLevel 7.5^^xsd:decimal ;
             :isArrested false^^xsd:boolean .

6. 객체 속성 (Object Properties)

1
2
3
4
5
6
7
8
# 개인 간 관계
:suspect_lee :memberOf :cobra_org ;
             :associatedWith :suspect_kim ;
             :partOf :case_001 .

:cobra_org a :CriminalOrganization ;
           :name "Cobra Syndicate" ;
           :basedIn :city_seoul .

7. 완성된 범죄 수사 온톨로지 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47

# ===== 클래스 정의 =====
:Person a owl:Class .
:Investigator rdfs:subClassOf :Person .
:Suspect      rdfs:subClassOf :Person .
:Witness      rdfs:subClassOf :Person .
:Victim       rdfs:subClassOf :Person .

:Organization a owl:Class .
:CriminalOrganization rdfs:subClassOf :Organization .
:LawEnforcementAgency rdfs:subClassOf :Organization .

:Case a owl:Class .
:Evidence a owl:Class .

# ===== 제약 규칙 =====
:Investigator owl:disjointWith :Suspect .
:Suspect      owl:disjointWith :Victim .
:Investigator owl:disjointWith :Victim .

# ===== 속성 정의 =====
:assignedTo rdfs:domain :Investigator ;
            rdfs:range :Case .

:partOf rdfs:domain [ owl:unionOf (:Suspect :Witness :Evidence) ] ;
        rdfs:range :Case .

:memberOf rdfs:domain :Person ;
          rdfs:range :CriminalOrganization .

# ===== 데이터 =====
:case_001 a :Case ;
          :caseNumber "2024-CASE-001" ;
          :title "Operation Nightfall" ;
          :status "active" .

:inv_alice a :Investigator ;
           :name "Alice Kim" ;
           :assignedTo :case_001 .

:sus_lee a :Suspect ;
         :name "Lee Minjun" ;
         :partOf :case_001 ;
         :memberOf :cobra .

:cobra a :CriminalOrganization ;
       :name "Cobra Syndicate" .

🔎 SPARQL — RDF 조회 언어 (Cypher에 대응)

Turtle로 정의한 데이터는 SPARQL로 조회합니다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# Cypher 예제와 동일한 의미: "수사관이 배정된 케이스의 엔티티 조회"
PREFIX : <http://crime-investigation.org/onto#>

SELECT ?entity
WHERE {
  ?user a :Investigator .
  ?user :assignedTo ?case .
  ?entity :partOf ?case .
}

# 추론 활용: Person 검색 시 Suspect/Victim/Investigator 모두 포함
SELECT ?person ?name
WHERE {
  ?person a :Person ;          # 추론 엔진이 하위 클래스도 포함
          :name ?name .
}

3부: 테스트 환경 구축

🧪 Cypher (Neo4j) 테스트 환경

옵션 1: Neo4j Desktop (로컬, 가장 추천)

1
2
3
4
5
# 1. 다운로드
# https://neo4j.com/download/ → Neo4j Desktop

# 2. 실행 후 프로젝트 생성 → Add Database → Local DBMS
# 3. Browser에서 Cypher 직접 실행

옵션 2: Docker (개발 환경)

1
2
3
4
5
6
7
8
9
10
# Neo4j 커뮤니티 에디션
docker run \
  --name neo4j \
  -p 7474:7474 -p 7687:7687 \
  -e NEO4J_AUTH=neo4j/password \
  -v $HOME/neo4j/data:/data \
  neo4j:5-community

# 접속: http://localhost:7474
# 초기 로그인: neo4j / password

옵션 3: AuraDB (클라우드, 무료 티어)

1
2
https://neo4j.com/cloud/platform/aura-graph-database/
→ Start Free → AuraDB Free (1GB, 무료)

옵션 4: Neo4j Sandbox (브라우저, 즉시)

1
2
3
https://sandbox.neo4j.com/
→ 로그인 없이 즉시 사용 가능
→ 범죄 수사 예제 데이터셋 포함! (Crime Investigation 선택)

Neo4j 드라이버 (애플리케이션 연동)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// Node.js
import neo4j from 'neo4j-driver'

const driver = neo4j.driver(
  'bolt://localhost:7687',
  neo4j.auth.basic('neo4j', 'password')
)

const session = driver.session()

const result = await session.run(
  `MATCH (u:User {id: $userId})-[:ASSIGNED_TO]->(case:Case)
        <-[:PART_OF]-(entity)
   RETURN entity`,
  { userId: 'user-001' }
)

result.records.forEach(record => {
  console.log(record.get('entity').properties)
})

await session.close()
await driver.close()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
# Python
from neo4j import GraphDatabase

driver = GraphDatabase.driver("bolt://localhost:7687",
                              auth=("neo4j", "password"))

with driver.session() as session:
    result = session.run(
        """MATCH (u:User {id: $userId})-[:ASSIGNED_TO]->(case:Case)
                <-[:PART_OF]-(entity)
           RETURN entity""",
        userId="user-001"
    )
    for record in result:
        print(record["entity"])

🧪 Turtle / RDF 테스트 환경

옵션 1: Protégé (OWL 편집기, 가장 추천)

1
2
3
4
https://protege.stanford.edu/
→ 무료, Stanford 제공
→ Turtle 파일 열기/편집/추론 실행 모두 가능
→ 온톨로지 시각화 지원

옵션 2: Apache Jena Fuseki (SPARQL 서버)

1
2
3
4
5
6
7
# Docker로 실행
docker run -p 3030:3030 \
  -e ADMIN_PASSWORD=password \
  stain/jena-fuseki

# 접속: http://localhost:3030
# Turtle 파일 업로드 → SPARQL 쿼리 실행

옵션 3: 온라인 도구 (즉시 사용)

1
2
3
4
5
6
7
8
# RDF 유효성 검사
https://www.w3.org/RDF/Validator/

# SPARQL + Turtle 온라인 실행
https://yasgui.triply.cc/

# 시각화
https://lod-cloud.net/

Python으로 Turtle 처리

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
# pip install rdflib
from rdflib import Graph, Namespace, RDF, RDFS, OWL

g = Graph()
g.parse("crime_ontology.ttl", format="turtle")

# 모든 Suspect 조회 (rdfs:subClassOf 추론 포함)
CRIME = Namespace("http://crime-investigation.org/onto#")

for suspect in g.subjects(RDF.type, CRIME.Suspect):
    name = g.value(suspect, CRIME.name)
    print(f"Suspect: {name}")

# SPARQL 실행
results = g.query("""
    PREFIX : <http://crime-investigation.org/onto#>
    SELECT ?entity WHERE {
        ?entity :partOf :case_001 .
    }
""")
for row in results:
    print(row.entity)

4부: Cypher vs Turtle 비교 — 언제 무엇을 쓰나?

시나리오추천 기술이유
앱 백엔드 APICypher + Neo4j빠른 조회, 트랜잭션, 실시간 처리
AI/LLM 지식 연결Turtle/RDF추론 가능, 의미론적 검색
소셜 네트워크 분석Cypher고속 그래프 탐색
의료/법률 온톨로지OWL/Turtle표준 준수, 상호운용성
금융 사기 탐지Cypher실시간 패턴 매칭
시맨틱 웹 퍼블리싱TurtleW3C 표준, Linked Data
하이브리드 (추천)Neo4j + OWL 레이어속도 + 의미론 동시 확보

5부: 수사 시스템 전체 아키텍처 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
┌─────────────────────────────────────────────────────┐
│                   수사 시스템                         │
├─────────────────────────────────────────────────────┤
│                                                     │
│  OWL/Turtle 온톨로지 레이어                           │
│  (:Suspect rdfs:subClassOf :Person)                 │
│  (:Investigator owl:disjointWith :Suspect)          │
│           ↓ (규칙 정의 → 스키마 생성)                  │
│                                                     │
│  Neo4j 그래프 DB 레이어                               │
│  (u:User)-[:ASSIGNED_TO]->(c:Case)                 │
│  (entity)-[:PART_OF]->(c:Case)                     │
│           ↓ (Cypher API)                            │
│                                                     │
│  애플리케이션 레이어 (Node.js / Python)               │
│  세션별 권한 검사 → 데이터 반환                         │
│                                                     │
└─────────────────────────────────────────────────────┘

RBAC 패턴 (Role-Based Access Control) 완성 예제

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
// 역할 기반 접근 제어 전체 패턴
MATCH (u:User {id: $userId})
MATCH (u)-[:HAS_ROLE]->(role:Role)

// 역할에 따른 다른 접근 수준
WITH u, role
CALL {
  WITH u, role
  // 수사관: 자신의 케이스만
  WITH u, role WHERE role.name = "Investigator"
  MATCH (u)-[:ASSIGNED_TO]->(case:Case)<-[:PART_OF]-(entity)
  RETURN entity, case

  UNION

  // 관리자: 모든 케이스
  WITH u, role WHERE role.name = "Admin"
  MATCH (case:Case)<-[:PART_OF]-(entity)
  RETURN entity, case
}

RETURN entity, case
ORDER BY case.created DESC

참고 자료

Cypher (Neo4j)

  • 공식 문서: https://neo4j.com/docs/cypher-manual/
  • 치트시트: https://neo4j.com/docs/cypher-cheat-sheet/
  • 무료 코스: https://graphacademy.neo4j.com/

Turtle / RDF / OWL

  • W3C RDF Primer: https://www.w3.org/TR/rdf11-primer/
  • OWL 2 Guide: https://www.w3.org/TR/owl2-primer/
  • Protégé 튜토리얼: https://protege.stanford.edu/publications/ontology_development/ontology101.pdf

통합 학습

  • Linked Data: https://www.w3.org/standards/semanticweb/data
  • Knowledge Graph 구축: https://github.com/google-research/google-research/tree/master/knowledge_graph
이 기사는 저작권자의 CC BY 4.0 라이센스를 따릅니다.