Skip to content

Commit 7bead93

Browse files
Auke Booijhasura-bot
Auke Booij
authored andcommitted
server: remove remnants of query plan caching (fix #1795)
Query plan caching was introduced by - I believe - #1934 in order to reduce the query response latency. During the development of PDV in #4111, it was found out that the new architecture (for which query plan caching wasn't implemented) performed comparably to the pre-PDV architecture with caching. Hence, it was decided to leave query plan caching until some day in the future when it was deemed necessary. Well, we're in the future now, and there still isn't a convincing argument for query plan caching. So the time has come to remove some references to query plan caching from the codebase. For the most part, any code being removed would probably not be very well suited to the post-PDV architecture of query execution, so arguably not much is lost. Apart from simplifying the code, this PR will contribute towards making the GraphQL schema generation more modular, testable, and easier to profile. I'd like to eventually work towards a situation in which it's easy to generate a GraphQL schema parser *in isolation*, without being connected to a database, and then parse a GraphQL query *in isolation*, without even listening any HTTP port. It is important that both of these operations can be examined in detail, and in isolation, since they are two major performance bottlenecks, as well as phases where many important upcoming features hook into. Implementation The following have been removed: - The entirety of `server/src-lib/Hasura/GraphQL/Execute/Plan.hs` - The core phases of query parsing and execution no longer have any references to query plan caching. Note that this is not to be confused with query *response* caching, which is not affected by this PR. This includes removal of the types: - - `Opaque`, which is replaced by a tuple. Note that the old implementation was broken and did not adequately hide the constructors. - - `QueryReusability` (and the `markNotReusable` method). Notably, the implementation of the `ParseT` monad now consists of two, rather than three, monad transformers. - Cache-related tests (in `server/src-test/Hasura/CacheBoundedSpec.hs`) have been removed . - References to query plan caching in the documentation. - The `planCacheOptions` in the `TenantConfig` type class was removed. However, during parsing, unrecognized fields in the YAML config get ignored, so this does not cause a breaking change. (Confirmed manually, as well as in consultation with @sordina.) - The metrics no longer send cache hit/miss messages. There are a few places in which one can still find references to query plan caching: - We still accept the `--query-plan-cache-size` command-line option for backwards compatibility. The `HASURA_QUERY_PLAN_CACHE_SIZE` environment variable is not read. hasura/graphql-engine-mono#1815 GitOrigin-RevId: 17d92b2
1 parent cc6c86a commit 7bead93

File tree

38 files changed

+306
-1464
lines changed

38 files changed

+306
-1464
lines changed

CHANGELOG.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,12 +6,12 @@
66
- server: Fixed a bug where MSSQL and BigQuery would ignore environment variables set from the console
77
- server: Fixing bug in ReplaceMetadata parser - Moving from Alternative to committed-choice.
88
- server: Relax the unique operation name constraint when adding a query to a query collection
9+
- server: officially deprecate query plan caching, which had already been disabled for a long time
910
- server/bigquery: Fix issues related to adding and querying from non-US datasets (closes [6937](https://github.com/hasura/graphql-engine/issues/6937)).
1011
- console: add pagination on the Raw SQL results page
1112
- console: fix issues with replacing invalid graphql identifiers in table and column names
1213
- console: show error message on inconsistent objects table
1314

14-
1515
## v2.0.3
1616
(Add entries below in the order of server, console, cli, docs, others)
1717

docs/graphql/core/databases/ms-sql-server/queries/performance.rst

+5-135
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ MS SQL Server: Query performance
1515
Introduction
1616
------------
1717

18-
Sometimes queries can become slow due to large data volumes or levels of nesting.
19-
This page explains how to identify the query performance, how the query plan caching in Hasura works, and how queries can be optimized.
18+
Sometimes queries can become slow due to large data volumes or levels of nesting.
19+
This page explains how to identify the query performance, and how queries can be optimized.
2020

2121
.. _ms_sql_server_analysing_query_performance:
2222

@@ -53,146 +53,16 @@ The ``cost`` of a query is an arbitrary number generated by MS SQL Server and is
5353

5454
Read more about query performance analysis in the `MS SQL Server explain statement docs <https://docs.microsoft.com/en-us/sql/t-sql/queries/explain-transact-sql?view=azure-sqldw-latest>`__.
5555

56-
.. _ms_sql_server_query_plan_caching:
57-
58-
Query plan caching
59-
------------------
60-
61-
How it works
62-
^^^^^^^^^^^^
63-
64-
Hasura executes GraphQL queries as follows:
65-
66-
1. The incoming GraphQL query is parsed into an `abstract syntax tree <https://en.wikipedia.org/wiki/Abstract_syntax_tree>`__ (AST) which is how GraphQL is represented.
67-
2. The GraphQL AST is validated against the schema to generate an internal representation.
68-
3. The internal representation is converted into an SQL statement (a `prepared statement <https://docs.microsoft.com/en-us/sql/relational-databases/native-client-odbc-queries/executing-statements/prepared-execution?view=sql-server-ver15>`__ whenever possible).
69-
4. The (prepared) statement is executed on MS SQL Server to retrieve the result of the query.
70-
71-
For most use cases, Hasura constructs a "plan" for a query, so that a new instance of the same query can be executed without the overhead of steps 1 to 3.
72-
73-
For example, let's consider the following query:
74-
75-
.. code-block:: graphql
76-
77-
query getAuthor($id: Int!) {
78-
authors(where: {id: {_eq: $id}}) {
79-
name
80-
rating
81-
}
82-
}
83-
84-
With the following variable:
85-
86-
.. code-block:: graphql
87-
88-
{
89-
"id": 1
90-
}
91-
92-
Hasura now tries to map a GraphQL query to a prepared statement where the parameters have a one-to-one correspondence to the variables defined in the GraphQL query.
93-
The first time a query comes in, Hasura generates a plan for the query which consists of two things:
94-
95-
1. The prepared statement
96-
2. Information necessary to convert variables into the prepared statement's arguments
97-
98-
For the above query, Hasura generates the following prepared statement (simplified):
99-
100-
.. code-block:: plpgsql
101-
102-
select name, rating from author where id = $1
103-
104-
With the following prepared variables:
105-
106-
.. code-block:: plpgsql
107-
108-
$1 = 1
109-
110-
This plan is then saved in a data structure called ``Query Plan Cache``. The next time the same query is executed,
111-
Hasura uses the plan to convert the provided variables into the prepared statement's arguments and then executes the statement.
112-
This will significantly cut down the execution time for a GraphQL query resulting in lower latencies and higher throughput.
113-
114-
Caveats
115-
^^^^^^^
116-
117-
The above optimization is not possible for all types of queries. For example, consider this query:
118-
119-
.. code-block:: graphql
120-
121-
query getAuthorWithCondition($condition: author_bool_exp!) {
122-
author(where: $condition)
123-
name
124-
rating
125-
}
126-
}
127-
128-
The statement generated for ``getAuthorWithCondition`` is now dependent on the variables.
129-
130-
With the following variables:
131-
132-
.. code-block:: json
133-
134-
{
135-
"condition": {"id": {"_eq": 1}}
136-
}
137-
138-
the generated statement will be:
139-
140-
.. code-block:: plpgsql
141-
142-
select name, rating from author where id = $1
143-
144-
However, with the following variables:
145-
146-
.. code-block:: json
147-
148-
{
149-
"condition": {"name": {"_eq": "John"}}
150-
}
151-
152-
the generated statement will be:
153-
154-
.. code-block:: plpgsql
155-
156-
select name, rating from author where name = 'John'
157-
158-
A plan cannot be generated for such queries because the variables defined in the GraphQL query don't have a one-to-one correspondence to the parameters in the prepared statement.
159-
16056
Query optimization
16157
------------------
16258

163-
Using GraphQL variables
164-
^^^^^^^^^^^^^^^^^^^^^^^
165-
166-
In order to leverage Hasura's query plan caching (as explained in the :ref:`previous section <ms_sql_server_query_plan_caching>`) to the full extent, GraphQL queries should be defined with
167-
variables whose types are **non-nullable scalars** whenever possible.
168-
169-
To make variables non-nullable, add a ``!`` at the end of the type, like here:
170-
171-
.. code-block:: graphql
172-
:emphasize-lines: 1
173-
174-
query getAuthor($id: Int!) {
175-
authors(where: {id: {_eq: $id}}) {
176-
name
177-
rating
178-
}
179-
}
180-
181-
If the ``!`` is not added and the variable is nullable, the generated query will be different depending on whether an ``id`` is passed or whether the variable is ``null``
182-
(for the latter, there is no ``where`` statement present). Therefore, it's not possible for Hasura to create a reusable plan for a query in this case.
183-
184-
.. note::
185-
186-
Hasura is fast even for queries which cannot have a reusable plan.
187-
This should concern you only if you face a high volume of traffic (thousands of requests per second).
188-
18959
.. _ms_sql_server_data_validation_mssql_indexes:
19060

19161
Using MS SQL indexes
19262
^^^^^^^^^^^^^^^^^^^^
19363

19464
`MS SQL Server indexes <https://docs.microsoft.com/en-us/sql/relational-databases/indexes/indexes?view=sql-server-ver15>`__ are special lookup tables that MS SQL Server can use to speed up data lookup.
195-
An index acts as a pointer to data in a table, and it works very similar to an index in the back of a book.
65+
An index acts as a pointer to data in a table, and it works very similar to an index in the back of a book.
19666
If you look in the index first, you'll find the data much quicker than searching the whole book (or - in this case - database).
19767

19868
Let's say we know that ``authors`` table is frequently queried by ``name``:
@@ -223,14 +93,14 @@ The following statement sets an index on ``name`` in the ``authors`` table.
22393

22494
.. tab:: CLI
22595

226-
:ref:`Create a migration manually <manual_migrations>` and add your create index statement to the ``up.sql`` file.
96+
:ref:`Create a migration manually <manual_migrations>` and add your create index statement to the ``up.sql`` file.
22797
Also, add an SQL statement to revert that statement to the ``down.sql`` file in case you need to :ref:`roll back <roll_back_migrations>` the migration.
22898

22999
Apply the migration by running:
230100

231101
.. code-block:: bash
232102
233-
hasura migrate apply
103+
hasura migrate apply
234104
235105
.. tab:: API
236106

docs/graphql/core/databases/ms-sql-server/queries/variables-aliases-fragments-directives.rst

-4
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ In order to make a query re-usable, it can be made dynamic by using variables.
5050
"author_id": 1
5151
}
5252

53-
.. admonition:: Variables and performance
54-
55-
Variables have an impact on query performance. Refer to :ref:`query performance <ms_sql_server_query_performance>` to learn more about Hasura's query plan caching and about optimizing when using variables.
56-
5753
Using aliases
5854
-------------
5955

docs/graphql/core/databases/postgres/queries/performance.rst

+5-135
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,8 @@ Postgres: Query performance
1515
Introduction
1616
------------
1717

18-
Sometimes queries can become slow due to large data volumes or levels of nesting.
19-
This page explains how to identify the query performance, how the query plan caching in Hasura works, and how queries can be optimized.
18+
Sometimes queries can become slow due to large data volumes or levels of nesting.
19+
This page explains how to identify the query performance, and how queries can be optimized.
2020

2121
.. _analysing_query_performance:
2222

@@ -53,146 +53,16 @@ The ``cost`` of a query is an arbitrary number generated by Postgres and is to b
5353

5454
Read more about query performance analysis in the `Postgres explain statement docs <https://www.postgresql.org/docs/current/sql-explain.html>`__.
5555

56-
.. _query_plan_caching:
57-
58-
Query plan caching
59-
------------------
60-
61-
How it works
62-
^^^^^^^^^^^^
63-
64-
Hasura executes GraphQL queries as follows:
65-
66-
1. The incoming GraphQL query is parsed into an `abstract syntax tree <https://en.wikipedia.org/wiki/Abstract_syntax_tree>`__ (AST) which is how GraphQL is represented.
67-
2. The GraphQL AST is validated against the schema to generate an internal representation.
68-
3. The internal representation is converted into an SQL statement (a `prepared statement <https://www.postgresql.org/docs/current/sql-prepare.html>`__ whenever possible).
69-
4. The (prepared) statement is executed on Postgres to retrieve the result of the query.
70-
71-
For most use cases, Hasura constructs a "plan" for a query, so that a new instance of the same query can be executed without the overhead of steps 1 to 3.
72-
73-
For example, let's consider the following query:
74-
75-
.. code-block:: graphql
76-
77-
query getAuthor($id: Int!) {
78-
authors(where: {id: {_eq: $id}}) {
79-
name
80-
rating
81-
}
82-
}
83-
84-
With the following variable:
85-
86-
.. code-block:: graphql
87-
88-
{
89-
"id": 1
90-
}
91-
92-
Hasura now tries to map a GraphQL query to a prepared statement where the parameters have a one-to-one correspondence to the variables defined in the GraphQL query.
93-
The first time a query comes in, Hasura generates a plan for the query which consists of two things:
94-
95-
1. The prepared statement
96-
2. Information necessary to convert variables into the prepared statement's arguments
97-
98-
For the above query, Hasura generates the following prepared statement (simplified):
99-
100-
.. code-block:: plpgsql
101-
102-
select name, rating from author where id = $1
103-
104-
With the following prepared variables:
105-
106-
.. code-block:: plpgsql
107-
108-
$1 = 1
109-
110-
This plan is then saved in a data structure called ``Query Plan Cache``. The next time the same query is executed,
111-
Hasura uses the plan to convert the provided variables into the prepared statement's arguments and then executes the statement.
112-
This will significantly cut down the execution time for a GraphQL query resulting in lower latencies and higher throughput.
113-
114-
Caveats
115-
^^^^^^^
116-
117-
The above optimization is not possible for all types of queries. For example, consider this query:
118-
119-
.. code-block:: graphql
120-
121-
query getAuthorWithCondition($condition: author_bool_exp!) {
122-
author(where: $condition)
123-
name
124-
rating
125-
}
126-
}
127-
128-
The statement generated for ``getAuthorWithCondition`` is now dependent on the variables.
129-
130-
With the following variables:
131-
132-
.. code-block:: json
133-
134-
{
135-
"condition": {"id": {"_eq": 1}}
136-
}
137-
138-
the generated statement will be:
139-
140-
.. code-block:: plpgsql
141-
142-
select name, rating from author where id = $1
143-
144-
However, with the following variables:
145-
146-
.. code-block:: json
147-
148-
{
149-
"condition": {"name": {"_eq": "John"}}
150-
}
151-
152-
the generated statement will be:
153-
154-
.. code-block:: plpgsql
155-
156-
select name, rating from author where name = 'John'
157-
158-
A plan cannot be generated for such queries because the variables defined in the GraphQL query don't have a one-to-one correspondence to the parameters in the prepared statement.
159-
16056
Query optimization
16157
------------------
16258

