Skip to content

perf(core): optimize benchmarked hot paths#2671

Open
Ahoo-Wang wants to merge 27 commits into
mainfrom
perf/wow-benchmarks-round2
Open

perf(core): optimize benchmarked hot paths#2671
Ahoo-Wang wants to merge 27 commits into
mainfrom
perf/wow-benchmarks-round2

Conversation

@Ahoo-Wang

Copy link
Copy Markdown
Owner

Summary

  • Optimizes benchmarked core command, metadata, header, event stream, and serialization hot paths.
  • Adds Redis benchmark coverage for command processing and event-store reads, and caches Redis Lua scripts as static text.
  • Refactors wow-benchmarks Gradle logic into focused reporting and JMH packaging scripts.
  • Removes the .gitignore rule that excluded docs/superpowers/.

Changes

  • Core runtime: reduces allocation and lookup overhead across command exchanges, command function metadata, event stream creation, header storage, accessor invocation, and event body reader reuse.
  • Redis: introduces RedisScripts and reuses script text in Redis prepare and event-store paths.
  • Benchmarks: adds grouped benchmark reporting tasks, PR-safe smoke execution, Redis read/pipeline benchmarks, dispatcher growth baselines, and stable benchmark fixtures.
  • JMH packaging: merges Wow metadata and critical SPI files before building the JMH jar so benchmark runtime metadata remains valid.
  • Configuration: allows docs/superpowers/ files to be tracked by removing the local ignore rule.

Testing

  • ./gradlew :wow-core:check :wow-redis:check :example-domain:check :wow-benchmarks:benchmarkSmoke && git diff --check
  • ./gradlew :wow-benchmarks:benchmarkSmoke --rerun-tasks

Risk & Compatibility

  • Medium risk because the PR changes core command/event hot paths, header mutation behavior, metadata lookup, and Redis script loading.
  • No breaking public API change is intended from the diff.
  • Redis changes should be reviewed for script text parity and startup/runtime behavior.

Review Notes

  • Pay special attention to copy-on-write header behavior, direct command/state invocation paths, event stream list reuse, event body reader caching, Redis script registration, and JMH metadata/SPI packaging.

Ahoo-Wang added 27 commits June 7, 2026 14:02
Merge critical JMH service provider files so benchmark forks load the same Jackson and id generator providers as the runtime classpath. Stabilize benchmark fixtures that previously accumulated unbounded in-memory state or buffered sink values, and use valid Wow ids/snapshot payloads in JMH inputs.

Optimize the example cart state sourcing path by mutating the backing item list instead of copying it on every added item event, reducing aggregate recovery allocation for long event streams.
@codacy-production

codacy-production Bot commented Jun 7, 2026

Copy link
Copy Markdown

Up to standards ✅

🟢 Issues 0 issues

Results:
0 new issues

View in Codacy

🟢 Metrics 161 complexity

Metric Results
Complexity 161

View in Codacy

NEW Get contextual insights on your PRs based on Codacy's metrics, along with PR and Jira context, without leaving GitHub. Enable AI reviewer
TIP This summary will be updated as you push new changes.

@codecov

codecov Bot commented Jun 7, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 74.47257% with 121 lines in your changes missing coverage. Please review.
✅ Project coverage is 92.09%. Comparing base (38d0e50) to head (1d58d70).

Files with missing lines Patch % Lines
...main/kotlin/me/ahoo/wow/messaging/DefaultHeader.kt 56.25% 50 Missing and 6 partials ⚠️
...main/kotlin/me/ahoo/wow/command/CommandExchange.kt 61.72% 19 Missing and 12 partials ⚠️
...oo/wow/modeling/metadata/StateAggregateMetadata.kt 39.13% 12 Missing and 2 partials ⚠️
.../wow/modeling/metadata/CommandAggregateMetadata.kt 91.78% 0 Missing and 6 partials ⚠️
...otlin/me/ahoo/wow/example/domain/cart/CartState.kt 58.33% 3 Missing and 2 partials ⚠️
.../me/ahoo/wow/infra/accessor/method/FastInvoke.java 75.00% 2 Missing ⚠️
...in/kotlin/me/ahoo/wow/event/DomainEventExchange.kt 87.50% 0 Missing and 2 partials ⚠️
...tlin/me/ahoo/wow/event/DomainEventStreamFactory.kt 96.49% 1 Missing and 1 partial ⚠️
...in/me/ahoo/wow/modeling/command/CommandFunction.kt 88.23% 2 Missing ⚠️
.../src/main/kotlin/me/ahoo/wow/redis/RedisScripts.kt 66.66% 0 Missing and 1 partial ⚠️
Additional details and impacted files
@@             Coverage Diff              @@
##               main    #2671      +/-   ##
============================================
- Coverage     92.75%   92.09%   -0.67%     
- Complexity     4073     4105      +32     
============================================
  Files           737      738       +1     
  Lines         13181    13518     +337     
  Branches        922      989      +67     
============================================
+ Hits          12226    12449     +223     
- Misses          578      665      +87     
- Partials        377      404      +27     
Flag Coverage Δ
contract 50.62% <40.39%> (?)
integration 76.13% <88.88%> (?)
local 81.19% <72.99%> (?)

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@Ahoo-Wang Ahoo-Wang marked this pull request as ready for review June 7, 2026 06:42

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1d58d7040a

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +312 to +314
lazyAttributes ?: ConcurrentHashMap<String, Any>(ATTRIBUTE_MAP_INITIAL_CAPACITY).also {
copyFieldAttributesTo(it)
lazyAttributes = it

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Synchronize attribute materialization with field writes

When a SimpleServerCommandExchange is shared across scheduler threads, a first call to attributes can snapshot the volatile field-backed attributes and install lazyAttributes while another thread is concurrently calling setError, setEventStream, or another typed setter. If that setter observes lazyAttributes == null and writes only the field just after copyFieldAttributesTo, all later getAttribute calls read only the map and the newly written attribute is effectively lost; the previous ConcurrentHashMap backing did not have this race. Please guard field-backed writes/removes with the same synchronization used for materialization, or re-check/copy fields after installing the map.

Useful? React with 👍 / 👎.

Comment on lines +397 to +398
val primaryMetric = result["primaryMetric"] as Map<String, Any>
key to (primaryMetric["score"] as Double)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Parse JsonSlurper scores as Number

When benchmarkCompare runs against normal JMH JSON, JsonSlurper materializes decimal scores as BigDecimal (and sometimes other Number types), so this as Double cast throws a ClassCastException before any comparison can be printed. The same unchecked Double casts remain in generateBenchmarkReport; reuse the existing parseMetricNumber/Number.toDouble() path here so the reporting tasks can consume JMH output produced by the new parser.

Useful? React with 👍 / 👎.

connectionFactory = LettuceConnectionFactory(redisConfig, lettuceClientConfiguration)
connectionFactory.afterPropertiesSet()
redisTemplate = ReactiveStringRedisTemplate(connectionFactory)
flushDb()

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid flushing the default Redis database

Running any Redis benchmark now constructs this fixture with RedisStandaloneConfiguration() defaults, which points at localhost database 0, and immediately calls FLUSHDB (again on close below). In a developer or shared benchmark host that already has Redis data in DB 0, simply starting benchmarkExternal deletes unrelated keys; use an isolated database/container or only clear the benchmark key prefix instead.

Useful? React with 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant