pg_graphql: Timeout on schema inspection, received: {"message":"The upstream server is timing out"}
Describe the bug Getting error 504 when trying to introspect the schema at my https://XXXX.supabase.co/graphql/v1
Versions:
- Running in Lastest Supabase Cloud
- PostgreSQL: PostgreSQL 14.1 on aarch64-unknown-linux-gnu, compiled by gcc (Ubuntu 10.3.0-1ubuntu1~20.04) 10.3.0, 64-bit
- pg_graphql: 0.3.1
Pg Log:
{
"file": "/var/log/postgresql/postgresql.csv",
"host": "db-bzupkzejwgejjkpyuqig",
"parsed": [
{
"application_name": "postgrest",
"backend_type": "client backend",
"command_tag": "SELECT",
"connection_from": "10.110.0.137:45127",
"context": "SQL expression \"coalesce(\n jsonb_object_agg(\n fa.field_alias,\n case\n when selection_name = 'name' and not has_modifiers then to_jsonb(gt.name::text)\n when selection_name = 'description' and not has_modifiers then to_jsonb(gt.description::text)\n when selection_name = 'specifiedByURL' and not has_modifiers then to_jsonb(null::text)\n when selection_name = 'kind' then (\n case\n when is_array_not_null then to_jsonb('NON_NULL'::text)\n when is_array then to_jsonb('LIST'::text)\n when is_not_null then to_jsonb('NON_NULL'::text)\n else to_jsonb(gt.type_kind::text)\n end\n )\n when selection_name = 'fields' and not has_modifiers then (\n select\n jsonb_agg(\n graphql.resolve_field(\n f.name,\n f.parent_type,\n null,\n x.sel\n )\n order by\n f.column_attribute_num,\n f.name\n )\n from\n graphql.field f\n where\n f.parent_type = gt.name\n and not f.is_hidden_from_schema\n and gt.type_kind = 'OBJECT'\n and not f.is_arg\n and (\n -- heartbeat is not visible unless the query type is empty\n gt.meta_kind <> 'Query'\n or f.meta_kind <> 'Query.heartbeat'\n or not exists(\n select 1\n from graphql.field fin\n where\n fin.parent_type = gt.name -- 'Query'\n and not fin.is_hidden_from_schema\n and fin.meta_kind <> 'Query.heartbeat'\n limit 1\n )\n )\n )\n when selection_name = 'interfaces' and not has_modifiers then (\n case\n -- Scalars get null, objects get an empty list. This is a poor implementation\n -- when gt.meta_kind not in ('Interface', 'BUILTIN', 'CURSOR') then '[]'::jsonb\n when gt.type_kind = 'SCALAR' then to_jsonb(null::text)\n when gt.type_kind = 'INTERFACE' then to_jsonb(null::text)\n when gt.meta_kind = 'Cursor' then to_jsonb(null::text)\n else '[]'::jsonb\n end\n )\n when selection_name = 'possibleTypes' and not has_modifiers then to_jsonb(null::text)\n when selection_name = 'enumValues' then graphql.\"resolve_enumValues\"(gt.name, x.sel)\n when selection_name = 'inputFields' and not has_modifiers then (\n select\n jsonb_agg(\n graphql.resolve_field(\n f.name,\n f.parent_type,\n f.parent_arg_field_id,\n x.sel\n )\n order by\n f.column_attribute_num,\n f.name\n )\n from\n graphql.field f\n where\n f.parent_type = gt.name\n and not f.is_hidden_from_schema\n and gt.type_kind = 'INPUT_OBJECT'\n )\n when selection_name = 'ofType' then (\n case\n -- NON_NULL(LIST(...))\n when is_array_not_null is true then graphql.\"resolve___Type\"(type_, x.sel, is_array_not_null := false, is_array := is_array, is_not_null := is_not_null)\n -- LIST(...)\n when is_array then graphql.\"resolve___Type\"(type_, x.sel, is_array_not_null := false, is_array := false, is_not_null := is_not_null)\n -- NON_NULL(...)\n when is_not_null then graphql.\"resolve___Type\"(type_, x.sel, is_array_not_null := false, is_array := false, is_not_null := false)\n -- TYPE\n else null\n end\n )\n else null\n end\n ),\n 'null'::jsonb\n )\n from\n graphql.type gt\n join jsonb_array_elements(ast -> 'selectionSet' -> 'selections') x(sel)\n on true,\n lateral (\n select\n graphql.alias_or_name_literal(x.sel) field_alias,\n graphql.name_literal(x.sel) as selection_name\n ) fa,\n lateral (\n select (coalesce(is_array_not_null, false) or is_array or is_not_null) as has_modifiers\n ) hm\n where\n gt.name = type_\"\nPL/pgSQL function graphql.\"resolve___Type\"(text,jsonb,boolean,boolean,boolean) line 4 at RETURN\nSQL expression \"coalesce(\n jsonb_object_agg(\n fa.field_alias,\n case\n when selection_name = 'name' then to_jsonb(field_rec.name)\n when selection_name = 'description' then to_jsonb(field_rec.description)\n when selection_name = 'isDeprecated' then to_jsonb(false) -- todo\n when selection_name = 'deprecationReason' then to_jsonb(null::text) -- todo\n when selection_name = 'type' then graphql.\"resolve___Type\"(\n field_rec.type_,\n x.sel,\n field_rec.is_array_not_null,\n field_rec.is_array,\n field_rec.is_not_null\n )\n when selection_name = 'args' then (\n select\n coalesce(\n jsonb_agg(\n graphql.resolve_field(\n ga.name,\n field_rec.type_,\n field_rec.id,\n x.sel\n )\n order by\n ga.column_attribute_num,\n case ga.name\n when 'first' then 80\n when 'last' then 81\n when 'before' then 82\n when 'after' then 83\n when 'after' then 83\n when 'filter' then 95\n when 'orderBy' then 96\n when 'atMost' then 97\n else 0\n end,\n ga.name\n ),\n '[]'\n )\n from\n graphql.field ga\n where\n ga.parent_arg_field_id = field_rec.id\n and not ga.is_hidden_from_schema\n and ga.is_arg\n and ga.parent_type = field_rec.type_\n )\n -- INPUT_OBJECT types only\n when selection_name = 'defaultValue' then to_jsonb(field_rec.default_value)\n else graphql.exception_unknown_field(selection_name, field_rec.type_)::jsonb\n end\n ),\n 'null'::jsonb\n )\n from\n jsonb_array_elements(ast -> 'selectionSet' -> 'selections') x(sel),\n lateral (\n select\n graphql.alias_or_name_literal(x.sel) field_alias,\n graphql.name_literal(x.sel) as selection_name\n ) fa\"\nPL/pgSQL function graphql.resolve_field(text,text,integer,jsonb) line 20 at RETURN\nSQL expression \"coalesce(\n jsonb_object_agg(\n fa.field_alias,\n case\n when selection_name = 'name' then to_jsonb(field_rec.name)\n when selection_name = 'description' then to_jsonb(field_rec.description)\n when selection_name = 'isDeprecated' then to_jsonb(false) -- todo\n when selection_name = 'deprecationReason' then to_jsonb(null::text) -- todo\n when selection_name = 'type' then graphql.\"resolve___Type\"(\n field_rec.type_,\n x.sel,\n field_rec.is_array_not_null,\n field_rec.is_array,\n field_rec.is_not_null\n )\n when selection_name = 'args' then (\n select\n coalesce(\n jsonb_agg(\n graphql.resolve_field(\n ga.name,\n field_rec.type_,\n field_rec.id,\n x.sel\n )\n order by\n ga.column_attribute_num,\n case ga.name\n when 'first' then 80\n when 'last' then 81\n when 'before' then 82\n when 'after' then 83\n when 'after' then 83\n when 'filter' then 95\n when 'orderBy' then 96\n when 'atMost' then 97\n else 0\n end,\n ga.name\n ),\n '[]'\n )\n from\n graphql.field ga\n where\n ga.parent_arg_field_id = field_rec.id\n and not ga.is_hidden_from_schema\n and ga.is_arg\n and ga.parent_type = field_rec.type_\n )\n -- INPUT_OBJECT types only\n when selection_name = 'defaultValue' then to_jsonb(field_rec.default_value)\n else graphql.exception_unknown_field(selection_name, field_rec.type_)::jsonb\n end\n ),\n 'null'::jsonb\n )\n from\n jsonb_array_elements(ast -> 'selectionSet' -> 'selections') x(sel),\n lateral (\n select\n graphql.alias_or_name_literal(x.sel) field_alias,\n graphql.name_literal(x.sel) as selection_name\n ) fa\"\nPL/pgSQL function graphql.resolve_field(text,text,integer,jsonb) line 20 at RETURN\nSQL expression \"coalesce(\n jsonb_object_agg(\n fa.field_alias,\n case\n when selection_name = 'name' and not has_modifiers then to_jsonb(gt.name::text)\n when selection_name = 'description' and not has_modifiers then to_jsonb(gt.description::text)\n when selection_name = 'specifiedByURL' and not has_modifiers then to_jsonb(null::text)\n when selection_name = 'kind' then (\n case\n when is_array_not_null then to_jsonb('NON_NULL'::text)\n when is_array then to_jsonb('LIST'::text)\n when is_not_null then to_jsonb('NON_NULL'::text)\n else to_jsonb(gt.type_kind::text)\n end\n )\n when selection_name = 'fields' and not has_modifiers then (\n select\n jsonb_agg(\n graphql.resolve_field(\n f.name,\n f.parent_type,\n null,\n x.sel\n )\n order by\n f.column_attribute_num,\n f.name\n )\n from\n graphql.field f\n where\n f.parent_type = gt.name\n and not f.is_hidden_from_schema\n and gt.type_kind = 'OBJECT'\n and not f.is_arg\n and (\n -- heartbeat is not visible unless the query type is empty\n gt.meta_kind <> 'Query'\n or f.meta_kind <> 'Query.heartbeat'\n or not exists(\n select 1\n from graphql.field fin\n where\n fin.parent_type = gt.name -- 'Query'\n and not fin.is_hidden_from_schema\n and fin.meta_kind <> 'Query.heartbeat'\n limit 1\n )\n )\n )\n when selection_name = 'interfaces' and not has_modifiers then (\n case\n -- Scalars get null, objects get an empty list. This is a poor implementation\n -- when gt.meta_kind not in ('Interface', 'BUILTIN', 'CURSOR') then '[]'::jsonb\n when gt.type_kind = 'SCALAR' then to_jsonb(null::text)\n when gt.type_kind = 'INTERFACE' then to_jsonb(null::text)\n when gt.meta_kind = 'Cursor' then to_jsonb(null::text)\n else '[]'::jsonb\n end\n )\n when selection_name = 'possibleTypes' and not has_modifiers then to_jsonb(null::text)\n when selection_name = 'enumValues' then graphql.\"resolve_enumValues\"(gt.name, x.sel)\n when selection_name = 'inputFields' and not has_modifiers then (\n select\n jsonb_agg(\n graphql.resolve_field(\n f.name,\n f.parent_type,\n f.parent_arg_field_id,\n x.sel\n )\n order by\n f.column_attribute_num,\n f.name\n )\n from\n graphql.field f\n where\n f.parent_type = gt.name\n and not f.is_hidden_from_schema\n and gt.type_kind = 'INPUT_OBJECT'\n )\n when selection_name = 'ofType' then (\n case\n -- NON_NULL(LIST(...))\n when is_array_not_null is true then graphql.\"resolve___Type\"(type_, x.sel, is_array_not_null := false, is_array := is_array, is_not_null := is_not_null)\n -- LIST(...)\n when is_array then graphql.\"resolve___Type\"(type_, x.sel, is_array_not_null := false, is_array := false, is_not_null := is_not_null)\n -- NON_NULL(...)\n when is_not_null then graphql.\"resolve___Type\"(type_, x.sel, is_array_not_null := false, is_array := false, is_not_null := false)\n -- TYPE\n else null\n end\n )\n else null\n end\n ),\n 'null'::jsonb\n )\n from\n graphql.type gt\n join jsonb_array_elements(ast -> 'selectionSet' -> 'selections') x(sel)\n on true,\n lateral (\n select\n graphql.alias_or_name_literal(x.sel) field_alias,\n graphql.name_literal(x.sel) as selection_name\n ) fa,\n lateral (\n select (coalesce(is_array_not_null, false) or is_array or is_not_null) as has_modifiers\n ) hm\n where\n gt.name = type_\"\nPL/pgSQL function graphql.\"resolve___Type\"(text,jsonb,boolean,boolean,boolean) line 4 at RETURN\nPL/pgSQL assignment \"agg = agg || (\n with uq as (\n select\n distinct gt.name\n from\n graphql.type gt\n -- Filter out object types with no fields\n join (\n select\n distinct parent_type\n from\n graphql.field\n where\n not is_hidden_from_schema\n -- scheam.queryType is non null so we must include it\n -- even when its empty. a client exception will be thrown\n -- if not fields exist\n or parent_type = 'Query'\n ) gf\n on gt.name = gf.parent_type\n or gt.type_kind not in ('OBJECT', 'INPUT_OBJECT')\n )\n select\n jsonb_build_object(\n graphql.alias_or_name_literal(node_field),\n jsonb_agg(graphql.\"resolve___Type\"(uq.name, node_field) order by uq.name)\n )\n from uq\n )\"\nPL/pgSQL function graphql.\"resolve___Schema\"(jsonb,jsonb) line 36 at assignment\nPL/pgSQL assignment \"data_ = case meta_kind\n when '__Schema' then\n graphql.\"resolve___Schema\"(\n ast := ast_inlined,\n variable_definitions := variable_definitions\n )\n when '__Type' then\n jsonb_build_object(\n graphql.alias_or_name_literal(ast_statement),\n graphql.\"resolve___Type\"(\n (\n select\n name\n from\n graphql.type type_\n where\n name = graphql.argument_value_by_name('name', ast_inlined)\n ),\n ast_inlined\n )\n )\n else null::jsonb\n end\"\nPL/pgSQL function graphql.resolve(text,jsonb,text,jsonb) line 206 at assignment",
"database_name": "postgres",
"detail": null,
"error_severity": "ERROR",
"hint": null,
"internal_query": null,
"internal_query_pos": null,
"leader_pid": null,
"location": null,
"process_id": 27974,
"query": "WITH pgrst_source AS (WITH pgrst_payload AS (SELECT $1::json AS json_data), pgrst_body AS ( SELECT CASE WHEN json_typeof(json_data) = 'array' THEN json_data ELSE json_build_array(json_data) END AS val FROM pgrst_payload), pgrst_args AS ( SELECT * FROM json_to_recordset((SELECT val FROM pgrst_body)) AS _(\"query\" text) )SELECT \"graphql_public\".\"graphql\"(\"query\" := (SELECT \"query\" FROM pgrst_args LIMIT 1)) AS pgrst_scalar) SELECT null::bigint AS total_result_set, pg_catalog.count(_postgrest_t) AS page_total, coalesce((json_agg(_postgrest_t.pgrst_scalar)->0)::text, 'null') AS body, nullif(current_setting('response.headers', true), '') AS response_headers, nullif(current_setting('response.status', true), '') AS response_status FROM (SELECT \"pgrst_source\".*FROM \"pgrst_source\" LIMIT $2 OFFSET $3) _postgrest_t",
"query_id": 3088342631653522000,
"query_pos": null,
"session_id": "629bb986.6d46",
"session_line_num": 8,
"session_start_time": "2022-06-04 19:59:02 UTC",
"sql_state_code": "57014",
"timestamp": "2022-06-04 20:01:02.945 UTC",
"transaction_id": 0,
"user_name": "authenticator",
"virtual_transaction_id": "5/17599"
}
],
"parsed_from": null,
"project": "bzupkzejwgejjkpyuqig",
"source_type": "file"
}
About this issue
- Original URL
- State: closed
- Created 2 years ago
- Reactions: 1
- Comments: 17 (8 by maintainers)
You could make a call like this https://github.com/supabase/pg_graphql/blob/f837b390a26d0c0f12703be4dd7816f172086866/test/sql/resolve_graphiql_schema.sql#L38 subbing in your query, operationName, and variables to match what the client is sending. That would build the cache and wouldn’t be subject to the same timeout limit
I worked on this issue a bit today and plan to continue next week. Will keep this thread in the loop when a patch is ready
Omg this is way faster! Thank you so much @olirice .
Thanks, I was able to reproduce the timeout
Your schema is one of the larger that we’ve seen on a GraphQL project with
The way pg_graphql runs introspection queries is different than the way it executes user queries in that it resolves them recursively using fixed functions rather than generating a single SQL statement and then executing it. We’re aware that it is a slow point and are working towards speeding it up.
In the short term, you can improve the performance of that query by
SELECT
permissions from the tables that don’t need to be exposed to the GraphQL API to reduce the size of the exposed schemaI’ll keep you in the loop in this thread as the introspection performance improvements land