Location via proxy:   [ UP ]  
[Report a bug]   [Manage cookies]                
Skip to content

Commit 54b6cd5

Browse files
committed
Speedup pgstat_report_activity by moving mb-aware truncation to read side.
Previously multi-byte aware truncation was done on every pgstat_report_activity() call - proving to be a bottleneck for workloads with long query strings that execute quickly. Instead move the truncation to the read side, which commonly is executed far less frequently. That's possible because all server encodings allow to determine the length of a multi-byte string from the first byte. Rename PgBackendStatus.st_activity to st_activity_raw so existing extension users of the field break - their code has to be adjusted to use pgstat_clip_activity(). Author: Andres Freund Tested-By: Khuntal Ghosh Reviewed-By: Robert Haas, Tom Lane Discussion: https://postgr.es/m/20170912071948.pa7igbpkkkviecpz@alap3.anarazel.de
1 parent d1687c6 commit 54b6cd5

File tree

3 files changed

+72
-20
lines changed

3 files changed

+72
-20
lines changed

src/backend/postmaster/pgstat.c

+49-14
Original file line numberDiff line numberDiff line change
@@ -2701,7 +2701,7 @@ CreateSharedBackendStatus(void)
27012701
buffer = BackendActivityBuffer;
27022702
for (i = 0; i < NumBackendStatSlots; i++)
27032703
{
2704-
BackendStatusArray[i].st_activity = buffer;
2704+
BackendStatusArray[i].st_activity_raw = buffer;
27052705
buffer += pgstat_track_activity_query_size;
27062706
}
27072707
}
@@ -2922,11 +2922,11 @@ pgstat_bestart(void)
29222922
#endif
29232923
beentry->st_state = STATE_UNDEFINED;
29242924
beentry->st_appname[0] = '\0';
2925-
beentry->st_activity[0] = '\0';
2925+
beentry->st_activity_raw[0] = '\0';
29262926
/* Also make sure the last byte in each string area is always 0 */
29272927
beentry->st_clienthostname[NAMEDATALEN - 1] = '\0';
29282928
beentry->st_appname[NAMEDATALEN - 1] = '\0';
2929-
beentry->st_activity[pgstat_track_activity_query_size - 1] = '\0';
2929+
beentry->st_activity_raw[pgstat_track_activity_query_size - 1] = '\0';
29302930
beentry->st_progress_command = PROGRESS_COMMAND_INVALID;
29312931
beentry->st_progress_command_target = InvalidOid;
29322932

@@ -3017,7 +3017,7 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
30173017
pgstat_increment_changecount_before(beentry);
30183018
beentry->st_state = STATE_DISABLED;
30193019
beentry->st_state_start_timestamp = 0;
3020-
beentry->st_activity[0] = '\0';
3020+
beentry->st_activity_raw[0] = '\0';
30213021
beentry->st_activity_start_timestamp = 0;
30223022
/* st_xact_start_timestamp and wait_event_info are also disabled */
30233023
beentry->st_xact_start_timestamp = 0;
@@ -3034,8 +3034,12 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
30343034
start_timestamp = GetCurrentStatementStartTimestamp();
30353035
if (cmd_str != NULL)
30363036
{
3037-
len = pg_mbcliplen(cmd_str, strlen(cmd_str),
3038-
pgstat_track_activity_query_size - 1);
3037+
/*
3038+
* Compute length of to-be-stored string unaware of multi-byte
3039+
* characters. For speed reasons that'll get corrected on read, rather
3040+
* than computed every write.
3041+
*/
3042+
len = Min(strlen(cmd_str), pgstat_track_activity_query_size - 1);
30393043
}
30403044
current_timestamp = GetCurrentTimestamp();
30413045

@@ -3049,8 +3053,8 @@ pgstat_report_activity(BackendState state, const char *cmd_str)
30493053

30503054
if (cmd_str != NULL)
30513055
{
3052-
memcpy((char *) beentry->st_activity, cmd_str, len);
3053-
beentry->st_activity[len] = '\0';
3056+
memcpy((char *) beentry->st_activity_raw, cmd_str, len);
3057+
beentry->st_activity_raw[len] = '\0';
30543058
beentry->st_activity_start_timestamp = start_timestamp;
30553059
}
30563060

@@ -3278,8 +3282,8 @@ pgstat_read_current_status(void)
32783282
*/
32793283
strcpy(localappname, (char *) beentry->st_appname);
32803284
localentry->backendStatus.st_appname = localappname;
3281-
strcpy(localactivity, (char *) beentry->st_activity);
3282-
localentry->backendStatus.st_activity = localactivity;
3285+
strcpy(localactivity, (char *) beentry->st_activity_raw);
3286+
localentry->backendStatus.st_activity_raw = localactivity;
32833287
localentry->backendStatus.st_ssl = beentry->st_ssl;
32843288
#ifdef USE_SSL
32853289
if (beentry->st_ssl)
@@ -3945,10 +3949,13 @@ pgstat_get_backend_current_activity(int pid, bool checkUser)
39453949
/* Now it is safe to use the non-volatile pointer */
39463950
if (checkUser && !superuser() && beentry->st_userid != GetUserId())
39473951
return "<insufficient privilege>";
3948-
else if (*(beentry->st_activity) == '\0')
3952+
else if (*(beentry->st_activity_raw) == '\0')
39493953
return "<command string not enabled>";
39503954
else
3951-
return beentry->st_activity;
3955+
{
3956+
/* this'll leak a bit of memory, but that seems acceptable */
3957+
return pgstat_clip_activity(beentry->st_activity_raw);
3958+
}
39523959
}
39533960

