Categorize cluster (de)serialization metrics and drop per-op allocation in metric wrappers#2486
Open
thjaeckle wants to merge 2 commits into
Conversation
Add low-cardinality `category` (event/command/response/acknowledgement/ announcement/error/other) and `resource_type` (from WithResource) tags to the `<serializer>_serializer_messages` counter in AbstractJsonifiableWithDittoHeadersSerializer, so spikes in cluster CBOR/JSON (de)serialization can be attributed - e.g. event fan-out vs command load, and per resource type incl. *-sudo policy fetches. Fully-tagged counters are cached per (category, resource_type) so the hot (de)serialization path stays a map lookup + increment. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…cost KamonCounter/Gauge/Histogram and PreparedKamonTimer re-resolved their underlying Kamon instrument on every operation via `Kamon.<metric>(name).withTags(convert(tags))` - a registry lookup plus a per-call Kamon TagSet allocation/conversion. Since (name, tags) is invariant for these wrappers, cache the resolved instrument in a volatile field so the hot path becomes a volatile read + the actual op. This benefits every metric in Ditto and in particular the per-message serializer counter on the cluster (de)serialization hot path. Counter/Gauge/Histogram have immutable tags (copy-on-write tag()/tags()), so a plain lazy memo is safe. PreparedKamonTimer mutates its tags in place, so the cache is invalidated in tag()/tags(). StoppedKamonTimer is single-use per stop with dynamic segment tags and is left unchanged. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Member
Author
|
System tests passed: https://github.com/eclipse-ditto/ditto/actions/runs/29031970314 |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What & why
Two related improvements around cluster (de)serialization metrics: better insight into where serialization traffic comes from, and a lower-cost, allocation-free metric increment path.
1. Categorize cluster (de)serialization metrics
The
<serializer>_serializer_messagescounter (inAbstractJsonifiableWithDittoHeadersSerializer) currently only carries adirection(in/out) tag. That makes it hard to explain spikes in cluster CBOR/JSON serialization: a doubling of the counter could equally be event fan-out to more subscribers, a burst of inbound commands, a wave of policysudofetches, or response traffic — all indistinguishable.This adds two low-cardinality tags derived at the (de)serialization site:
category— coarse signal kind:event,command,response,acknowledgement,announcement,error,other(viainstanceof, ordered soAcknowledgementis classified beforeCommandResponse).resource_type— fromWithResource#getResourceType()(thing,policy,connectivity,message,thing-search, and the*-sudovariants…), orotherfor non-signal envelopes.Together these let operators attribute spikes directly — e.g.
category=event,resource_type=thingfor live/streaming fan-out vscategory=command,resource_type=thingfor write load, andresource_type=policy-sudo/thing-sudoto spot cross-node enforcement policy fetches.directionis unchanged, so existing dashboards keep working; the new tags are additive.Cardinality stays modest: ~7 categories × ~18 resource types × 2 directions per serializer, and most combinations never co-occur. The fully-tagged counters are cached per
(category, resource_type)so the hot path is a map lookup + increment.2. Memoize the resolved Kamon instrument in the metric wrappers
KamonCounter,KamonGauge,KamonHistogramandPreparedKamonTimerre-resolved their underlying Kamon instrument on every operation:That is a metric-registry lookup plus a per-call allocation of a Kamon
TagSet(thegetKamonTagSetbuilder) on every increment/record — even though(name, tags)is invariant for a given wrapper. This is now memoized in avolatilefield, so the hot path becomes a volatile read + the actual op, with no per-op allocation.Counter/Gauge/Histogramhave immutable tags (copy-on-writetag()/tags()), so a plain lazy memo is safe.PreparedKamonTimermutates its tags in place, so the cache is invalidated intag()/tags().StoppedKamonTimeris single-use per stop with dynamic segment tags and is intentionally left unchanged.This benefits every metric in Ditto, and in particular the now-per-message serializer counter on the cluster (de)serialization hot path.
Micro-benchmark (JMH, AverageTime, JDK, 1 fork · 3 warmup · 5 measurement iterations)
The memoized path matches the theoretical floor (the volatile read is free), and is ~10.6× faster than re-resolving. Of the ~84 ns saved, roughly ~50 ns was the per-call
TagSetallocation/conversion and ~25 ns the registrywithTagslookup. The allocation removal also reduces GC pressure on the hot path.Compatibility
directiontag are unchanged; thecategory/resource_typetags are additive.