Files
ExperionCrawler/pid-그래프병목진단-검증보고서.md
2026-05-08 17:22:10 +09:00

9.2 KiB
Raw Blame History

pid-그래프병목진단-수정방안.md 진단 검증 보고서

검증일: 2026-05-04
검증 기준: diagnosis-checklist.md (8단계)
검증 대상: pid-그래프병목진단-수정방안.md


검증 요약

진단 항목 원 심각도 재검증 심각도 결론
병목 1: 배관-설비 연결 O(n²) HIGH HIGH 정당함
병목 2: 태그-설비 매핑 O(n²) MED MED 정당함
병목 3: DXF 엔티티 순회 LOW LOW 정당함
— 추가 발견 — MED ⚠️ build_graph() 중복 호출

전체 결론: 진단 문서의 3개 항목 모두 정당함. 추가 1개 항목 발견.


STEP 4 — 호출 계층 지도

pid_worker._build_pid_graph_parallel()  (async, pid_worker.py:318)
  │
  ├─ Phase 1: PidGeometricExtractor.extract_and_save()
  │   └─ for entity in self.msp:          (28,819개 순회)
  │       └─ get_bbox(entity)             — BBox 계산, O(n)
  │
  ├─ Phase 2: PidTopologyBuilder.build_graph()  ← 1차 호출
  │   ├─ 노드 추가 (geo_data)
  │   ├─ _find_nearest_equipment()        ← O(TEXT × EQUIPMENT) ≈ 1,280만 회
  │   │   └─ tag_bbox.distance(eq_bbox)   — shapely 거리 계산
  │   └─ 배관-설비 연결                   ← O(LINE × EQUIPMENT) ≈ 7,110만 회
  │       ├─ line_geom.intersects(eq_bbox)  — shapely 교차 계산
  │       └─ line_geom.distance(eq_bbox)    — shapely 거리 계산
  │
  ├─ Phase 3: IntelligentMapper (병렬 LLM)
  │
  └─ Phase 4: PidTopologyBuilder.build_graph()  ← 2차 호출 (동일 O(n²) 반복)
      └─ 동일한 배관-설비 연결 + 태그-설비 매핑 재실행

STEP 5~6 — 진단 항목逐个 검증


1. 병목 1: Phase 2 — build_graph() 내 배관-설비 연결 로직 (HIGH) 정당

진단 내용: LINE/LWPOLYLINE(약 21,733개)과 설비(약 3,272개)를 전체 조합으로 비교

코드 확인: topology.py:82-99

lines = [n for n, d in self.G.nodes(data=True) if d['type'] in ['LINE', 'LWPOLYLINE']]
for line_id in lines:                          # 21,733개
    ...
    for eq_id in equipments:                   # 3,272개
        eq_bbox = self.G.nodes[eq_id]['bbox']
        if line_geom.intersects(eq_bbox):      # shapely 교차 계산 (고비용)
            connected_nodes.append(eq_id)
        elif line_geom.distance(eq_bbox) < self.config['dist_threshold']:
            connected_nodes.append(eq_id)

교차 검증 4문항:

질문 결과 판단
Q1. 이미 수정된 문제인가? 코드 확인: 여전히 중첩 for문, 공간 인덱스 없음 아님 → 유지
Q2. 다른 레이어에서 처리되고 있는가? 상위 호출자인 _build_pid_graph_parallel()도 필터링 없음 아님 → 유지
Q3. 의도적 설계인가? 문서/주석에 "의도적 O(n²)" 설명 없음 아님 → 유지
Q4. 실제 장애 시나리오가 있는가? 21,733 × 3,272 = 7,110만 회 shapely 연산 → 10분 이상 타임아웃 있음 → 유지

재검증 심각도: 🔴 HIGH (유지)


2. 병목 2: Phase 2 — _find_nearest_equipment() 태그-설비 매핑 (MED) 정당

진단 내용: TEXT/MTEXT(약 3,925개)와 설비(약 3,272개)를 전체 비교

코드 확인: topology.py:143-148

for eq_id in equipment_ids:                    # 3,272개
    eq_bbox = self.G.nodes[eq_id]['bbox']
    dist = tag_bbox.distance(eq_bbox)          # shapely 거리 계산
    if dist > self.config['tag_threshold']:
        continue

교차 검증 4문항:

질문 결과 판단
Q1. 이미 수정된 문제인가? 코드 확인: 여전히 전체 순회, 빠른 필터 없음 아님 → 유지
Q2. 다른 레이어에서 처리되고 있는가? _find_nearest_equipment()가 독립 함수 — 상위에서 필터링 없음 아님 → 유지
Q3. 의도적 설계인가? 주석에 "위상 기반 가중치 매핑"이라고 하지만 O(n²)은 의도적이지 않음 아님 → 유지
Q4. 실제 장애 시나리오가 있는가? 3,925 × 3,272 = 1,280만 회 — 수초~수십초 지연 발생 있음 → 유지

