En complément bref de l'article .
La tĂąche
Il est nĂ©cessaire de lier l'historique des vues pg_stat_statements et pg_stat_activity. En consĂ©quence, en utilisant l'historique des plans d'exĂ©cution de la table de service log_query, il est possible d'obtenir de nombreuses informations utiles pour rĂ©soudre les incidents de performance et optimiser les requĂȘtes.
Avertissement.
En raison de la poursuite des tests et du dĂ©veloppement, cet article ne peut prĂ©tendre Ă la description d'une solution industrielle prĂȘte.
Les critiques et commentaires concernant la mise en Ćuvre sont les bienvenus et attendus.
Données d'entrée
Table history_pg_stat_activity
--ACTIVITY_HIST.HISTORY_PG_STAT_ACTIVITY
DROP TABLE IF EXISTS activity_hist.history_pg_stat_activity;
CREATE TABLE activity_hist.history_pg_stat_activity
(
timepoint timestamp without time zone ,
datid oid ,
datname name ,
pid integer,
usesysid oid ,
usename name ,
application_name text ,
client_addr inet ,
client_hostname text ,
client_port integer,
backend_start timestamp without time zone ,
xact_start timestamp without time zone ,
query_start timestamp without time zone ,
state_change timestamp without time zone ,
wait_event_type text ,
wait_event text ,
state text ,
backend_xid xid ,
backend_xmin xid ,
query text ,
backend_type text ,
queryid bigint
);Table pg_stat_db_queries
CREATE TABLE pg_stat_db_queries
(
database_id integer ,
queryid bigint ,
query text ,
max_time double precision
);Vue matérialisée mvw_pg_stat_queries
CREATE MATERIALIZED VIEW public.mvw_pg_stat_queries AS
SELECT t.queryid,
t.max_time,
t.query
FROM public.dblink('LINK1'::text, 'SELECT queryid , max_time , query FROM pg_stat_statements WHERE dbid=(SELECT oid FROM pg_database WHERE datname=current_database() ) AND max_time >= 0 '::text) t(queryid bigint, max_time double precision, query text)
WITH NO DATA;
Table log_query
CREATE TABLE log_query
(
id integer ,
queryid bigint ,
query_md5hash text ,
database_id integer ,
timepoint timestamp without time zone ,
query text ,
explained_plan text[] ,
plan_md5hash text ,
explained_plan_wo_costs text[] ,
plan_hash_value text ,
ip text,
port text ,
pid integer
);Algorithme général
Mettre Ă jour la table pg_stat_db_queries
Mettre à jour la vue matérialisée mvw_pg_stat_queries
CREATE OR REPLACE FUNCTION refresh_pg_stat_queries_list( database_id int) RETURNS BOOLEAN AS $$
DECLARE
result BOOLEAN ;
database_rec record ;
BEGIN
SELECT *
INTO database_rec
FROM endpoint e JOIN database d ON e.id = d.endpoint_id
WHERE d.id = database_id ;
IF NOT database_rec.is_need_monitoring THEN RAISE NOTICE 'AUCUN BESOIN DE SURVEILLANCE POUR database_id=%',database_id; return TRUE ; END IF ;
EXECUTE 'SELECT dblink_connect(''LINK1'',''host='||database_rec.host||' port=5432 dbname='||database_rec.name||
' user='||database_rec.s_name||' password='||database_rec.s_pass|| ' '')';
REFRESH MATERIALIZED VIEW mvw_pg_stat_queries ;
PERFORM dblink_disconnect('LINK1');
RETURN result;
END
$$ LANGUAGE plpgsql;Remplir la table pg_stat_db_queries
CRĂER OU REMPLACER LA FONCTION refresh_pg_stat_db_queries( ) RETOURNE BOOLEAN AS $$
DĂCLARE
result BOOLEAN ;
base_de_données_rec enregistrement ;
pg_stat_rec enregistrement ;
DĂBUT
TRONQUER pg_stat_db_queries;
POUR base_de_données_rec DANS
SĂLECTIONNER *
DE la base_de_données d
BOUCLE
SI PAS base_de_donnĂ©es_rec.is_need_monitoring ALORS SOULĂVE AVIS 'PAS BESOIN DE SURVEILLER POUR database_id=%',base_de_donnĂ©es_rec.id; CONTINUER ; FIN SI ;
EFFECTUER refresh_pg_stat_queries_list( base_de_données_rec.id ) ;
POUR pg_stat_rec DANS
SĂLECTIONNER *
DE mvw_pg_stat_queries
BOUCLE
INSĂRER DANS pg_stat_db_queries
( database_id , queryid , query , max_time )
VALEURS
( base_de_données_rec.id , pg_stat_rec.queryid , pg_stat_rec.query , pg_stat_rec.max_time);
FIN BOUCLE;
FIN BOUCLE;
RETOURNER TRUE;
FIN
$$ LANGAGE plpgsql;En consĂ©quence, la table contient des textes de requĂȘtes normalisĂ©s, queryid, et le temps d'exĂ©cution maximum de la requĂȘte Ă l'heure actuelle (utilisĂ© pour le monitoring).
Remplissage de log_query et formation de l'historique des plans d'exécution.
Le texte actuel de la requĂȘte est extrait du fichier log. Le fichier log est transfĂ©rĂ© de l'hĂŽte cible vers l'hĂŽte de monitoring par morceaux, Ă l'aide d'un script bash, via cron. Pour Ă©conomiser de l'espace et en raison de la simplicitĂ© de la tĂąche de copie d'un morceau de fichier texte de l'hĂŽte Ă l'hĂŽte, le script n'est pas fourni.
Analyse du fichier log et extraction du texte de la requĂȘte.
#!/bin/bash
#########################################################
# upload_log_query.sh
# Upload table table from dowloaded aws file
# version 12.0
###########################################################
echo 'TIMESTAMP:'$(date +%c)' Upload log_query table '
source_file=$1
echo 'source_file='$source_file
database_id=$2
echo 'database_id='$database_id
database_name=$3
echo 'database_name='$database_name
beginer=' '
first_line='1'
let "line_count=0"
sql_line=' '
sql_flag=' '
space=' '
cat $source_file | while read line
do
#first line will be passed
if [[ $line_count == '0' ]]; then
let "line_count++"
continue
fi
line="$space$line"
#echo 'line='$line
if [[ $first_line == "1" ]]; then
beginer=`echo $line | awk -F" " '{ print $1}' `
first_line='0'
fi
current_beginer=`echo $line | awk -F" " '{ print $1}' `
#echo 'current_beginer='$current_beginer
#echo 'beginer='$beginer
if [[ $current_beginer == $beginer ]]; then
if [[ $sql_flag == '1' ]]; then
sql_flag='0'
#echo 'TIMESTAMP:'$(date +%c)' Upload log_query table : SQL STATEMENT ='"$sql_line"
log_date=`echo $sql_line | awk -F" " '{ print $1}' `
#echo 'log_date='$log_date
log_time=`echo $sql_line | awk -F" " '{ print $2}' `
#echo 'log_time='$log_time
duration=`echo $sql_line | awk -F" " '{ print $5}' `
#echo 'duration='$duration
connect=`echo $sql_line | awk -F" " '{ print $3}' `
userdb=`echo $connect | awk -F":" '{ print $3}' `
userdb2=$userdb'@'
db_port_log=`echo $connect | awk -F"@" '{ print $2}' `
log_database_name=`echo $db_port_log | awk -F":" '{ print $1}' `
#echo 'connect='$connect
#echo 'userdb='$userdb
#echo 'userdb2='$userdb2
#echo 'db_port_log='$db_port_log
#echo 'log_database_name='$log_database_name
if [[ "$log_database_name" != "$database_name" ]];
then
echo '*** database_name '$log_database_name' from log is not equal '$database_name' CONTINUE '
continue;
fi
#replace ' to ''
sql_modline=`echo "$sql_line" | sed 's/'''/''''''/g'`
sql_line=' '
#echo '*********************************log_query start'
#echo 'pid_str='$pid_str
#echo 'ip_port='$ip_port
#echo 'database_id='$database_id
#echo 'log_date='$log_date
#echo 'log_time='$log_time
#echo 'duration='$duration
#echo 'sql_modline='$sql_modline
if ! psql -U monitor -d monitor -v ON_ERROR_STOP=1 -A -t -q -c "select log_query( '$pid_str' , '$ip_port' , $database_id , '$log_date' , '$log_time' , '$duration' , '$sql_modline' )"
then
echo 'FATAL_ERROR - log_query '
exit 1
fi
#echo '**********************************log_query finish'
fi #if [[ $sql_flag == '1' ]]; then
let "line_count=line_count+1"
#echo 'line_count= '$line_count
#echo $line
#check=`echo $line | awk -F" " '{ print $8}' `
#check_sql=${check^^}
#echo 'check_sql='$check_sql
if [[ ${line^^} =~ "SELECT" ]];
then
if [[ $line =~ "duration:" ]];
then
test_statement=`echo $line | awk -F" " '{ print $8}'`
is_select=${test_statement^^}
#echo 'test_statement='$test_statement
#echo 'is_select='$is_select
if [[ $is_select == 'SELECT' ]];
then
sql_flag='1'
sql_line="$sql_line$line"
ip_port=`echo $sql_line | awk -F":" '{ print $4}' `
pid_str=`echo $sql_line | awk -F":" '{ print $6}' `
fi
fi
fi
else
#echo $line
#echo 'sql_flag ='$sql_flag
if [[ $sql_flag == '1' ]]; then
sql_line="$sql_line$line"
fi
fi #if [[ $current_beginer == $beginer ]]; then
doneRemplissage de la table log_query.
--log_query.sql
--insĂ©rer une nouvelle requĂȘte dans la table log_query
CRĂER OU REMPLACER LA FONCTION log_query( pid_str texte , ip_port texte , log_database_id entier , log_date texte , log_time texte , duration texte , sql_line texte ) RENVERSER boolean AS $$
DĂCLARE
résultat boolean ;
log_timepoint horodatage sans fuseau horaire ;
log_duration double précision ;
pos entier ;
log_query texte ;
activity_string texte ;
log_md5hash texte ;
log_explain_plan texte[] ;
log_planhash texte ;
log_plan_wo_costs texte[] ;
database_rec enregistrement ;
pg_stat_query texte ;
test_log_query texte ;
metric_rec enregistrement;
log_query_rec enregistrement;
found_flag boolean;
pg_stat_history_rec enregistrement ;
port_start entier ;
port_end entier ;
client_ip texte ;
client_port texte ;
log_queryid biginteger ;
log_query_text texte ;
pg_stat_query_text texte ;
current_pid_str texte ;
current_pid entier ;
pid_start_pos entier ;
pid_finish_pos entier ;
DĂBUT
résultat = TRUE ;
SI ip_port != '[local]' ALORS
port_start = position('(' dans ip_port);
port_end = position(')' dans ip_port);
client_ip = substring( ip_port Ă partir de 1 pour port_start-1 );
client_port = substring( ip_port Ă partir de port_start+1 pour port_end-port_start-1 );
SINON
client_ip = 'local';
client_port = 'local';
FIN SI;
pid_start_pos = position('[' dans pid_str);
pid_finish_pos = position(']' dans pid_str);
current_pid_str=substring( pid_str Ă partir de 2 pour pid_finish_pos - pid_start_pos -1 );
current_pid = to_number(current_pid_str , '999999999999');
SĂLECTIONNER e.host , d.name , d.owner_pwd , d.owner_user
DANS database_rec
DE database d JOINDRE endpoint e SUR e.id = d.endpoint_id
OĂ d.id = log_database_id ;
log_timepoint = to_timestamp(log_date||' '||log_time,'YYYY-MM-DD HH24-MI-SS');
log_duration = duration::double précision ;
pos = position ('SELECT' dans UPPER(sql_line) );
log_query = substring( sql_line Ă partir de pos pour LONGUEUR(sql_line));
log_query = regexp_replace(log_query,' +',' ','g');
log_query = regexp_replace(log_query,';+','','g');
log_query = trim(trailing ' ' de log_query);
log_md5hash = md5( log_query::texte );
--Expliquer le plan d'exécution--
EXĂCUTER 'SĂLECTIONNER dblink_connect(''LINK1'',''host='||database_rec.host||' port=5432 dbname='||database_rec.name||' user='||database_rec.owner_user||' password='||database_rec.owner_pwd||' '')';
log_explain_plan = ARRAY ( SĂLECTIONNER * DE dblink('LINK1', 'EXPLAIN '||log_query ) AS t (plan texte) );
log_plan_wo_costs = ARRAY ( SĂLECTIONNER * DE dblink('LINK1', 'EXPLAIN ( COSTS FALSE ) '||log_query ) AS t (plan texte) );
EFFECTUER dblink_disconnect('LINK1');
--------------------------
DĂBUT
INSĂRER DANS log_query
(
query_md5hash ,
database_id ,
timepoint ,
duration ,
query ,
explained_plan ,
plan_md5hash ,
explained_plan_wo_costs ,
plan_hash_value ,
ip ,
port ,
pid
)
VALEURS
(
log_md5hash ,
log_database_id ,
log_timepoint ,
log_duration ,
log_query ,
log_explain_plan ,
md5(log_explain_plan::texte) ,
log_plan_wo_costs ,
md5(log_plan_wo_costs::texte),
client_ip ,
client_port ,
current_pid
);
activity_string = 'Nouvelle requĂȘte enregistrĂ©e '||
' database_id = '|| log_database_id ||
' query_md5hash='||log_md5hash||
' , timepoint = '||to_char(log_timepoint,'YYYYMMDD HH24:MI:SS');
EFFECTUER pg_log( log_database_id , 'log_query' , activity_string);
EXCEPTION
QUAND unique_violation ALORS
activity_string = 'EXCEPTION *** la requĂȘte a dĂ©jĂ Ă©tĂ© enregistrĂ©e '||
' database_id = '|| log_database_id ||
' query_md5hash='||log_md5hash||
' , timepoint = '||to_char(log_timepoint,'YYYYMMDD HH24:MI:SS');
EFFECTUER pg_log( log_database_id , 'log_query' , activity_string);
FIN;
SĂLECTIONNER queryid
DANS log_queryid
DE log_query
OĂ query_md5hash = log_md5hash ET
timepoint = log_timepoint;
SI log_queryid EST NON NULL
ALORS
RETOURNER résultat;
FIN SI;
------------------------------------------------
SĂLECTIONNER *
DANS log_query_rec
DE log_query
OĂ query_md5hash = log_md5hash ET timepoint = log_timepoint ;
log_query_rec.query=regexp_replace(log_query_rec.query,';+','','g');
POUR pg_stat_history_rec DANS
SĂLECTIONNER
queryid ,
query
DE
pg_stat_db_queries
OĂ
database_id = log_database_id ET
queryid est non nul
BOUCLE
pg_stat_query = pg_stat_history_rec.query ;
pg_stat_query=regexp_replace(pg_stat_query,'n+',' ','g');
pg_stat_query=regexp_replace(pg_stat_query,'t+',' ','g');
pg_stat_query=regexp_replace(pg_stat_query,' +',' ','g');
pg_stat_query=regexp_replace(pg_stat_query,'$.','%','g');
log_query_text = trim(trailing ' ' de log_query_rec.query);
pg_stat_query_text = pg_stat_query;
SI (log_query_text LIKE pg_stat_query_text) ALORS
found_flag = TRUE ;
SINON
found_flag = FALSE ;
FIN SI;
SI found_flag ALORS
METTRE Ă JOUR log_query FIXER queryid = pg_stat_history_rec.queryid OĂ query_md5hash = log_md5hash ET timepoint = log_timepoint ;
activity_string = ' queryid mis Ă jour = '||pg_stat_history_rec.queryid||
' pour log_query avec id = '||log_query_rec.id;
SORTIR ;
FIN SI ;
FIN BOUCLE ;
RETOURNER résultat ;
FIN
$$ LANGAGE plpgsql;En consĂ©quence, la table contient le texte de la requĂȘte actuelle, les plans d'exĂ©cution, la valeur de hachage du plan d'exĂ©cution et la valeur de hachage du texte de la requĂȘte.
Remplir la valeur queryid dans la table history_pg_stat_activity
update_history_pg_stat_activity_by_queryid.sql
--update_history_pg_stat_activity_by_queryid.sql
CREATE OR REPLACE FUNCTION update_history_pg_stat_activity_by_queryid() RETURNS boolean AS $$
DECLARE
result boolean ;
history_pg_stat_activity_rec record ;
pg_stat_query text ;
pg_stat_query_text text ;
pg_stat_history_rec record;
found_flag boolean;
history_pg_stat_activity_query text ;
query_text text ;
activity_string text ;
BEGIN
RAISE NOTICE '***update_history_pg_stat_activity_by_queryid';
result = TRUE ;
FOR history_pg_stat_activity_rec IN
SELECT DISTINCT(query) AS query
FROM activity_hist.history_pg_stat_activity
WHERE queryid IS NULL
LOOP
history_pg_stat_activity_query = regexp_replace(history_pg_stat_activity_rec.query,'n+',' ','g');
history_pg_stat_activity_query = regexp_replace(history_pg_stat_activity_query,'t+',' ','g');
history_pg_stat_activity_query = regexp_replace(history_pg_stat_activity_query,' +',' ','g');
history_pg_stat_activity_query = regexp_replace(history_pg_stat_activity_query,';','','g');
query_text = trim(trailing ' ' from history_pg_stat_activity_query);
FOR pg_stat_history_rec IN
SELECT
queryid ,
query
FROM
--pg_stat_history
pg_stat_db_queries
WHERE
queryid is not null
GROUP BY queryid , query
LOOP
pg_stat_query = pg_stat_history_rec.query ;
pg_stat_query=regexp_replace(pg_stat_query,'n+',' ','g');
pg_stat_query=regexp_replace(pg_stat_query,'t+',' ','g');
pg_stat_query=regexp_replace(pg_stat_query,' +',' ','g');
pg_stat_query=regexp_replace(pg_stat_query,'$.','%','g');
pg_stat_query_text = pg_stat_query;
IF (query_text LIKE pg_stat_query_text) THEN
found_flag = TRUE ;
ELSE
found_flag = FALSE ;
END IF;
IF found_flag
THEN
UPDATE activity_hist.history_pg_stat_activity
SET queryid = pg_stat_history_rec.queryid
WHERE regexp_replace(regexp_replace(regexp_replace(regexp_replace(query,'n+',' ','g'),'t+',' ','g'),' +',' ','g'),';','','g')
LIKE query_text||'%' ;
activity_string = 'history_pg_stat_activity has updated by queryid = '||pg_stat_history_rec.queryid;
RAISE NOTICE '%',activity_string;
PERFORM pg_log( 999 , 'update_history_pg_stat_activity_by_queryid' , activity_string);
EXIT ;
END IF ;
END LOOP ;
IF NOT found_flag
THEN
activity_string = 'WARNING : Not FOUND queryid for the query : '||query_text ;
RAISE NOTICE '%',activity_string;
PERFORM pg_log( 999 , 'update_history_pg_stat_activity_by_queryid' , activity_string);
END IF ;
RAISE NOTICE 'UPDATE log_query if query has not logged in log-file';
END LOOP;
RETURN result ;
END
$$ LANGUAGE plpgsql;En consĂ©quence, la table contient la valeur queryid correspondant Ă la valeur queryid de la requĂȘte.
Conclusion
En reliant pg_stat_activity, pg_stat_statements, log_query, on peut obtenir de nombreuses informations utiles sur la requĂȘte, notamment :
- Historique des plans d'exécution.
- Historique du temps CPU de la requĂȘte.
- Historique des attentes de la requĂȘte.
Les données et de nombreux rapports supplémentaires seront décrits dans l'article suivant.
Développement
En liant les informations disponibles avec l'historique de prĂ©sentation de pg_locks, on peut obtenir des informations sur le type de verrouillage que la requĂȘte attendait et, surtout, quel processus (requĂȘte) dĂ©tenait ce verrouillage.
La solution à ce problÚme sera décrite dans l'article suivant. Actuellement, nous sommes en phase de test et de développement.
Source : habr.com
