Skip to content

Conversation

@kamilkisiela
Copy link
Contributor

@kamilkisiela kamilkisiela commented Oct 30, 2025

Adds short-lived (5s) cache of decoded and verified JWT payloads.

With this optimization we go from 21.415 rps to 37.638 rps.

The 5s TTL is imo a good balance between performance and any cache invalidations that may occur.
Yeah I thought about doing it configurable, but does it really have to be? People may abuse it and set it to a high number that could interfere with expiration date of the token itself. We can always add it, when requested and needed.

  • Added JwtClaimsCache as a shared state.
  • Updated validate_request to use JWT caching.
  • Updated error types to be Clone to allow caching to work.
Before After
Screenshot 2025-10-30 at 12 45 40 Screenshot 2025-10-30 at 12 45 47

- Added `JwtClaimsCache` as a shared state. - Updated `validate_request`
to use JWT caching. - Updated error types to be `Clone` to allow caching
to work.
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @kamilkisiela, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request introduces a crucial performance optimization by implementing a caching mechanism for JSON Web Token (JWT) claims. By storing decoded and verified JWT payloads in a short-lived cache, the system avoids redundant processing for frequently accessed tokens, leading to a substantial increase in throughput. The changes involve integrating this cache into the shared application state, updating the JWT validation flow to leverage the cache, and making necessary adjustments to error types to support the new caching architecture.

Highlights

  • Performance Improvement: The implementation of JWT claims caching has significantly boosted performance, increasing requests per second from 21.415 rps to 37.638 rps.
  • JWT Claims Caching: A new short-lived (5-second) cache for decoded and verified JWT payloads has been introduced to prevent redundant parsing and verification on subsequent requests.
  • Shared State Integration: The JwtClaimsCache is now part of the RouterSharedState, making it a globally accessible resource for efficient JWT handling.
  • Validation Logic Update: The validate_request function in JwtAuthRuntime has been refactored to be asynchronous and now utilizes the new JWT caching mechanism.
  • Error Type Adaptations: JWT error types, specifically LookupError and JwtError, have been made Clone to facilitate their use within the caching logic.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a caching layer for JWT claims to improve performance, which is a great optimization. The changes involve adding a moka cache to the shared state and updating the JWT validation logic to use it.

My review has identified a critical issue in the implementation of validate_request that will prevent compilation and is also inefficient. I've also found a high-severity issue related to potential unbounded memory usage in the new cache, which violates the repository's style guide.

Please address these points to ensure the stability and performance of the router.

Comment on lines +286 to +290
.try_get_with(token.clone(), async {
let valid_jwks = self.jwks.all();
self.authenticate(&valid_jwks, request)
.map(|(payload, _, _)| Arc::new(payload))
})
Copy link
Contributor

Choose a reason for hiding this comment

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

critical

This implementation has a critical issue: it attempts to pass &mut HttpRequest into an async block. HttpRequest is not Send, so this will cause a compilation error because the request cannot be safely sent across threads.

Additionally, the current approach is inefficient. validate_request calls self.lookup(request) to get the token, and then the async block calls self.authenticate(...), which in turn calls self.lookup(request) again, performing the same work twice on a cache miss.

To fix this, the authentication logic should be performed directly on the token string that has already been extracted. This avoids the compilation error and the redundant work.

Suggested change
.try_get_with(token.clone(), async {
let valid_jwks = self.jwks.all();
self.authenticate(&valid_jwks, request)
.map(|(payload, _, _)| Arc::new(payload))
})
.try_get_with(token.clone(), async {
let valid_jwks = self.jwks.all();
let header = decode_header(&token).map_err(JwtError::InvalidJwtHeader)?;
let jwk = self.find_matching_jwks(&header, &valid_jwks)?;
let payload = self.decode_and_validate_token(&token, &jwk.keys)?;
Ok(Arc::new(payload))
})

Copy link
Contributor Author

Choose a reason for hiding this comment

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

It's fine

@github-actions
Copy link

github-actions bot commented Oct 30, 2025

k6-benchmark results

     ✓ response code was 200
     ✓ no graphql errors
     ✓ valid response structure

     █ setup

     checks.........................: 100.00% ✓ 231015      ✗ 0    
     data_received..................: 6.8 GB  225 MB/s
     data_sent......................: 90 MB   3.0 MB/s
     http_req_blocked...............: avg=2.89µs   min=676ns   med=1.68µs  max=4.25ms   p(90)=2.41µs  p(95)=2.78µs  
     http_req_connecting............: avg=352ns    min=0s      med=0s      max=1.38ms   p(90)=0s      p(95)=0s      
     http_req_duration..............: avg=19.04ms  min=2.18ms  med=18.2ms  max=111.06ms p(90)=25.89ms p(95)=28.85ms 
       { expected_response:true }...: avg=19.04ms  min=2.18ms  med=18.2ms  max=111.06ms p(90)=25.89ms p(95)=28.85ms 
     http_req_failed................: 0.00%   ✓ 0           ✗ 77025
     http_req_receiving.............: avg=128.27µs min=25.77µs med=43.5µs  max=71.98ms  p(90)=78.35µs p(95)=378.45µs
     http_req_sending...............: avg=21.75µs  min=4.46µs  med=10.08µs max=18.48ms  p(90)=14.83µs p(95)=22.7µs  
     http_req_tls_handshaking.......: avg=0s       min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s      
     http_req_waiting...............: avg=18.89ms  min=2.13ms  med=18.08ms max=108.78ms p(90)=25.64ms p(95)=28.62ms 
     http_reqs......................: 77025   2561.993091/s
     iteration_duration.............: avg=19.47ms  min=5.31ms  med=18.51ms max=242.02ms p(90)=26.3ms  p(95)=29.32ms 
     iterations.....................: 77005   2561.327854/s
     vus............................: 50      min=50        max=50 
     vus_max........................: 50      min=50        max=50 

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
@github-actions
Copy link

🐋 This PR was built and pushed to the following Docker images:

Image Names: ghcr.io/graphql-hive/router

Platforms: linux/amd64,linux/arm64

Image Tags: ghcr.io/graphql-hive/router:pr-537 ghcr.io/graphql-hive/router:sha-207f8c9

Docker metadata
{
"buildx.build.ref": "builder-c9ea2d1f-37ee-4515-823c-c05d64739df0/builder-c9ea2d1f-37ee-4515-823c-c05d64739df00/p9oxvxmr3yf5fnnktdyo2fvpl",
"containerimage.descriptor": {
  "mediaType": "application/vnd.oci.image.index.v1+json",
  "digest": "sha256:b4d544aba0ea0cbcf932a15345c4831c2689481b26b725f2e2dcc3a6cebb4923",
  "size": 1609
},
"containerimage.digest": "sha256:b4d544aba0ea0cbcf932a15345c4831c2689481b26b725f2e2dcc3a6cebb4923",
"image.name": "ghcr.io/graphql-hive/router:pr-537,ghcr.io/graphql-hive/router:sha-207f8c9"
}

@dotansimha dotansimha force-pushed the main branch 4 times, most recently from 4cffb36 to c709bec Compare November 4, 2025 11:27
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