diff --git a/doc/rbac-performance-analysis.md b/doc/rbac-performance-analysis.md index dcc96621..fcd9781a 100644 --- a/doc/rbac-performance-analysis.md +++ b/doc/rbac-performance-analysis.md @@ -380,13 +380,21 @@ We found some solution approaches: 1. Getting rid of the `rbacrole` and `rbacpermission` table and only having implicit roles with implicit grants (OWNER->ADMIN->AGENT->TENENT->REFERRER) by comparison of ordered enum values and fixed permission assignments (e.g. OWENER->DELETE, ADMIN->UPDATE etc.). We could also get rid of the table `rbacreferece` if we enter users as business objects. - This should reduce + This should dramatically reduce the size of the table `rbackgrant` as well as the recusion levels. + + But since we only apply this query once for each business query, that would only improve performance once we have way more objects in our system, but does not help our current problem. + + It's quite some effort to implement even just a prototype, so we did not further explore this idea. + +2. Adding the object type to the table `rbacObject` to reduce the size of the result of the recursive CTE query. + + See chapter below. ### Adding The Object Type To The Table `rbacObject` -This optimization idea came from Michael Hierweck. -Its idea is to reduce the size of the result of the recursive CTE query and maybe even speed up that query itself. +This optimization idea came from Michael Hierweck and was promising. +The idea is to reduce the size of the result of the recursive CTE query and maybe even speed up that query itself. To evaluate this, I added a type column to the `rbacObject` table, initially as an enum hsHostingAssetType. Then I entered the type there for all rows from hs_hosting_asset. This means that 83,886 of 92,545 rows in `rbacobject` have a type set, leaving 8,659 without. @@ -403,7 +411,11 @@ joined with business query for all `'EMAIL_ADDRESSES'`: As you can see, the query is no problem at all for normal customers (in the example, yours truly). With Hostsharing (D-1000000-hsh) it is quite slow. -My optimization, which is not easy to try out, would only improve the part of the recursive CTE query. This is not necessary at the moment, but perhaps if we grow significantly. +Luckily this experiment also shows that it's not a big problem, having all hosting assets in the same database table. + +Implementing this approach would be a bit difficult anyway, because we would need to transfer the type query parameter into the definition of the restricted view. We have not even the slightest idea how this could be done. + +See the related queries in [recursive-cte-experiments-for-accessible-uuids.sql](../sql/recursive-cte-experiments-for-accessible-uuids.sql). They might have changed independently since this document was written, but you can still check out the old version from git. ## Summary diff --git a/sql/recursive-cte-experiments-for-accessible-uuids.sql b/sql/recursive-cte-experiments-for-accessible-uuids.sql index 296fa71a..7fa5ab32 100644 --- a/sql/recursive-cte-experiments-for-accessible-uuids.sql +++ b/sql/recursive-cte-experiments-for-accessible-uuids.sql @@ -2,19 +2,55 @@ select * from hs_statistics_view; -rollback transaction; -begin transaction; -SET TRANSACTION READ ONLY; -call defineContext('performance testing', null, 'superuser-alex@hostsharing.net', - 'hs_booking_project#D-1000000-hshdefaultproject:ADMIN'); --- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN'); -select count(type) as counter, type from hs_hosting_asset_rv - group by type - order by counter desc; -commit transaction; - -- ======================================================== +-- An example for a restricted view (_rv) as generated by our RBAC system: +drop view if exists hs_hosting_asset_example_rv; +create view hs_hosting_asset_example_rv as + with accessible_hs_hosting_asset_uuids as ( + + with recursive + recursive_grants as ( + select distinct rbacgrants.descendantuuid, + rbacgrants.ascendantuuid, + 1 as level, + true + from rbacgrants + where (rbacgrants.ascendantuuid = any (currentsubjectsuuids())) + --and rbacgrants.assumed + union all + select distinct g.descendantuuid, + g.ascendantuuid, + grants.level + 1 as level, + assertTrue(grants.level < 22, 'too many grant-levels: ' || grants.level) + from rbacgrants g + join recursive_grants grants on grants.descendantuuid = g.ascendantuuid + where g.assumed + ), + grant_count as ( + select count(*) as grant_count from recursive_grants + ), + count_check as ( + select assertTrue((select grant_count from grant_count) < 600000, + 'too many grants for current subjects: ' || (select grant_count from grant_count)) as valid + ) + select distinct perm.objectuuid + from recursive_grants + join rbacpermission perm on recursive_grants.descendantuuid = perm.uuid + join rbacobject obj on obj.uuid = perm.objectuuid + join count_check cc on cc.valid + where obj.objecttable::text = 'hs_hosting_asset'::text + and obj.type = 'EMAIL_ADDRESS'::hshostingassettype -- with/without this type condition + ) + select target.* + from hs_hosting_asset target + where (target.uuid in (select accessible_hs_hosting_asset_uuids.objectuuid + from accessible_hs_hosting_asset_uuids)); +-- end of the example view. + +-- ------------------------------------------------------------------------------- + +rollback transaction; DO language plpgsql $$ DECLARE start_time timestamp; @@ -24,73 +60,30 @@ DECLARE BEGIN start_time := clock_timestamp(); - call defineContext('performance testing', null, 'superuser-alex@hostsharing.net', + CALL defineContext('performance testing', null, 'superuser-alex@hostsharing.net', 'hs_booking_project#D-1000000-hshdefaultproject:ADMIN'); -- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN'); SET TRANSACTION READ ONLY; - FOR i IN 0..0 LOOP + FOR i IN 0..25 LOOP letter := chr(i+ascii('a')); + PERFORM count(*) from ( - perform count(*) from ( + -- An example for a business query based on the view: + select type, uuid, identifier, caption + from hs_hosting_asset_example_rv + where type = 'EMAIL_ADDRESS' + and identifier like letter || '%' + -- end of the business query example. - -- start of VIEW hs_hosting_asset_rv: - with accessible_hs_hosting_asset_uuids as ( + ) AS timed; - with recursive - recursive_grants as ( - select distinct rbacgrants.descendantuuid, - rbacgrants.ascendantuuid, - 1 as level, - true - from rbacgrants - where (rbacgrants.ascendantuuid = any (currentsubjectsuuids())) - --and rbacgrants.assumed - union all - select distinct g.descendantuuid, - g.ascendantuuid, - grants.level + 1 as level, - assertTrue(grants.level < 22, 'too many grant-levels: ' || grants.level) - from rbacgrants g - join recursive_grants grants on grants.descendantuuid = g.ascendantuuid - where g.assumed - ), - grant_count as ( - select count(*) as grant_count from recursive_grants - ), - count_check as ( - select assertTrue((select grant_count from grant_count) < 600000, - 'too many grants for current subjects: ' || (select grant_count from grant_count)) as valid - ) - select distinct perm.objectuuid - from recursive_grants - join rbacpermission perm on recursive_grants.descendantuuid = perm.uuid - join rbacobject obj on obj.uuid = perm.objectuuid - join count_check cc on cc.valid - where obj.objecttable::text = 'hs_hosting_asset'::text - and obj.type = 'EMAIL_ADDRESS'::hshostingassettype -- with/without this type condition - ) - -- end of VIEW hs_hosting_asset_rv. - - -- start of business query, usually based on a view according to the above CTE query: - select type, - target.uuid, - target.identifier, - target.caption - from hs_hosting_asset target - where (target.uuid in (select accessible_hs_hosting_asset_uuids.objectuuid - from accessible_hs_hosting_asset_uuids)) - and target.type = 'EMAIL_ADDRESS' - and identifier like letter || '%' - -- end of business query. - ) timed; END LOOP; -end_time := clock_timestamp(); -total_time := end_time - start_time; - -RAISE NOTICE 'average execution time: %', total_time/26; + end_time := clock_timestamp(); + total_time := end_time - start_time; + RAISE NOTICE 'average execution time: %', total_time/26; END; $$; @@ -103,75 +96,14 @@ $$; -- ============================================================================= -rollback transaction; -begin transaction; -SET TRANSACTION READ ONLY; -call defineContext('performance testing', null, 'superuser-alex@hostsharing.net', - 'hs_booking_project#D-1000000-hshdefaultproject:ADMIN'); --- 'hs_booking_project#D-1000300-mihdefaultproject:ADMIN'); - -with one_path as (with recursive path as ( - -- Base case: Start with the row where ascending equals the starting UUID - select ascendantuuid, - descendantuuid, - array [ascendantuuid] as path_so_far - from rbacgrants - where ascendantuuid = any (currentsubjectsuuids()) - - union all - - -- Recursive case: Find the next step in the path - select c.ascendantuuid, - c.descendantuuid, - p.path_so_far || c.ascendantuuid - from rbacgrants c - inner join - path p on c.ascendantuuid = p.descendantuuid - where c.ascendantuuid != all (p.path_so_far) -- Prevent cycles - ) - -- Final selection: Output all paths that reach the target UUID - select distinct array_length(path_so_far, 1), - path_so_far || descendantuuid as full_path - from path - join rbacpermission perm on perm.uuid = path.descendantuuid - join hs_hosting_asset ha on ha.uuid = perm.objectuuid - -- JOIN rbacrole_ev re on re.uuid = any(path_so_far) - where ha.identifier = 'vm1068' - order by array_length(path_so_far, 1) - limit 1 - ) -select - ( - SELECT ARRAY_AGG(re.roleidname ORDER BY ord.idx) - FROM UNNEST(one_path.full_path) WITH ORDINALITY AS ord(uuid, idx) - JOIN rbacrole_ev re ON ord.uuid = re.uuid - ) AS name_array - from one_path; -commit transaction; - -with grants as ( - select uuid - from rbacgrants - where descendantuuid in ( - select uuid - from rbacrole - where objectuuid in ( - select uuid - from hs_hosting_asset - -- where type = 'DOMAIN_MBOX_SETUP' - -- and identifier = 'example.org|MBOX' - where type = 'EMAIL_ADDRESS' - and identifier='test@example.org' - )) -) -select * from rbacgrants_ev gev where exists ( select uuid from grants where gev.uuid = grants.uuid ); - - +-- extending the rbacobject table: alter table rbacobject -- just for performance testing, we would need a joined enum or a varchar(16) which would make it slow add column type hshostingassettype; +-- and fill the type column with hs_hosting_asset types: + rollback transaction; begin transaction; call defineContext('setting rbacobject.type from hs_hosting_asset.type', null, 'superuser-alex@hostsharing.net'); @@ -183,9 +115,7 @@ call defineContext('setting rbacobject.type from hs_hosting_asset.type', null, ' end transaction; -select - (select count(*) from hs_office_relation) as "relation", - (select count(*) from hs_booking_item) as "booking item"; +-- check the result: select (select count(*) as "total" from rbacobject),