Conversation
…from auth load User#admin? was called once per serialized user via serialize_filter (added during the agroportal sync for lastLoginAt visibility), re-loading the requesting user's role on every call. With ~21k users and security enabled, GET /users?include=all took ~15 minutes; with security disabled (where &.admin? short-circuits on nil), the same call returned in ~10s. - Make admin? idempotent: bring(role: [:role]) only when role isn't loaded at the User level OR the Role objects haven't had their nested :role attribute loaded. The two-level guard handles the auth-middleware load shape, which loads :role shallowly without the nested embed_values pattern. - Auth middleware: exclude inverse attributes from the User load. attributes(:all) included :provisionalClasses, an inverse attribute that fans out across the full ProvisionalClass graph on every authenticated request. - Add test_admin_with_shallow_role_load covering the auth-middleware load shape (admin and non-admin) and the idempotency contract. Refs #285
| # middleware and access-control checks actually read on every request); | ||
| # leaving forward scalars in for now to avoid surprising any callers | ||
| # that quietly depend on profile fields being preloaded. | ||
| attrs_to_load = LinkedData::Models::User.attributes(:all) - LinkedData::Models::User.attributes(:inverse) |
There was a problem hiding this comment.
I'm not sure if the existing API already includes createdOntologies as part of this call:
LinkedData::Models::User.attributes(:inverse)
There was a problem hiding this comment.
Good catch — checked.
User.attributes(:inverse) returns just [:provisionalClasses]. :createdOntologies is declared with handler:, not inverse:, so it isn't in that list.
However, the auth load doesn't end up fetching :createdOntologies either, because Goo's where#include silently drops handler-backed attrs at the include stage (goo/lib/goo/base/where.rb:262-264):
if @klass.handler?(opt)
next
endSo even though attrs_to_load still contains :createdOntologies after the subtraction, the symbol never makes it into the SPARQL pattern and load_created_ontologies is never invoked on the auth path.
|
Following up on #286 (comment) — the inline thread answers why Should The same shape — "ontologies where this user is in attribute :createdOntologies, inverse: { on: :ontology, attribute: :administeredBy }That's how
The handler does do two things an
If those two properties were a real design requirement, the handler stays. If they were convenience (or just inherited from upstream agroportal code without a specific reason), the inverse form is simpler and avoids the issues above. Does anyone know the original reasoning? If not, I can file a separate issue to track switching to |
mdorf
left a comment
There was a problem hiding this comment.
Looks good aside from the comment about createdOntologies. Ready to merge.
Summary
User#admin?idempotent so it doesn't re-load:roleper call.serialize_filter(added during the agroportal sync forlastLoginAtvisibility) invokes
Thread.current[:remote_user]&.admin?once perserialized user. With security enabled, this turned every authenticated
/users?include=allinto an N+1 association reload over the requestinguser's role.
attributes(:all)was including:provisionalClasses, which fans outacross the entire ProvisionalClass graph on every authenticated request.
test_admin_with_shallow_role_loadcovering the auth-middlewareload shape (admin and non-admin) and the idempotency contract.
Partially addresses #285. This PR fixes the
performance issue itself (the per-instance
admin?fanout and the inverse-attr fanoutin the auth middleware).