ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Micrometer Meter 관련 성능 이슈
    develop 2023. 10. 28. 22:56

    Micrometer를 통한 metrics을 관리하고 있는데, Profiling 시 가장 많은 CPU를 소모하고 있어서 분석을 해 보았습니다.

    meterRegistry.find("testMetricName").timer();

    문제되는 코드인데, meterRegistry에서 단순 조회를 한다고 생각했는데 MeterRegistry.find()의 내부 구현은 다음과 같습니다.

    Class MicrometerRegistry {
        public Search find(String name) {
           return Search.in(this).name(name);
        }
    }
    
    class Search {
        public Timer timer() {
            return findOne(Timer.class);
        }
        
        private <M extends Meter> M findOne(Class<M> clazz) {
            return meterStream().filter(clazz::isInstance).findAny().map(clazz::cast).orElse(null);
        }
        
        private Stream<Meter> meterStream() {
            Stream<Meter> meterStream = registry.getMeters().stream().filter(m -> nameMatches.test(m.getId().getName()));
            if (!tags.isEmpty() || !requiredTagKeys.isEmpty() || !tagMatches.isEmpty()) {
                meterStream = meterStream.filter(m -> isTagsMatched(m.getId()));
            }
            return meterStream;
        }
    }

    여기서부터 좀 고민됩니다. find()에서 Search.in()으로 Search 객체를 생성하는데, Timer를 얻어 올 때에 findOnce()의 구현 이 stream을 생성해서 iteration 하게 됩니다.

     

    MicrometerRegistry의 구현은 다음과 같습니다.

    class MeterRegistry {
        private final Map<Id, Meter> meterMap = new ConcurrentHashMap<>();
        
        public List<Meter> getMeters() {
            return Collections.unmodifiableList(new ArrayList<>(meterMap.values()));
        }
    
        /**
         * Iterate over each meter in the registry.
         * @param consumer Consumer of each meter during iteration.
         */
        public void forEachMeter(Consumer<? super Meter> consumer) {
            meterMap.values().forEach(consumer);
        }
    }

    객체 생성 관점에서 Search -> meterMap.values() -> ArrayList -> unmodifiableList -> stream 의 주요 객체가 생성되고 있어서 메모리 overhead가 상당하네요.

     

     

    이 부분은 다음과 같이 수정했습니다. 일반적으로 사용할 수는 없고 제 서비스 특화된 구현이기 때문에 어디까지나 참고사항입니다.

    Timer findTimer(List<Tag> tags) {
        AtomicReference<Timer> timer = new AtomicReference<>(null);
        meterRegistry.forEachMeter(it -> {
            if ("testMetricsName".equals(it.getId().getName()) && it instanceof Timer && isTagsMatched(it.getId(), tags)) {
                timer.set((Timer) it);
            }
        });
        return timer.get();
    }
    
    private boolean isTagsMatched(Meter.Id id, List<Tag> tags) {
        HashSet<Tag> set = new HashSet<>();
        id.getTagsAsIterable()
                .forEach(set::add);
        return set.containsAll(tags);
    }

    P.S. 위의 findTimer()에는 버그가 있습니다.

    'develop' 카테고리의 다른 글

    Cloud Native Spring Log4j Logging  (0) 2024.03.03
    REST API design guide  (1) 2024.02.12
    Kubernetes에서 node 내 thread 분리 이슈  (0) 2023.08.20
    Ref: Sync 10,000 Argo CD Applications in One Shot  (0) 2023.06.29
    Spring Cloud Config  (0) 2023.05.01
Designed by Tistory.