기술

그래프 관계 6만 건을 눈으로 검수할 수 없어서, GDS한테 맡겼습니다

2026.04.2413분 읽기

노드 1.3만, 관계 6만을 넘어서면서

지식그래프에 이메일·DOCX·PPT를 계속 임포트하다 보니 어느 순간 노드 13,718개, 관계 62,816개가 됐습니다. 처음엔 노드 몇백 개일 때라 품질 문제가 보이면 손으로 고쳤거든요. 중복 노드는 합치고, 잘못된 관계는 지우는 식으로요.

그런데 관계가 6만 건을 넘어가니까 사람 눈으로 검수한다는 말 자체가 성립을 안 해요. schema.yaml 자체는 잘 정의돼 있는데, 거기에 맞춰 쌓인 데이터 쪽에 품질 문제가 있는 거거든요. 스키마는 멀쩡한데 데이터가 어긋나 있는 상황입니다.

어떤 문제가 쌓이고 있었냐면요.

같은 이름인데 System·Service 양쪽에 동시에 존재하는 노드

이중화 쌍으로 묶여야 할 서버들인데 관계가 안 걸린 경우

schema.yaml에는 MANAGES 관계가 정의돼 있는데 그래프에는 0건

관계가 하나도 없는 고립 노드들이 수백 개씩 쌓여 있는 경우

관계 하나하나 쿼리해서 본다? 현실적으로 불가능해요. 게다가 이런 문제들은 "무엇이 잘못인지"를 사전에 정의하기도 어렵습니다. 중복 후보를 찾으려면 "이름이 비슷한 쌍"을 봐야 하고, 빠진 관계를 찾으려면 "늘 같이 등장하는데 연결은 없는 쌍"을 찾아야 하거든요. 이런 건 규칙 기반으로 짜기보다 그래프 알고리즘에 맡기는 게 맞다고 판단했습니다.


GDS를 품질 개선 도구로 쓴다는 발상

Neo4j GDS(Graph Data Science) 라이브러리를 이전에 설치해두긴 했는데, 그때는 노드 유사도 한두 가지만 돌려본 정도였어요. 이번엔 본격적으로 "품질 검수용"으로 엮어봤습니다.

발상은 단순합니다. 그래프 품질 문제는 크게 네 가지 결로 나뉘거든요.

중복: 같은 걸 가리키는 노드가 둘 이상 존재

누락: 있어야 할 관계가 빠져 있음

집중: 한 노드에 관계가 과하게 몰려 있음(허브·슈퍼노드)

고립: 관계가 없어서 검색에 잡히지 않는 노드

각 결마다 맞는 GDS 알고리즘이 따로 있어요. 그걸 파이프라인으로 엮고, 마지막에 LLM으로 종합 해석을 붙였습니다.

1.jpeg


Phase별로 뭘 찾았나

여섯 단계로 나눠 돌렸는데, 각 단계마다 다른 결의 문제가 드러났어요.

Phase 0 — 데이터 정리 (고립 노드 탐지)
순수 Cypher로 "관계가 0인 노드"를 전부 찾았습니다. Error_Code 라벨 노드 269개와 ErrorCode 라벨 노드 259개가 전량 고립이었어요. 레거시 라벨이었는데 데이터만 남아 검색을 어지럽히고 있었습니다. 전부 DETACH DELETE 했습니다. 한 번에 528건이 정리됐어요.

Phase 1 — Node Similarity (Jaccard 유사도)
두 노드가 공유하는 이웃의 비율을 계산합니다. 유사도가 높으면 "같은 걸 가리키는 다른 이름"일 가능성이 크거든요. 이번엔 0.5 이상인 쌍이 0건으로 나왔어요. 이전에 대소문자 중복 정규화를 돌려둔 덕분이었습니다. 한 번 정리해둔 효과가 여기서 드러났어요.

Phase 2 — Centrality (Betweenness + PageRank)
"이 노드를 얼마나 많이 거쳐 가는가"를 봅니다. 결과를 보니 portal이라는 Service 노드 하나가 Betweenness 3,768,667로 압도적 1위였어요. 그래프 트래픽 대부분이 이 노드 하나를 경유하고 있다는 뜻입니다.

