검색을 위한 온톨로지 설계 — 클래스·속성·관계 잡기

@JavaPark · May 21, 2026 · 15 min read

안녕하세요, 자바파커입니다.

"일단 Neo4j부터 깔고, 데이터는 넣다 보면 정리되겠지."

지난번 1편에서 온톨로지 기반 검색이 왜 다시 주목받는지 정리하고, 이번 시리즈에서 blog.javapark.kr 글 검색을 직접 만들어보기로 했습니다. 본격 구현에 앞서 가장 어려운 단계가 남았습니다. 바로 설계입니다.

결론부터 말씀드리면 — 온톨로지 설계는 "어떤 질의에 답할 것인가"에서 거꾸로 풀어야 합니다. 클래스부터 뽑기 시작하면 백이면 백, 3편 구현에서 통째로 갈아엎게 됩니다. 저도 그랬고, 다른 케이스도 비슷합니다.

이번 편에서는 블로그 검색 도메인의 온톨로지를 처음부터 설계해봅니다. 1) 답할 질의 정의 → 2) 클래스 도출 → 3) 속성·관계 정리 → 4) 함정 회피, 4단계로 갑니다.


1단계: 답할 질의를 먼저 적는다

설계의 출발점은 도메인 분석이 아니라 유저 질의 리스트입니다. 어떤 질문에 답할 시스템인지 모르고 시작하면, 만들어 놓고 보니 핵심 질의를 못 푸는 그래프가 됩니다.

블로그 검색이라는 도메인에서, 제가 실제로 답하고 싶은 질의를 다음과 같이 적어봤습니다.

# 질의 예시 필요한 정보
Q1 "Claude Code 시리즈의 첫 번째 글" 시리즈 소속 + 시리즈 내 순번
Q2 "4월에 쓴 GraphRAG 관련 글" 작성일 + 태그
Q3 "AI 카테고리 글 중 가장 최근 5개" 카테고리 + 최신순 정렬
Q4 "이 글이 인용한 외부 자료들" 인용 관계
Q5 "이 글의 후속편이나 관련 시리즈" 시리즈 관계 + 연관 글
Q6 "Claude Code 글과 GraphRAG 글을 모두 쓴 시리즈" 글-시리즈-태그 교차
Q7 "초보자가 이 글을 이해하려면 먼저 읽어야 할 글" 선수 관계 (prerequisite)

이 7개 질의가 곧 온톨로지가 통과해야 할 테스트 케이스입니다. 설계 끝에 "이 7개를 다 표현할 수 있는가?"로 검증할 거예요.

팁: 질의는 최소 5개 이상 적어두세요. 1~2개로 시작하면 추후 확장에서 스키마가 무너집니다. 너무 많이 적을 필요는 없고 — 시스템이 답해야 할 대표 질의면 충분합니다.


2단계: 클래스 후보 뽑기 — 명사를 모은다

이제 질의 안에서 명사를 추출합니다. 명사는 곧 클래스 후보예요.

명사 뽑기: 글(Post), Claude Code(시리즈), 첫 번째 글(순번 속성), 4월(날짜 속성), GraphRAG(태그), AI(카테고리), 인용 자료(외부 문서), 후속편(글), 초보자(?), 선수 글(글)

여기서 클래스로 승격할 것 vs 속성으로 남길 것을 골라냅니다. 기준은 단순합니다.

명사 클래스로? 이유
Post (글) O 핵심 엔티티
Series (시리즈) O 여러 Post가 묶임, 자체 메타데이터(이름, 순서) 있음
Tag (태그) O 여러 Post와 N:N
Category (카테고리) O Post와 1:N, 자체 식별자 필요
Author (저자) O 향후 다저자 가능성, Person 메타데이터
ExternalResource (외부 인용 자료) O 블로그 외부 URL, 별개 식별자
4월, 첫 번째 X Post의 속성(date, seriesOrder)
초보자 X 사용자 페르소나일 뿐, 그래프 노드 아님

6개 클래스가 나왔습니다. 너무 많아 보이면 좋은 신호예요 — 다음 단계에서 줄이거나 합치게 됩니다.


3단계: 속성과 관계 도출

클래스가 정해지면 두 가지를 채웁니다.

  • 속성(Property): 클래스 자체가 갖는 값 (제목, 날짜, URL 등)
  • 관계(Relation): 클래스끼리 연결 (writtenBy, hasTag 등)

