Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit d365ae7

Browse files
Optimize roles_is_member_of() with a Bloom filter.
When the list of roles gathered by roles_is_member_of() grows very large, a Bloom filter is created to help avoid some linear searches through the list. The threshold for creating the Bloom filter is set arbitrarily high and may require future adjustment. Suggested-by: Tom Lane Reviewed-by: Tom Lane Discussion: https://postgr.es/m/CAGvXd3OSMbJQwOSc-Tq-Ro1CAz%3DvggErdSG7pv2s6vmmTOLJSg%40mail.gmail.com
1 parent fad3b5b commit d365ae7

File tree

1 file changed

+65
-3
lines changed
  • src/backend/utils/adt

1 file changed

+65
-3
lines changed

src/backend/utils/adt/acl.c

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,7 @@
3636
#include "common/hashfn.h"
3737
#include "foreign/foreign.h"
3838
#include "funcapi.h"
39+
#include "lib/bloomfilter.h"
3940
#include "lib/qunique.h"
4041
#include "miscadmin.h"
4142
#include "utils/acl.h"
@@ -78,6 +79,13 @@ static Oid cached_role[] = {InvalidOid, InvalidOid, InvalidOid};
7879
static List *cached_roles[] = {NIL, NIL, NIL};
7980
static uint32 cached_db_hash;
8081

82+
/*
83+
* If the list of roles gathered by roles_is_member_of() grows larger than the
84+
* below threshold, a Bloom filter is created to speed up list membership
85+
* checks. This threshold is set arbitrarily high to avoid the overhead of
86+
* creating the Bloom filter until it seems likely to provide a net benefit.
87+
*/
88+
#define ROLES_LIST_BLOOM_THRESHOLD 1024
8189

8290
static const char *getid(const char *s, char *n, Node *escontext);
8391
static void putid(char *p, const char *s);
@@ -4918,6 +4926,53 @@ RoleMembershipCacheCallback(Datum arg, int cacheid, uint32 hashvalue)
49184926
cached_role[ROLERECURSE_SETROLE] = InvalidOid;
49194927
}
49204928

4929+
/*
4930+
* A helper function for roles_is_member_of() that provides an optimized
4931+
* implementation of list_append_unique_oid() via a Bloom filter. The caller
4932+
* (i.e., roles_is_member_of()) is responsible for freeing bf once it is done
4933+
* using this function.
4934+
*/
4935+
static inline List *
4936+
roles_list_append(List *roles_list, bloom_filter **bf, Oid role)
4937+
{
4938+
unsigned char *roleptr = (unsigned char *) &role;
4939+
4940+
/*
4941+
* If there is a previously-created Bloom filter, use it to try to
4942+
* determine whether the role is missing from the list. If it says yes,
4943+
* that's a hard fact and we can go ahead and add the role. If it says
4944+
* no, that's only probabilistic and we'd better search the list. Without
4945+
* a filter, we must always do an ordinary linear search through the
4946+
* existing list.
4947+
*/
4948+
if ((*bf && bloom_lacks_element(*bf, roleptr, sizeof(Oid))) ||
4949+
!list_member_oid(roles_list, role))
4950+
{
4951+
/*
4952+
* If the list is large, we take on the overhead of creating and
4953+
* populating a Bloom filter to speed up future calls to this
4954+
* function.
4955+
*/
4956+
if (*bf == NULL &&
4957+
list_length(roles_list) > ROLES_LIST_BLOOM_THRESHOLD)
4958+
{
4959+
*bf = bloom_create(ROLES_LIST_BLOOM_THRESHOLD * 10, work_mem, 0);
4960+
foreach_oid(roleid, roles_list)
4961+
bloom_add_element(*bf, (unsigned char *) &roleid, sizeof(Oid));
4962+
}
4963+
4964+
/*
4965+
* Finally, add the role to the list and the Bloom filter, if it
4966+
* exists.
4967+
*/
4968+
roles_list = lappend_oid(roles_list, role);
4969+
if (*bf)
4970+
bloom_add_element(*bf, roleptr, sizeof(Oid));
4971+
}
4972+
4973+
return roles_list;
4974+
}
4975+
49214976
/*
49224977
* Get a list of roles that the specified roleid is a member of
49234978
*
@@ -4946,6 +5001,7 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
49465001
ListCell *l;
49475002
List *new_cached_roles;
49485003
MemoryContext oldctx;
5004+
bloom_filter *bf = NULL;
49495005

49505006
Assert(OidIsValid(admin_of) == PointerIsValid(admin_role));
49515007
if (admin_role != NULL)
@@ -5023,16 +5079,22 @@ roles_is_member_of(Oid roleid, enum RoleRecurseType type,
50235079
* graph, we must test for having already seen this role. It is
50245080
* legal for instance to have both A->B and A->C->B.
50255081
*/
5026-
roles_list = list_append_unique_oid(roles_list, otherid);
5082+
roles_list = roles_list_append(roles_list, &bf, otherid);
50275083
}
50285084
ReleaseSysCacheList(memlist);
50295085

50305086
/* implement pg_database_owner implicit membership */
50315087
if (memberid == dba && OidIsValid(dba))
5032-
roles_list = list_append_unique_oid(roles_list,
5033-
ROLE_PG_DATABASE_OWNER);
5088+
roles_list = roles_list_append(roles_list, &bf,
5089+
ROLE_PG_DATABASE_OWNER);
50345090
}
50355091

5092+
/*
5093+
* Free the Bloom filter created by roles_list_append(), if there is one.
5094+
*/
5095+
if (bf)
5096+
bloom_free(bf);
5097+
50365098
/*
50375099
* Copy the completed list into TopMemoryContext so it will persist.
50385100
*/

0 commit comments

Comments
 (0)