현재까지 공부한 내용을 토대로 프로젝트에 적용할 때 발생할 문제점에 대해 정리하겠습니다.
저희 프로젝트는 웹페이지에서 조회 후 검수 처리를 진행하고, 검수 처리 유무에 따라 [검수, 의심, 정상]으로 조회를 진행합니다. 또한 해당 조회 페이지는 페이징 처리를 현재 제공하고 있으며, 이에 따라 운영 조직에서는 ES를 적용함에 따른 기능 변화를 싫어할 것으로 예측됩니다.
이에 따라 ES를 적용하면 발생할 문제점은 다음과 같습니다.
쓰기 증폭
B-Tree는 문서 하나를 INSERT할 때 인덱스가 걸린 컬럼 개수만큼만 갱신합니다.
인덱스 3개면 트리 3개에 각각 1건씩 반영됩니다.
역인덱스는 문서 하나에 토큰 개수만큼 posting list를 건드립니다. 500단어짜리 문서에서 중복 제거 후 200개의 고유 term이 나오면, 200개의 posting list에 doc id를 추가해야 합니다.
MySQL: INSERT 1건 → 인덱스 3개 갱신
ES: INSERT 1건 → term 200개 × (posting list + term frequency + position) 갱신
여기에 색인 시점 분석(형태소 분석, nori 같은 건 특히 무겁습니다) 비용까지 붙습니다. 읽기를 빠르게 하려고 쓰기 비용을 앞당겨 지불하는 구조입니다.
그래서 ES는 이를 배치로 상쇄합니다. 메모리 버퍼에 모았다가 1초에 한 번 세그먼트로 떨굽니다. 대신 그 1초가 near real-time이라는 또 다른 대가가 됩니다.
예를 들면 아래와 같습니다.
{
"name": "카카오프렌즈 라이언 쿠션",
"description": "부드러운 극세사 소재로 제작된 대형 쿠션입니다. 거실 소파나 ... (200자)",
"stock": 100
}
이 문서 1건을 분석기에 통과시키면 다음과 같습니다.
[부드럽다, 극세사, 소재, 제작, 대형, 쿠션, 거실, 소파, 침대, 사용, 커버, 분리, 세탁, 가능, ...]
이렇게 나온 토큰의 수가 200개라면, 재색인 시 실제로 일어나는 일은 stock의 개수를 99로 수정한다면 새 세그먼트를 만들고 기존 세그먼트는 사용하지 않는다고 표기해 두는 것입니다.
이후 Merge를 통해 문서를 정리합니다.
관련 상세 내용은 색인 순서 및 구조 편에서 다루었습니다.
Paging 문제
검색 조건이 from: 100,000, size: 10 이라면 다음과 같은 문제가 발생합니다.
단일 서버라면 정렬된 인덱스를 앞에서부터 진행하여 10만 건부터 10개를 읽으면 되지만, ES에서는 샤드가 5개에 흩어져 있다면 coordinating 노드는 어느 샤드에 상위 결과가 몰려 있는지 알 수 없으므로 각 샤드에서 100,000건의 데이터를 가져와야 하고, 데이터가 기하급수적으로 늘어납니다.
from: 0, size: 10 (1페이지)인 경우입니다.
샤드1 → 로컬 상위 10개
샤드2 → 로컬 상위 10개
샤드3 → 로컬 상위 10개
샤드4 → 로컬 상위 10개
샤드5 → 로컬 상위 10개
↓
coordinating: 50개 병합·정렬 → 상위 10개 확정
from: 100000, size: 10 (10001페이지)인 경우입니다.
샤드1 → 로컬 상위 100,010개 ← from + size
샤드2 → 로컬 상위 100,010개
샤드3 → 로컬 상위 100,010개
샤드4 → 로컬 상위 100,010개
샤드5 → 로컬 상위 100,010개
↓
coordinating: 500,050개 병합·정렬 → 100,001~100,010번째 잘라내기
이로 인해 발생할 문제점은 다음과 같습니다.
- 메모리: coordinating 노드 힙에 50만 건의
(doc id, sort value)가 한꺼번에 올라갑니다. 동시 요청 몇 개만 겹쳐도 GC가 폭주하게 되고, OOM으로 이어집니다. 한 노드가 죽으면 클러스터 전체가 흔들립니다. - CPU: 샤드마다 10만 건 우선순위 큐 정렬에 더해 중앙에서 50만 건을 재정렬해야 합니다.
- 네트워크: 50만 건의 정렬 키가 노드 간에 오갑니다.
- 비용이 페이지 번호에 비례합니다: 1페이지는 50건이지만 10001페이지는 50만 건입니다. 뒤로 갈수록 계속 무거워집니다.
이 때문에 ES에서는 자체적으로 기본값인 index.max_result_window를 10,000으로 아예 막아둡니다.
이 때문에 무한 스크롤 방식이 적절하지만, 현업에서는 기존에 페이징 방식을 사용하기 때문에 ES로 변경한다고 하여 해당 기능을 강제로 바꾸는 것을 원하지 않습니다.
이를 해결하기 위해 무한 스크롤 방식이지만 페이징처럼 노출하는 방식을 택하게 되었습니다.
- 1~10페이지가 있다면 5페이지를 클릭했을 때 해당하는 인덱스만큼 조회해서 노출하는 방식입니다.
정리를 통해 ElasticSearch를 사용할 때 고려해야 할 점과 주의해야 할 점, 그리고 현재 프로젝트에 적용할 때 신경 써야 할 부분을 알 수 있었습니다.
댓글
GitHub 계정으로 로그인하시면 댓글과 질문을 남기실 수 있습니다. 남겨 주신 글은 이 저장소의 Discussions에 쌓입니다.