05-infrastructure-graph-schema

Dec 7, 2025

Graph Schema & Indexes

코드: lib/graph/schema.ts

개요

FalkorDB는 스키마리스(schemaless) 그래프 DB이지만, 인덱스를 통해:

  1. 쿼리 성능 최적화

  2. 전문 검색(Full-text) 지원

  3. 벡터 유사도 검색 지원 (별도 커밋)

노드 레이블 구조

TypeScript 타입과 FalkorDB 레이블 매핑:

TypeScript

FalkorDB Label

Primary Key

주요 인덱스

MemoryNode

Memory

id (UUID)

category, importance, occurredAt

PersonNode

Person

id (UUID)

email (unique*), name

ThreadNode

Thread

id (UUID)

threadId (unique*), subject

EntityNode

Entity

id (UUID)

canonicalName, entityType

TaskNode

Task

id (UUID)

status, priority, dueDate

*FalkorDB는 아직 unique constraint를 지원하지 않음. 애플리케이션 레벨에서 중복 검사 필요.

인덱스 전략

Range Index (기본 인덱스)

정확한 값 매칭과 범위 쿼리에 사용:

-- id로 노드 찾기 (O(1))
MATCH (m:Memory {id: $id}) RETURN m

-- importance >= 0.8메모리 (범위 쿼리)
MATCH (m:Memory) WHERE m.importance >= 0.8 RETURN m

-- 특정 기간의 메모리 (범위 쿼리)
MATCH (m:Memory) WHERE m.occurredAt >= $start AND m.occurredAt <= $end RETURN m

Fulltext Index (전문 검색)

텍스트 검색에 사용:

-- 내용에서 "회의" 검색
CALL db.idx.fulltext.queryNodes('Memory', 'content', '회의')
YIELD node RETURN node

-- 사람 이름 검색 (부분 매칭)
CALL db.idx.fulltext.queryNodes('Person', 'name', '김*')
YIELD node RETURN node

인덱스 목록

Memory 노드

Property

Index Type

용도

id

range

기본 키 조회

category

range

카테고리 필터링

importance

range

중요도 필터링, 정렬

occurredAt

range

시간 범위 쿼리

sourceType

range

소스별 필터링

content

fulltext

내용 검색

Person 노드

Property

Index Type

용도

id

range

기본 키 조회

email

range

이메일로 사람 찾기 (사실상 unique)

name

fulltext

이름 검색

organization

range

조직별 필터링

Thread 노드

Property

Index Type

용도

id

range

기본 키 조회

threadId

range

Gmail threadId로 찾기 (사실상 unique)

subject

fulltext

제목 검색

status

range

상태별 필터링

lastMessageAt

range

최근 활동순 정렬

Entity 노드

Property

Index Type

용도

id

range

기본 키 조회

canonicalName

range

정규화된 이름으로 찾기

entityType

range

타입별 필터링

name

fulltext

이름 검색

Task 노드

Property

Index Type

용도

id

range

기본 키 조회

status

range

상태별 필터링

priority

range

우선순위 정렬

dueDate

range

마감일 정렬/필터링

assigneeId

range

담당자별 조회

title

fulltext

제목 검색

스키마 초기화

앱 시작 시 초기화

import { client, initializeSchema } from '@/lib/graph';

// 앱 시작 시 한 번 호출
const result = await initializeSchema(client);

console.log(`Created: ${result.indexesCreated.length} indexes`);
console.log(`Existed: ${result.indexesExisted.length} indexes`);

if (!result.success) {
  console.error('Schema init errors:', result.errors);
}

초기화 확인

import { client, isSchemaInitialized } from '@/lib/graph';

if (await isSchemaInitialized(client)) {
  console.log('Schema is ready');
}

설계 결정

왜 모든 노드에 UUID id를 사용하는가?

  1. 일관성: 모든 노드를 동일한 방식으로 참조

  2. 충돌 방지: 분산 환경에서도 ID 충돌 없음

  3. 보안: 예측 불가능한 ID로 enumeration 공격 방지

대안: 자연 키 (email, threadId 등)

  • 장점: 의미 있는 ID

  • 단점: 변경 시 모든 관계 업데이트 필요

  • 선택하지 않음: 유연성 부족

왜 unique constraint 없이 운영하는가?

FalkorDB는 현재 unique constraint를 지원하지 않습니다. 대신:

  1. MERGE 사용: 노드 생성 시 MERGE 쿼리로 중복 방지

  2. 애플리케이션 검증: 생성 전 존재 여부 확인

-- MERGE로 중복 방지 (email 기준)
MERGE (p:Person {email: $email})
ON CREATE SET p.id = $id, p.name = $name, p.createdAt = $now
ON MATCH SET p.updatedAt = $now
RETURN p

다음 문서

  • 벡터 인덱스 - 임베딩 기반 유사도 검색 (예정)

  • 관계 스키마 - 노드 간 관계 정의