163-
Using GraphQL variables
164-
^^^^^^^^^^^^^^^^^^^^^^^
165-
166-
In order to leverage Hasura's query plan caching (as explained in the :ref:`previous section <query_plan_caching>`) to the full extent, GraphQL queries should be defined with
167-
variables whose types are **non-nullable scalars** whenever possible.
168-
169-
To make variables non-nullable, add a ``!`` at the end of the type, like here:
170-
171-
.. code-block:: graphql
172-
:emphasize-lines: 1
173-
174-
query getAuthor($id: Int!) {
175-
authors(where: {id: {_eq: $id}}) {
176-
name
177-
rating
178-
}
179-
}
180-
181-
If the ``!`` is not added and the variable is nullable, the generated query will be different depending on whether an ``id`` is passed or whether the variable is ``null``
182-
(for the latter, there is no ``where`` statement present). Therefore, it's not possible for Hasura to create a reusable plan for a query in this case.
183-
184-
.. note::
185-
186-
Hasura is fast even for queries which cannot have a reusable plan.
187-
This should concern you only if you face a high volume of traffic (thousands of requests per second).
188-
18959
.. _data_validation_pg_indexes:
19060

19161
Using PG indexes
19262
^^^^^^^^^^^^^^^^
19363

19464
`Postgres indexes <https://www.tutorialspoint.com/postgresql/postgresql_indexes.htm>`__ are special lookup tables that Postgres can use to speed up data lookup.
195-
An index acts as a pointer to data in a table, and it works very similar to an index in the back of a book.
65+
An index acts as a pointer to data in a table, and it works very similar to an index in the back of a book.
19666
If you look in the index first, you'll find the data much quicker than searching the whole book (or - in this case - database).
19767