재검증 심각도: 🟠 MED (유지)


3. 병목 3: Phase 1 — DXF 엔티티 순회 추출 (LOW) 정당

진단 내용: 28,819개 엔티티를 하나씩 순회하며 BBox 계산

코드 확인: extractor.py:124-162

for entity in self.msp:                        # 28,819개
    try:
        bbox_obj = self.get_bbox(entity)       # 각 엔티티당 BBox 계산
        if not bbox_obj:
            continue
        ...

교차 검증 4문항:

질문 결과 판단
Q1. 이미 수정된 문제인가? N/A — O(n) 순회는 DXF 파싱의 본질 N/A
Q2. 다른 레이어에서 처리되고 있는가? N/A N/A
Q3. 의도적 설계인가? DXF 파일은 순차 읽기 구조 — 필수적 맞음 → LOW 유지
Q4. 실제 장애 시나리오가 있는가? ~1.4초 — 허용 범위 없음 → LOW 유지

재검증 심각도: 🟡 LOW (유지)


⚠️ 추가 발견 사항

4. build_graph() 중복 호출 (MED) — 진단 문서에 누락

위치: pid_worker.py:350-351, pid_worker.py:400-401

문제: _build_pid_graph_parallel()에서 PidTopologyBuilder.build_graph()2번 호출

# Phase 2: 1차 위상 빌더 (Mapper용 그래프)
builder = PidTopologyBuilder(geo_data)
builder.build_graph()                          # ← 1차: O(n²) 전체 실행

# ... Phase 3: LLM 매핑 ...

# Phase 4: 최종 위상 모델링 + 저장
final_builder = PidTopologyBuilder(geo_data, all_extracted_tags=all_mapped_tags)
final_builder.build_graph()                    # ← 2차: 동일한 O(n²) 전체 재실행

근거:

  • 1차 호출: 배관-설비 연결 (7,110만 회) + 태그-설비 매핑 (1,280만 회)
  • 2차 호출: 동일한 연산을 all_mapped_tags를 포함해서 다시 실행
  • 총 연산량: (7,110만 + 1,280만) × 2 = 약 16,780만 회

영향: Phase 2 + Phase 4가 합쳐져 병목 시간이 약 2배 증가

수정 방향:

  • Phase 4에서 build_graph() 대신 1차 그래프를 재사용하고, all_mapped_tags만 추가 연결
  • 또는 Phase 2에서 all_mapped_tags를 미리 받아서 1회 호출로 통합

교차 검증 4문항:

질문 결과 판단
Q1. 이미 수정된 문제인가? 코드 확인: 2번 호출 그대로 아님 → 추가
Q2. 다른 레이어에서 처리되고 있는가? N/A — 같은 함수 내에서 발생 아님 → 추가
Q3. 의도적 설계인가? "1차 위상 빌더 (Mapper용)" / "최종 위상 모델링" 주석이 있지만, 동일한 O(n²)을 2번 하는 것은 설계 실수 아님 → 추가
Q4. 실제 장애 시나리오가 있는가? build_graph()가 10분 걸리면 20분으로 증가 있음 → 추가

재검증 심각도: 🟠 MED


근본 원인 검증

진단 문서 주장: "공간 인덱스(R-tree, Quadtree) 미사용으로 모든 노드 쌍을 O(n²)으로 비교"

코드 검증 결과: 정확함

  • topology.py — import: networkx, shapely, json, typing — 공간 인덱스 라이브러리 없음
  • extractor.py:176-183is_near() 함수도 매번 shapely.box() 생성 후 거리 계산
  • 전체 프로젝트에서 rtree, strtree, quadtree 관련 import 없음

수정 계획 검증

Step 진단 문서 제안 검증 결과
Step 1: BBox 빠른 필터링 shapely 없이 min/max 좌표로 사전 필터 타당 — O(1) 비교로 90% 이상 후보 제거 가능
Step 2: 그리드 기반 공간 인덱스 SpatialGrid 클래스 추가, 셀 크기 = dist_threshold 타당 — O(n log n)으로 감소
Step 3: pid_worker.py 통합 E2E 테스트 타당 — 하지만 #4(중복 호출)도 함께 수정 필요

추가 제안: Step 1-2 실행 시 pid_worker.py의 Phase 2/4 중복 호출도 함께 수정할 것.


최종 판단

항목 진단 문서 재검증 일치
병목 1: 배관-설비 O(n²) HIGH HIGH
병목 2: 태그-설비 O(n²) MED MED
병목 3: DXF 순회 LOW LOW
근본 원인: 공간 인덱스 부재 O(n²) O(n²)
수정 계획: 3단계 점진적 BBox→Grid→E2E BBox→Grid→E2E
누락: build_graph() 2중 호출 MED ⚠️

진단 문서의 정확도: 5/5 항목 정확, 1개 추가 발견

수정 계획에 반영할 사항: Step 3 실행 시 pid_worker.py:350-401build_graph() 중복 호출을 단일 호출로 통합할 것.