Skip to content

perf: Purge JWT cache asynchronously in a separate thread #3889

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 17 commits into from
Apr 18, 2025

Conversation

mkleczek
Copy link
Contributor

A follow-up to the JWT cache purging fix - looks like we still left expensive operation on a hot path affecting response times for request triggering cache miss.

A simple fix is to purge in a separate thread started upon a cache miss.

@steve-chavez
Copy link
Member

It does look like throughput increases from the "load test summary" on https://github.com/PostgREST/postgrest/actions/runs/13062249392?pr=3889.

But doing forkIO on every request makes me uncomfortable, could this lead to more CPU consumption?

Ideally we would have a background thread that does this periodically and is outside of the hot path.

@mkleczek
Copy link
Contributor Author

It does look like throughput increases from the "load test summary" on https://github.com/PostgREST/postgrest/actions/runs/13062249392?pr=3889.

But doing forkIO on every request makes me uncomfortable, could this lead to more CPU consumption?

It is no worse than synchronous purge but has better latency.

Ideally we would have a background thread that does this periodically and is outside of the hot path.

Periodical purge requires tuning (ie. adding a configuration parameter) to make sure cleanup happens often enough but not too often. Triggering purging upon cache miss solves this issue.
Current implementation is based on assumption that cache miss is rare (otherwise it is better to turn off JWT caching altogether) and does not cause too much stress.

@taimoorzaeem
Copy link
Collaborator

@mkleczek Could you perhaps wait for the next major if we switch to a different cache implementation, discussed in 3802#issuecomment?

@mkleczek
Copy link
Contributor Author

mkleczek commented Jan 31, 2025

It does look like throughput increases from the "load test summary" on https://github.com/PostgREST/postgrest/actions/runs/13062249392?pr=3889.

But doing forkIO on every request makes me uncomfortable, could this lead to more CPU consumption?

Ideally we would have a background thread that does this periodically and is outside of the hot path.

@steve-chavez @taimoorzaeem
How about we make sure there is at most one purging thread running concurrently?

It required some more changes and the patch is no longer one-liner but should address your concerns?

@taimoorzaeem
Copy link
Collaborator

@mkleczek Great attempt for this change. It might take us some time to decide where to go with this. Meanwhile, if you'd like this change urgently, just to save you some time, you can see how to build PostgREST from source here. That's the beauty of open source 🎉.

@steve-chavez
Copy link
Member

@steve-chavez @taimoorzaeem
How about we make sure there is at most one purging thread running concurrently?

That sounds better, yes. If you want to do it, please go ahead. Although note that as @taimoorzaeem mentioned, we're going to switch to an LRU cache soon, if there's considerable complexitiy/effort in adding the purging thread, maybe it would be better to wait for v13.

@mkleczek
Copy link
Contributor Author

mkleczek commented Feb 1, 2025

@steve-chavez @taimoorzaeem
How about we make sure there is at most one purging thread running concurrently?

That sounds better, yes. If you want to do it, please go ahead. Although note that as @taimoorzaeem mentioned, we're going to switch to an LRU cache soon, if there's considerable complexitiy/effort in adding the purging thread, maybe it would be better to wait for v13.

It's already done - could you review the updated PR?

Still some linting issues reported - I could use some advice on how to find the reports or run it locally.

Not sure about the coverage of the patch - there is an uncovered path of skipping starting a new thread when one is running - not sure how to test it and if it is worth doing.

I would be really grateful if it could be merged as building patched version from source would be too much hassle for my organisation...

@steve-chavez
Copy link
Member

Still some linting issues reported - I could use some advice on how to find the reports or run it locally.

You can run it locally with:

$ nix-shell
$ postgrest-lint && postgrest-style

@mkleczek
Copy link
Contributor Author

mkleczek commented Feb 5, 2025

Still some linting issues reported - I could use some advice on how to find the reports or run it locally.

You can run it locally with:

$ nix-shell
$ postgrest-lint && postgrest-style

Thanks @steve-chavez. Done.

I've also moved JwtCacheState to Auth/Types.hs introduced by @taimoorzaeem in the meantime.

Do you think this is mergeable now?

@steve-chavez
Copy link
Member

steve-chavez commented Mar 26, 2025

Sorry for the late reply here.

Using postgrest-benchmark, I've benched different versions performance on lots of requests with new JWTs.

# $ export PGRSTBENCH_EC2_CLIENT_INSTANCE_TYPE="m5a.4xlarge"
# $ postgrest-bench-deploy

