S
STONI
GraphDB
Neo4j
Code Analysis
AWS
Visualization
Architecture

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 다른 데이터베이스

그래프 데이터베이스의 노드-엣지-속성 구조
특성RDBMSDocument DBKey-ValueGraphDB
데이터 모델테이블/행문서/컬렉션키-값노드-엣지
관계 표현외래키/JOIN임베디드/참조제한적일급 객체
관계 쿼리느림 (JOIN)느림 (N+1)거의 불가빠름
스키마고정유연없음유연
트랜잭션ACIDBASE/ACIDBASEACID
최적 사용정형 데이터반정형 데이터캐싱연결 데이터

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

RDB-NEO4J 비교

그림 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

성능 특성

측면RDBMSNeo4j
관계 순회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에 배포하기 전에 로컬에서 먼저 실습해봅시다.

  1. Neo4j Desktop 다운로드
  2. 설치 후 새 프로젝트 생성
  3. 로컬 데이터베이스 생성 (이름: code-analysis)
  4. 데이터베이스 시작
  5. 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의 기본 개념을 살펴보고, 프로그램 구조를 그래프로 모델링하는 방법을 배웠습니다.

주요 내용 정리:

  1. GraphDB 기초: 노드-엣지-속성 기반 데이터 모델과 핵심 특징
  2. 코드 분석 적용: 관계 중심 모델링으로 복잡한 의존성 표현
  3. Neo4j 핵심: ACID 트랜잭션과 Cypher를 제공하는 인기 GraphDB
  4. 모델링 기초: Package, Class, Method 노드와 관계 설계 패턴
  5. 실습 환경: Neo4j Desktop을 활용한 로컬 개발 환경 구축

다음 글 예고:

Part 2에서는 더 깊이 들어가서:

  • 복잡한 프로그래밍 패턴을 그래프로 표현하는 방법
  • 언어별 특성을 반영한 모델링 (Java, Python, JavaScript)
  • 실전 의존성 및 영향도 분석 기법
  • 그래프 메트릭 계산과 패턴 탐지

을 다룰 예정입니다.

Graph visualization


AWS 환경에서의 실전 활용이 궁금하시다면 다음 편을 기대해주세요!

Clickable cat