Elasticsearch¶
RAG 검색의 1차 저장소. 14개 세무 인덱스(tax-*)에 법령·판례·예규·상담·회계기준 등이 적재되고, 백엔드는 BM25 텍스트 검색 + kNN 벡터 검색을 RRF(Reciprocal Rank Fusion)로 합쳐 hybrid 검색한다. 모든 인덱스가 동일한 분석기/벡터 매핑 컨벤션(Nori + dense_vector 3072 cosine)을 공유하여 source-agnostic하게 검색 로직을 통합한다.
인덱스 카탈로그¶
| 인덱스 | 적재 모듈 | 백엔드 Repository | 임베딩 |
|---|---|---|---|
tax-laws |
LawIndexer |
SemugptLawElasticRepository |
✅ |
tax-precedents |
PrecedentIndexer (+ NTSPrecedentCollector) |
SemugptPrecedentElasticRepository |
✅ |
tax-counsel |
CounselIndexer |
SemugptCounselElasticRepository |
✅ |
tax-written-inquiry |
WrittenInquiryIndexer |
SemugptWrittenInquiryElasticRepository |
✅ |
tax-glossary |
GlossaryIndexer |
SemugptGlossaryElasticRepository |
✅ |
tax-enforcement |
EnforcementIndexer |
SemugptEnforcementElasticRepository |
✅ |
tax-oldnew |
OldAndNewIndexer |
SemugptOldAndNewElasticRepository |
✅ |
tax-basic-rules |
BasicRulesIndexer |
SemugptBasicRulesElasticRepository |
✅ |
tax-taxoffice |
TaxofficeIndexer |
SemugptTaxofficeElasticRepository |
✅ |
tax-tribunal |
TribunalIndexer |
SemugptTribunalElasticRepository |
✅ |
tax-supreme-court |
SupremeCourtIndexer |
SemugptSupremeCourtElasticRepository |
✅ |
tax-scourt |
ScourtIndexer |
SemugptScourtElasticRepository |
✅ |
tax-accounting |
AccountingIndexer |
SemugptAccountingElasticRepository |
✅ |
tax-threeway |
ThreeWayIndexer |
(백엔드 ThreewayService 직접 query) |
✅ |
tax-relations |
RelationIndexer |
백엔드 미사용 (legacy) | ❌ |
총 적재 인덱스는 15개지만 백엔드 RAG가 사용하는 핵심 인덱스는 14개. 자세한 RAG 라우팅은 리포트 생성 참조.
사용자 여정¶
인덱스 생성 → 검색 흐름¶
sequenceDiagram
autonumber
participant CLI as semugpt-index
participant ES as Elasticsearch
participant API as Backend
participant FE as 프론트엔드
CLI->>ES: PUT /tax-laws (settings + mappings)
Note over ES: nori_tokenizer, korean_text analyzer
content_vector: dense_vector(3072, cosine)
CLI->>ES: POST /_bulk (documents + content_vector)
ES-->>CLI: 적재 완료
Note over API: 사용자 질문 도착
API->>API: HyDE로 가상 답변 생성 → embed
API->>ES: POST /tax-laws/_search
{ retriever: { rrf: { retrievers: [bm25, knn] } } }
ES-->>API: 점수순 결과 (BM25 + kNN RRF 융합)
API-->>FE: 답변 + 참고자료
Dev 환경 직접 조회¶
sequenceDiagram
participant U as 운영자
participant ES as Dev ES (localhost:9200 on Lightsail box)
U->>U: printf 'elastic:uiti0701!' | base64
→ ZWxhc3RpYzp1aXRpMDcwMSE=
U->>ES: curl -H 'Authorization: Basic ...' /_cat/indices
ES-->>U: tax-laws / tax-precedents / ... 문서 수
U->>ES: curl ... /tax-laws/_search?q=양도소득세
ES-->>U: BM25 결과 (벡터 검색 없이)
백엔드 구현¶
| 계층 | 클래스 / 파일 | 역할 |
|---|---|---|
| ES client | Spring RestClient Bean |
백엔드 측 ES 클라이언트, application-{prod,local}.yml의 elastic.host/port/username/password 로드 |
| Repository (인덱스별) | SemugptXxxElasticRepository (apps/backend/.../persistence/) |
인덱스마다 1:1. findByTextOnly, findByVectorOnly, findBatchById 등. RRF hybrid는 Law 인덱스에만 findSimilarLawListWithRRF()로 구현 |
| RAG 검색 오케스트레이션 | StreamingRagProcessor (apps/backend/.../service/) |
인덱스 라우팅 + hybrid search + 정렬·필터. 리포트 생성 참조 |
| Admin | ElasticAdminRepository (apps/backend/.../persistence/ElasticAdminRepository.kt) |
인덱스 상태/통계 조회 (admin console) |
| Indexer 측 client | create_es_client (packages/data-pipeline/.../utils/elasticsearch.py) |
Python 파이프라인의 공통 ES 클라이언트 |
도메인 규칙¶
| 규칙 | 위치 | 값 / 설명 |
|---|---|---|
| ES 버전 | dev/prod 동일 | 8.17 (elasticsearch~=8.17.0 Python client) — RRF retriever API 지원 |
| 분석 플러그인 | dev/prod 모두 설치 | analysis-nori — 한국어 형태소 분석 |
| 단일 노드 운영 | 각 인덱스 settings | number_of_shards: 1, number_of_replicas: 0. dev/prod 모두 single-node |
| Tokenizer | 매핑 settings | nori_tokenizer with decompound_mode: "mixed" — 복합어를 원형 + 분해형 모두 색인 |
| Analyzer | 매핑 settings | korean_text = nori_tokenizer + lowercase + nori_readingform 필터 |
| 텍스트 필드 패턴 | 각 인덱서 매핑 | { "type": "text", "analyzer": "korean_text", "copy_to": "_all_text" } — 다중 필드를 통합 검색용 단일 필드로 복사 |
| 벡터 필드 | 각 인덱서 매핑 | content_vector: { type: "dense_vector", dims: 3072, index: true, similarity: "cosine" } |
| 임베딩 모델 | indexer + backend 동일 | text-embedding-3-large (OpenAI). 백엔드 EmbeddingUtil.embed()와 인덱서가 동일 모델 사용 — 차원 미스매치 방지 |
| Hybrid 검색 (RRF) | SemugptLawElasticRepository.findSimilarLawListWithRRF() (Law 인덱스에만 구현됨) |
ES 8.x retriever.rrf API. BM25 + kNN을 rank_constant, window_size로 융합. 타 인덱스는 application layer에서 BM25 + kNN 결과 융합 |
| BM25 매치 필드 | repository별 query | 보통 multi_match로 title^2, content, _all_text 등 |
| kNN 파라미터 | repository별 | k, num_candidates=k*2 |
| 벡터 검색 fallback | findByVectorOnly |
cosineSimilarity(params.query_vector, 'content_vector') + 1.0 스크립트 점수 (script_score) |
| 인덱스 명명 | indexer INDEX_NAME 상수 |
tax-{source} (snake_case 대신 dash). 백엔드 indexName 상수와 1:1 일치 |
| 운영 단일 노드 + Docker | Lightsail docker-compose.dev.yml |
dev/prod 모두 Lightsail box 안의 Docker 컨테이너로 운영 (RDS 분리 안 함). ES_JAVA_OPTS=-Xms1g -Xmx1g (Lightsail-only patch) |
API 엔드포인트¶
해당 없음 — Elasticsearch는 내부 데이터 저장소이며 외부 API로 노출되지 않는다. 백엔드의 REST endpoint를 통해서만 접근. RAG 검색 API는 리포트 생성의 /conversations/** 참조.
ES 자체 API (dev 직접 조회용, 운영자 한정):
| Method | Path | 설명 |
|---|---|---|
| GET | /_cat/indices?v |
인덱스 목록 + 문서 수 |
| GET | /{index}/_count |
단일 인덱스 문서 수 |
| GET | /{index}/_search?q=... |
BM25 검색 |
| POST | /{index}/_search |
DSL 쿼리 (RRF, kNN 등) |
| GET | /{index}/_mapping |
매핑 조회 |
데이터 모델¶
각 인덱스가 가진 공통 매핑 패턴 (구체 필드는 source별 추가):
erDiagram
TAX_INDEX_COMMON {
keyword type "문서 타입 (예: law_article, precedent)"
text title "한국어 분석"
text content "한국어 분석, copy_to _all_text"
text _all_text "통합 검색 필드"
dense_vector content_vector "3072 dims, cosine"
keyword source "출처 도메인"
keyword source_url
date collected_at
}
TAX_LAWS {
keyword law_id
keyword law_name
keyword law_type "현행 / 시행령 / 시행규칙"
keyword article_key "7자 정규화 키 (A/B/C prefix)"
integer article_no
integer article_sub
text article_title
text content
dense_vector content_vector
}
TAX_WRITTEN_INQUIRY {
keyword doc_id
keyword inquiry_number "정규화된 문서번호"
integer inquiry_year
keyword inquiry_tax_category
integer inquiry_sequence
text question "질의"
text answer "회신"
}
TAX_THREEWAY {
keyword law_article_key
keyword decree_article_key
keyword rules_article_key
text law_content
text decree_content
text rules_content
}
TAX_INDEX_COMMON ||--o| TAX_LAWS : "tax-laws 추가 필드"
TAX_INDEX_COMMON ||--o| TAX_WRITTEN_INQUIRY : "tax-written-inquiry 추가 필드"
TAX_INDEX_COMMON ||--o| TAX_THREEWAY : "tax-threeway는 법-시행령-시행규칙 묶음"
전체 14개 인덱스의 상세 필드는 각 indexer 파일의 매핑 정의 (packages/data-pipeline/src/semugpt_pipeline/indexers/*_indexer.py) 또는 백엔드 Index DTO (apps/backend/src/main/kotlin/me/uiti/taxgpt/application/service/data/dto/Semugpt*Index.kt)에서 확인.
설정¶
| 항목 | 위치 | 비고 |
|---|---|---|
| dev ES 호스트 | application-local.yml elastic.host |
localhost:9200 (docker-compose.dev.yml의 컨테이너) |
| prod ES 호스트 | application-prod.yml elastic.host |
3.39.210.101:9200 (Lightsail prod, Docker로 운영) |
| ES username | yaml elastic.username |
"elastic" (dev/prod 동일) |
| ES password | yaml elastic.password |
"uiti0701!" (dev/prod 동일 — Memory: feedback_accepted_repo_secrets) |
| 인덱서 측 URL | ES_URL env 또는 --es-url |
URL 임베드 시 !는 %21로 인코딩 |
| Docker compose (dev) | docker-compose.dev.yml |
ES 8.17 + analysis-nori, UID 1000, single-node discovery |
| Lightsail-only patch | /opt/semugpt-2026/docker-compose.dev.yml (커밋 안 됨) |
ES_JAVA_OPTS=-Xms1g -Xmx1g (4GB box 적응) |
| reindex remote whitelist | Lightsail-only patch | reindex.remote.whitelist=172.17.0.1:9202 |
알려진 이슈 / 개선 예정¶
- 매핑 변경 → 재인덱싱 수동 —
create_index()가 멱등이지만 기존 인덱스를 무시. 새 필드 추가나 분석기 변경 시DELETE /{index}후bulk재실행 필요. Reindex API 자동화 미구현. - 단일 노드 / 단일 shard 운영 — 고가용성 없음. ES Docker 컨테이너가 죽으면 RAG 검색 전면 중단. Lightsail 박스 디스크 풀 시 자동 복구 안 됨 (CLAUDE.md "디스크 정리" 런북 참조).
tax-relations는 사용 안 함 — 3단 비교는tax-threeway로 통합됨. RelationIndexer는 유지되지만 backend가 조회하지 않음 — 정리 후보.- ES password 평문 노출 —
application-{local,prod}.yml에"uiti0701!"이 평문. PR #167 등 일부 정리됐지만 (Memory: prod readiness), 완전한 secret 외부화는 미완. accepted blocker. - 임베딩 모델 변경 시 양쪽 동시 변경 필요 — Python 인덱서(
text-embedding-3-large) ↔ 백엔드EmbeddingUtil(text-embedding-3-large)가 같은 모델이어야 검색 정합성 유지. 한쪽만 바꾸면 차원 미스매치 또는 의미 어긋남 — Issue로 alarm 필요. - 분석기 변경 회귀 위험 —
nori_readingform이 한자 → 한글 변환을 수행. 이 필터가 빠지면 한자 검색이 깨지는데 운영 중 분석기 교체는 위 매핑 변경 이슈와 결합되어 다운타임 발생.
관련 문서¶
- 인덱서 (Indexers) — ES 인덱스를 채우는 Python 모듈
- 임베딩 —
content_vector생성 + 백엔드 query 임베딩 정합성 - 리포트 생성 — RAG 검색 (
StreamingRagProcessor, RRF, 카테고리 백필, 4-tier 정렬) packages/data-pipeline/CLAUDE.md(리포 안 직접 참조) — Phase 2 매핑 템플릿