Skip to content

Commit

Permalink
Add query performance statistics (#7869)
Browse files Browse the repository at this point in the history
Add edb_stat_statements Postgres extension (forked from the master
branch of the upstream pg_stat_statement extension) to handle custom
query performance statistics. `sys::QueryStats` is added as a view of
the statistics.

This is done in a way that, for each stats-significant SQL we send to the
 backend, one or more comment lines of "query stats info" JSONs are
prepended for the Postgres extension to ingest and record in the modified
statistics hash table. Among the stats info fields, `id: uuid` is especially
important to identify different queries and accumulate stats of the same
query onto the same hash table entry, which reflects some settings that
affected the compilation (excluding the user schema version for common
grouping of stats). Particularly, the first-8-bytes of `id` is also used by
the Postgres extension to replace the underlying `queryId` of the SQL
statement, so that the same frontend query can be recognized across all
PARSE/EXECUTE operations in Postgres for stats recording.

System queries, DDLs, and unrecognized queries are not recorded.

Refs #7725
  • Loading branch information
fantix authored Nov 21, 2024
1 parent 41afb84 commit f5396fd
Show file tree
Hide file tree
Showing 55 changed files with 9,388 additions and 46 deletions.
5 changes: 3 additions & 2 deletions .editorconfig
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ indent_style = space
indent_size = 2
indent_style = space

[ext/*.{c,cpp,h}]
indent_size = 4
[edb_stat_statements/*.{c,h,l,y,pl,pm}]
indent_style = tab
indent_size = tab
tab_width = 4
2 changes: 1 addition & 1 deletion edb/buildmeta.py
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,7 @@
# The merge conflict there is a nice reminder that you probably need
# to write a patch in edb/pgsql/patches.py, and then you should preserve
# the old value.
EDGEDB_CATALOG_VERSION = 2024_11_12_01_00
EDGEDB_CATALOG_VERSION = 2024_11_15_00_00
EDGEDB_MAJOR_VERSION = 6


Expand Down
143 changes: 143 additions & 0 deletions edb/lib/sys.edgeql
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,14 @@ CREATE SCALAR TYPE sys::VersionStage
EXTENDING enum<dev, alpha, beta, rc, final>;


CREATE SCALAR TYPE sys::QueryType
EXTENDING enum<EdgeQL, SQL>;


CREATE SCALAR TYPE sys::OutputFormat
EXTENDING enum<BINARY, JSON, JSON_ELEMENTS, NONE>;


CREATE ABSTRACT TYPE sys::SystemObject EXTENDING schema::Object;

CREATE ABSTRACT TYPE sys::ExternalObject EXTENDING sys::SystemObject;
Expand Down Expand Up @@ -86,6 +94,141 @@ ALTER TYPE sys::Role {
};


CREATE TYPE sys::QueryStats EXTENDING sys::ExternalObject {
CREATE LINK branch -> sys::Branch {
CREATE ANNOTATION std::description :=
"The branch this statistics entry was collected in.";
};
CREATE PROPERTY query -> std::str {
CREATE ANNOTATION std::description :=
"Text string of a representative query.";
};
CREATE PROPERTY query_type -> sys::QueryType {
CREATE ANNOTATION std::description :=
"Type of the query.";
};

CREATE PROPERTY compilation_config -> std::json;
CREATE PROPERTY protocol_version -> tuple<major: std::int16,
minor: std::int16>;
CREATE PROPERTY default_namespace -> std::str;
CREATE OPTIONAL PROPERTY namespace_aliases -> std::json;
CREATE OPTIONAL PROPERTY output_format -> sys::OutputFormat;
CREATE OPTIONAL PROPERTY expect_one -> std::bool;
CREATE OPTIONAL PROPERTY implicit_limit -> std::int64;
CREATE OPTIONAL PROPERTY inline_typeids -> std::bool;
CREATE OPTIONAL PROPERTY inline_typenames -> std::bool;
CREATE OPTIONAL PROPERTY inline_objectids -> std::bool;

CREATE PROPERTY plans -> std::int64 {
CREATE ANNOTATION std::description :=
"Number of times the query was planned in the backend.";
};
CREATE PROPERTY total_plan_time -> std::duration {
CREATE ANNOTATION std::description :=
"Total time spent planning the query in the backend.";
};
CREATE PROPERTY min_plan_time -> std::duration {
CREATE ANNOTATION std::description :=
"Minimum time spent planning the query in the backend. "
++ "This field will be zero if the counter has been reset "
++ "using the `sys::reset_query_stats` function "
++ "with the `minmax_only` parameter set to `true` "
++ "and never been planned since.";
};
CREATE PROPERTY max_plan_time -> std::duration {
CREATE ANNOTATION std::description :=
"Maximum time spent planning the query in the backend. "
++ "This field will be zero if the counter has been reset "
++ "using the `sys::reset_query_stats` function "
++ "with the `minmax_only` parameter set to `true` "
++ "and never been planned since.";
};
CREATE PROPERTY mean_plan_time -> std::duration {
CREATE ANNOTATION std::description :=
"Mean time spent planning the query in the backend.";
};
CREATE PROPERTY stddev_plan_time -> std::duration {
CREATE ANNOTATION std::description :=
"Population standard deviation of time spent "
++ "planning the query in the backend.";
};

CREATE PROPERTY calls -> std::int64 {
CREATE ANNOTATION std::description :=
"Number of times the query was executed.";
};
CREATE PROPERTY total_exec_time -> std::duration {
CREATE ANNOTATION std::description :=
"Total time spent executing the query in the backend.";
};
CREATE PROPERTY min_exec_time -> std::duration {
CREATE ANNOTATION std::description :=
"Minimum time spent executing the query in the backend, "
++ "this field will be zero until this query is executed "
++ "first time after reset performed by the "
++ "`sys::reset_query_stats` function with the "
++ "`minmax_only` parameter set to `true`";
};
CREATE PROPERTY max_exec_time -> std::duration {
CREATE ANNOTATION std::description :=
"Maximum time spent executing the query in the backend, "
++ "this field will be zero until this query is executed "
++ "first time after reset performed by the "
++ "`sys::reset_query_stats` function with the "
++ "`minmax_only` parameter set to `true`";
};
CREATE PROPERTY mean_exec_time -> std::duration {
CREATE ANNOTATION std::description :=
"Mean time spent executing the query in the backend.";
};
CREATE PROPERTY stddev_exec_time -> std::duration {
CREATE ANNOTATION std::description :=
"Population standard deviation of time spent "
++ "executing the query in the backend.";
};

CREATE PROPERTY rows -> std::int64 {
CREATE ANNOTATION std::description :=
"Total number of rows retrieved or affected by the query.";
};
CREATE PROPERTY stats_since -> std::datetime {
CREATE ANNOTATION std::description :=
"Time at which statistics gathering started for this query.";
};
CREATE PROPERTY minmax_stats_since -> std::datetime {
CREATE ANNOTATION std::description :=
"Time at which min/max statistics gathering started "
++ "for this query (fields `min_plan_time`, `max_plan_time`, "
++ "`min_exec_time` and `max_exec_time`).";
};
};


CREATE FUNCTION
sys::reset_query_stats(
named only branch_name: OPTIONAL std::str = {},
named only id: OPTIONAL std::uuid = {},
named only minmax_only: OPTIONAL std::bool = false,
) -> OPTIONAL std::datetime {
CREATE ANNOTATION std::description :=
'Discard query statistics gathered so far corresponding to the '
++ 'specified `branch_name` and `id`. If either of the '
++ 'parameters is not specified, the statistics that match with the '
++ 'other parameter will be reset. If no parameter is specified, '
++ 'it will discard all statistics. When `minmax_only` is `true`, '
++ 'only the values of minimum and maximum planning and execution '
++ 'time will be reset (i.e. `min_plan_time`, `max_plan_time`, '
++ '`min_exec_time` and `max_exec_time` fields). The default value '
++ 'for `minmax_only` parameter is `false`. This function returns '
++ 'the time of a reset. This time is saved to `stats_reset` or '
++ '`minmax_stats_since` field of `sys::QueryStats` if the '
++ 'corresponding reset was actually performed.';
SET volatility := 'Volatile';
USING SQL FUNCTION 'edgedb.reset_query_stats';
};


# An intermediate function is needed because we can't
# cast JSON to tuples yet. DO NOT use directly, it'll go away.
CREATE FUNCTION
Expand Down
Loading

0 comments on commit f5396fd

Please sign in to comment.