속성 정리

Post
  ├─ slug          (식별자)
  ├─ title         (제목)
  ├─ description   (요약)
  ├─ publishedAt   (작성일)
  ├─ updatedAt     (수정일)
  └─ seriesOrder   (시리즈 내 순번, optional)

Series
  ├─ name          (시리즈명)
  └─ description   (시리즈 소개)

Tag
  └─ name          (태그명)

Category
  ├─ slug          (식별자)
  └─ displayName   (표시명)

Author
  ├─ name          (이름)
  └─ url           (프로필 URL)

ExternalResource
  ├─ url           (식별자)
  └─ title         (자료 제목, optional)

식별자는 가능하면 사람이 읽을 수 있는 키(slug, url, name)를 씁니다. UUID는 마지막 수단.

관계 도출

이제 관계를 그립니다. 1단계의 질의 7개를 다시 보면서, 각 질의가 어떤 관계를 타고 도달하는지 따져봅니다.

관계 출발 도착 카디널리티 어느 질의를 위한 건가
writtenBy Post Author N:1 (모든 질의의 기반)
hasTag Post Tag N:N Q2, Q6
inCategory Post Category N:1 Q3
belongsToSeries Post Series N:1 Q1, Q5, Q6
cites Post ExternalResource N:N Q4
relatedTo Post Post N:N Q5
prerequisiteOf Post Post N:N Q7

prerequisiteOf는 방향이 중요합니다. "A는 B의 선수다"가 A prerequisiteOf B인지 B prerequisiteOf A인지 한 번에 정해두지 않으면 데이터 입력에서 무조건 헷갈립니다. 저는 선수 → 후속 방향으로 못 박았습니다.

한 장으로 그리면

                    [Author]
                       ▲
                       │ writtenBy
                       │
[Tag] ◀──hasTag──── [Post] ──cites──▶ [ExternalResource]
                       │
                       │ inCategory          ┌─ relatedTo ─┐
                       ▼                     │             │
                  [Category]                 │             ▼
                                          [Post] ─prerequisiteOf─▶ [Post]
                       ▲
                       │ belongsToSeries
                       │
                       │
                  [Series]

이 그림이 곧 블로그 검색 온톨로지의 첫 버전입니다.


4단계: 흔히 빠지는 함정 4가지

여기까지 잘 와도, 실제 데이터를 부어 보면 깨지는 지점이 있습니다. 직접 부딪힌 함정 4가지를 공유합니다.

함정 1) 분류 vs 관계 — Category를 클래스로 둘까 속성으로 둘까?

처음엔 Category를 그냥 Post의 문자열 속성으로 두려고 했습니다. post.category = "ai"처럼요. 그러다 Q3 ("AI 카테고리 글 중 최신 5개")까지는 잘 돌아가지만, 카테고리에 자체 메타데이터(displayName, description, 정렬 순서)가 생기는 순간 속성으로는 부족해집니다.

판정 기준: 그 명사 자체에 메타데이터가 붙는가? 붙으면 → 클래스. 안 붙으면 → 속성.

Tag도 같은 고민이 있었지만, Tag는 향후 동의어·상위어 관계(broader, synonym)가 붙을 가능성이 높아서 클래스로 두는 게 안전했습니다.

함정 2) is-a vs has-a — 상속을 남발하지 말 것

OWL 같은 시맨틱 웹 도구는 subClassOf를 자유롭게 쓸 수 있어서, "Post의 하위에 BlogPost가 있고 그 아래에 TutorialPost가…" 식으로 트리를 키우기 쉽습니다.

이건 함정입니다. 검색용 온톨로지에서 상속이 의미 있는 경우는 다형성 질의가 필요할 때뿐입니다. ("모든 Post를 찾아줘"가 BlogPost·TutorialPost를 다 잡아야 하는 상황) 그게 아니면 그냥 Tag로 모델링하는 편이 가볍고 유연합니다.

저는 이번 설계에서 subClassOf를 한 번도 안 썼습니다.

함정 3) 양방향 관계를 두 개 만들지 말 것

Post hasAuthor AuthorAuthor wrote Post를 둘 다 정의하면, 한쪽만 입력했을 때 그래프가 망가집니다. 그래프 DB는 대부분 단방향 관계 + 역방향 탐색을 지원하니, 관계는 한 방향으로만 정의합니다.

