묻고 답해요
141만명의 커뮤니티!! 함께 토론해봐요.
인프런 TOP Writers
-
미해결ElasticSearch Essential
Compressed OOP 조건에 따른 ES Heap Size 제약
안녕하세요, 대용량 데이터 검색엔진 구축을 위해 Elasticsearch를 도입했습니다. 1. 개발환경 및 Spec 설명On-premise Kubernetes 환경에 Helm 배포를 통해 Master, Coordinating, Data Node 각각 4, 4, 10대로 Elasticsearch 클러스터를 구성했습니다.(HA 구성을 위해 Data Node는 모두 다른 Kubernetes Node에 배포되며, statefulSet을 통한 Rolling Update 방식입니다.)예상되는 클러스터 전체 Data Usage는 50TB 수준이고, primary shard와 replica shard의 개수는 각각 10과 1로 둘 예정입니다. 하나의 shard 용량은 10~20GB 수준으로 유지할 예정입니다. 현재는 초기 적재를 위해 replica shard 개수를 0으로 설정한 상황입니다.)pod container의 limit resource는 8core, 64Gi이며, ES_JAVA_OPTS 값으로는 -Xms30g -Xmx30g 옵션을 통해 Elasticsearch의 Heap Memory로는 30GB를 할당했습니다. 32Bit 포인터 관리 방식에서 object 그 자체가 아닌 object의 offset을 참조하는 Compressed OOP 사용을 위해, Elasticsearch의 Heap Size는 32GB를 권장하고 있습니다. 여기에 시작 주소를 0으로 두는 Zero-based 까지 고려하여 보수적으로 30GB를 사용했습니다.위와는 독립적인 권장 사항인 'JVM의 50%을 ES에 할당하라' 조건까지 고려하여 JVM Heapsize를 64Gi 로 두었습니다. 2. issue데이터 색인(bulk가 아닌 일반적인 PUT API) 중, kibana를 비롯하여 Elasticsearch 클러스터 전체에 503 에러가 발생했고 쿠버네티스 클러스터에 배포된 pod(Master, Coordinating Node 전부, 그리고 Data Node는 2대를 제외한 나머지 8개)가 restart 없이 죽었습니다. (원인은 CircuitBreaker입니다.)NAME READY STATUS RESTARTS AGE edms-p01-srep01-coordinating-0 0/1 Running 0 37h edms-p01-srep01-coordinating-1 0/1 Running 0 37h edms-p01-srep01-coordinating-2 0/1 Running 0 37h edms-p01-srep01-coordinating-3 0/1 Running 0 37h edms-p01-srep01-data-0 0/1 Running 0 37h edms-p01-srep01-data-1 0/1 Running 0 37h edms-p01-srep01-data-2 1/1 Running 0 37h edms-p01-srep01-data-3 0/1 Running 0 37h edms-p01-srep01-data-4 0/1 Running 0 37h edms-p01-srep01-data-5 0/1 Running 0 37h edms-p01-srep01-data-6 1/1 Running 0 37h edms-p01-srep01-data-7 0/1 Running 0 37h edms-p01-srep01-data-8 0/1 Running 0 26h edms-p01-srep01-data-9 0/1 Running 0 37h edms-p01-srep01-es-exporter-8457b87fb7-wsshd 1/1 Running 0 39h edms-p01-srep01-kb-84dcb6d7f7-gdhd9 0/1 Running 0 39h edms-p01-srep01-master-0 0/1 Running 0 37h edms-p01-srep01-master-1 0/1 Running 0 37h edms-p01-srep01-master-2 0/1 Running 0 37h edms-p01-srep01-master-3 0/1 Running 0 37h Data Node의 경우 빈번한 Young GC, 그리고 Old GC가 발생했지만 점차 확보하는 Memory 양이 적어지다가 CircuitBreakingException이 발생했습니다.Master와 Coordinating Node는 Old GC 없이 Young GC만으로 heap size가 잘 관리되다가 모든 Data Node 메모리 부하가 심해지니 Pod가 죽었는데, 유추하기로는 처리되지 못한 색인 데이터의 transport가 loop 되다가 Master/Coordinating 메모리에도 영향을 준 것으로 보입니다. CircuitBreakingException이 발생 전의 한 Data Node의 stats은 아래와 같습니다. (GET _nodes/stats) "mem" : { "heap_used_in_bytes" : 28558120848, "heap_used_percent" : 88, "heap_committed_in_bytes" : 32212254720, "heap_max_in_bytes" : 32212254720, "non_heap_used_in_bytes" : 191720344, "non_heap_committed_in_bytes" : 201515008, "pools" : { "young" : { "used_in_bytes" : 771751936, "max_in_bytes" : 0, "peak_used_in_bytes" : 19243466752, "peak_max_in_bytes" : 0 }, "old" : { "used_in_bytes" : 27784679400, "max_in_bytes" : 32212254720, "peak_used_in_bytes" : 32129130920, "peak_max_in_bytes" : 32212254720 }, "survivor" : { "used_in_bytes" : 1689512, "max_in_bytes" : 0, "peak_used_in_bytes" : 1520169584, "peak_max_in_bytes" : 0 } } }, ... "gc" : { "collectors" : { "young" : { "collection_count" : 226, "collection_time_in_millis" : 15603 }, "old" : { "collection_count" : 1, "collection_time_in_millis" : 6322 } } } 3. 의문점Data node 역할을 담당하는 pod가 죽은 것은 그럴 수 있다 쳐도 색인과 관련 없는 Coordinating/Master Node 역할의 pod에까지 영향을 미치는 이유는 무엇인가요?(위의 pod metric을 살펴보아도 OOM과는 전혀 거리가 멀어보이긴 합니다만) Elasticsearch가 분산시스템이지만 위와 같이 Kubernetes 노드에 문제가 없는 상태에서 pod만 죽어버리니 고가용성이 무색해지는군요... 앞서 말씀드린 것처럼 색인 데이터의 transport 내부 동작이 영향을 미쳤을까요?위와 같은 CircuitBreakingException에 대응하는 방법에는 ES_JAVA_OPT의 Heap Memory 용량을 증설하거나, 클러스터 세팅 indices.breaker.total.limit 값은 이미 95%입니다. (indices.breaker.total.use_real_memory가 true 이므로) 이때, 이미 Heap이 30GB라면, Compressed OOP의 조건인 32GB를 넘는 수준의 ES_JAVA_OPTS 설정을 시도하려면 어느 정도로 높게 하는게 좋을지 고견을 여쭙습니다.(Container의 jvm memory 자체에는 큰 제약이 없는 개발환경입니다. 즉, 128GB, 256GB처럼 높은 수준의 resources.limit 설정도 가능합니다.)
-
미해결실전! 스프링 부트와 JPA 활용2 - API 개발과 성능 최적화
one-to-may 의 관계에서 side effet 로 oom 의 발생가능성
안녕하세요 김영한 개발자님 수업을 듣던중 one-to-may 의 관계에서 side effet 로 oom 의 발생가능성에 대해 고민을 하게되어 질문드립니다. 테이블이 category - product - display_info 순으로 one-to-may 관계를 맺고있을 때, 만약category 가 10개, product 테이블의 데이터의 갯수가 10만개, display_info 의 테이블의 데이터가 100 만개일때,각각의 category 가 가지고있는 product 의 display_info 의 갯수를 확인하고 싶은 need 가 있으면,아래와 같이 entity 의 메서드를 통해 받아올수 있다는것을 이해를 했습니다. List<Category> categories = categoryRepository.findAll();List<CategoryResult> categoryResults = categories .stream() .map(category -> { //데이터 커지면 통계쿼리 처리가 좋을수도 있음. List<Product> products = category.getProducts(); int count = products .stream() .map(product -> product.getDisplayInfos().size()) .reduce(0, Integer::sum); return new CategoryResult(category.getId(), category.getName(), count); }) .collect(Collectors.toList());return categoryResults; 그런데 걱정이 되는것은 실제 데이터가 많으면 많아질수록 batch size 를 설정한다 하여도 가져올수 있는 양은 한정적이고결국 categorise 의 products 에 쌓인 데이터들로 인해 oom 이 발생할것 같은데 어떻게 처리를 하면 되나요??( 보통의 webpage 를 생각해보면 이렇게 많은 데이터를 가져올 일이없고 paging 을 이용할것 같긴 합니다. )제가 생각하기에는 아래의 2가지 정도 해결책이 있어보입니다.(1) one-to-many 의 관계를 사용하지 않고 명시적으로 fk 를 이용해서 repository 에서 find 를 호출 하는 방법.을 사용해 봤는데 이방법은 batch size 셋팅을 한다하여도 당연하겠지만 in query 로 sql 이 호출되지 않았습니다.당연히 속도도 현저히 늘려서 엄청난 시간이 걸렸습니다. ( 3분 -> 50분 )( ps. 아래의 코드에서도 inquery 셋팅하는 방법이 있을까요? ) List<Category> categories = categoryRepository.findAll();List<CategoryResult> categoryResults = categories.stream() .map(category -> { long categoryId = category.getId(); List<Product> products = productRepository.findAllByCategoryId(categoryId); int count = products .stream() .map(product -> { Long productId = product.getId(); List<DisplayInfo> displayInfos = displayInfoRepository.findAllByProductId(productId); return displayInfos.size(); }) .reduce(0, Integer::sum); return new CategoryResult(category.getId(), category.getName(), count); }) .collect(Collectors.toList()); (2) count query 를 설정하는 방법위의 needs 의 경우 통계쿼리처리를 하면 되겠지만, 만약 통계 쿼리로 처리하지못하는 로직이 있으면 어떻게 처리를 할수있을까요? 마지막 질문으로 위와 같이 oom 의 위험에도 불구하고 one-to-many 관계를 써야하나요??서비스에서의 가장큰 위험은 예상되지않은 에러라고 생각이 되는데 이경우 어떤 테이블이 어떻게 데이터가 많아질지 모르는 상황에서 위의 상황이 발생할수 있다고 생각이 됩니다.그럼에도 불구하고 성능적인 측면에서 one-to-many 를 쓰지않으면 성능이 너무나도 좋지않아서 고민입니다.긴글 읽어주셔서 감사합니다.