19868
Let's say we know that ``authors`` table is frequently queried by ``name``:
@@ -223,14 +93,14 @@ The following statement sets an index on ``name`` in the ``authors`` table.
22393

22494
.. tab:: CLI
22595

226-
:ref:`Create a migration manually <manual_migrations>` and add your create index statement to the ``up.sql`` file.
96+
:ref:`Create a migration manually <manual_migrations>` and add your create index statement to the ``up.sql`` file.
22797
Also, add an SQL statement to revert that statement to the ``down.sql`` file in case you need to :ref:`roll back <roll_back_migrations>` the migration.
22898

22999
Apply the migration by running:
230100

231101
.. code-block:: bash
232102
233-
hasura migrate apply
103+
hasura migrate apply
234104
235105
.. tab:: API
236106

docs/graphql/core/databases/postgres/queries/variables-aliases-fragments-directives.rst

-4
Original file line numberDiff line numberDiff line change
@@ -50,10 +50,6 @@ In order to make a query re-usable, it can be made dynamic by using variables.
5050
"author_id": 1
5151
}
5252

53-
.. admonition:: Variables and performance
54-
55-
Variables have an impact on query performance. Refer to :ref:`query performance <query_performance>` to learn more about Hasura's query plan caching and about optimizing when using variables.
56-
5753
Using aliases
5854
-------------
5955

0 commit comments

Comments
 (0)