F.29. pg_stat_statements

F.29.1. pg_stat_statements
F.29.2. 함수들
F.29.3. 환경 설정 매개 변수들
F.29.4. 예제 출력
F.29.5. 만든이

pg_stat_statements 모듈은 서버에서 실행 되었던 쿼리들에 대한 실행계획과 실행 통계 정보를 보여준다.

이 모듈을 사용하려면, 먼저 shared_preload_libraries 서버 환경 설정 값에 pg_stat_statements 값이 추가 되어 있어야한다. 이 모듈은 추가적인 공유 메모리를 사용하기 때문에, 이렇게 서버 환경 설정도 바꾸어야 하며, 이것이 적용 하기 위해서는 서버도 다시 실행 해야한다. 물론 이 모듈을 더 이상 사용하지 않아 추가적인 공유 메모리 사용을 하지 않으려고 할 때도 마찮가지다.

pg_stat_statements 모듈이 로드되면, 이 때부터 해당 서버의 모든 데이터베이스에서 일어라는 쿼리 통계가 수집된다. 하지만, 이 통계를 보는 pg_stat_statements 뷰, 통계를 비우는 pg_stat_statements_reset 함수, 통계를 보는 pg_stat_statements 함수들은 각 데이터베이스 별로 지정해야 한다. 이 작업은 해당 데이터베이스로 접속해서 CREATE EXTENSION pg_stat_statements 명령을 이용하면 된다.

F.29.1. pg_stat_statements

이 모듈로 제공하는 쿼리 실행 통계 정보는 pg_stat_statements 이름의 뷰로 제공된다. 이 뷰는 하나의 로우가 하나의 식별되는 쿼리를 뜻하며, 부가적으로 그 쿼리가 실행되었던 데이터베이스 ID, 사용자 ID등을 포함 하고 있다. (이 뷰에서 제공하고 있는 최대 쿼리 개수는 이 모듈 환경 설정값에 따라 결정 된다.) 표 F.21 표에서는 이 뷰의 각 칼럼에 대한 설명을 하고 있다.

표 F.21. pg_stat_statements 칼럼들

칼럼명 자료형

설명

userid oid (references pg_authid.oid)

해당 쿼리를 실행 했던 사용자 OID

dbid oid (references pg_database.oid)

해당 쿼리가 실행 되었던 데이터베이스 OID

queryid bigint

해당 쿼리 통계 작업을 위한 내부용 계산된 해시 코드

query text

해당 쿼리의 구문 내용

plans bigint

해당 쿼리의 실행 계획을 짰던 총 수 (pg_stat_statements.track_planning 설정값이 on인 경우, off면 0)

total_plan_time double precision

해당 쿼리의 실행 계획을 짜는데 쓴 총 시간, 단위는 밀리세컨드 (pg_stat_statements.track_planning 설정값이 on인 경우, off면 0)

min_plan_time double precision

해당 쿼리의 실행 계획을 짜는데 쓴 시간 중 제일 짧은 시간, 단위는 밀리세컨드 (pg_stat_statements.track_planning 설정값이 on인 경우, off면 0)

max_plan_time double precision

해당 쿼리의 실행 계획을 짜는데 쓴 시간 중 제일 긴 시간, 단위는 밀리세컨드 (pg_stat_statements.track_planning 설정값이 on인 경우, off면 0)

mean_plan_time double precision

해당 쿼리의 실행 계획을 짜는데 쓴 평균 시간, 단위는 밀리세컨드 Mean time spent planning the statement, in milliseconds (pg_stat_statements.track_planning 설정값이 on인 경우, off면 0)

stddev_plan_time double precision

해당 쿼리가 실행 계획 짜는데 쓴 시간의 표준 편차, 단위는 밀리세컨드 (pg_stat_statements.track_planning 설정값이 on인 경우, off면 0)

calls bigint

해당 쿼리 총 실행 회수

total_exec_time double precision

해당 쿼리 실행 시간의 총 누적 시간, 단위는 밀리세컨드

min_exec_time double precision

해당 쿼리 실행 시간 중 제일 작은 값, 단위는 밀리세컨드

max_exec_time double precision

해당 쿼리 실행 시간 중 제일 큰 값, 단위는 밀리세컨드

mean_exec_time double precision

해당 쿼리의 평균 실행 시간, 단위는 밀리세컨드

stddev_exec_time double precision

해당 쿼리를 실행하는데 걸린 시간의 표준 편차, 단위는 밀리세컨드