On v12.2.5

This version didn't have the purge fix.

$ postgrest-bench-k6 10 k6/GETSingleDifferentJWT.js 

Running k6 with 10 vus
Pseudo-terminal will not be allocated because stdin is not a terminal.
Pseudo-terminal will not be allocated because stdin is not a terminal.

     data_received..................: 13 MB 254 kB/s
     data_sent......................: 20 MB 404 kB/s
     http_req_blocked...............: avg=3.24µs  min=1.12µs  med=2.38µs  max=1.63ms   p(90)=2.96µs  p(95)=3.29µs 
     http_req_connecting............: avg=462ns   min=0s      med=0s      max=867.36µs p(90)=0s      p(95)=0s     
   ✓ http_req_duration..............: avg=12.21ms min=1.86ms  med=5.54ms  max=25.54s   p(90)=8.15ms  p(95)=9.11ms 
       { expected_response:true }...: avg=5.88ms  min=1.86ms  med=5.54ms  max=128.66ms p(90)=8.15ms  p(95)=9.1ms  
   ✓ http_req_failed................: 0.02% ✓ 10         ✗ 40355
     http_req_receiving.............: avg=43.61µs min=18.71µs med=42.75µs max=683.5µs  p(90)=52.76µs p(95)=65.16µs
     http_req_sending...............: avg=17.66µs min=7.05µs  med=12.36µs max=843.8µs  p(90)=28.86µs p(95)=30.29µs
     http_req_tls_handshaking.......: avg=0s      min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=12.15ms min=1.8ms   med=5.48ms  max=25.54s   p(90)=8.09ms  p(95)=9.05ms 
     http_reqs......................: 40365 808.510748/s
     iteration_duration.............: avg=12.35ms min=1.99ms  med=5.68ms  max=25.54s   p(90)=8.3ms   p(95)=9.25ms 
     iterations.....................: 40365 808.510748/s
     vus............................: 10    min=10       max=10 
     vus_max........................: 10    m9

As expected some requests fail at the end because the OOM killer is triggered and postgREST restarts.

Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Starting PostgREST 12.2.5...
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Admin server listening on port 3001
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Listening on unix socket "/tmp/pgrst.sock"
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Listening for notifications on the "pgrst" channel
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Successfully connected to PostgreSQL 12.19 on x86_64-pc-linux-gnu, compiled by gcc (GCC) 13.2.0, 64-bit
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Config reloaded
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Schema cache queried in 5.3 milliseconds
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Schema cache loaded 16 Relations, 10 Relationships, 3 Functions, 0 Domain Representations, 4 Media Type Handlers, 1194 T>
Mar 26 17:51:42 pgrst postgrest[3489]: 26/Mar/2025:17:51:42 +0000: Schema cache loaded in 1.9 milliseconds
Mar 26 17:53:47 pgrst systemd[1]: postgrest.service: A process of this unit has been killed by the OOM killer.
Mar 26 17:53:47 pgrst systemd[1]: postgrest.service: Main process exited, code=killed, status=9/KILL
Mar 26 17:53:47 pgrst systemd[1]: postgrest.service: Failed with result 'oom-kill'.
Mar 26 17:53:47 pgrst systemd[1]: postgrest.service: Consumed 34.626s CPU time, received 61.8M IP traffic, sent 43.4M IP traffic.
Mar 26 17:53:47 pgrst systemd[1]: postgrest.service: Scheduled restart job, restart counter is at 1.
Mar 26 17:53:47 pgrst systemd[1]: Started postgrest daemon.
Mar 26 17:53:47 pgrst postgrest[4048]: 26/Mar/2025:17:53:47 +0000: Starting PostgREST 12.2.5...

On v12.2.8

Which already contains the purge fix:

$ postgrest-bench-k6 10 k6/GETSingleDifferentJWT.js 

