Skip to content

[REFACTOR] 그래프 조회를 영속 참조 기반 읽기 경로로 전환 - #212

Merged
sun007021 merged 4 commits into
developfrom
refactor/211-persisted-reference-graph-read
Sep 6, 2026
Merged

sun007021 merged 4 commits into
developfrom
refactor/211-persisted-reference-graph-read

Conversation

@sun007021

@sun007021 sun007021 commented Aug 31, 2026 •

Copy link
Copy Markdown
Member

관련 이슈

Closes #211

변경 배경

그래프 읽기 경로가 계속 contents를 조회하고 요청마다 extractor를 실행하면 참조 영속화의 읽기 성능 이득을 얻을 수 없습니다. 저장된 node/reference 조회 결과로 기존 CrewGraphResponse를 조립하도록 전환했습니다.

변경 내용

  • graph node 조회를 findAllGraphNodesByGenerationTitle로 전환
  • reference 조회를 findAllReadModelsByGenerationTitle로 전환
  • 읽기 경로의 contents와 CrewDocumentReferenceExtractor 의존 제거
  • 방향성 reference row를 기존 무방향 API edge로 정규화
  • 자기참조 제외, 중복 제거, 안정 정렬 유지
  • 조직 선택 그래프 응답 계약 유지
  • service/controller 회귀 테스트 보강과 query 수·SQL 증거 추가

읽기 경로는 CrewGraphReader 전략으로 분리했고, CrewGraphQueryService는 reader에 위임하며 조직 그래프 합성만 담당합니다.

이전 구현은 nodeDocumentUuids(현재 기수 노드 집합)로 target을 필터했는데, findAllReadModelsByGenerationTitle이 source·target 양쪽을 같은 기수 크루로 제한하고 TYPE(target) = CrewDocument까지 거르므로 그 필터를 쿼리에 위임했습니다.

리뷰 반영

배포 즉시 전환되어 기존 데이터의 간선이 누락될 수 있다는 리뷰를 반영해, feature flag와 backfill 실행 진입점을 둘 다 추가했습니다.

두 방안은 택일 관계가 아닙니다. flag는 "배포 시점 = 전환 시점"이라는 결합만 끊어줄 뿐이고, 기존 문서의 document_reference를 채우려면 backfill을 실행할 수단이 별도로 필요합니다. DocumentReferenceBackfillService는 production 호출자가 0건이었습니다.

feature flag

graph.read.source로 읽기 경로를 고릅니다. 기본값은 legacy 입니다.

값 동작
legacy (기본) 요청 시 본문을 파싱하는 기존 경로. backfill 이전 데이터에서도 안전합니다.
persisted 저장된 document_reference 조회. backfill 완주 후에만 켭니다.

@ConditionalOnProperty(matchIfMissing = true)이므로 설정이 없으면 legacy가 선택됩니다. 머지와 배포만으로는 동작이 바뀌지 않습니다.

두 경로가 같은 응답을 만들도록 무방향화·자기참조 제외·중복 제거·안정 정렬을 CrewGraphEdgeNormalizer 하나로 공유합니다. edge 규칙이 갈라질 여지가 구조적으로 없습니다. CrewGraphReader가 node와 edge를 함께 반환하므로 legacy 경로가 contents를 두 번 조회하지도 않습니다.

CrewGraphReaderParityTest가 이를 고정합니다.

  • backfill 이후: 두 경로의 node와 edge가 완전히 같다
  • backfill 이전: 영속 참조 경로만 간선을 잃는다 (리뷰에서 지적된 실패 모드)

backfill 엔드포인트

POST /admin/graph/document-references/backfill

