mirror of
https://gitlab.com/depesz/explain.depesz.com.git
synced 2024-11-28 08:58:52 +02:00
Add hints from Pg::Explain::Hinter
This commit is contained in:
parent
eddd052137
commit
23f69af2d8
2
cpanfile
2
cpanfile
@ -18,7 +18,7 @@ requires 'Encode';
|
|||||||
requires 'English';
|
requires 'English';
|
||||||
requires 'File::Spec';
|
requires 'File::Spec';
|
||||||
requires 'Mojolicious';
|
requires 'Mojolicious';
|
||||||
requires 'Pg::Explain', '>= 1.13';
|
requires 'Pg::Explain', '>= 2.0';
|
||||||
requires 'Number::Bytes::Human';
|
requires 'Number::Bytes::Human';
|
||||||
|
|
||||||
# vim: set ft=perl:
|
# vim: set ft=perl:
|
||||||
|
@ -40,6 +40,9 @@ sub startup {
|
|||||||
# load number_format plugin
|
# load number_format plugin
|
||||||
$self->plugin( 'number_format' );
|
$self->plugin( 'number_format' );
|
||||||
|
|
||||||
|
# Plugin to to smart quoting of DB identifiers
|
||||||
|
$self->plugin( 'DBIdentQuote' );
|
||||||
|
|
||||||
# Plugin to check if static file exists
|
# Plugin to check if static file exists
|
||||||
$self->plugin( 'Explain::Plugin::StaticExists' );
|
$self->plugin( 'Explain::Plugin::StaticExists' );
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ use Mojo::Base 'Mojolicious::Controller';
|
|||||||
use English -no_match_vars;
|
use English -no_match_vars;
|
||||||
|
|
||||||
use Pg::Explain;
|
use Pg::Explain;
|
||||||
|
use Pg::Explain::Hinter;
|
||||||
use pgFormatter::Beautify;
|
use pgFormatter::Beautify;
|
||||||
use Encode;
|
use Encode;
|
||||||
use Email::Valid;
|
use Email::Valid;
|
||||||
@ -356,6 +357,10 @@ sub show {
|
|||||||
$self->stash->{ optimization_path } = $self->database->get_optimization_path( $id );
|
$self->stash->{ optimization_path } = $self->database->get_optimization_path( $id );
|
||||||
$self->stash->{ suboptimizations } = $self->database->get_optimizations_for( $id );
|
$self->stash->{ suboptimizations } = $self->database->get_optimizations_for( $id );
|
||||||
|
|
||||||
|
# Add hinter, if it makes sense
|
||||||
|
my $hinter = Pg::Explain::Hinter->new( $explain );
|
||||||
|
$self->stash->{ hinter } = $hinter if $hinter->any_hints;
|
||||||
|
|
||||||
# render will be called automatically
|
# render will be called automatically
|
||||||
|
|
||||||
return $self->render( 'controller/iframe' ) if 'iframe' eq $self->match->endpoint->name;
|
return $self->render( 'controller/iframe' ) if 'iframe' eq $self->match->endpoint->name;
|
||||||
|
21
lib/Explain/Plugin/DBIdentQuote.pm
Normal file
21
lib/Explain/Plugin/DBIdentQuote.pm
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
package Explain::Plugin::DBIdentQuote;
|
||||||
|
|
||||||
|
use Number::Bytes::Human qw(format_bytes);
|
||||||
|
use Mojo::Base 'Mojolicious::Plugin';
|
||||||
|
|
||||||
|
sub register {
|
||||||
|
my ( $self, $app ) = @_;
|
||||||
|
|
||||||
|
# register helpers
|
||||||
|
$app->helper( db_ident_quote => \&db_ident_quote );
|
||||||
|
}
|
||||||
|
|
||||||
|
sub db_ident_quote {
|
||||||
|
my $self = shift;
|
||||||
|
local $_ = shift;
|
||||||
|
|
||||||
|
return $_ if /\A[a-z][a-z0-9_]*\z/;
|
||||||
|
return sprintf '"%s"', $_;
|
||||||
|
}
|
||||||
|
|
||||||
|
1;
|
@ -117,7 +117,7 @@ section p a:hover {background:#234;color:#fff;border:1px solid #123}
|
|||||||
|
|
||||||
.result .tabs ul {margin:0;padding:0;list-style:none}
|
.result .tabs ul {margin:0;padding:0;list-style:none}
|
||||||
.result .tabs li {display:block;float:left;margin-right:1px}
|
.result .tabs li {display:block;float:left;margin-right:1px}
|
||||||
.result .tabs a {display:block;padding:4px 12px;background:#cfcfcf;color:#fff;text-decoration:none;font-weight:bold}
|
.result .tabs a {display:block;padding:4px 12px;background:#ababab;color:#fff;text-decoration:none;font-weight:bold}
|
||||||
.result .tabs a:hover {color:#234}
|
.result .tabs a:hover {color:#234}
|
||||||
.result .tabs a.current,
|
.result .tabs a.current,
|
||||||
.result .tabs a.current:hover {background:#234;color:#fff}
|
.result .tabs a.current:hover {background:#234;color:#fff}
|
||||||
@ -285,3 +285,19 @@ section p a:hover {background:#234;color:#fff;border:1px solid #123}
|
|||||||
#plan-settings th { font-size: 1.2em; font-weight: bold; padding: 0.2em 0.5em;}
|
#plan-settings th { font-size: 1.2em; font-weight: bold; padding: 0.2em 0.5em;}
|
||||||
#plan-settings thead th {text-align: center; font-size: 1.5em;}
|
#plan-settings thead th {text-align: center; font-size: 1.5em;}
|
||||||
#plan-settings td {text-align: right; padding: 0.2em 0.5em;}
|
#plan-settings td {text-align: right; padding: 0.2em 0.5em;}
|
||||||
|
|
||||||
|
.result-hints h1 {
|
||||||
|
border: none;
|
||||||
|
padding: 10px 20px;
|
||||||
|
color: #444;
|
||||||
|
}
|
||||||
|
.result-hints ol {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
.result-hints ol li {
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.result-hints ol li p,
|
||||||
|
.result-hints ol li pre {
|
||||||
|
margin: 5px;
|
||||||
|
}
|
||||||
|
@ -90,7 +90,7 @@ div.explain-form a:hover {background:#e5ecf9}
|
|||||||
* html #explain-form input {border:0}
|
* html #explain-form input {border:0}
|
||||||
*:first-child+html #explain-form input {border:0}
|
*:first-child+html #explain-form input {border:0}
|
||||||
|
|
||||||
.result-text, .result-source, .result-html, .result-stats {margin:0;padding:0;border:1px solid #ccc;overflow:auto}
|
.result-text, .result-source, .result-html, .result-stats, .result-hints {margin:0;padding:0;border:1px solid #ccc;overflow:auto}
|
||||||
|
|
||||||
/* all ico(ns) */
|
/* all ico(ns) */
|
||||||
.result-html table#explain td.n div.ico {position:absolute;top:-4px;left:-22px;width:22px;height:22px;background:url('../img/sprite.png') no-repeat 0px -180px;text-indent:-1000px;overflow:hidden}
|
.result-html table#explain td.n div.ico {position:absolute;top:-4px;left:-22px;width:22px;height:22px;background:url('../img/sprite.png') no-repeat 0px -180px;text-indent:-1000px;overflow:hidden}
|
||||||
@ -205,3 +205,12 @@ button.copy {
|
|||||||
background: #e5ecf9;
|
background: #e5ecf9;
|
||||||
color: #444;
|
color: #444;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
div.tabs span.newblink {
|
||||||
|
margin: 10px;
|
||||||
|
animation: newblink .3s alternate infinite;
|
||||||
|
}
|
||||||
|
@keyframes newblink {
|
||||||
|
0% { color: #ff0000; }
|
||||||
|
100% { color: #ffff00; }
|
||||||
|
}
|
||||||
|
@ -340,7 +340,7 @@
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if(['html', 'source', 'text', 'query', 'bquery', 'stats'].indexOf(want) == -1 ) {
|
if(['html', 'source', 'text', 'hints', 'query', 'bquery', 'stats'].indexOf(want) == -1 ) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -112,7 +112,8 @@
|
|||||||
% }
|
% }
|
||||||
|
|
||||||
% my $global_node_id = 0;
|
% my $global_node_id = 0;
|
||||||
|
% my $table_node_id_to_explain_node_id = {};
|
||||||
|
% my $explain_node_id_to_table_node_id = {};
|
||||||
|
|
||||||
|
|
||||||
% my $prev_row_level = 0;
|
% my $prev_row_level = 0;
|
||||||
@ -212,6 +213,8 @@
|
|||||||
% $row_class .= ' c-' . $row_color;
|
% $row_class .= ' c-' . $row_color;
|
||||||
|
|
||||||
% my $node_id = $global_node_id++;
|
% my $node_id = $global_node_id++;
|
||||||
|
% $table_node_id_to_explain_node_id->{$global_node_id} = $node->id;
|
||||||
|
% $explain_node_id_to_table_node_id->{$node->id} = $global_node_id;
|
||||||
|
|
||||||
<tr id="l<%= $global_node_id =%>" class="n <%= $row_class %>" data-node_id="<%= $node_id =%>" data-node_parent="<%= $parent =%>" data-level="<%= $level =%>" data-e="<%= $exclusive_point =%>" data-i="<%= $inclusive_point =%>" data-x="<%= $rows_x_point =%>">
|
<tr id="l<%= $global_node_id =%>" class="n <%= $row_class %>" data-node_id="<%= $node_id =%>" data-node_parent="<%= $parent =%>" data-level="<%= $level =%>" data-e="<%= $exclusive_point =%>" data-i="<%= $inclusive_point =%>" data-x="<%= $rows_x_point =%>">
|
||||||
<td class="u <%= $cfg->{ vu } ? '' : ' tight' %>"><a href="#l<%= $global_node_id =%>"><%= $global_node_id =%>.</a></td>
|
<td class="u <%= $cfg->{ vu } ? '' : ' tight' %>"><a href="#l<%= $global_node_id =%>"><%= $global_node_id =%>.</a></td>
|
||||||
@ -617,6 +620,14 @@
|
|||||||
onkeypress="return this.onclick( );">TEXT</a>
|
onkeypress="return this.onclick( );">TEXT</a>
|
||||||
</li>
|
</li>
|
||||||
% }
|
% }
|
||||||
|
% if ( stash('hinter') ) {
|
||||||
|
<li class="hints">
|
||||||
|
<a href="#hints"
|
||||||
|
title="view plan hints"
|
||||||
|
onclick="$( this ).explain( 'toggleView', 'hints', this ); return false;"
|
||||||
|
onkeypress="return this.onclick( );">HINTS <span class="newblink">!NEW!</span></a>
|
||||||
|
</li>
|
||||||
|
% }
|
||||||
% if ( stash('query') ) {
|
% if ( stash('query') ) {
|
||||||
<li class="query">
|
<li class="query">
|
||||||
<a href="#query"
|
<a href="#query"
|
||||||
@ -824,6 +835,45 @@
|
|||||||
</div>
|
</div>
|
||||||
% }
|
% }
|
||||||
|
|
||||||
|
% if ( stash('hinter') ) {
|
||||||
|
% my $H = stash('hinter')->hints;
|
||||||
|
<div class="result-hints res-tab hidden">
|
||||||
|
% if ( 1 == scalar @{ $H } ) {
|
||||||
|
<h1>I have one hint for you:</h1>
|
||||||
|
% } else {
|
||||||
|
<h1>I have <%= scalar @{ $H } %> hints for you:</h1>
|
||||||
|
% }
|
||||||
|
<ol>
|
||||||
|
% for my $hint ( @{ $H }) {
|
||||||
|
% my $node_num = $explain_node_id_to_table_node_id->{ $hint->node->id };
|
||||||
|
<li>
|
||||||
|
% if ( $hint->type eq 'DISK_SORT' ) {
|
||||||
|
<p>You have <a href="#<%= $node_num %>" target="_new">sort node (#<%= $node_num %>)</a> that is using disk space to sort.</p>
|
||||||
|
<p>This is because your <a href="https://www.postgresql.org/docs/current/runtime-config-<%= $guc_docs->{ 'work_mem' } %>" target='_new'>work_mem</a> setting is too low.</p>
|
||||||
|
<p>Increasing it can make the sort run in memory, or, at least, use less of disk. Sort used <%= $hint->details->[0] %>kB, so you would need to set your work_mem to <em>at least</em> that much to have a chance at sorting in memory only.</p>
|
||||||
|
% } elsif ( $hint->type eq 'INDEXABLE_SEQSCAN_SIMPLE' ) {
|
||||||
|
% my $table_name = $hint->node->scan_on->{'table_name'};
|
||||||
|
% my $column_name = $hint->details->[0];
|
||||||
|
% my $operator = $hint->details->[1];
|
||||||
|
% my $fetched_rows = $hint->node->total_rows + $hint->node->total_rows_removed;
|
||||||
|
% my $dropped_rows = $hint->node->total_rows_removed;
|
||||||
|
<p>You have <a href="#<%= $node_num %>" target="_new"><%= $hint->node->type %> (#<%= $node_num %>)</a> that could use an index.</p>
|
||||||
|
<p>This node searches in table <em><%= $table_name %></em> using <em><%= $operator %></em> operator on column <em><%= $column_name %></em>. In process it fetches from disk <%= commify_number( $fetched_rows ) %> rows, just to drop
|
||||||
|
% if ( $fetched_rows == $dropped_rows ) {
|
||||||
|
<em>all</em>
|
||||||
|
% } else {
|
||||||
|
<%= commify_number( $dropped_rows ) %>
|
||||||
|
% }
|
||||||
|
of them!</p>
|
||||||
|
<p>This should be possible to speed up by creating this index:</p>
|
||||||
|
<pre><code class="sql">CREATE INDEX CONCURRENTLY <%= db_ident_quote( "explain_depesz_com_hint_${id}_${node_num}" ) %> ON <%= db_ident_quote($table_name) %> ( <%= db_ident_quote($column_name) %> );</code></pre>
|
||||||
|
% }
|
||||||
|
</li>
|
||||||
|
% }
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
% }
|
||||||
|
|
||||||
% if ( stash('query') ) {
|
% if ( stash('query') ) {
|
||||||
<div class="result-query res-tab hidden">
|
<div class="result-query res-tab hidden">
|
||||||
<pre id="query"><code class="sql"><%= stash('query') %></code></pre>
|
<pre id="query"><code class="sql"><%= stash('query') %></code></pre>
|
||||||
|
Loading…
Reference in New Issue
Block a user