rows bigint

해당 쿼리로 출력되거나 영향받은 총 로우 수

shared_blks_hit bigint

해당 쿼리가 공유 블록 캐시를 히트한 총 수

shared_blks_read bigint

해당 쿼리로 읽은 공유 블록 총 수

shared_blks_dirtied bigint

해당 쿼리로 생긴 더티 공유 블록 총 수

shared_blks_written bigint

해당 쿼리로 쓴 공유 블록 총 수

local_blks_hit bigint

해당 쿼리가 로컬 블록 캐시를 히트한 총 수

local_blks_read bigint

해당 쿼리로 읽은 로컬 블록 총 수

local_blks_dirtied bigint

해당 쿼리로 생긴 더티 로컬 블록 총 수

local_blks_written bigint

해당 쿼리로 쓴 로컬 블록 총 수

temp_blks_read bigint

해당 쿼리로 읽은 임시 블록 총 수

temp_blks_written bigint

해당 쿼리로 쓴 임시 블록 총 수

blk_read_time double precision

해당 쿼리가 블록 읽기에 쓴 총 시간, 단위는 밀리세컨드 (track_io_timing 설정값이 on인 경우, off면 0)

blk_write_time double precision

해당 쿼리가 블록 쓰기에 쓴 총 시간, 단위는 밀리세컨드 (track_io_timing 설정값이 on인 경우, off면 0)

wal_records bigint

해당 쿼리로 만들어진 WAL 레코드 총 수

wal_fpi bigint

해당 쿼리로 만들어진 WAL full page 이미지 총 수

wal_bytes numeric

해당 쿼리로 만들어진 WAL 총 크기, 단위는 바이트


보안 문제로, 슈퍼 유저와 pg_read_all_stats 롤 소속 사용자만 다른 사용자가 실행한 queryid나 쿼리 내용을를 볼 수 있다. 한편, 그 외 통계 정보는 일반 사용자도 볼 수 있다. (물론 해당 데이터베이스 이 모듈의 확장 기능이 설치 되어 있는 경우를 말한다)

실행계획을 짤 수 있는 쿼리들(SELECT, INSERT, UPDATE, DELETE 구문들)은 그 실행계획이 같은 쿼리들에 대해서는 그 조건 값들이 다르더라도 같은 쿼리로 취급한다. 즉, 쿼리에서 사용한 글자 그대로의 상수 값들만 달라서 실질적으로 그 실행계획이 같은 것들은 pg_stat_statements 뷰에서는 하나의 쿼리로 취급한다. 하지만, 앞에서 설명한 쿼리들이 아닌 객체 정의 명령들은 상수 값들과 관계 없이 무조건 개별 쿼리로 취급한다.

pg_stat_statements 뷰에서 상수 부분에 $1 형태로 표시된 것이 앞에서 설명한 동일 쿼리로 취급하기 위한 상수 값의 변경된 값이다. 이렇게 변경된 쿼리 문이 다음에도 사용된다면, 그 queryid로 같이 취급한다.

또한 몇몇 상황에서는 실제 쿼리가 다른 쿼리임에도 불구하고, pg_stat_statements 뷰에서 같은 항목의 쿼리로 취급되기도 한다. 일반적으로 이런 경우는 구문적으로 보면 동일 쿼리이지만, 그 해시 값이 약간 다른 경우, 같은 쿼리로 취급한다. (하지만, 이런 통합작업은 사용자나 데이터베이스가 다른 상황에서는 일어나지 않는다.)

이 해시 값 비교 방식은 쿼리 구문분석 다음에 처리 되기 때문에, 위와 반대로 동일 쿼리임에도 불구하고, search_path 와 같은 설정값이 다르기 때문에 다른 쿼리로 처리하는 경우도 발생할 수 있다.

Consumers of pg_stat_statements may wish to use queryid (perhaps in combination with dbid and userid) as a more stable and reliable identifier for each entry than its query text. However, it is important to understand that there are only limited guarantees around the stability of the queryid hash value. Since the identifier is derived from the post-parse-analysis tree, its value is a function of, among other things, the internal object identifiers appearing in this representation. This has some counterintuitive implications. For example, pg_stat_statements will consider two apparently-identical queries to be distinct, if they reference a table that was dropped and recreated between the executions of the two queries. The hashing process is also sensitive to differences in machine architecture and other facets of the platform. Furthermore, it is not safe to assume that queryid will be stable across major versions of PostgreSQL.