더 심각한 건 여기서 드러난 건데요. CDS라는 이름이 System 라벨에도, Service 라벨에도 동시에 존재했습니다. 이름은 같은데 라벨만 다른 두 노드가 따로 돌아가고 있던 거예요.

Phase 3 — Link Prediction (Common Neighbors + Adamic-Adar)
두 노드가 공유하는 이웃이 많으면, "관계가 있어야 하는데 아직 없는" 쌍일 가능성이 큽니다. 결과가 직관적이었어요.

공유 이웃 score

해석

aa01 ↔ aa02

243

HA 쌍일 가능성

bb11 ↔ bb12

219

HA 쌍일 가능성

cc01 ↔ cc02

129

DB 이중화

dd01 ↔ dd02

72

LDAP 이중화

이메일에서 항상 같이 언급되는 서버 쌍들인데, 그래프에는 둘 사이에 HA_PAIR 관계가 하나도 없던 상태였어요.

Phase 4 — Path Analysis (shortestPath)
"노드 사이 최단 경로가 얼마나 멀리 돌아가는가"를 봅니다. Person→System 경로가 전부 길이 2(Person→Email→System)였어요. 사람이 서버를 직접 담당한다는 MANAGES 관계가 0건이어서 이메일을 우회할 수밖에 없는 구조였습니다.

Phase 5 — LLM 종합 해석
Phase 1~4의 수치를 Gemini에 넣고 "어떤 조치가 필요한지"를 자연어로 정리했어요. 토큰은 input 8,526 / output 1,165 정도로, 비용은 거의 안 들었습니다. 결과는 변경 제안 6건으로 정리됐어요.


사람이 결국 하는 판단 — 알고리즘이 준 건 "후보 목록"

여기서 중요한 지점이 있어요. Phase 3이 "aa01 ↔ aa02를 HA_PAIR로 묶어야 할 것 같다"고 후보를 주지만, 실제로 HA 쌍이 맞는지는 도메인 지식으로 확인해야 합니다. 단순히 "자주 같이 등장한다"만 봐서는 실수할 수 있거든요. 그냥 같은 팀에서 다루는 두 서버일 수도 있으니까요.

그래서 GDS가 준 후보 목록을 보고 사람이 최종 판단을 내렸어요. 판단 결과는 다음과 같았습니다.

aa01/02, uaa11/12 — WAS 이중화 (L4 SWITCH 방식)

aa1/2 — 구형 WAS 이중화

bb01/02, ubb11/12 — DB 이중화 (Oracle CRS for RAC)

cc01/02, rcc03/04 — LDAP 이중화 (Pacemaker)

dd01/02 — LDAP 이중화 (Pacemaker)

총 8쌍에 HA_PAIR 관계를 붙였습니다. 노드 자체가 없던 mldap03/04 쌍은 스킵했고요.

이 구조의 요점은 이거예요. GDS가 6만 건 중에서 "의심 구간"만 수십 건으로 좁혀주니까, 사람은 그 수십 건만 검토하면 됩니다. 전체를 다 볼 필요가 없어져요.


CDS 노드 혼재 해소

Phase 2에서 드러난 CDS 문제도 같이 정리했어요. System:CDS는 관계가 11건(DEPENDS_ON 5 + SOURCED_FROM 5 + 기타)이었고, Service:CDS는 관계가 583건이었습니다. 누가 봐도 Service 쪽이 진짜고, System 쪽은 LLM 추출 과정에서 라벨이 잘못 붙은 거였어요.

처리 순서는 이렇게 했습니다.

System:CDS의 나가는 관계 6건을 Service:CDS로 이관

System:CDS의 들어오는 관계 5건을 Service:CDS로 이관

System:CDS DETACH DELETE

이전엔 "CDS"로 검색하면 System과 Service 두 노드가 같이 나와서 그래프 탐색할 때 잘못된 경로가 생성될 수 있었거든요. 이제 하나로 통합돼서 그런 애매함이 사라졌습니다.


MANAGES 관계를 이메일 데이터에서 추론

schema.yaml에는 MANAGES 관계가 정의돼 있는데 그래프에는 0건이었어요. 이건 임포트 과정에서 안 뽑힌 게 아니라, 애초에 "누가 어떤 서버 담당인가"를 명시한 문서 자체가 없었거든요. 이메일이나 매뉴얼에 직접 "A가 B를 담당한다"고 쓰여 있지 않으니까요.

