baseline document potential rbac optimizations
This commit is contained in:
parent
a1163bfc8d
commit
aede5bc8d6
@ -15,7 +15,7 @@ We could not find a pattern, why that was the case. The impression that it had t
|
|||||||
|
|
||||||
## Preparation
|
## Preparation
|
||||||
|
|
||||||
### Configuring PostgreSQL
|
### Configuring PostgreSQL
|
||||||
|
|
||||||
The pg_stat_statements PostgreSQL-Extension can be used to measure how long queries take and how often they are called.
|
The pg_stat_statements PostgreSQL-Extension can be used to measure how long queries take and how often they are called.
|
||||||
|
|
||||||
@ -355,6 +355,56 @@ In production, the `SELECT ... FROM hs_office_relation_rv` (No. 2) with about 0.
|
|||||||
3. For the production code, we could use raw-entities for referenced entities, here usually RBAC SELECT permission is given anyway.
|
3. For the production code, we could use raw-entities for referenced entities, here usually RBAC SELECT permission is given anyway.
|
||||||
|
|
||||||
|
|
||||||
|
## The Problematically Huge Join
|
||||||
|
|
||||||
|
The origin problem was the expensive RBAC check for many SELECT queries.
|
||||||
|
This consists of two parts:
|
||||||
|
|
||||||
|
1. The recursive CTE query to determine which object's UUIDs are visible for the current subject.
|
||||||
|
This query itself takes currently about 250ms thus is no problem by itself as long as we only need it once per request.
|
||||||
|
2. Joining the result from 1. with the result if a business query.
|
||||||
|
The performance of the business query itself is no problem, for the join see the following explanations.
|
||||||
|
|
||||||
|
Superusers can see all objects (currently already over 90.000)
|
||||||
|
and even high level roles of customers with many hosting assets can see several thousand objects.
|
||||||
|
This is the one side of that problematic join.
|
||||||
|
|
||||||
|
The other side of that problematic is the result of the business query.
|
||||||
|
For example if a user wants to select all of their e-mail-addresses, that might easily half of the visible objects.
|
||||||
|
|
||||||
|
Thus, we would have a join of for example 5.000 x 2.500 rows, which is going to be slow.
|
||||||
|
As there are currently about 84.000 objects are hosting assets and 33.000 e-mail-addresses in our system,
|
||||||
|
for a superuser we would even run into an 84.0000 x 33.0000 join.
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
|
||||||
|
### 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.
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
If we do this for other types (we currently have 1,271 relations and 927 booking items), it gets more complicated because they are different enum types. As varchar(16), we could lose performance again due to the higher storage space requirements.
|
||||||
|
|
||||||
|
But the performance gained is not particularly high anyway.
|
||||||
|
See the average seconds per recursive CTE select as role 'hs_hosting_asset:<DEBITOR>defaultproject:ADMIN',
|
||||||
|
joined with business query for all `'EMAIL_ADDRESSES'`:
|
||||||
|
|
||||||
|
| | D-1000000-hsh | D-1000300-mih |
|
||||||
|
|-----------------------------------------------------|------------------|---------------|
|
||||||
|
| currently (without type comparision in rbacobject): | ~3.30 - ~3.49 | ~0.23 |
|
||||||
|
| optimized (with type comparision in rbacobject): | ~2.99 - ~3.08 | ~0.21 |
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
## Summary
|
## Summary
|
||||||
|
|
||||||
### What we did Achieve?
|
### What we did Achieve?
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
-- just a permanent playground to explore optimization of the central recursive CTE query for RBAC
|
-- just a permanent playground to explore optimization of the central recursive CTE query for RBAC
|
||||||
|
|
||||||
|
select * from hs_statistics_view;
|
||||||
|
|
||||||
rollback transaction;
|
rollback transaction;
|
||||||
begin transaction;
|
begin transaction;
|
||||||
SET TRANSACTION READ ONLY;
|
SET TRANSACTION READ ONLY;
|
||||||
@ -11,71 +13,99 @@ select count(type) as counter, type from hs_hosting_asset_rv
|
|||||||
order by counter desc;
|
order by counter desc;
|
||||||
commit transaction;
|
commit transaction;
|
||||||
|
|
||||||
|
-- ========================================================
|
||||||
|
|
||||||
|
DO language plpgsql $$
|
||||||
|
DECLARE
|
||||||
|
start_time timestamp;
|
||||||
|
end_time timestamp;
|
||||||
|
total_time interval;
|
||||||
|
letter char(1);
|
||||||
|
BEGIN
|
||||||
|
start_time := clock_timestamp();
|
||||||
|
|
||||||
|
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..25 LOOP
|
||||||
|
letter := chr(i+ascii('a'));
|
||||||
|
|
||||||
|
perform count(*) from (
|
||||||
|
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.assumed
|
||||||
|
and (rbacgrants.ascendantuuid = any (currentsubjectsuuids()))
|
||||||
|
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
|
||||||
|
)
|
||||||
|
select type,
|
||||||
|
-- count(*) as counter
|
||||||
|
target.uuid,
|
||||||
|
-- target.version,
|
||||||
|
-- target.bookingitemuuid,
|
||||||
|
-- target.type,
|
||||||
|
-- target.parentassetuuid,
|
||||||
|
-- target.assignedtoassetuuid,
|
||||||
|
target.identifier,
|
||||||
|
target.caption
|
||||||
|
-- target.config,
|
||||||
|
-- target.alarmcontactuuid
|
||||||
|
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 || '%'
|
||||||
|
-- and target.type in ('EMAIL_ADDRESS', 'CLOUD_SERVER', 'MANAGED_SERVER', 'MANAGED_WEBSPACE')
|
||||||
|
-- order by target.identifier;
|
||||||
|
-- group by type
|
||||||
|
-- order by counter desc
|
||||||
|
) timed;
|
||||||
|
END LOOP;
|
||||||
|
|
||||||
|
end_time := clock_timestamp();
|
||||||
|
total_time := end_time - start_time;
|
||||||
|
|
||||||
|
RAISE NOTICE 'average execution time: %', total_time/26;
|
||||||
|
|
||||||
|
END;
|
||||||
|
$$;
|
||||||
|
|
||||||
|
-- average seconds per recursive CTE select as role 'hs_hosting_asset:<DEBITOR>defaultproject:ADMIN'
|
||||||
|
-- joined with business query for all 'EMAIL_ADDRESSES':
|
||||||
|
-- D-1000000-hsh D-1000300-mih
|
||||||
|
-- - without type comparision in rbacobject: ~3.30 - ~3.49 ~0.23
|
||||||
|
-- - with type comparision in rbacobject: ~2.99 - ~3.08 ~0.21
|
||||||
|
|
||||||
|
|
||||||
|
-- =============================================================================
|
||||||
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 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.assumed
|
|
||||||
and (rbacgrants.ascendantuuid = any (currentsubjectsuuids()))
|
|
||||||
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 count(*) as grant_count from recursive_grants) < 300000,
|
|
||||||
'too many grants for current subjects: ' || (select count(*) as grant_count from recursive_grants))
|
|
||||||
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)
|
|
||||||
select type,
|
|
||||||
-- count(*) as counter
|
|
||||||
target.uuid,
|
|
||||||
-- target.version,
|
|
||||||
-- target.bookingitemuuid,
|
|
||||||
-- target.type,
|
|
||||||
-- target.parentassetuuid,
|
|
||||||
-- target.assignedtoassetuuid,
|
|
||||||
target.identifier,
|
|
||||||
target.caption
|
|
||||||
-- target.config,
|
|
||||||
-- target.alarmcontactuuid
|
|
||||||
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 in ('EMAIL_ADDRESS', 'CLOUD_SERVER', 'MANAGED_SERVER', 'MANAGED_WEBSPACE')
|
|
||||||
-- and target.type = 'EMAIL_ADDRESS'
|
|
||||||
-- order by target.identifier;
|
|
||||||
-- group by type
|
|
||||||
-- order by counter desc
|
|
||||||
;
|
|
||||||
commit transaction;
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
rollback transaction;
|
rollback transaction;
|
||||||
begin transaction;
|
begin transaction;
|
||||||
@ -140,3 +170,29 @@ with grants as (
|
|||||||
)
|
)
|
||||||
select * from rbacgrants_ev gev where exists ( select uuid from grants where gev.uuid = grants.uuid );
|
select * from rbacgrants_ev gev where exists ( select uuid from grants where gev.uuid = grants.uuid );
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
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;
|
||||||
|
|
||||||
|
rollback transaction;
|
||||||
|
begin transaction;
|
||||||
|
call defineContext('setting rbacobject.type from hs_hosting_asset.type', null, 'superuser-alex@hostsharing.net');
|
||||||
|
|
||||||
|
UPDATE rbacobject
|
||||||
|
SET type = hs.type
|
||||||
|
FROM hs_hosting_asset hs
|
||||||
|
WHERE rbacobject.uuid = hs.uuid;
|
||||||
|
|
||||||
|
end transaction;
|
||||||
|
|
||||||
|
select
|
||||||
|
(select count(*) from hs_office_relation) as "relation",
|
||||||
|
(select count(*) from hs_booking_item) as "booking item";
|
||||||
|
|
||||||
|
select
|
||||||
|
(select count(*) as "total" from rbacobject),
|
||||||
|
(select count(*) as "not null" from rbacobject where type is not null),
|
||||||
|
(select count(*) as "null" from rbacobject where type is null);
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user