Running k6 with 10 vus
Pseudo-terminal will not be allocated because stdin is not a terminal.
Pseudo-terminal will not be allocated because stdin is not a terminal.

     data_received..................: 2.2 MB 73 kB/s
     data_sent......................: 3.5 MB 116 kB/s
     http_req_blocked...............: avg=3.98µs  min=1.14µs  med=2.4µs   max=1.34ms   p(90)=2.95µs  p(95)=3.25µs  
     http_req_connecting............: avg=802ns   min=0s      med=0s      max=908.62µs p(90)=0s      p(95)=0s      
   ✓ http_req_duration..............: avg=43.05ms min=2.94ms  med=32.66ms max=379.5ms  p(90)=84.76ms p(95)=115.8ms 
       { expected_response:true }...: avg=43.05ms min=2.94ms  med=32.66ms max=379.5ms  p(90)=84.76ms p(95)=115.8ms 
   ✓ http_req_failed................: 0.00%  ✓ 0          ✗ 6950
     http_req_receiving.............: avg=45.35µs min=22.56µs med=44.76µs max=532.59µs p(90)=52.06µs p(95)=63.82µs 
     http_req_sending...............: avg=19.47µs min=7.81µs  med=13.14µs max=757.21µs p(90)=28.87µs p(95)=30.25µs 
     http_req_tls_handshaking.......: avg=0s      min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s      
     http_req_waiting...............: avg=42.99ms min=2.83ms  med=32.6ms  max=379.42ms p(90)=84.71ms p(95)=115.71ms
     http_reqs......................: 6950   231.201961/s
     iteration_duration.............: avg=43.2ms  min=3.13ms  med=32.82ms max=379.62ms p(90)=84.89ms p(95)=115.93ms
     iterations.....................: 6950   231.201961/s
     vus............................: 10     min=10       max=10
     vus_max........................: 10     min=10       max=10

On this PR

$ postgrest-bench-k6 10 k6/GETSingleDifferentJWT.js 

Running k6 with 10 vus
Pseudo-terminal will not be allocated because stdin is not a terminal.
Pseudo-terminal will not be allocated because stdin is not a terminal.

     data_received..................: 13 MB 212 kB/s
     data_sent......................: 20 MB 337 kB/s
     http_req_blocked...............: avg=3.21µs  min=1.05µs  med=2.37µs  max=1.13ms   p(90)=2.94µs  p(95)=3.31µs 
     http_req_connecting............: avg=457ns   min=0s      med=0s      max=638.35µs p(90)=0s      p(95)=0s     
   ✓ http_req_duration..............: avg=5.59ms  min=1.81ms  med=5.32ms  max=122.38ms p(90)=7.66ms  p(95)=8.5ms  
       { expected_response:true }...: avg=5.59ms  min=1.81ms  med=5.32ms  max=122.38ms p(90)=7.66ms  p(95)=8.5ms  
   ✓ http_req_failed................: 0.00% ✓ 0          ✗ 40472
     http_req_receiving.............: avg=44.04µs min=17.69µs med=42.71µs max=1.41ms   p(90)=53.34µs p(95)=65.72µs
     http_req_sending...............: avg=17.6µs  min=6.94µs  med=12.3µs  max=688.87µs p(90)=28.75µs p(95)=30.16µs
     http_req_tls_handshaking.......: avg=0s      min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=5.53ms  min=1.76ms  med=5.26ms  max=122.32ms p(90)=7.6ms   p(95)=8.44ms 
     http_reqs......................: 40472 674.526298/s
     iteration_duration.............: avg=5.74ms  min=1.96ms  med=5.47ms  max=122.5ms  p(90)=7.81ms  p(95)=8.65ms 
     iterations.....................: 40472 674.526298/s
     vus............................: 10    min=10       max=10 
     vus_max........................: 10    min=10       max=10 

Conclusion

So there is an improvement, from 231 req/s to 674 req/s.


Note that there could potentially be even more loss on #3802 (comment).

@taimoorzaeem
Copy link
Collaborator

@mkleczek If you could rebase this, I could then suggest code changes (to make more maintainable to us) and then this can be merged.

@mkleczek
Copy link
Contributor Author

@mkleczek If you could rebase this, I could then suggest code changes (to make more maintainable to us) and then this can be merged.

Done

@taimoorzaeem
Copy link
Collaborator

taimoorzaeem commented Mar 27, 2025

@mkleczek Let's do it cleanly with debounce. It automatically runs the action in a new thread. If a new action is to happen, it automatically cancels the current action and runs the new action.

First add the debounceTimeout lock to JwtCacheState which holds the current IO action:

data JwtCacheState = JwtCacheState
  -- | Jwt Cache
  { jwtCache                 :: C.Cache ByteString AuthResult
  -- | purge entries with debounce
  , purgeWithDebounceTimeout :: MVar (IO ())
  }

Ideally we would need a function like:

