Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 2cd3220

Browse files
committed
Merge branch '189-show-locks' into 'master'
feat: show locks for a single query analyzed with EXPLAIN (#189) Closes #189 See merge request postgres-ai/joe!175
2 parents 66c4031 + 7c42b86 commit 2cd3220

File tree

3 files changed

+112
-6
lines changed

3 files changed

+112
-6
lines changed

‎pkg/bot/command/explain.go

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,8 @@ import (
1010
"strings"
1111

1212
"github.com/jackc/pgtype/pgxtype"
13+
"github.com/jackc/pgx/v4"
14+
"github.com/jackc/pgx/v4/pgxpool"
1315
"github.com/pkg/errors"
1416

1517
"gitlab.com/postgres-ai/database-lab/v2/pkg/log"
@@ -30,6 +32,9 @@ const (
3032
// Query Explain prefixes.
3133
queryExplain = "EXPLAIN (FORMAT TEXT) "
3234
queryExplainAnalyze = "EXPLAIN (ANALYZE, COSTS, VERBOSE, BUFFERS, FORMAT JSON) "
35+
36+
// locksTitle shows locks for a single query analyzed with EXPLAIN.
37+
locksTitle = "*Query heavy locks:*\n"
3338
)
3439

3540
// Explain runs an explain query.
@@ -41,29 +46,51 @@ func Explain(ctx context.Context, msgSvc connection.Messenger, command *platform
4146

4247
serviceConn, err := getConn(ctx, session.Pool)
4348
if err != nil {
44-
log.Err("failed to get connection:", err)
49+
log.Err("failed to get connection:", err)
4550
return err
4651
}
4752

4853
defer func() {
54+
if err := serviceConn.Conn().Close(ctx); err != nil {
55+
log.Err("failed to close connection: ", err)
56+
}
57+
4958
serviceConn.Release()
5059
}()
5160

61+
tx, err := serviceConn.BeginTx(ctx, pgx.TxOptions{})
62+
if err != nil {
63+
log.Err("failed to begin transaction:", err)
64+
return err
65+
}
66+
67+
defer func() {
68+
if err := tx.Rollback(ctx); err != nil {
69+
log.Err("failed to rollback transaction:", err)
70+
}
71+
}()
72+
73+
txPID, err := querier.GetBackendPID(ctx, tx)
74+
if err != nil {
75+
log.Err("failed to get backend PID:", err)
76+
return err
77+
}
78+
5279
cmd := NewPlan(command, msg, session.CloneConnection, msgSvc)
5380
msgInitText, err := cmd.explainWithoutExecution(ctx)
5481
if err != nil {
5582
return errors.Wrap(err, "failed to run explain without execution")
5683
}
5784

58-
// Explain analyze request and processing.
59-
explainAnalyze, err := querier.DBQueryWithResponse(ctx, session.CloneConnection, queryExplainAnalyze+command.Query)
85+
explainAnalyze, err := querier.DBQueryWithResponse(ctx, tx, queryExplainAnalyze+command.Query)
6086
if err != nil {
6187
return err
6288
}
6389

64-
if err := serviceConn.Conn().Close(ctx); err != nil {
65-
log.Err("Failed to close connection: ", err)
66-
return err
90+
// Observe query locks.
91+
result, err := observeLocks(ctx, session.Pool, txPID)
92+
if err != nil {
93+
log.Err("failed to observe locks:", err)
6794
}
6895

6996
command.PlanExecJSON = explainAnalyze
@@ -84,6 +111,14 @@ func Explain(ctx context.Context, msgSvc connection.Messenger, command *platform
84111
msg.SetText(msgInitText)
85112
msg.AppendText(fmt.Sprintf("*Plan with execution:*\n```%s```", planExecPreview))
86113

114+
// Show query locks.
115+
tableString := &strings.Builder{}
116+
querier.RenderTable(tableString, result)
117+
118+
queryLocks := tableString.String()
119+
command.QueryLocks = strings.Trim(queryLocks, "`")
120+
msg.AppendText(locksTitle + queryLocks)
121+
87122
if err = msgSvc.UpdateText(msg); err != nil {
88123
log.Err("Show the plan with execution:", err)
89124

@@ -153,6 +188,23 @@ func Explain(ctx context.Context, msgSvc connection.Messenger, command *platform
153188
return nil
154189
}
155190

191+
func observeLocks(ctx context.Context, db *pgxpool.Pool, txPID int) ([][]string, error) {
192+
observeConn, err := getConn(ctx, db)
193+
if err != nil {
194+
return nil, err
195+
}
196+
197+
defer func() {
198+
if err := observeConn.Conn().Close(ctx); err != nil {
199+
log.Err("failed to close observer connection:", err)
200+
}
201+
202+
observeConn.Release()
203+
}()
204+
205+
return querier.ObserveLocks(ctx, observeConn, txPID)
206+
}
207+
156208
func listHypoIndexes(ctx context.Context, db pgxtype.Querier) ([]string, error) {
157209
rows, err := db.Query(ctx, "SELECT indexname FROM hypopg_list_indexes()")
158210
if err != nil {

‎pkg/bot/querier/sql.go

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,48 @@ func DBQueryWithResponse(ctx context.Context, db pgxtype.Querier, query string)
3434
return runQuery(ctx, db, query)
3535
}
3636

37+
const observeQuery = `with data as (
38+
select
39+
c.relkind,
40+
c.relnamespace::regnamespace,
41+
c.relname,
42+
format(
43+
'%s%s',
44+
nullif(c.relnamespace::regnamespace::text, 'public') || '.',
45+
coalesce(ind.tablename, relname)
46+
) as belongs_to_relation, -- todo: do the same for TOAST tables/indexes?
47+
l.locktype,
48+
l.mode,
49+
l.granted::text,
50+
l.fastpath::text
51+
from pg_locks as l
52+
join pg_class as c on c.oid = l.relation
53+
left join pg_indexes as ind on
54+
c.relkind = 'i'
55+
and indexname = c.relname
56+
and schemaname = c.relnamespace::regnamespace::name
57+
where l.pid = 1ドル
58+
)
59+
select *
60+
from data
61+
where
62+
belongs_to_relation not in ( -- not a perfect solution: <query> can also work with them
63+
'pg_catalog.pg_class',
64+
'pg_catalog.pg_indexes',
65+
'pg_catalog.pg_index',
66+
'pg_catalog.pg_locks',
67+
'pg_catalog.pg_namespace',
68+
'pg_catalog.pg_tablespace'
69+
)
70+
order by
71+
belongs_to_relation,
72+
case relkind when 'r' then 0 when 'v' then 1 when 'i' then 9 else 5 end;`
73+
74+
// ObserveLocks selects locks details filtered by pid.
75+
func ObserveLocks(ctx context.Context, db pgxtype.Querier, pid int) ([][]string, error) {
76+
return runTableQuery(ctx, db, observeQuery, pid)
77+
}
78+
3779
func runQuery(ctx context.Context, db pgxtype.Querier, query string) (string, error) {
3880
log.Dbg("DB query:", query)
3981

@@ -148,3 +190,14 @@ func clarifyQueryError(query []byte, err error) error {
148190

149191
return err
150192
}
193+
194+
// GetBackendPID returns backend pid.
195+
func GetBackendPID(ctx context.Context, conn pgxtype.Querier) (int, error) {
196+
var backendPID int
197+
198+
if err := conn.QueryRow(ctx, `select pg_backend_pid()`).Scan(&backendPID); err != nil {
199+
return backendPID, err
200+
}
201+
202+
return backendPID, nil
203+
}

‎pkg/services/platform/platform.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ type Command struct {
4343
PlanExecJSON string `json:"plan_execution_json"`
4444
Recommendations string `json:"recommendations"`
4545
Stats string `json:"stats"`
46+
QueryLocks string `json:"query_locks"`
4647

4748
Error string `json:"error"`
4849

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /