-
-
Notifications
You must be signed in to change notification settings - Fork 610
Description
Summary
We attempted to move from a classic multi-tenant (organization_id + RLS) model to a “one schema per organization” model in order to reduce materialized view refresh times. After restructuring, we discovered that PostGraphile’s auto‑generated CRUD resolvers always target the statically introspected “model schema” instead of resolving tables/functions dynamically via search_path. This removes most of the benefit of using PostGraphile unless we re‑implement all mutations manually with dynamic SQL.
Our questions:
- Is there a supported pattern for per-request schema routing (dynamic tenant schema) while keeping auto‑generated CRUD?
- If not, is this something that could be (or is planned to be) supported?
- Should we instead abandon per‑tenant physical schemas and solve the original performance problem differently?
Original Motivation
We have large materialized views (MVs) whose full REFRESH MATERIALIZED VIEW time grows rapidly (both data volume × refresh frequency).
Objective:
Isolate per organization to refresh smaller MV segments independently and avoid long refresh window.
Chosen approach
Split main application schema → N org schemas.
common: helpers, shared metadataorg_model: canonical schema introspected by PostGraphile- One schema per org:
org_foo,org_bar, ...
=> Use search_path with org_model, common at initialization (postgraphile)
=> Dynamically set search_path to org_X, common for each request (in pgSettings)
Issue
Postgraphile qualifies functions at initialization based on model schema. It seems to make auto-generated functions useless as they all point to model schema instead following search_path.
=> Is their a way to keep auto-generated functions and point to schema dynamically at runtime ?
Details
Function qualification problem
The issue with intended approach is that functions are qualified at initialization. Take this function:
create function common.my_function()
returns ... as $
begin
select * from my_table;
end;Even though we are calling this function with search_path set to org_X,common, it will select in org_model.my_table instead of org_X.my_table.
This problem can be solved with more code by setting schema dynamically
create function common.my_function()
returns ... as $
declare
v_org_schema text;
begin
v_org_schema := current_setting('app.org_schema', true);
EXECUTE format('select * from %I.my_table', v_org_schema);
end;=> The real problem lies with auto-generated CRUD functions: they are all qualified on org_model. Our only option seems to be to omit every auto-generated functions and add them "by hand" using dynamic schema injection, which removes 90% of postgraphile power.
Metadata
Metadata
Assignees
Type
Projects
Status