/admin/** 이므로 LoginCheckInterceptor 인증을 그대로 적용받습니다. 응답은 AdminDocumentReferenceBackfillResponse로 변환해 서비스 DTO를 노출하지 않습니다.

추가·변경 파일

파일 역할
CrewGraphReader 읽기 전략 인터페이스
ContentsParsingCrewGraphReader 기존 본문 파싱 경로 (기본)
PersistedCrewGraphReader 영속 참조 조회 경로
CrewGraphEdgeNormalizer 두 경로가 공유하는 edge 정규화
CrewGraphElements reader가 node·edge를 함께 반환하는 캐리어
AdminDocumentReferenceBackfillController backfill 실행 진입점
application.yml graph.read.source: ${GRAPH_READ_SOURCE:legacy}
dev-cd.yml / prod-cd.yml .env heredoc에 GRAPH_READ_SOURCE 추가

검증

  • CrewGraphQueryServiceTest: 통과 (정규화·정렬 회귀 테스트 1건 추가)
  • CrewGraphControllerTest: 통과 (JSON field, node/edge 순서 유지 확인)
  • CrewGraphQueryRepositoryTest: 통과
  • DocumentReferenceRepositoryTest: 통과
  • CrewGraphQueryServiceQueryCountTest: 통과 (신규)
  • CrewGraphReaderParityTest: 통과 (신규, 두 읽기 경로 동치성)
  • CrewGraphQueryServiceDefaultReadSourceTest: 통과 (신규, flag 기본값)
  • AdminDocumentReferenceBackfillControllerTest: 통과 (신규)
  • Java 17 전체 테스트: ./gradlew clean test BUILD SUCCESSFUL, tests=149 failures=0 errors=0 skipped=0
  • git diff --check: 통과
  • query 수 2/4 확인: 조직 미선택 정확히 2개(node + reference), 조직 선택 정확히 4개(+ 조직 문서 조회 + 조직 링크 조회)
  • graph read SQL select list에 contents 없음 확인: 조직 미선택 경로에서 실행된 SQL 전체에 contents 미포함

query 수와 SQL 증거는 Hibernate StatementInspector를 구현한 테스트 전용 RecordingStatementInspector로 실제 실행 SQL을 기록해 검증했습니다. 새 dependency는 추가하지 않았고 spring.jpa.properties.hibernate.session_factory.statement_inspector만 테스트 프로퍼티로 설정했습니다.

영향 및 참고사항

배포 순서

flag 기본값이 legacy이므로 이 PR을 머지하고 배포해도 동작은 그대로입니다. 전환은 아래 순서로 진행합니다.

  1. 머지 → 배포 (legacy 동작, 무해)
  2. POST /admin/graph/document-references/backfill 실행
  3. 한 번 더 실행해 addedCount와 removedCount가 모두 0인지 확인
  4. GRAPH_READ_SOURCE=persisted 설정 후 재기동
  5. 이상이 있으면 값을 되돌리고 재기동

운영 .env는 CD 워크플로의 heredoc에서 생성되므로 GRAPH_READ_SOURCE 줄을 함께 추가했습니다. Secret(PROD_GRAPH_READ_SOURCE / DEV_GRAPH_READ_SOURCE)을 만들지 않으면 legacy입니다. prod는 workflow_dispatch의 redeploy 모드로 이미지 재빌드 없이 전환과 롤백이 가능합니다.

그 외

  • API 응답 schema는 변경하지 않았습니다.
  • 기존 CrewGraphReadModel과 contents projection(findAllCrewDocumentsByGenerationTitle)은 ContentsParsingCrewGraphReader가 계속 사용합니다. 영속 참조로 완전히 전환해 안정화 판정이 끝나면 flag와 함께 제거합니다.
  • CrewDocumentReferenceExtractor는 쓰기 동기화에서 계속 사용하므로 삭제하지 않았습니다.
  • 조직 선택 경로는 findOrganizationDocument가 OrganizationDocument 엔티티를 통째로 로드해 select list에 contents가 포함됩니다. 기존 동작이고 조직 선택 로직 유지가 요구사항이라 손대지 않았습니다. 핵심 크루 그래프 읽기 경로에는 contents가 없습니다.
  • 읽기 측 자기참조 가드를 남겼습니다. 쓰기 측에서 이미 거르지만 uk_document_reference_source_target은 source == target을 막지 않습니다.
  • 운영 DB backfill 실행과 배포는 이번 PR 범위가 아닙니다.
  • backfill 엔드포인트는 동기 호출입니다. 문서가 많아지면 프록시 idle timeout에 걸릴 수 있습니다. 응답만 끊기고 작업은 서버에서 계속 진행됩니다.
  • backfill 동시 실행 방어는 넣지 않았습니다. uk_document_reference_source_target 덕에 데이터가 깨지지는 않지만 한쪽이 실패로 집계될 수 있으므로 한 번에 하나만 호출해 주세요.

🤖 Generated with Claude Code

기수 문서의 contents를 읽어 요청마다 참조를 파싱하지 않고,
저장된 node와 reference 조회 결과로 응답을 조립한다.
읽기 경로에서 CrewDocumentReferenceExtractor 의존을 제거한다.

DB에는 방향성 참조를 그대로 두고 API는 기존처럼 UUID가 작은 문서를
source로 둔 무방향 edge 하나만 반환한다. 자기참조 방어 제외와
중복 제거, 안정 정렬을 유지한다.

기존 테스트 fixture를 contents 링크에서 실제 참조 row로 바꾼다.
저장 방향과 무관하게 UUID 순서로 정규화한 간선이 정렬되는지 검증한다.

StatementInspector로 실제 실행 SQL을 기록해 조직 미선택 조회가
node와 reference query 2개만 실행하고 select list에 contents가 없음을,
조직 선택 조회가 4개를 넘지 않음을 검증한다.
@sun007021 sun007021 self-assigned this Aug 31, 2026
@coderabbitai

coderabbitai Bot commented Aug 31, 2026 •

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: 556b67a9-4181-4239-a464-45500ea500f7

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@poketopa poketopa left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

리뷰 반영 후 머지하면 될 것 같습니다!

Comment on lines +46 to +49
List<CrewGraphNodeReadModel> nodeReadModels = crewGraphQueryRepository
.findAllGraphNodesByGenerationTitle(generation);
List<DocumentReferenceReadModel> referenceReadModels = documentReferenceRepository
.findAllReadModelsByGenerationTitle(generation);

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

develop 머지 시 dev CD가 즉시 배포되는데, 현재부터는 document_reference만 조회합니다. backfill 실행 진입점이 없어 기존 데이터의 간선이 edges: []로 누락될 수 있습니다. 머지 전에 backfill을 강제하는 배포 단계나, 완료 전 legacy 경로를 유지하는 feature flag를 마련해주세요.

sun007021 and others added 2 commits September 6, 2026 22:11
DocumentReferenceBackfillService는 production 호출자가 없어 배포 후에도
기존 문서의 document_reference를 채울 수단이 없었다.
POST /admin/graph/document-references/backfill로 실행 진입점을 연다.

/admin/** 이므로 LoginCheckInterceptor의 인증을 그대로 적용받는다.
서비스 결과는 AdminDocumentReferenceBackfillResponse로 변환해 반환한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
develop·main 머지가 곧 배포이므로, 영속 참조 읽기 경로가 backfill보다
먼저 배포되면 그래프 API가 예외 없이 edges: []를 반환한다.
graph.read.source로 읽기 경로를 고르게 해 코드 배포와 동작 전환을 분리한다.

legacy는 요청 시 본문을 파싱하는 기존 경로이고 기본값이다.
persisted는 저장된 document_reference를 조회하며 backfill 완주 후에만 켠다.
설정이 없으면 legacy가 선택되므로 머지와 배포만으로는 동작이 바뀌지 않는다.

두 경로가 같은 응답을 만들도록 무방향화와 자기참조 제외, 중복 제거,
안정 정렬을 CrewGraphEdgeNormalizer 하나로 공유한다.
CrewGraphReader가 node와 edge를 함께 반환해 legacy가 contents를
두 번 조회하지 않게 한다.

CrewGraphQueryService는 reader에 위임하고 조직 그래프 합성만 담당한다.

CrewGraphReaderParityTest로 backfill 이후 두 경로의 node·edge가 같음과
backfill 이전에 영속 경로만 간선을 잃음을 고정한다.
CrewGraphQueryServiceDefaultReadSourceTest로 기본값이 legacy임을 검증한다.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@sun007021
sun007021 merged commit 1b093b1 into develop Sep 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants