8000 [PERF] Jackson reflection-free serialization/deserialization by mabartos · Pull Request #42946 · keycloak/keycloak · GitHub
[go: up one dir, main page]

Skip to content

[PERF] Jackson reflection-free serialization/deserialization - #42946

Merged
vmuzikar merged 6 commits into
keycloak:mainfrom
mabartos:KC-42945
Oct 17, 2025
Merged

vmuzikar merged 6 commits into
keycloak:mainfrom
mabartos:KC-42945

Conversation

@mabartos
@mabartos mabartos commented Sep 25, 2025 •
Copy link
Copy Markdown
Member

I've followed up on the guide provided by the Quarkus team. The Jackson-free serdes was mentioned during the presentation about Observability performance in Quarkus, so they're including that even in their benchmarks.

In the guide, there's mentioned ~12% improvement in the throughput for HTTP calls. I made a brief performance analysis, and these are my findings:

Performance analysis

  • Run official KC distribution (OLD) and official KC distribution with the optimization (NEW)
  • Used wrk from the Hyperfoil

Test characteristic

  • Benchmarking how many req/s can KC server handle on one node
  • Only the host:8080/admin/realms/master/clients endpoint was called (multiple clients that are deserialized) with the access token
  • On a client, changed how many threads and connections are used when introducing the load on the server
    -- Matrix 1: 3 threads, 3 connections
    -- Matrix 2: 10 threads, 10 connections
    -- Matrix 3: 15 threads, 15 connections
  • Every matrix run for 15s
  • Every matrix is executed 5x

Matrix 1 (3 threads, 3 connections)

Requests per second

8000
Run Old New Diff %
1 1224 1385 ⬆️ 13.15 ✅
2 1494 1486 ⬇️ -0.54 ❌
3 1483 1550 ⬆️ 4.52 ✅
4 1501 1491 ⬇️ -0.67 ❌
5 1492 1634 ⬆️ 9.52 ✅

Average: 5.196% improvement ✅

Matrix 2 (10 threads, 10 connections)

Requests per second

Run Old New Diff %
1 3455 3636 ⬆️ 5.24 ✅
2 3416 3504 ⬆️ 2.58 ✅
3 3395 3496 ⬆️ 2.97 ✅
4 3341 3419 ⬆️ 2.33 ✅
5 3347 3738 ⬆️ 11.68 ✅

Average: 4.96% improvement ✅

Matrix 3 (15 threads, 15 connections)

Requests per second

Run Old New Diff %
1 3268 3100 ⬇️ -5.14 ❌
2 4034 4278 ⬆️ 6.05 ✅
3 3976 4586 ⬆️ 15.34 ✅
4 4000 4272 ⬆️ 6.80 ✅
5 3993 4281 ⬆️ 7.21 ✅

Average: 6.052% improvement ✅

Augmentation time

As some work is moved to the build phase, we experience a small increase in the augmentation time (which should be ok):

Run Old New Diff Diff %
1 4341ms 4501ms ⬆️ 160ms ❌ ⬆️ 3.69% ❌
2 4482ms 5131ms ⬆️ 649ms ❌ ⬆️ 14.48% ❌
3 8233ms 8630ms ⬆️ 397ms ❌ ⬆️ 4.82% ❌
4 7552ms 7691ms ⬆️ 139ms ❌ ⬆️ 1.84% ❌

Average Old: 6152ms
Average New: 6488ms
Difference: +336ms ❌
Difference in %: +6.20% ❌

Node characteristics

Architecture:                x86_64
  CPU op-mode(s):            32-bit, 64-bit
  Address sizes:             46 bits physical, 48 bits virtual
  Byte Order:                Little Endian
CPU(s):                      20
  On-line CPU(s) list:       0-19
Vendor ID:                   GenuineIntel
  Model name:                12th Gen Intel(R) Core(TM) i7-1280P
    CPU family:              6
    Model:                   154
    Thread(s) per core:      2
    Core(s) per socket:      14
Memory:                      32GB

Conclusion

In total, in this brief performance analysis, we experience 5.40% ✅ improvement in the throughput in our API. Note that values for some runs differ as it might be influenced by other tasks done on the CPU and not related to this benchmark. This test only gets clients from the realm, so for some endpoints, where the serdes is not used much does not have a big effect. On the other hand, the effect should be higher when serdes is used frequently.

I think it's a nice improvement, and there should not be any issues I'm not aware of.

cc: @keycloak/cloud-native @keycloak/sre
cc: @franz1981 @brunobat