purgeWithDebounce :: JwtCacheState -> IO ()
purgeWithDebounce jwtCacheState = do
  purgeDebouncer <- tryReadMVar $ purgeWithDebounceTimeout jwtCacheState
  case purgeDebouncer of
    Just d -> d
    Nothing -> do
      newDebouncer <-
        mkDebounce defaultDebounceSettings
          -- debounceFreq is set to default 1 second
          { debounceAction = C.purgeExpired (jwtCache jwtCacheState)
          , debounceEdge = leadingEdge -- logs at the start and the end
          }
    putMVar (purgeWithDebounceTimeout jwtCacheState) newDebouncer
    newDebouncer

Comment on lines 62 to 68
-- trigger asynchronous purging of expired cache entries
-- but only if not already running anotherpurging
tryPutMVar purgeLock () >>= \case
-- start purge make sure lock is released
True -> void $ forkFinally (C.purgeExpired jwtCache) (const $ takeMVar purgeLock)
-- purge already running - do nothing
_ -> pure ()
Copy link
Collaborator

@taimoorzaeem taimoorzaeem Mar 27, 2025

Choose a reason for hiding this comment

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

Finally, call the funtion here:

Suggested change
-- trigger asynchronous purging of expired cache entries
-- but only if not already running anotherpurging
tryPutMVar purgeLock () >>= \case
-- start purge make sure lock is released
True -> void $ forkFinally (C.purgeExpired jwtCache) (const $ takeMVar purgeLock)
-- purge already running - do nothing
_ -> pure ()
purgeWithDebounce jwtCacheState

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Are you sure it makes sense to create debouncer lazily? What do we gain? It would be much simpler to create it in init.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Sorry, I don't understand why you think we are "creating" the debouncer lazily? IO actions are evaluated strictly. Also, we are initializing the it with newEmptyMVar in init.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

@taimoorzaeem see updated code - purgeCache IO action is created in init

@mkleczek
Copy link
Contributor Author

mkleczek commented Apr 8, 2025

@mkleczek

If a new action is to happen, it automatically cancels the current action and runs the new action.

Sorry, when I wrote the above, this isn't true for our debounce library. It actually starts a new purge action concurrently on a new thread.
This would create many threads concurrently running.

No, it won't. See https://hackage.haskell.org/package/auto-update-0.2.6/docs/Control-Debounce.html:
Debounce an action, ensuring it doesn't occur more than once for a given period of time.

Also - see source code of mkDebounce: https://hackage.haskell.org/package/auto-update-0.2.6/docs/src/Control.Debounce.Internal.html#mkDebounceInternal

Debounce uses MVar internally to ensure only one action instance is running at any one time (encapsulating logic we want to implement).

The MVar approach that I mentioned in #3889 (comment) is there to protect running multiple concurrent threads.

The approach you proposed has race conditions and is not only unnecessary but also wrong.

Could you please change the code according to the mentioned comment or if you don't mind, can I submit this patch for better maintainability for us maintainers (I'll add you as co-author obviously)?

IMO current implementation is optimal (baring changes in variable naming). If you think it should be implemented differently - please provide a different PR

@steve-chavez I think you need to take a look at this by yourself as there seems to be some confusion on how debouncer works.

@taimoorzaeem
Copy link
Collaborator

taimoorzaeem commented Apr 8, 2025

Debounce an action, ensuring it doesn't occur more than once for a given period of time.

@mkleczek Yes, the given period here is 1 second by default (which is the debounce frequency). For large in-memory caches, once the 1 second period is over, the purge will take several seconds (even minutes for very large caches, it runs in O(n^2), we have no upper bound 😢). Once the purging starts after the debounce timeout, it would run till end. So, once the purging starts on a new thread, another request after 1 second will trigger another purge action on a new thread.

@mkleczek
Copy link
Contributor Author

mkleczek commented Apr 8, 2025

Debounce an action, ensuring it doesn't occur more than once for a given period of time.

@mkleczek Yes, the given period here is 1 second by default (which is the debounce frequency). For large in-memory caches, once the 1 second period is over, the purge will take several seconds (even minutes for very large caches, it runs in O(n^2), we have no upper bound 😢). Once the purging starts after the debounce timeout, it would run till end. So, once the purging starts on a new thread, another request after 1 second will trigger another purge action on a new thread.

No, there is only going to be one thread running at a time - see implementation: https://hackage.haskell.org/package/auto-update-0.2.6/docs/src/Control.Debounce.Internal.html#local-6989586621679032459