방향 선택 기준은 단순합니다. 카디널리티가 작은 쪽이 출발점.

  • Author → Post는 1:N (저자 한 명이 글 여러 개)
  • 그래서 Post → Author 방향(writtenBy)으로 단일 정의

함정 4) 너무 잘게 쪼개기

"제목"과 "부제목"을 분리할까? "본문"과 "도입부"를 분리할까? 답은 거의 항상 NO입니다. 검색에서 구분이 필요 없는 정보는 한 덩어리로 두세요. 나중에 검색 요구가 생기면 그때 쪼개도 늦지 않습니다.

설계 단계에서 가장 비싼 실수는 과소 설계가 아니라 과대 설계입니다.


검증 — 질의 7개를 다 표현할 수 있는가?

설계가 끝났으면 1단계의 질의 7개로 다시 검증합니다. Cypher 같은 그래프 질의어로 머릿속에 그려봅니다.

질의 경로 표현 가능?
Q1 시리즈 첫 글 Series ◀ belongsToSeries ◀ Post (seriesOrder=1)
Q2 4월 GraphRAG 글 Post(publishedAt in 4월) ─hasTag▶ Tag(name=GraphRAG)
Q3 AI 카테고리 최신 5개 Category(slug=ai) ◀ inCategory ◀ Post ORDER BY publishedAt
Q4 인용 자료 Post ─cites▶ ExternalResource
Q5 후속/관련 Post ─belongsToSeries▶ Series ◀ ... 또는 Post ─relatedTo▶ Post
Q6 두 태그 모두 쓴 시리즈 Series ◀ Post ─hasTag▶ Tag (두 번 매치)
Q7 선수 글 Post ◀ prerequisiteOf ◀ Post

7개 모두 표현 가능합니다. 이 시점에서 설계가 일단락된 거예요.

여기서 한 개라도 표현 안 되는 게 있으면, 그 질의를 위한 관계나 속성을 추가하거나 — 정 안 되면 그 질의를 검색 범위 밖으로 빼는 결정을 합니다.


다음 편 예고 — 그래프 DB 선정과 적재

설계가 끝났으니 다음 편은 본격 구현입니다.

  • Neo4j vs GraphDB vs Memgraph 비교 — 어떤 기준으로 고르나
  • 위에서 만든 온톨로지를 실제 그래프 DB 스키마로 옮기기
  • contents/posts/**/index.md 파일을 파싱해서 그래프에 적재
  • 적재 후 Cypher로 질의 7개 직접 돌려보기

스택을 거기서 확정하니, 1~2편을 따라오신 분들은 자신의 도메인에 맞춰 다른 그래프 DB로 옮겨가셔도 무방합니다. 설계가 스택 독립적이라는 게 이 단계의 가장 큰 장점이에요.


자주 묻는 질문 (FAQ)

Q1. OWL을 꼭 써야 하나요? 그냥 도식만 그려도 되나요? 도식부터 시작해도 됩니다. 이번 편도 OWL 문법 없이 표·다이어그램으로만 정리했죠. OWL은 추론(reasoning)이 필요할 때, 예컨대 subClassOf로 자동 분류를 시키고 싶을 때 의미가 생깁니다. 검색만 목적이라면 OWL 없이 충분합니다.

Q2. 속성으로 둘지 클래스로 둘지 매번 헷갈립니다. 더 명확한 판정법이 있을까요? 세 가지를 보세요. ① 그 명사가 여러 다른 엔티티와 N:N으로 연결되는가, ② 그 명사 자체에 메타데이터가 붙는가, ③ 그 명사를 검색의 진입점으로 쓸 가능성이 있는가. 하나라도 YES면 클래스. 셋 다 NO면 그냥 속성으로 두세요.

Q3. 시리즈 진행하며 온톨로지가 바뀌면 어떡하나요? 바뀝니다. 그리고 그게 정상입니다. 중요한 건 1단계의 질의 리스트를 같이 갱신하는 거예요. 질의를 그대로 두고 스키마만 바꾸면 다음 사람(=미래의 나)이 왜 이런 모양인지 모르게 됩니다.


여러분이 다루는 도메인에서 답하고 싶은 질의 5개를 적어보세요. 그게 곧 온톨로지 설계의 출발점입니다. 댓글로 공유해주시면 시리즈에 인용할 수도 있어요.

다음 편에서 본격 구축 들어갑니다.

@JavaPark
AI 시대의 개발자 도구, 실전 경험을 공유합니다