GraphDB로 코드 구조 시각화 하기 (1/4): Neo4j와 프로그램 분석의 만남
시리즈 전체 목차
Part 1: Neo4j와 프로그램 분석의 만남 (현재 글)
- 왜 코드 분석에 GraphDB인가?
- Neo4j 핵심 개념과 기본 구조
- 프로그램 구조의 그래프 모델링 기초
- 첫 번째 실습: 로컬 환경 구축
Part 2: 코드 분석을 위한 데이터 모델링과 실습
- 상세 그래프 스키마 설계
- 다양한 프로그래밍 언어 지원
- 실전 의존성 및 영향도 분석
- 그래프 메트릭과 패턴 탐지
Part 3: AWS 환경에서 Neo4j 배포와 파이프라인 구축
- EC2/ECS/EKS 배포 전략
- 코드 수집 및 분석 파이프라인
- AWS 서비스 통합 (S3, Lambda, Step Functions)
- 보안 및 네트워크 설정
Part 4: 고급 시각화와 프로덕션 운영
- 커스텀 시각화 구축
- 성능 최적화 및 스케일링
- 모니터링 및 운영 자동화
- 실제 사용 사례
Part 5: Cypher 쿼리 언어 완벽 가이드
- Cypher 기초 문법과 패턴 매칭
- 고급 쿼리 기법과 최적화
- 그래프 알고리즘 활용
- 실전 쿼리 패턴 모음
1. GraphDB란 무엇인가?
1.1 GraphDB의 정의
Graph Database(그래프 데이터베이스)는 데이터를 노드(Node), 엣지(Edge), 속성(Property)으로 표현하는 NoSQL 데이터베이스입니다. 전통적인 테이블 기반 구조 대신, 엔티티 간의 관계(Relationship)를 일급 객체(First-class Citizen)로 다루는 것이 핵심 특징입니다.
전통적 DB: 데이터 + 관계(외래키, JOIN)
GraphDB: 데이터 = 노드 + 관계(직접 연결)
1.2 GraphDB의 핵심 구성 요소
노드 (Node/Vertex)
- 엔티티나 객체를 표현하는 기본 단위
- 고유 식별자와 여러 속성(키-값 쌍)을 가짐
- 예: 사람, 제품, 문서, 코드 클래스
엣지 (Edge/Relationship)
- 노드 간의 연결을 표현
- 방향성을 가질 수 있음 (단방향/양방향)
- 관계의 타입과 가중치 등의 속성 포함 가능
- 예: FRIENDS_WITH, PURCHASED, DEPENDS_ON
속성 (Property)
- 노드나 엣지에 붙는 메타데이터
- 유연한 스키마로 다양한 데이터 타입 지원
- 예: name, age, timestamp, amount
그림 1. GraphDB의 기본 구조: 노드, 엣지, 속성
1.3 GraphDB의 핵심 특징
1. 관계의 직접 저장
RDBMS: User → (JOIN) → Friendship ← (JOIN) ← User
GraphDB: User --[FRIENDS_WITH]--> User
전통적인 데이터베이스는 관계를 별도 테이블과 JOIN으로 표현하지만, GraphDB는 관계를 물리적으로 직접 저장합니다.
2. 상수 시간 관계 순회
- 인접한 노드를 찾는데 걸리는 시간이 그래프 크기와 무관
- 포인터 기반 탐색으로 O(1) 시간 복잡도
- 깊은 관계 쿼리도 빠른 성능 유지
3. 유연한 스키마
- 스키마리스(Schema-less) 또는 스키마-옵셔널
- 다양한 타입의 노드와 관계를 같은 그래프에 저장
- 실시간 스키마 변경 가능
4. 직관적인 데이터 모델링
- 화이트보드에 그리는 방식 그대로 저장
- 비즈니스 도메인을 자연스럽게 표현
- 그래프 시각화로 즉시 이해 가능
1.4 GraphDB vs 다른 데이터베이스
| 특성 | RDBMS | Document DB | Key-Value | GraphDB |
|---|---|---|---|---|
| 데이터 모델 | 테이블/행 | 문서/컬렉션 | 키-값 | 노드-엣지 |
| 관계 표현 | 외래키/JOIN | 임베디드/참조 | 제한적 | 일급 객체 |
| 관계 쿼리 | 느림 (JOIN) | 느림 (N+1) | 거의 불가 | 빠름 |
| 스키마 | 고정 | 유연 | 없음 | 유연 |
| 트랜잭션 | ACID | BASE/ACID | BASE | ACID |
| 최적 사용 | 정형 데이터 | 반정형 데이터 | 캐싱 | 연결 데이터 |
1.5 GraphDB의 일반적인 사용 사례
소셜 네트워크
- 친구 관계, 팔로우, 추천 알고리즘
- "친구의 친구" 같은 복잡한 관계 쿼리
추천 시스템
- 사용자-제품-카테고리 관계 분석
- 협업 필터링, 유사도 계산
지식 그래프
- 엔티티 간 의미적 관계 표현
- 질의 응답, 검색 엔진
사기 탐지
- 금융 거래 패턴 분석
- 이상 관계 탐지
네트워크 및 IT 운영
- 인프라 의존성 매핑
- 영향도 분석, 장애 전파 예측
1.6 주요 GraphDB 제품
Neo4j (이 시리즈의 주제)
- 가장 인기 있는 그래프 DB
- Cypher 쿼리 언어
- ACID 트랜잭션 지원
- 풍부한 생태계
Amazon Neptune
- AWS 관리형 서비스
- Gremlin, SPARQL 지원
- 높은 가용성과 확장성
ArangoDB
- 멀티 모델 (문서 + 그래프)
- AQL 쿼리 언어
- 수평적 확장
JanusGraph
- 오픈소스
- 대규모 그래프 지원
- 다양한 백엔드 스토리지
2. 왜 코드 분석에 GraphDB인가?
2.1 전통적 코드 분석 도구의 한계
대규모 코드베이스를 이해하고 유지보수하는 것은 현대 소프트웨어 개발의 핵심 과제입니다.
문제점:
- 선형적 분석의 한계: 정적 분석 도구들은 파일 단위로 코드를 분석하지만, 모듈 간 복잡한 의존성은 파악하기 어려움
- 의존성 추적의 복잡성: "이 함수를 변경하면 어디에 영향을 미칠까?" 같은 질문에 답하기 어려움
- 레거시 코드 이해: 수년간 축적된 코드베이스의 전체 구조를 파악하는데 막대한 시간 소요
- 관계 데이터 표현: RDBMS는 여러 단계의 JOIN으로 관계를 표현하지만, 성능과 복잡도 문제 발생
2.2 GraphDB가 제공하는 가치
GraphDB는 이러한 문제들을 근본적으로 해결합니다.
핵심 강점:
관계 중심 데이터 모델
- 자연스러운 표현: 코드의 구조는 본질적으로 그래프입니다
- 클래스는 다른 클래스를 참조
- 함수는 다른 함수를 호출
- 모듈은 다른 모듈에 의존
- 직관적 모델링: 개발자가 생각하는 방식 그대로 데이터 저장
복잡한 의존성 그래프 표현
// 3단계 떨어진 의존성을 간단하게 표현
MATCH (start:Class {name: 'UserService'})
-[:DEPENDS_ON*1..3]->(dependency:Class)
RETURN dependency.name
빠른 관계 순회
- JOIN 없이 포인터 기반으로 관계 순회
- 깊이가 깊어져도 성능 저하 최소화
- 실시간 영향도 분석 가능
유연한 쿼리
- 패턴 매칭으로 복잡한 관계 표현
- 그래프 알고리즘 내장 (최단 경로, 중심성 등)
- 동적 스키마로 변화에 유연
2.3 코드 분석 특화 시나리오
마이크로서비스 의존성 관리
- 서비스 간 호출 관계 시각화
- 장애 전파 경로 분석
- API 버전 관리
레거시 시스템 현대화
- 기존 코드베이스 구조 파악
- 모듈화 경계 식별
- 리팩토링 우선순위 결정
보안 및 컴플라이언스
- 민감 데이터 흐름 추적
- 권한 체크 누락 탐지
- 의존성 보안 취약점 분석
3. Neo4j 핵심 개념
Neo4j는 가장 인기 있는 그래프 데이터베이스로, ACID 트랜잭션과 풍부한 쿼리 언어를 제공합니다.
3.1 Graph 데이터베이스 기초
노드 (Node)
- 엔티티를 표현하는 기본 단위
- 예: Class, Function, Package
// 노드 생성
CREATE (c:Class {
name: 'UserService',
filePath: '/src/services/UserService.java',
lineCount: 250
})
관계 (Relationship)
- 노드 간의 연결을 표현
- 방향성이 있으며 타입과 속성을 가짐
- 예: DEPENDS_ON, CALLS, IMPLEMENTS
// 관계 생성
MATCH (a:Class {name: 'UserController'})
MATCH (b:Class {name: 'UserService'})
CREATE (a)-[:DEPENDS_ON {type: 'field'}]->(b)
속성 (Property)
- 노드나 관계에 붙는 키-값 쌍
- 다양한 타입 지원 (문자열, 숫자, 배열, 날짜 등)
// 속성이 있는 노드
CREATE (m:Method {
name: 'getUserById',
visibility: 'public',
returnType: 'User',
complexity: 5,
parameters: ['String id']
})
3.2 레이블과 타입
레이블 (Label)
- 노드를 분류하는 태그
- 하나의 노드가 여러 레이블을 가질 수 있음
- 인덱싱과 쿼리 성능에 영향
// 다중 레이블
CREATE (n:Class:Abstract:PublicAPI {name: 'BaseController'})
관계 타입 (Relationship Type)
- 관계의 의미를 정의
- 필수 속성 (관계는 반드시 하나의 타입을 가짐)
// 다양한 관계 타입
MATCH (a:Method)-[r:CALLS|INVOKES]->(b:Method)
RETURN type(r), a.name, b.name
3.3 Neo4j vs RDBMS

