develop
Micrometer Meter 관련 성능 이슈
레이니블루
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()에는 버그가 있습니다.