Skip to content
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

Eval routes and options given to context at initialization time #1394

Merged
merged 32 commits into from
Dec 12, 2023

Conversation

frenchy64
Copy link
Contributor

@frenchy64 frenchy64 commented Dec 8, 2023

Close https://github.com/advthreat/iroh/issues/6619

Compojure{-api}'s context wraps its body in a function. This causes anything inside the route to be evaluated on every call.

We have a very expensive call in the POST /bundle/import route that recreates the entire Bundle schema on every request. @devn found a request where this took 10% of the request time.

Additionally, since everything under "/ctia" is under a context, I believe many of the routes themselves are being recreated on every request.

To comprehensively prevent this issue, I've restricted our context macro to only allow options that don't bind variables. This way, we can evaluate the routes and the options separately and then close the context over the value of routes. This seems to substantially decrease the number of values reinitialized during a request.

CI seems three times faster after the change. Most of the speedup is from tests for individual entities, where typically each namespaces takes 2-3 minutes, and now takes 30 seconds. Compare these metrics, where slowest tests are first:

Before (master): 1 hour 28 minutes
After: 40 minutes

I have not tested individual route performance.

§ QA

No QA is needed.

§ Release Notes

intern: Stage initialization code under context

§ Squashed Commits

@frenchy64 frenchy64 changed the title stage bundle schema WIP: stage initialization code under context Dec 8, 2023
@frenchy64 frenchy64 changed the title WIP: stage initialization code under context Stage initialization code under context Dec 8, 2023
@frenchy64 frenchy64 marked this pull request as ready for review December 8, 2023 02:59
@frenchy64 frenchy64 self-assigned this Dec 8, 2023
@frenchy64 frenchy64 force-pushed the bundle-routes-stage branch from 734580f to b703165 Compare December 8, 2023 06:37
@frenchy64 frenchy64 added wip and removed review labels Dec 8, 2023
@frenchy64 frenchy64 marked this pull request as draft December 8, 2023 06:45
@frenchy64 frenchy64 changed the title Stage initialization code under context Eval routes and options given to context at initialization time Dec 8, 2023
@frenchy64 frenchy64 marked this pull request as ready for review December 11, 2023 21:02
@frenchy64 frenchy64 added review and removed wip labels Dec 11, 2023
Copy link
Contributor

@brookeswanson brookeswanson left a comment

Choose a reason for hiding this comment

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

I'd warn UI folks before merging this, but this is a good find. Thank you @devn and @frenchy64

Copy link
Contributor

@yogsototh yogsototh left a comment

Choose a reason for hiding this comment

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

I do not see anything wrong, and I might not be familiar enough with CTIA.

That being said, from the problem description it looks like maybe the issue might be in ctia.bundle.routes/bundle-routes (here https://github.com/threatgrid/ctia/blob/master/src/ctia/bundle/routes.clj#L99):

The (let [bundle-schema (prep-bundle-schema services) ,,, is inside the form, I think this let should be put on top of route like:

(let [bundle-schema (prep-bundle-schema services)]
  (routes 
    (context ,,,,

And I think this might generate the schema not for every request but only once during the route init.

@@ -48,7 +50,36 @@
[middleware & body]
`(core/middleware ~middleware ~@body))

(defmacro context {:style/indent 2} [& args] `(core/context ~@args))
(def ^:private allowed-context-options #{:tags :capabilities :description :return :summary})
Copy link
Contributor

Choose a reason for hiding this comment

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

This is very difficult to really understand precisely what is going on. But I think the main problem comes from the auth middleware that set a :lets. By doing so, the context macro detects we have a lets and consider the context to be treated as dynamic (https://github.com/metosin/compojure-api/blob/master/src/compojure/api/meta.clj#L674) and this is AFAIU where the schema is potentially rebuilt.

Thus this put the let form just on top of the body and run for every request and not just at route init time.

So I am not sure this macro will be enough if you still allow the :capabilities param declaration at the context level.

Copy link
Contributor Author

@frenchy64 frenchy64 Dec 12, 2023

Choose a reason for hiding this comment

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

But I think the main problem comes from the auth middleware that set a :lets.

Yes, this approach is incompatible with any middleware that sets :lets (other than _). It throws a compile-time error. That's why things like :auth-identity and :identity-map are pushed into the HTTP verbs.

By doing so, the context macro detects we have a lets and consider the context to be treated as dynamic (https://github.com/metosin/compojure-api/blob/master/src/compojure/api/meta.clj#L674) and this is AFAIU where the schema is potentially rebuilt.

Ah I see. We use compojure-api 1.1.13 which doesn't have this optimization. The difference between my macro and compojure-api master is that mine throws a compile-time error on a dynamic route.

So I am not sure this macro will be enough if you still allow the :capabilities param declaration at the context level.

FWIW here's the expansion:

(macroexpand
  `(context "" []
     :capabilities #{:foo :bar}
     (GET "" req# {:status 200})))
(let* [routes__207013__auto__ (compojure.api.core/routes
                                (ctia.lib.compojure.api.core/GET
                                  "" req__207089__auto__
                                  {:status 200}))
       capabilities207092 #{:bar :foo}]
  (compojure.api.core/context
    "" []
    :capabilities capabilities207092
    routes__207013__auto__))

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Perhaps even with the optimizations from compojure-api master, it would ok? This macro takes advantage of the fact that most arguments to context are expressions (as opposed to values like :tags and :scopes).

@frenchy64
Copy link
Contributor Author

That being said, from the problem description it looks like maybe the issue might be in ctia.bundle.routes/bundle-routes (here https://github.com/threatgrid/ctia/blob/master/src/ctia/bundle/routes.clj#L99):

@yogsototh I initially did that, then I realized that everything is under a context. The bundle routes are also under /ctia. The current compojure-api we use doesn't have the optimization you pointed out: https://github.com/metosin/compojure-api/blob/1.1.13/src/compojure/api/meta.clj#L310

Copy link
Contributor

@yogsototh yogsototh left a comment

Choose a reason for hiding this comment

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

LGTM!

Thanks for the tests they are beautiful.

@frenchy64 frenchy64 merged commit d5c2419 into master Dec 12, 2023
14 checks passed
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.

4 participants