그림 2. RDBMS vs Neo4j
데이터 모델 비교
RDBMS 방식:
-- 3단계 의존성 찾기 위해 복잡한 JOIN
SELECT DISTINCT d.name
FROM classes c
JOIN dependencies d1 ON c.id = d1.from_class_id
JOIN dependencies d2 ON d1.to_class_id = d2.from_class_id
JOIN dependencies d3 ON d2.to_class_id = d3.from_class_id
JOIN classes d ON d3.to_class_id = d.id
WHERE c.name = 'UserService'
Neo4j 방식:
-- 같은 쿼리를 패턴 매칭으로 간단하게
MATCH (c:Class {name: 'UserService'})
-[:DEPENDS_ON*3]->(d:Class)
RETURN DISTINCT d.name
성능 특성
| 측면 | RDBMS | Neo4j |
|---|---|---|
| 관계 순회 | JOIN 수에 비례하여 느려짐 | 일정한 속도 유지 |
| 복잡한 쿼리 | 인덱스 의존도 높음 | 그래프 구조 활용 |
| 스키마 변경 | DDL 작업 필요 | 즉시 추가 가능 |
| 트랜잭션 | ACID 완벽 지원 | ACID 완벽 지원 |
적합한 사용 사례
Neo4j가 더 나은 경우:
- 깊은 관계 순회 (3단계 이상)
- 패턴 탐지 (예: 순환 의존성)
- 동적 스키마 필요
- 실시간 추천/분석
RDBMS가 더 나은 경우:
- 집계 중심 작업 (SUM, AVG)
- 단순 CRUD
- 정형화된 리포팅
- 트랜잭션 범위가 넓은 경우
4. 프로그램 구조의 그래프 모델링
4.1 기본 노드 설계
Package/Module 노드
CREATE (p:Package {
name: 'com.example.user',
path: '/src/main/java/com/example/user',
type: 'source'
})
Class 노드
CREATE (c:Class {
name: 'UserService',
fullyQualifiedName: 'com.example.user.UserService',
type: 'class',
visibility: 'public',
abstract: false,
lineCount: 150,
filePath: '/src/main/java/com/example/user/UserService.java'
})
Method 노드
CREATE (m:Method {
name: 'getUserById',
signature: 'getUserById(String)',
returnType: 'User',
visibility: 'public',
static: false,
complexity: 3,
lineCount: 15,
startLine: 45,
endLine: 60
})
4.2 관계 설계
CONTAINS (포함 관계)
// Package가 Class를 포함
MATCH (p:Package {name: 'com.example.user'})
MATCH (c:Class {name: 'UserService'})
CREATE (p)-[:CONTAINS]->(c)
// Class가 Method를 포함
MATCH (c:Class {name: 'UserService'})
MATCH (m:Method {name: 'getUserById'})
CREATE (c)-[:CONTAINS]->(m)
DEPENDS_ON (의존 관계)
MATCH (a:Class {name: 'UserController'})
MATCH (b:Class {name: 'UserService'})
CREATE (a)-[:DEPENDS_ON {
type: 'field', // field, parameter, local, return
required: true
}]->(b)
CALLS (호출 관계)
MATCH (caller:Method {signature: 'createUser(UserDto)'})
MATCH (callee:Method {signature: 'validateUser(User)'})
CREATE (caller)-[:CALLS {
lineNumber: 23,
conditional: false // if/switch 내부 호출 여부
}]->(callee)
IMPLEMENTS/EXTENDS (상속 관계)
MATCH (impl:Class {name: 'UserServiceImpl'})
MATCH (intf:Class {name: 'UserService', type: 'interface'})
CREATE (impl)-[:IMPLEMENTS]->(intf)
MATCH (child:Class {name: 'AdminUser'})
MATCH (parent:Class {name: 'User'})
CREATE (child)-[:EXTENDS]->(parent)
4.3 간단한 예제: 간단한 서비스 구조
// 1. Package 생성
CREATE (pController:Package {name: 'com.example.controller'})
CREATE (pService:Package {name: 'com.example.service'})
CREATE (pRepository:Package {name: 'com.example.repository'})
// 2. Classes 생성
CREATE (controller:Class {
name: 'UserController',
type: 'class'
})
CREATE (service:Class {
name: 'UserService',
type: 'class'
})
CREATE (repository:Class {
name: 'UserRepository',
type: 'interface'
})
CREATE (entity:Class {
name: 'User',
type: 'class'
})
// 3. Methods 생성
CREATE (getUser:Method {
name: 'getUser',
signature: 'getUser(Long)',
returnType: 'User'
})
CREATE (findById:Method {
name: 'findById',
signature: 'findById(Long)',
returnType: 'Optional<User>'
})
// 4. Package 포함 관계
CREATE (pController)-[:CONTAINS]->(controller)
CREATE (pService)-[:CONTAINS]->(service)
CREATE (pRepository)-[:CONTAINS]->(repository)
CREATE (pRepository)-[:CONTAINS]->(entity)
// 5. Class 포함 관계
CREATE (controller)-[:CONTAINS]->(getUser)
CREATE (repository)-[:CONTAINS]->(findById)
// 6. 의존 관계
CREATE (controller)-[:DEPENDS_ON {type: 'field'}]->(service)
CREATE (service)-[:DEPENDS_ON {type: 'field'}]->(repository)
CREATE (repository)-[:DEPENDS_ON {type: 'return'}]->(entity)
// 7. 호출 관계
CREATE (getUser)-[:CALLS]->(findById)
5. 첫 번째 실습: Neo4j 로컬 설치와 샘플 데이터
5.1 Neo4j Desktop 설치
AWS에 배포하기 전에 로컬에서 먼저 실습해봅시다.
- Neo4j Desktop 다운로드
- 설치 후 새 프로젝트 생성
- 로컬 데이터베이스 생성 (이름:
code-analysis) - 데이터베이스 시작
- Neo4j Browser 열기 (http://localhost:7474)
5.2 샘플 데이터 입력
Neo4j Browser에서 다음 Cypher 쿼리를 실행:
// 간단한 웹 애플리케이션 구조 생성
// 1. Packages
CREATE (web:Package {name: 'web', type: 'presentation'})
CREATE (service:Package {name: 'service', type: 'business'})
CREATE (data:Package {name: 'data', type: 'persistence'})
// 2. Web Layer
CREATE (userCtrl:Class:Controller {
name: 'UserController',
path: 'web/UserController.java'
})
CREATE (productCtrl:Class:Controller {
name: 'ProductController',
path: 'web/ProductController.java'
})
// 3. Service Layer
CREATE (userSvc:Class:Service {
name: 'UserService',
path: 'service/UserService.java'
})
CREATE (productSvc:Class:Service {
name: 'ProductService',
path: 'service/ProductService.java'
})
CREATE (emailSvc:Class:Service {
name: 'EmailService',
path: 'service/EmailService.java'
})
// 4. Data Layer
CREATE (userRepo:Class:Repository {
name: 'UserRepository',
path: 'data/UserRepository.java'
})
CREATE (productRepo:Class:Repository {
name: 'ProductRepository',
path: 'data/ProductRepository.java'
})
// 5. Package containment
CREATE (web)-[:CONTAINS]->(userCtrl)
CREATE (web)-[:CONTAINS]->(productCtrl)
CREATE (service)-[:CONTAINS]->(userSvc)
CREATE (service)-[:CONTAINS]->(productSvc)
CREATE (service)-[:CONTAINS]->(emailSvc)
CREATE (data)-[:CONTAINS]->(userRepo)
CREATE (data)-[:CONTAINS]->(productRepo)
// 6. Dependencies
CREATE (userCtrl)-[:DEPENDS_ON {type: 'field'}]->(userSvc)
CREATE (productCtrl)-[:DEPENDS_ON {type: 'field'}]->(productSvc)
CREATE (userSvc)-[:DEPENDS_ON {type: 'field'}]->(userRepo)
CREATE (userSvc)-[:DEPENDS_ON {type: 'field'}]->(emailSvc)
CREATE (productSvc)-[:DEPENDS_ON {type: 'field'}]->(productRepo)
RETURN 'Sample data created!' AS result
5.3 기본 탐색
Neo4j Browser에서 간단한 시각화를 확인할 수 있습니다:
전체 구조 보기:
MATCH (n)
RETURN n
LIMIT 50
레이어별 의존성:
MATCH path = (controller:Controller)
-[:DEPENDS_ON*]->(repo:Repository)
RETURN path
참고: Cypher 쿼리 언어에 대한 상세한 내용은 Part 5에서 다룹니다.
결론
이번 글에서는 GraphDB와 Neo4j의 기본 개념을 살펴보고, 프로그램 구조를 그래프로 모델링하는 방법을 배웠습니다.
주요 내용 정리:
- GraphDB 기초: 노드-엣지-속성 기반 데이터 모델과 핵심 특징
- 코드 분석 적용: 관계 중심 모델링으로 복잡한 의존성 표현
- Neo4j 핵심: ACID 트랜잭션과 Cypher를 제공하는 인기 GraphDB
- 모델링 기초: Package, Class, Method 노드와 관계 설계 패턴
- 실습 환경: Neo4j Desktop을 활용한 로컬 개발 환경 구축
다음 글 예고:
Part 2에서는 더 깊이 들어가서:
- 복잡한 프로그래밍 패턴을 그래프로 표현하는 방법
- 언어별 특성을 반영한 모델링 (Java, Python, JavaScript)
- 실전 의존성 및 영향도 분석 기법
- 그래프 메트릭 계산과 패턴 탐지
을 다룰 예정입니다.
AWS 환경에서의 실전 활용이 궁금하시다면 다음 편을 기대해주세요!