@pruivo
pruivo commented Sep 25, 2025
Copy link
Copy Markdown
Member

@mabartos, what is happening in Matrix 1? Win/Lose alternates between runs.
How many clients and what is the JDK version?

@mabartos
mabartos commented Sep 25, 2025 •
Copy link
Copy Markdown
Member Author

what is happening in Matrix 1? Win/Lose alternates between runs.

We can't ensure the runs are 1:1 mapped (in a sense they have the same env conditions, etc.), f.e. there might be the case when some run for the New loses, but when compared with other run, it'd win. You can see that for the matrix 1, these values oscillates for the Old in range 1224-1492, where for the New 1385-1634. I haven't checked stdev and other props.

How many clients and what is the JDK version?

The default ones - ~5? OpenJDK 21.0.3

@pruivo It'd be nice to compare it in the KC benchmark that could show more interesting data. I can maybe also provide a comparison of flame graphs.

@franz1981
Copy link
Copy Markdown

And in term of native version, it is faster?
Or no difference?

@mabartos
mabartos commented Sep 25, 2025 •
Copy link
Copy Markdown
Member Author

And in term of native version, it is faster? Or no difference?

@franz1981 We don't use native builds in Keycloak. Or are you referring to sth else?

@franz1981
Copy link
Copy Markdown

I remember an issue where keycloak got bad XP with native image but I wasn't aware is not available as native app 🙏

@pruivo
pruivo commented Sep 25, 2025
Copy link
Copy Markdown
Member

what is happening in Matrix 1? Win/Lose alternates between runs.

We can't ensure the runs are 1:1 mapped (in a sense they have the same env conditions, etc.), f.e. there might be the case when some run for the New loses, but when compared with other run, it'd win. You can see that for the matrix 1, these values oscillates for the Old in range 1224-1492, where for the New 1385-1634. I haven't checked stdev and other props.

hmm, I'm guessing I'm not expecting the value to have such a large range between the best and worst runs.

How many clients and what is the JDK version?

The default ones - ~5? OpenJDK 21.0.3

@pruivo It'd be nice to compare it in the KC benchmark that could show more interesting data. I can maybe also provide a comparison of flame graphs.

I can run it locally. Do authentication/token refreshes go through Jackson serialization frequently enough to be measured by KC Benchmark? Or should I create a micro-benchmark (JMH)?

@franz1981
Copy link
Copy Markdown

Just FYI, loolking how reflection work, it got bad and worse as the number and type of fields grows and the number of serialized/deserialized types too. Not to mention the RSS consumed due to method handle generation, which won't happen in reflection free case

@mabartos
Copy link
Copy Markdown
Member Author

hmm, I'm guessing I'm not expecting the value to have such a large range between the best and worst runs.

@pruivo IMO, in order to find out what exactly is happening there, we'd need to go into wrk internals, checking TCP connections, how these requests are assigned to those threads, etc. I don't think it's worth the effort to go deeper into that, as it clearly shows the ranges increased with the optimization. Even the range diff of the Old/New is almost the same, so it does not show that the high variability should be caused by Keycloak.

Do authentication/token refreshes go through Jackson serialization frequently enough to be measured by KC Benchmark?

AFAIK, not really very frequent. I don't know what are exact current available scenarios in KC benchmark, so I cannot tell what would be the best. The point is, and would be nice-to-have, to run some benchmark with a ~real load and see how it improved the execution.

@franz1981
franz1981 commented Sep 25, 2025 •
Copy link
Copy Markdown

You can use jbang with wrk2 and have fixed rate with coordinated omission free measurements AND compare latencies.
You can pick the 80/90% of your peak throughput and you are ready to go.
See
https://github.com/Hyperfoil/jbang-catalog

The suggestion clearly is to make sure your application is the bottleneck which means running it with perf stat or pidstat and make sure is CPU (usr) intensive

@pruivo
pruivo commented Sep 25, 2025
Copy link
Copy Markdown
Member

@mabartos I run the benchmark locally, so take the results with a grain of salt as I was using the PC while benchmarking. I run twice, and your PR seems more stable. The committed memory at the end of the test is lower with your PR.

This PR