39543961
beentry++;
@@ -3994,7 +4001,7 @@ pgstat_get_crashed_backend_activity(int pid, char *buffer, int buflen)
39944001
if (beentry->st_procpid == pid)
39954002
{
39964003
/* Read pointer just once, so it can't change after validation */
3997-
const char *activity = beentry->st_activity;
4004+
const char *activity = beentry->st_activity_raw;
39984005
const char *activity_last;
39994006

40004007
/*
@@ -4017,7 +4024,8 @@ pgstat_get_crashed_backend_activity(int pid, char *buffer, int buflen)
40174024
/*
40184025
* Copy only ASCII-safe characters so we don't run into encoding
40194026
* problems when reporting the message; and be sure not to run off
4020-
* the end of memory.
4027+
* the end of memory. As only ASCII characters are reported, it
4028+
* doesn't seem necessary to perform multibyte aware clipping.
40214029
*/
40224030
ascii_safe_strlcpy(buffer, activity,
40234031
Min(buflen, pgstat_track_activity_query_size));
@@ -6270,3 +6278,30 @@ pgstat_db_requested(Oid databaseid)
62706278

62716279
return false;
62726280
}
6281+
6282+
/*
6283+
* Convert a potentially unsafely truncated activity string (see
6284+
* PgBackendStatus.st_activity_raw's documentation) into a correctly truncated
6285+
* one.
6286+
*
6287+
* The returned string is allocated in the caller's memory context and may be
6288+
* freed.
6289+
*/
6290+
char *
6291+
pgstat_clip_activity(const char *activity)
6292+
{
6293+
int rawlen = strnlen(activity, pgstat_track_activity_query_size - 1);
6294+
int cliplen;
6295+
6296+
/*
6297+
* All supported server-encodings make it possible to determine the length
6298+
* of a multi-byte character from its first byte (this is not the case for
6299+
* client encodings, see GB18030). As st_activity is always stored using
6300+
* server encoding, this allows us to perform multi-byte aware truncation,
6301+
* even if the string earlier was truncated in the middle of a multi-byte
6302+
* character.
6303+
*/
6304+
cliplen = pg_mbcliplen(activity, rawlen,
6305+
pgstat_track_activity_query_size - 1);
6306+
return pnstrdup(activity, cliplen);
6307+
}

src/backend/utils/adt/pgstatfuncs.c

+13-4
Original file line numberDiff line numberDiff line change
@@ -664,6 +664,7 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
664664
is_member_of_role(GetUserId(), DEFAULT_ROLE_READ_ALL_STATS))
665665
{
666666
SockAddr zero_clientaddr;
667+
char *clipped_activity;
667668

668669
switch (beentry->st_state)
669670
{
@@ -690,7 +691,9 @@ pg_stat_get_activity(PG_FUNCTION_ARGS)
690691
break;
691692
}
692693

693-
values[5] = CStringGetTextDatum(beentry->st_activity);
694+
clipped_activity = pgstat_clip_activity(beentry->st_activity_raw);
695+
values[5] = CStringGetTextDatum(clipped_activity);
696+
pfree(clipped_activity);
694697

695698
proc = BackendPidGetProc(beentry->st_procpid);
696699
if (proc != NULL)
@@ -906,17 +909,23 @@ pg_stat_get_backend_activity(PG_FUNCTION_ARGS)
906909
int32 beid = PG_GETARG_INT32(0);
907910
PgBackendStatus *beentry;
908911
const char *activity;
912+
char *clipped_activity;
913+
text *ret;
909914

910915
if ((beentry = pgstat_fetch_stat_beentry(beid)) == NULL)
911916
activity = "<backend information not available>";
912917
else if (!has_privs_of_role(GetUserId(), beentry->st_userid))
913918
activity = "<insufficient privilege>";
914-
else if (*(beentry->st_activity) == '\0')
919+
else if (*(beentry->st_activity_raw) == '\0')
915920
activity = "<command string not enabled>";
916921
else
917-
activity = beentry->st_activity;
922+
activity = beentry->st_activity_raw;
918923

919-
PG_RETURN_TEXT_P(cstring_to_text(activity));
924+
clipped_activity = pgstat_clip_activity(activity);
925+
ret = cstring_to_text(activity);
926+
pfree(clipped_activity);
927+
928+
PG_RETURN_TEXT_P(ret);
920929
}
921930

922931
Datum

src/include/pgstat.h

+10-2
Original file line numberDiff line numberDiff line change
@@ -1003,8 +1003,14 @@ typedef struct PgBackendStatus
10031003
/* application name; MUST be null-terminated */
10041004
char *st_appname;
10051005

1006-
/* current command string; MUST be null-terminated */
1007-
char *st_activity;
1006+
/*
1007+
* Current command string; MUST be null-terminated. Note that this string
1008+
* possibly is truncated in the middle of a multi-byte character. As
1009+
* activity strings are stored more frequently than read, that allows to
1010+
* move the cost of correct truncation to the display side. Use
1011+
* pgstat_clip_activity() to truncate correctly.
1012+
*/
1013+
char *st_activity_raw;
10081014

10091015
/*
10101016
* Command progress reporting. Any command which wishes can advertise
@@ -1193,6 +1199,8 @@ extern PgStat_BackendFunctionEntry *find_funcstat_entry(Oid func_id);
11931199

11941200
extern void pgstat_initstats(Relation rel);
11951201

1202+
extern char *pgstat_clip_activity(const char *activity);
1203+
11961204
/* ----------
11971205
* pgstat_report_wait_start() -
11981206
*

0 commit comments

Comments
 (0)