As a rule of thumb, queryid values can be assumed to be stable and comparable only so long as the underlying server version and catalog metadata details stay exactly the same. Two servers participating in replication based on physical WAL replay can be expected to have identical queryid values for the same query. However, logical replication schemes do not promise to keep replicas identical in all relevant details, so queryid will not be a useful identifier for accumulating costs across a set of logical replicas. If in doubt, direct testing is recommended.

The parameter symbols used to replace constants in representative query texts start from the next number after the highest $n parameter in the original query text, or $1 if there was none. It's worth noting that in some cases there may be hidden parameter symbols that affect this numbering. For example, PL/pgSQL uses hidden parameter symbols to insert values of function local variables into queries, so that a PL/pgSQL statement like SELECT i + 1 INTO j would have representative text like SELECT i + $2.

The representative query texts are kept in an external disk file, and do not consume shared memory. Therefore, even very lengthy query texts can be stored successfully. However, if many long query texts are accumulated, the external file might grow unmanageably large. As a recovery method if that happens, pg_stat_statements may choose to discard the query texts, whereupon all existing entries in the pg_stat_statements view will show null query fields, though the statistics associated with each queryid are preserved. If this happens, consider reducing pg_stat_statements.max to prevent recurrences.

plans and calls aren't always expected to match because planning and execution statistics are updated at their respective end phase, and only for successful operations. For example, if a statement is successfully planned but fails during the execution phase, only its planning statistics will be updated. If planning is skipped because a cached plan is used, only its execution statistics will be updated.

F.29.2. 함수들

pg_stat_statements_reset(userid Oid, dbid Oid, queryid bigint) returns void

pg_stat_statements_reset discards statistics gathered so far by pg_stat_statements corresponding to the specified userid, dbid and queryid. If any of the parameters are not specified, the default value 0(invalid) is used for each of them and the statistics that match with other parameters will be reset. If no parameter is specified or all the specified parameters are 0(invalid), it will discard all statistics. By default, this function can only be executed by superusers. Access may be granted to others using GRANT.

pg_stat_statements(showtext boolean) returns setof record