[benchamrk-run] ================================================================================
[benchamrk-run] ---- Global Information --------------------------------------------------------
[benchamrk-run] > request count                                     793000 (OK=793000 KO=0     )
[benchamrk-run] > min response time                                      2 (OK=2      KO=-     )
[benchamrk-run] > max response time                                    869 (OK=869    KO=-     )
[benchamrk-run] > mean response time                                    16 (OK=16     KO=-     )
[benchamrk-run] > std deviation                                         21 (OK=21     KO=-     )
[benchamrk-run] > response time 50th percentile                          9 (OK=9      KO=-     )
[benchamrk-run] > response time 75th percentile                         16 (OK=16     KO=-     )
[benchamrk-run] > response time 95th percentile                         62 (OK=62     KO=-     )
[benchamrk-run] > response time 99th percentile                        102 (OK=102    KO=-     )
[benchamrk-run] > mean requests/sec                                1099.86 (OK=1099.86 KO=-     )
[benchamrk-run] ---- Response Time Distribution ------------------------------------------------
[benchamrk-run] > t < 800 ms                                        792999 (   100%)
[benchamrk-run] > 800 ms <= t < 1200 ms                                  1 (     0%)
[benchamrk-run] > t >= 1200 ms                                           0 (     0%)
[benchamrk-run] > failed                                                 0 (     0%)
[benchamrk-run] ================================================================================

Main (commit 29bee21)

[benchamrk-run] ================================================================================
[benchamrk-run] ---- Global Information --------------------------------------------------------
[benchamrk-run] > request count                                     793000 (OK=793000 KO=0     )
[benchamrk-run] > min response time                                      2 (OK=2      KO=-     )
[benchamrk-run] > max response time                                   2823 (OK=2823   KO=-     )
[benchamrk-run] > mean response time                                    54 (OK=54     KO=-     )
[benchamrk-run] > std deviation                                        213 (OK=213    KO=-     )
[benchamrk-run] > response time 50th percentile                         10 (OK=10     KO=-     )
[benchamrk-run] > response time 75th percentile                         21 (OK=21     KO=-     )
[benchamrk-run] > response time 95th percentile                        153 (OK=153    KO=-     )
[benchamrk-run] > response time 99th percentile                       1246 (OK=1242   KO=-     )
[benchamrk-run] > mean requests/sec                                1099.86 (OK=1099.86 KO=-     )
[benchamrk-run] ---- Response Time Distribution ------------------------------------------------
[benchamrk-run] > t < 800 ms                                        779570 ( 98.31%)
[benchamrk-run] > 800 ms <= t < 1200 ms                               4380 (  0.55%)
[benchamrk-run] > t >= 1200 ms                                        9050 (  1.14%)
[benchamrk-run] > failed                                                 0 (     0%)
[benchamrk-run] ================================================================================

pruivo
pruivo previously approved these changes Sep 25, 2025
@mabartos
mabartos commented Sep 26, 2025 •
Copy link
Copy Markdown
Member Author

@pruivo Great! I think we have enough data to proceed with the approach. Thanks!

@keycloak/cloud-native Could anyone look at this? Thanks!

@brunobat
Copy link
Copy Markdown

Did you measured the startup time?

@ahus1
ahus1 commented Sep 26, 2025
Copy link
Copy Markdown
Member

I would be +1 to use it, at the same time Quarkus is stating this is a technology preview feature.

So I'd say we would need to wrap it as an opt-in preview feature in KC as we don't want to support it downstream, and some tests enabled and disabled. We might open a ticket with the Quarkus team make it non-preview eventually.

Reading the docs, the following part worries me a bit:

Developers can further customize JSON processing by implementing the ObjectMapperCustomizer interface. This interface allows fine-grained control over the ObjectMapper, enabling the registration of custom serializers and deserializers while ensuring compatibility with the reflection-free optimization. If additional configuration is needed, implement an ObjectMapperCustomizer bean and register any necessary modules or settings.

@ahus1
ahus1 commented Sep 26, 2025
Copy link
Copy Markdown
Member

@brunobat / @franz1981 - thank for joining this discussion. Do you have additional information for us on the maturity of the feature and any plans for graduation of this feature?

@brunobat
Copy link
Copy Markdown

I believe @franz1981 has details about why it's still in preview.

@mabartos
Copy link
Copy Markdown
Member Author

Did you measured the startup time?

@brunobat Nope. Should we expect some bigger differences?

I would be +1 to use it, at the same time Quarkus is stating this is a technology preview feature.

@ahus1 Oh, totally missed this. Thanks for pointing this out! Yes, unfortunately, we'd need to have it disabled by default :(

@mabartos
mabartos marked this pull request as draft September 26, 2025 08:36
@brunobat
brunobat commented Sep 26, 2025 •
Copy link
Copy Markdown

Did you measured the startup time?

@brunobat Nope. Should we expect some bigger differences?

The expectation is that it will start faster because more work was done at build, however, I think we should measure the real world behavior differences in the C2 compiler under load, during warmup.