IO action is going to be run in a loop by a single thread as long as there was a trigger in the meantime. Single thread is guarded by so called "baton" (ie. exactly the same mechanism that I've implemented in a previous version of the PR).

@wolfgangwalther
Copy link
Member

Since the behavior and implementation details of debounce etc. are obviously not immediately clear (and I must admit I can't follow what you guys are discussing anyway...), I think a good summary of what has been discussed here should end up as code comments, explaining this.

Otherwise this knowledge is lost in the PR, hard to retrieve and likely to break in the future.

@mkleczek
Copy link
Contributor Author

mkleczek commented Apr 8, 2025

Since the behavior and implementation details of debounce etc. are obviously not immediately clear (and I must admit I can't follow what you guys are discussing anyway...), I think a good summary of what has been discussed here should end up as code comments, explaining this.

Otherwise this knowledge is lost in the PR, hard to retrieve and likely to break in the future.

@wolfgangwalther done - see 46532a9

@taimoorzaeem
Copy link
Collaborator

taimoorzaeem commented Apr 8, 2025

@mkleczek After testing the auto-update library locally (I wrote a small haskell program to confirm, I can share on demand), yes you are right 🎉. Sorry for misleading. This code is good and should work correctly.

This is almost good to merge. Just needs some minor changes.

Copy link
Collaborator

@taimoorzaeem taimoorzaeem left a comment

Choose a reason for hiding this comment

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

@steve-chavez Looks good to merge. Please bench this to check performance.

@steve-chavez
Copy link
Member

steve-chavez commented Apr 8, 2025

The perf gains are noticeable but memory usage raises much faster somehow:

v12.2.8

[nix-shell:~/Projects/postgrest-benchmark]$ postgrest-bench-k6 10 k6/GETSingleDifferentJWT.js

     data_received..................: 2.7 MB 85 kB/s
     data_sent......................: 4.2 MB 136 kB/s
     http_req_blocked...............: avg=3.21µs  min=1.18µs  med=2.12µs  max=1.11ms   p(90)=2.59µs  p(95)=3.07µs 
     http_req_connecting............: avg=462ns   min=0s      med=0s      max=873.24µs p(90)=0s      p(95)=0s     
   ✓ http_req_duration..............: avg=35.21ms min=1.75ms  med=25.34ms max=595.03ms p(90)=67.6ms  p(95)=97.2ms 
       { expected_response:true }...: avg=35.21ms min=1.75ms  med=25.34ms max=595.03ms p(90)=67.6ms  p(95)=97.2ms 
   ✓ http_req_failed................: 0.00%  ✓ 0          ✗ 8477
     http_req_receiving.............: avg=44.67µs min=20.52µs med=43.37µs max=332.48µs p(90)=55.47µs p(95)=67.59µs
     http_req_sending...............: avg=12.93µs min=7.33µs  med=10.8µs  max=234.24µs p(90)=20.57µs p(95)=26.3µs 
     http_req_tls_handshaking.......: avg=0s      min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=35.16ms min=1.69ms  med=25.29ms max=594.96ms p(90)=67.55ms p(95)=97.15ms
     http_reqs......................: 8477   272.075813/s
     iteration_duration.............: avg=35.42ms min=1.82ms  med=25.43ms max=1.02s    p(90)=67.76ms p(95)=97.31ms
     iterations.....................: 8477   272.075813/s
     vus............................: 10     min=0        max=10
     vus_max........................: 10     min=10       max=10

top output on each run:

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                             
   4594 root      20   0 1024.0g 109404  11136 S 0.0  24.0   1:38.94 postgrest                                                                                                           

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                             
   4594 root      20   0 1024.0g 110172  11136 S   0.0  24.2   1:46.80 postgrest                                                                                                           

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                             
   4594 root      20   0 1024.0g 110520  11136 S   0.0  24.2   2:41.24 postgrest                                                                                                           

On this PR

[nix-shell:~/Projects/postgrest-benchmark]$ postgrest-bench-k6 10 k6/GETSingleDifferentJWT.js

Running k6 with 10 vus
Pseudo-terminal will not be allocated because stdin is not a terminal.
Pseudo-terminal will not be allocated because stdin is not a terminal.

     █ setup

     data_received..................: 15 MB 476 kB/s
     data_sent......................: 24 MB 757 kB/s
     http_req_blocked...............: avg=2.71µs min=1.03µs  med=2.1µs   max=1.11ms   p(90)=2.52µs  p(95)=2.84µs 
     http_req_connecting............: avg=273ns  min=0s      med=0s      max=415.55µs p(90)=0s      p(95)=0s     
   ✓ http_req_duration..............: avg=6.25ms min=1.59ms  med=5.04ms  max=115.34ms p(90)=8.46ms  p(95)=9.93ms 
       { expected_response:true }...: avg=6.25ms min=1.59ms  med=5.04ms  max=115.34ms p(90)=8.46ms  p(95)=9.93ms 
   ✓ http_req_failed................: 0.00% ✓ 0           ✗ 47197
     http_req_receiving.............: avg=42µs   min=16.85µs med=41.17µs max=2.64ms   p(90)=49.54µs p(95)=59.83µs
     http_req_sending...............: avg=12.5µs min=6.61µs  med=10.56µs max=159.8µs  p(90)=21.79µs p(95)=26.32µs
     http_req_tls_handshaking.......: avg=0s     min=0s      med=0s      max=0s       p(90)=0s      p(95)=0s     
     http_req_waiting...............: avg=6.19ms min=1.56ms  med=4.99ms  max=115.27ms p(90)=8.4ms   p(95)=9.88ms 
     http_reqs......................: 47197 1514.824116/s
     iteration_duration.............: avg=6.35ms min=1.65ms  med=5.12ms  max=1.03s    p(90)=8.54ms  p(95)=10.01ms
     iterations.....................: 47197 1514.824116/s
     vus............................: 10    min=0         max=10 
     vus_max........................: 10    min=10        max=10 



top output on each run:

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                             
   1050 root      20   0 1024.1g 198956  11264 S   0.0  43.6   0:40.57 postgrest                                                                                                           

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                             
   1050 root      20   0 1024.1g 275016   9344 S   0.3  60.3   1:24.69 postgrest                                                                                                           

    PID USER      PR  NI    VIRT    RES    SHR S  %CPU  %MEM     TIME+ COMMAND                                                                                                             
   1050 root      20   0 1024.1g 273264   9344 S   0.0  59.9   2:00.26 postgrest                                                                                                           

And the memory doesn't seem to go down when I do new requests and the exp time of the JWTs has passed.

@steve-chavez
Copy link
Member

I think we need to do #4001, otherwise all JWT cache related PRs have to wait on my manual testing.

Additionally, I'm finding hard to test the correct behavior of purge. Maybe we could have an endpoint on the admin server that allows to purge manually.

@taimoorzaeem
Copy link
Collaborator

taimoorzaeem commented Apr 9, 2025

I think we need to do #4001, otherwise all JWT cache related PRs have to wait on my manual testing.

Agreed. 👍

Additionally, I'm finding hard to test the correct behavior of purge. Maybe we could have an endpoint on the admin server that allows to purge manually.

If we are to work on #3802 (comment) then maybe we should stop working on this too? But yes, I think it would be nice for v12 users, so we should provide a patch.

@steve-chavez
Copy link
Member

Additionally, I'm finding hard to test the correct behavior of purge. Maybe we could have an endpoint on the admin server that allows to purge manually.
If we are to work on #3802 (comment) then maybe we should stop working on this too?

Right, not necessary to work on the above.

But yes, I think it would be nice for v12 users, so we should provide a patch.

Agree.

@steve-chavez
Copy link
Member

@mkleczek Could you rebase against main? #4001 was just solved and it will automatically test this PR.

@steve-chavez
Copy link
Member

@mkleczek Could you rebase against main? #4001 was just solved and it will automatically test this PR.

Helped with this and made #4024

@steve-chavez
Copy link
Member

Closed #4024 since it proved the new CI automated testing was ineffective.

I'll just merge here since the manual tests proved that this improved perf. Also I'll consider this a "fix" since perf was unnecessarily impacted.

@steve-chavez steve-chavez merged commit 4d85023 into PostgREST:main Apr 18, 2025
2 checks passed
@wolfgangwalther
Copy link
Member

The perf gains are noticeable but memory usage raises much faster somehow:

Has the memory usage concern been addressed?

@steve-chavez
Copy link
Member

steve-chavez commented Apr 20, 2025

Has the memory usage concern been addressed?

I wasn't able to repro that on non-t3a.nano AWS EC2 instances at the time I did the test, so I wasn't sure if it was a major concern. Additionally, I'm no longer able to run the manual tests :/ PostgREST/postgrest-benchmark#5

Also, maybe it doesn't matter much that now we're going for #4008

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

Successfully merging this pull request may close these issues.

4 participants