The pg_stat_statements view is defined in terms of a function also named pg_stat_statements. It is possible for clients to call the pg_stat_statements function directly, and by specifying showtext := false have query text be omitted (that is, the OUT argument that corresponds to the view's query column will return nulls). This feature is intended to support external tools that might wish to avoid the overhead of repeatedly retrieving query texts of indeterminate length. Such tools can instead cache the first query text observed for each entry themselves, since that is all pg_stat_statements itself does, and then retrieve query texts only as needed. Since the server stores query texts in a file, this approach may reduce physical I/O for repeated examination of the pg_stat_statements data.

F.29.3. 환경 설정 매개 변수들

pg_stat_statements.max (integer)

pg_stat_statements.max 값은 이 모듈에서 처리할 수 있는 최대 쿼리 수를 지정한다. pg_stat_statements 뷰에 보일 최대 로우 수를 뜻한다. If more distinct statements than that are observed, information about the least-executed statements is discarded. 초기값은 1000. 이 값은 서버 기동할 때만 지정할 수 있다.

pg_stat_statements.track (enum)

pg_stat_statements.track 설정은 수집할 쿼리문의 사용빈도에 따른 쿼리문 사용 통계 정보 수집범위를 지정한다. top으로 지정하면, 자주 사용하는 쿼리들을 대상으로 하고, all로 지정하면 모든 쿼리들을 대상으로, none으로 지정하면, 아무 쿼리도 수집하지 않는다. 초기값은 top 이다. 이 값은 슈퍼유저만 바꿀 수 있다.

pg_stat_statements.track_utility (boolean)

pg_stat_statements.track_utility 설정은 SELECT, INSERT, UPDATE, DELETE 구문 외 다른 구문들도 수집 대상으로 할 것인지를 지정한다. 초기값은 on 이다. 이 값은 슈퍼유저만 바꿀 수 있다.

pg_stat_statements.track_planning (boolean)

pg_stat_statements.track_planning controls whether planning operations and duration are tracked by the module. Enabling this parameter may incur a noticeable performance penalty, especially when a fewer kinds of queries are executed on many concurrent connections. The default value is off. Only superusers can change this setting.

pg_stat_statements.save (boolean)

pg_stat_statements.save 설정은 서버가 중지 되고, 재실행 되었을 때, 마지막 중지 시점의 쿼리문 통계 정보를 저장할 것인지를 지정한다. off로 설정하면, 서버가 재실행될 때 항상 모든 쿼리문에 대한 통계정보를 초기화 한다. 초기값은 on 이다. 이 환경설정 값은 postgresql.conf 파일에서 지정하거나 서버 실행 명령행 옵션에서만 지정할 수 있다.

이 모듈은 pg_stat_statements.max * track_activity_query_size 바이트만큼의 공유 메모리를 추가로 필요로 한다. 참고할 사항은 pg_stat_statements.track 값을 none으로 지정했다 하더라도 앞의 계산식 만큼의 공유 메모리는 항상 사용한다는 점이다.

postgresql.conf 파일에 이 모듈을 사용할 때의 기본 내용은 다음과 같다:

# postgresql.conf
shared_preload_libraries = 'pg_stat_statements'

pg_stat_statements.max = 10000
pg_stat_statements.track = all

F.29.4. 예제 출력

bench=# SELECT pg_stat_statements_reset();

$ pgbench -i bench
$ pgbench -c10 -t300 bench

bench=# \x
bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
               nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
          FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2
calls           | 3000
total_exec_time | 25565.855387
rows            | 3000
hit_percent     | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls           | 3000
total_exec_time | 20756.669379
rows            | 3000
hit_percent     | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------​------------------
query           | copy pgbench_accounts from stdin
calls           | 1
total_exec_time | 291.865911
rows            | 100000
hit_percent     | 100.0000000000000000
-[ RECORD 4 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls           | 3000
total_exec_time | 271.232977
rows            | 3000
hit_percent     | 98.8454011741682975
-[ RECORD 5 ]---+--------------------------------------------------​------------------
query           | alter table pgbench_accounts add primary key (aid)
calls           | 1
total_exec_time | 160.588563
rows            | 0
hit_percent     | 100.0000000000000000

bench=# SELECT pg_stat_statements_reset(0,0,s.queryid) FROM pg_stat_statements AS s
            WHERE s.query = 'UPDATE pgbench_branches SET bbalance = bbalance + $1 WHERE bid = $2';

bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
               nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
          FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_tellers SET tbalance = tbalance + $1 WHERE tid = $2
calls           | 3000
total_exec_time | 20756.669379
rows            | 3000
hit_percent     | 100.0000000000000000
-[ RECORD 2 ]---+--------------------------------------------------​------------------
query           | copy pgbench_accounts from stdin
calls           | 1
total_exec_time | 291.865911
rows            | 100000
hit_percent     | 100.0000000000000000
-[ RECORD 3 ]---+--------------------------------------------------​------------------
query           | UPDATE pgbench_accounts SET abalance = abalance + $1 WHERE aid = $2
calls           | 3000
total_exec_time | 271.232977
rows            | 3000
hit_percent     | 98.8454011741682975
-[ RECORD 4 ]---+--------------------------------------------------​------------------
query           | alter table pgbench_accounts add primary key (aid)
calls           | 1
total_exec_time | 160.588563
rows            | 0
hit_percent     | 100.0000000000000000
-[ RECORD 5 ]---+--------------------------------------------------​------------------
query           | vacuum analyze pgbench_accounts
calls           | 1
total_exec_time | 136.448116
rows            | 0
hit_percent     | 99.9201915403032721

bench=# SELECT pg_stat_statements_reset(0,0,0);

bench=# SELECT query, calls, total_exec_time, rows, 100.0 * shared_blks_hit /
               nullif(shared_blks_hit + shared_blks_read, 0) AS hit_percent
          FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT 5;
-[ RECORD 1 ]---+--------------------------------------------------​---------------------------
query           | SELECT pg_stat_statements_reset(0,0,0)
calls           | 1
total_exec_time | 0.189497
rows            | 1
hit_percent     | 
-[ RECORD 2 ]---+--------------------------------------------------​---------------------------
query           | SELECT query, calls, total_exec_time, rows, $1 * shared_blks_hit /          +
                |                nullif(shared_blks_hit + shared_blks_read, $2) AS hit_percent+
                |           FROM pg_stat_statements ORDER BY total_exec_time DESC LIMIT $3
calls           | 0
total_exec_time | 0
rows            | 0
hit_percent     | 

F.29.5. 만든이

Takahiro Itagaki . Query normalization added by Peter Geoghegan .