@franz1981
Copy link
Copy Markdown

@brunobat IDK why it's in tech preview; I think we were waiting for user feedbacks
@gsmet @geoand ?

@geoand
geoand commented Sep 26, 2025
Copy link
Copy Markdown
Contributor

Correct

@gsmet
gsmet commented Sep 26, 2025
Copy link
Copy Markdown
Contributor

Yes, we were waiting for user feedback. We had some and we fixed some of the issues along the way.

I haven't seen a new issue for a while now (but then it's hard to know how widely this feature is used). But we had feedback so it has been tested by some users.

The issues were mostly about corner cases not handled properly in serialization/deserialization, not about structural unstability.
So if your payloads are serialized/deserialized correctly, I think it could make sense using it but I think you should provide a way to disable it if users can extend Keycloak in a way that their own code goes through REST JSON serialization/deserialization.

Maybe you could provide the option with a default value to false for a while and then switch it to true at some point (and keeping the ability to disable it?).

@gsmet
gsmet commented Sep 26, 2025 •
Copy link
Copy Markdown
Contributor

Maybe you could provide the option with a default value to false for a while and then switch it to true at some point (and keeping the ability to disable it?).

And if you do that, make sure you have a prominent note in the release notes to gather as much feedback as possible.

@mabartos
Copy link
Copy Markdown
Member Author

Maybe you could provide the option with a default value to false for a while and then switch it to true at some point (and keeping the ability to disable it?).

Yes, that's the plan.

Thank you all for your feedback!

@geoand
geoand commented Sep 26, 2025
Copy link
Copy Markdown
Contributor

🙏🏽

@mabartos
mabartos commented Oct 15, 2025 •
Copy link
Copy Markdown
Member Author

@ahus1 Thanks for the feedback. Should be done now.

image

Token exchange docs

Old

image

New

image

ahus1
ahus1 previously approved these changes Oct 15, 2025
@ahus1 ahus1 left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Thank you for the changes, I think it looks great. I am not merging it yet as there is another review from @vmuzikar pending.

@vmuzikar vmuzikar left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

@mabartos Thank you for the PR. Nice work with the analysis!

Added a couple of comments, but none of them are blocking.

Comment thread docs/guides/templates/features.adoc Resolved
mabartos and others added 5 commits October 17, 2025 13:33
Closes keycloak#42945

Signed-off-by: Martin Bartoš <mabartos@redhat.com>
Co-authored-by: Alexander Schwartz <alexander.schwartz@gmx.net>
Signed-off-by: Martin Bartoš <mabartos@redhat.com>
Signed-off-by: Martin Bartoš <mabartos@redhat.com>
Co-authored-by: Václav Muzikář <vaclav@muzikari.cz>
Signed-off-by: Martin Bartoš <mabartos@redhat.com>
Signed-off-by: Martin Bartoš <mabartos@redhat.com>
@mabartos
Copy link
Copy Markdown
Member Author

Ok, added some additional minor docs changes.

@ahus1 @vmuzikar Would be good if you could take another look. Thanks!

vmuzikar
vmuzikar previously approved these changes Oct 17, 2025
@vmuzikar vmuzikar left a comment
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

LGTM, thanks.

@vmuzikar
vmuzikar requested a review from ahus1 October 17, 2025 12:50
@vmuzikar
Copy link
Copy Markdown
Contributor

@mabartos Some tests failed but seems unrelated?

@mabartos
mabartos commented Oct 17, 2025 •
Copy link
Copy Markdown
Member Author

@vmuzikar Yes, those tests should be unrelated. Retried

Signed-off-by: Martin Bartoš <mabartos@redhat.com>
@mabartos
Copy link
Copy Markdown
Member Author

Resolved conflict

@ahus1 ahus1 left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@mabartos - I think I approved it before, please proceed. You only need to add me when there are significant new changes.

@vmuzikar
vmuzikar merged commit 37bea12 into keycloak:main Oct 17, 2025
135 of 137 checks passed
@mabartos
mabartos commented Nov 7, 2025 •
Copy link
Copy Markdown
Member Author

Progress: We run the whole old testsuite (thousands of tests) with this settings for ~3 weeks and AFAIK, we have not encounter any issues - I think it's a nice indicator it works properly.

cc: @brunobat @franz1981 @gsmet @geoand

@geoand
geoand commented Nov 7, 2025
Copy link
Copy Markdown
Contributor

That's great to hear!

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[PERF] Jackson reflection-free serialization/deserialization

9 participants

0