ELK 조인에 관한 고찰
오늘 고객사 회의중 인덱스가 장문텍스트가 포함되어 있어 매우 무거운데 잦은 업데이트가 있어 어떻게 해결해야 하나 하는 질문이 있었다.
2023년 중반에도 비슷한 문제가 있었고 그때는 색인속도 최적화로 넘겻다.
그떄보다 양이 워낙 크다 보니 다른 방법을 필요로 하는거 같은데...
1번 어플리케이션 조인은 데이터 건수가 많아서 일단 이것은 내가 반대
2번 색인 생성시 난이도가 매우 높아 일단 이건 언급 제외
그리고 결국은 돌고 돌아 CQRS 패턴 제안을 했다.
해당으로 결론나지는 않았지만 AI 질의 결과 AI도 아래와 같이 해결책을 제시 했다.
생각 크게 다르지 않다.
1. 애플리케이션 단에서의 조인 (Application-side Join) - 🏆 가장 권장
검색 엔진 내부에서 두 인덱스를 합치는 대신, 백엔드 애플리케이션(서버) 코드로 두 번 호출하여 데이터를 조합하는 방식입니다. 가장 직관적이고 성능 예측이 쉽습니다.
- 동작 방식:
- 자주 바뀌는 인덱스에 먼저 검색 쿼리를 날려서 조건에 맞는 문서들의 ID만 빠르게 가져옵니다.
- 확보한 ID 목록을 가지고 초장문 텍스트 인덱스에 _mget (Multi-Get) API를 호출하여 필요한 원문 텍스트만 쏙쏙 뽑아옵니다.
- 백엔드 서버에서 두 데이터를 합쳐서 클라이언트에게 내려줍니다.
- 장점: 오픈서치 클러스터에 무거운 JOIN 연산 부하를 주지 않으며, 캐싱을 적용하기도 매우 좋습니다.
- 단점: 네트워크 요청이 2번 발생합니다 (하지만 검색 엔진 내부의 조인보다 훨씬 빠릅니다).
2. Parent-Child 모델 (join 데이터 타입) 사용
오픈서치에는 RDBMS의 외래키(Foreign Key)와 비슷한 개념으로, 한 인덱스 안에서 부모-자식 관계를 맺을 수 있는 join 데이터 타입이 있습니다.
- 동작 방식:
- 부모 문서: 초장문 텍스트 등 변경이 없는 무거운 데이터.
- 자식 문서: 상태값 등 자주 업데이트되는 가벼운 데이터.
- 자식 문서를 업데이트하더라도 부모 문서는 전혀 건드리지 않으므로 업데이트 비용이 획기적으로 줄어듭니다.
- 검색 방식: has_child 또는 has_parent 쿼리를 사용하여, "특정 상태값(자식)을 가진 문서의 텍스트(부모)를 검색"하는 식의 양방향 검색이 가능합니다.
- 주의점 (매우 중요):
- 부모와 자식 문서는 **반드시 같은 샤드(Shard)**에 저장되어야 하므로, 자식 문서를 색인할 때 routing 파라미터로 부모의 ID를 지정해 주어야 합니다.
- 일반 단일 문서 검색보다 쿼리 성능이 떨어집니다 (메모리 사용량 증가).
3. 필드 단위 업데이트 (_update API)의 한계 인지
혹시 현재 통째로 문서를 밀어넣는 방식(Index API)을 사용 중이시라면, _update API를 통한 부분 업데이트를 고려해보셨을 수 있습니다.
- 하지만 앞서 말씀드린 대로, 루씬(Lucene) 구조상 _update를 호출해도 내부적으로는 새 문서를 생성하고 초장문 텍스트를 복사하는 작업이 일어납니다.
- 텍스트 필드가 메가바이트(MB) 단위로 정말 크고 업데이트가 빈번하다면, 이 방식은 여전히 I/O 병목을 유발하므로 인덱스를 분리하는 1번이나 2번 방식이 맞습니다.
-------
1. Parent-Child 모델 구조 유지 + 집계 쿼리 활용 (엔진 내부 해결)
데이터 외부 유출 없이 오픈서치 내부에서 끝내야 한다면, 앞서 말씀드린 join 데이터 타입(Parent-Child)이 유일한 해답입니다.
- 집계(Aggregation) 방법: 부모-자식 구조에서는 children 집계나 parent 집계를 사용하여 통계를 낼 수 있습니다. 예를 들어, 부모 문서(초장문 텍스트)의 특정 키워드로 필터링한 후, 연결된 자식 문서(상태값)들의 개수를 그룹화하여 통계를 내는 식입니다.
- 대량 다운로드 (Scroll / PIT): 수십만 건의 데이터를 다운로드하려면 Scroll API나 Point In Time(PIT)을 사용해야 합니다.
- 치명적인 단점: 부모-자식 간의 조인 상태로 수십만 건을 집계하는 것은 일반 집계보다 메모리를 수 배 이상 소모합니다. 쿼리 속도가 느린 것은 물론, 자칫하면 무거운 쿼리 하나가 전체 검색 엔진 서버를 다운시킬 수 있어 타임아웃(Timeout) 설정이 필수입니다.
2. CQRS 패턴: 서비스용 인덱스와 분석/통계용 인덱스 분리
데이터가 자주 변경되는 '실시간 서비스 환경'과 무거운 통계를 돌리는 '분석 환경'을 완전히 분리하는 실무적인 아키텍처입니다.
- 동작 방식:
- 서비스용: 초장문 텍스트 인덱스와 자주 바뀌는 상태 인덱스를 분리하여 가볍게 유지합니다. (업데이트 부하 최소화)
- 데이터 파이프라인: Apache Kafka나 Apache NiFi와 같은 스트리밍/데이터 연동 도구를 중간에 배치하여, 상태값이 업데이트될 때마다 변경 사항을 캐치합니다.
- 통계용 인덱스 (Denormalized): 파이프라인에서 텍스트 데이터와 상태 데이터를 하나로 합쳐서(Join) '통계 전용 인덱스'에 밀어 넣습니다.
- 장점: 통계를 낼 때는 이미 합쳐진 단일 인덱스를 검색하므로 오픈서치의 집계/다운로드 성능을 100% 끌어낼 수 있습니다. 무거운 통계를 돌려도 메인 서비스에 영향을 주지 않습니다.
- 단점: 통계용 인덱스에는 초장문 텍스트가 중복 저장되므로 스토리지(디스크) 비용이 증가하며, 데이터가 완전히 동기화되는 데 약간의 지연(Eventual Consistency)이 발생합니다.
'빅데이터' 카테고리의 다른 글
| 오브젝트 스토리지 (0) | 2025.12.23 |
|---|---|
| rag rag rag 간단히 하나 해보자 (0) | 2024.10.16 |
| meilisearch 설치방법 (0) | 2024.08.17 |
| meilisearch (0) | 2024.08.16 |
| NIFI Attribute json Array 저장 방법 (0) | 2023.07.20 |