그런데 이메일 데이터에서 간접적으로 추론할 수 있었습니다. "어떤 사람이 어떤 서버를 자주 언급하는가"를 보면 되거든요. SENT→Email→MENTIONS_SYSTEM 경로를 타고 집계했더니 패턴이 분명히 나왔어요.

Person

담당 추정 서버

mention 수

관리자1

aa01, aa02, bb01, bb11, abc01 등

10~69회

관리자2

aa01, ee01, ea11 등

10~49회

관리자3

aa01, ee01, ea11 등

10~50회

10회 이상 언급한 쌍만 추리고, postmaster 같은 팀 계정은 제외했습니다. 총 44건의 MANAGES 관계를 생성했어요.

효과가 바로 나왔습니다. "ee01 담당자 누구?"라는 질문에 이전엔 이메일을 전부 훑어야 답이 나왔는데, 이제는 MANAGES 관계 하나만 역추적하면 끝납니다. Phase 4에서 아쉬웠던 Person→System 직접 경로도 생겼고요.

2.jpeg


적용 결과 — 5가지 변화

Phase별로 찾은 문제를 종합해서 정리한 결과입니다.

항목

변경 전

변경 후

HA_PAIR 관계

0건

8건

MANAGES 관계

0건

44건

System:CDS

존재 (degree=11)

삭제 (Service로 통합)

팀/시스템 Person 노드

12건 존재

전량 삭제

Error_Code/ErrorCode 고립

528건

전량 삭제

팀 계정 Person 정리는 Phase 2 Centrality에서 드러난 또 다른 발견이었어요. Part AnS Network(Infra지원) 같은 팀 공유 계정이 degree 558로 허브처럼 잡혀 있었거든요. 이런 노드가 있으면 "누가 가장 중심 인물인가" 분석이 왜곡됩니다. postmaster, 팀 공유 계정, 시스템 계정 12건을 정리했어요.


판단을 보류한 것들

모든 제안을 다 적용한 건 아닙니다. Phase 2에서 나온 portal 슈퍼노드 문제는 일단 보류했어요. degree가 2,184인데, 관계의 98%가 MENTIONS_SERVICE(2,143건)였습니다.

원인은 명확해요. 이메일 본문에 "portal"이라는 단어가 포함된 메일이 전부 이 하나의 노드에 연결된 겁니다. 그런데 실제로는 "ABC portal", "BA portal", "Admin portal" 같이 맥락이 다른 portal들이 하나로 뭉쳐 있었어요.

이걸 분리하려면 어떤 portal이 어떤 서비스에 속하는지 도메인 지식이 필요한데, 지금 단계에선 판단이 어려웠어요. ServiceGroup 같은 중간 노드를 둬야 할지도 생각 중이고요. GDS가 문제를 드러내줬지만 해법까지 자동으로 만들 수는 없는 케이스였습니다.


마무리

그래프 관계가 6만 건을 넘어가면서 사람이 눈으로 품질을 검수하는 건 현실적으로 불가능해졌습니다. GDS의 Node Similarity·Centrality·Link Prediction·Path Analysis를 엮고 마지막에 LLM으로 종합하니까, 6만 건이 변경 제안 6건으로 정리됐어요. HA_PAIR 8건과 MANAGES 44건이 자동으로 드러났고, CDS 라벨 혼재와 팀 계정 허브 문제도 같이 해소됐습니다.

사람이 사라진 구조는 아닙니다. 알고리즘이 의심 구간을 좁혀주고, 도메인 판단은 여전히 사람이 합니다. portal 슈퍼노드처럼 판단이 어려운 건 보류로 남기고, HA_PAIR처럼 도메인적으로 확정 가능한 건 바로 적용했어요.

지식그래프를 운영하면서 데이터가 쌓여갈수록 품질 문제도 같이 누적되는데, 주기적으로 GDS 품질 검수 파이프라인을 돌려두면 사일로처럼 쌓이는 문제들을 한꺼번에 드러낼 수 있습니다. 그래프 규모가 수만 건을 넘어간 프로젝트라면 한번 돌려보는 걸 추천합니다.