public inbox for [email protected]
help / color / mirror / Atom feed[PATCH 3/5] pg_rewind: Replace the hybrid list+array data structure with simplehash.
4+ messages / 3 participants
[nested] [flat]
* [PATCH 3/5] pg_rewind: Replace the hybrid list+array data structure with simplehash.
@ 2020-08-19 12:34 Heikki Linnakangas <[email protected]>
0 siblings, 0 replies; 4+ messages in thread
From: Heikki Linnakangas @ 2020-08-19 12:34 UTC (permalink / raw)
Now that simplehash can be use in frontend code, let's make use of it.
---
src/bin/pg_rewind/copy_fetch.c | 4 +-
src/bin/pg_rewind/fetch.c | 2 +-
src/bin/pg_rewind/fetch.h | 2 +-
src/bin/pg_rewind/filemap.c | 284 ++++++++++++++------------------
src/bin/pg_rewind/filemap.h | 67 +++-----
src/bin/pg_rewind/libpq_fetch.c | 4 +-
src/bin/pg_rewind/pg_rewind.c | 12 +-
7 files changed, 168 insertions(+), 207 deletions(-)
diff --git a/src/bin/pg_rewind/copy_fetch.c b/src/bin/pg_rewind/copy_fetch.c
index 18fad32600e..61aed8018b6 100644
--- a/src/bin/pg_rewind/copy_fetch.c
+++ b/src/bin/pg_rewind/copy_fetch.c
@@ -207,9 +207,9 @@ copy_executeFileMap(filemap_t *map)
file_entry_t *entry;
int i;
- for (i = 0; i < map->narray; i++)
+ for (i = 0; i < map->nactions; i++)
{
- entry = map->array[i];
+ entry = map->actions[i];
execute_pagemap(&entry->target_modified_pages, entry->path);
switch (entry->action)
diff --git a/src/bin/pg_rewind/fetch.c b/src/bin/pg_rewind/fetch.c
index f18fe5386ed..f41d0f295ea 100644
--- a/src/bin/pg_rewind/fetch.c
+++ b/src/bin/pg_rewind/fetch.c
@@ -37,7 +37,7 @@ fetchSourceFileList(void)
* Fetch all relation data files that are marked in the given data page map.
*/
void
-executeFileMap(void)
+execute_file_actions(filemap_t *filemap)
{
if (datadir_source)
copy_executeFileMap(filemap);
diff --git a/src/bin/pg_rewind/fetch.h b/src/bin/pg_rewind/fetch.h
index 7cf8b6ea090..b20df8b1537 100644
--- a/src/bin/pg_rewind/fetch.h
+++ b/src/bin/pg_rewind/fetch.h
@@ -25,7 +25,7 @@
*/
extern void fetchSourceFileList(void);
extern char *fetchFile(const char *filename, size_t *filesize);
-extern void executeFileMap(void);
+extern void execute_file_actions(filemap_t *filemap);
/* in libpq_fetch.c */
extern void libpqProcessFileList(void);
diff --git a/src/bin/pg_rewind/filemap.c b/src/bin/pg_rewind/filemap.c
index 431e8e760e0..6d48b6076e3 100644
--- a/src/bin/pg_rewind/filemap.c
+++ b/src/bin/pg_rewind/filemap.c
@@ -3,6 +3,19 @@
* filemap.c
* A data structure for keeping track of files that have changed.
*
+ * This source file contains the logic to decide what to do with different
+ * kinds of files, and the data structure to support it. Before modifying
+ * anything, pg_rewind collects information about all the files and their
+ * attributes in the target and source data directories. It also scans the
+ * WAL log in the target, and collects information about data blocks that
+ * were changed. All this information is stored in a hash table, using the
+ * file path, relative to the root of the data directory, as the key.
+ *
+ * After collecting all the information required, the filemap_finalize()
+ * function scans the hash table and decides what action needs to be taken
+ * for each file. Finally, it sorts the array to the final order that the
+ * actions should be executed in.
+ *
* Copyright (c) 2013-2020, PostgreSQL Global Development Group
*
*-------------------------------------------------------------------------
@@ -14,22 +27,39 @@
#include <unistd.h>
#include "catalog/pg_tablespace_d.h"
+#include "common/hashfn.h"
#include "common/string.h"
#include "datapagemap.h"
#include "filemap.h"
#include "pg_rewind.h"
#include "storage/fd.h"
-filemap_t *filemap = NULL;
+/*
+ * Define a hash table which we can use to store information about the files
+ * mentioned in the backup manifest.
+ */
+static uint32 hash_string_pointer(const char *s);
+#define SH_PREFIX filehash
+#define SH_ELEMENT_TYPE file_entry_t
+#define SH_KEY_TYPE const char *
+#define SH_KEY path
+#define SH_HASH_KEY(tb, key) hash_string_pointer(key)
+#define SH_EQUAL(tb, a, b) (strcmp(a, b) == 0)
+#define SH_SCOPE static inline
+#define SH_RAW_ALLOCATOR pg_malloc0
+#define SH_DECLARE
+#define SH_DEFINE
+#include "lib/simplehash.h"
+
+static filehash_hash *filehash;
static bool isRelDataFile(const char *path);
static char *datasegpath(RelFileNode rnode, ForkNumber forknum,
BlockNumber segno);
-static int path_cmp(const void *a, const void *b);
-static file_entry_t *get_filemap_entry(const char *path, bool create);
+static file_entry_t *insert_filehash_entry(const char *path);
+static file_entry_t *lookup_filehash_entry(const char *path);
static int final_filemap_cmp(const void *a, const void *b);
-static void filemap_list_to_array(filemap_t *map);
static bool check_file_excluded(const char *path, bool is_source);
/*
@@ -131,54 +161,26 @@ static const struct exclude_list_item excludeFiles[] =
};
/*
- * Create a new file map (stored in the global pointer "filemap").
+ * Initialize the hash table for the file map.
*/
void
-filemap_create(void)
+filemap_init(void)
{
- filemap_t *map;
-
- map = pg_malloc(sizeof(filemap_t));
- map->first = map->last = NULL;
- map->nlist = 0;
- map->array = NULL;
- map->narray = 0;
-
- Assert(filemap == NULL);
- filemap = map;
+ filehash = filehash_create(1000, NULL);
}
-/* Look up or create entry for 'path' */
+/* Look up entry for 'path', creating new one if it doesn't exists */
static file_entry_t *
-get_filemap_entry(const char *path, bool create)
+insert_filehash_entry(const char *path)
{
- filemap_t *map = filemap;
file_entry_t *entry;
- file_entry_t **e;
- file_entry_t key;
- file_entry_t *key_ptr;
+ bool found;
- if (map->array)
+ entry = filehash_insert(filehash, path, &found);
+ if (!found)
{
- key.path = (char *) path;
- key_ptr = &key;
- e = bsearch(&key_ptr, map->array, map->narray, sizeof(file_entry_t *),
- path_cmp);
- }
- else
- e = NULL;
-
- if (e)
- entry = *e;
- else if (!create)
- entry = NULL;
- else
- {
- /* Create a new entry for this file */
- entry = pg_malloc(sizeof(file_entry_t));
entry->path = pg_strdup(path);
entry->isrelfile = isRelDataFile(path);
- entry->action = FILE_ACTION_UNDECIDED;
entry->target_exists = false;
entry->target_type = FILE_TYPE_UNDEFINED;
@@ -192,21 +194,18 @@ get_filemap_entry(const char *path, bool create)
entry->source_size = 0;
entry->source_link_target = NULL;
- entry->next = NULL;
-
- if (map->last)
- {
- map->last->next = entry;
- map->last = entry;
- }
- else
- map->first = map->last = entry;
- map->nlist++;
+ entry->action = FILE_ACTION_UNDECIDED;
}
return entry;
}
+static file_entry_t *
+lookup_filehash_entry(const char *path)
+{
+ return filehash_lookup(filehash, path);
+}
+
/*
* Callback for processing source file list.
*
@@ -220,8 +219,6 @@ process_source_file(const char *path, file_type_t type, size_t size,
{
file_entry_t *entry;
- Assert(filemap->array == NULL);
-
/*
* Skip the control file. It is handled specially, after copying all the
* other files.
@@ -262,7 +259,9 @@ process_source_file(const char *path, file_type_t type, size_t size,
pg_fatal("data file \"%s\" in source is not a regular file", path);
/* Remember this source file */
- entry = get_filemap_entry(path, true);
+ entry = insert_filehash_entry(path);
+ if (entry->source_exists)
+ pg_fatal("duplicate source file \"%s\"", path);
entry->source_exists = true;
entry->source_type = type;
entry->source_size = size;
@@ -280,7 +279,6 @@ void
process_target_file(const char *path, file_type_t type, size_t size,
const char *link_target)
{
- filemap_t *map = filemap;
file_entry_t *entry;
/*
@@ -295,21 +293,6 @@ process_target_file(const char *path, file_type_t type, size_t size,
* from the target data folder all paths which have been filtered out from
* the source data folder when processing the source files.
*/
- if (map->array == NULL)
- {
- /* on first call, initialize lookup array */
- if (map->nlist == 0)
- {
- /* should not happen */
- pg_fatal("source file list is empty");
- }
-
- filemap_list_to_array(map);
-
- Assert(map->array != NULL);
-
- qsort(map->array, map->narray, sizeof(file_entry_t *), path_cmp);
- }
/*
* Like in process_source_file, pretend that pg_wal is always a directory.
@@ -318,7 +301,9 @@ process_target_file(const char *path, file_type_t type, size_t size,
type = FILE_TYPE_DIRECTORY;
/* Remember this target file */
- entry = get_filemap_entry(path, true);
+ entry = insert_filehash_entry(path);
+ if (entry->target_exists)
+ pg_fatal("duplicate source file \"%s\"", path);
entry->target_exists = true;
entry->target_type = type;
entry->target_size = size;
@@ -331,7 +316,7 @@ process_target_file(const char *path, file_type_t type, size_t size,
* changed blocks in the pagemap of the file.
*
* NOTE: All the files on both systems must have already been added to the
- * file map!
+ * hash table!
*/
void
process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
@@ -342,47 +327,45 @@ process_target_wal_block_change(ForkNumber forknum, RelFileNode rnode,
BlockNumber blkno_inseg;
int segno;
- Assert(filemap->array);
-
segno = blkno / RELSEG_SIZE;
blkno_inseg = blkno % RELSEG_SIZE;
path = datasegpath(rnode, forknum, segno);
- entry = get_filemap_entry(path, false);
+ entry = lookup_filehash_entry(path);
pfree(path);
+ /*
+ * If the block still exists in both systems, remember it. Otherwise we
+ * can safely ignore it.
+ *
+ * If the block is beyond the EOF in the source system, or the file doesn't
+ * doesn'exist in the source at all, we're going to truncate/remove it away
+ * from the target anyway. Likewise, if it doesn't exist in the target
+ * anymore, we will copy it over with the "tail" from the source system,
+ * anyway.
+ *
+ * It is possible to find WAL for a file that doesn't exist on either
+ * system anymore. It means that the relation was dropped later in the
+ * target system, and independently on the source system too, or that
+ * it was created and dropped in the target system and it never existed
+ * in the source. Either way, we can safely ignore it.
+ */
if (entry)
{
- int64 end_offset;
-
Assert(entry->isrelfile);
if (entry->target_type != FILE_TYPE_REGULAR)
pg_fatal("unexpected page modification for directory or symbolic link \"%s\"",
entry->path);
- /*
- * If the block beyond the EOF in the source system, no need to
- * remember it now, because we're going to truncate it away from the
- * target anyway. Also no need to remember the block if it's beyond
- * the current EOF in the target system; we will copy it over with the
- * "tail" from the source system, anyway.
- */
- end_offset = (blkno_inseg + 1) * BLCKSZ;
- if (end_offset <= entry->source_size &&
- end_offset <= entry->target_size)
- datapagemap_add(&entry->target_modified_pages, blkno_inseg);
- }
- else
- {
- /*
- * If we don't have any record of this file in the file map, it means
- * that it's a relation that doesn't exist in the source system. It
- * could exist in the target system; we haven't moved the target-only
- * entries from the linked list to the array yet! But in any case, if
- * it doesn't exist in the source it will be removed from the target
- * too, and we can safely ignore it.
- */
+ if (entry->target_exists && entry->source_exists)
+ {
+ off_t end_offset;
+
+ end_offset = (blkno_inseg + 1) * BLCKSZ;
+ if (end_offset <= entry->source_size && end_offset <= entry->target_size)
+ datapagemap_add(&entry->target_modified_pages, blkno_inseg);
+ }
}
}
@@ -444,34 +427,6 @@ check_file_excluded(const char *path, bool is_source)
return false;
}
-/*
- * Convert the linked list of entries in map->first/last to the array,
- * map->array.
- */
-static void
-filemap_list_to_array(filemap_t *map)
-{
- int narray;
- file_entry_t *entry,
- *next;
-
- map->array = (file_entry_t **)
- pg_realloc(map->array,
- (map->nlist + map->narray) * sizeof(file_entry_t *));
-
- narray = map->narray;
- for (entry = map->first; entry != NULL; entry = next)
- {
- map->array[narray++] = entry;
- next = entry->next;
- entry->next = NULL;
- }
- Assert(narray == map->nlist + map->narray);
- map->narray = narray;
- map->nlist = 0;
- map->first = map->last = NULL;
-}
-
static const char *
action_to_str(file_action_t action)
{
@@ -499,32 +454,31 @@ action_to_str(file_action_t action)
* Calculate the totals needed for progress reports.
*/
void
-calculate_totals(void)
+calculate_totals(filemap_t *filemap)
{
file_entry_t *entry;
int i;
- filemap_t *map = filemap;
- map->total_size = 0;
- map->fetch_size = 0;
+ filemap->total_size = 0;
+ filemap->fetch_size = 0;
- for (i = 0; i < map->narray; i++)
+ for (i = 0; i < filemap->nactions; i++)
{
- entry = map->array[i];
+ entry = filemap->actions[i];
if (entry->source_type != FILE_TYPE_REGULAR)
continue;
- map->total_size += entry->source_size;
+ filemap->total_size += entry->source_size;
if (entry->action == FILE_ACTION_COPY)
{
- map->fetch_size += entry->source_size;
+ filemap->fetch_size += entry->source_size;
continue;
}
if (entry->action == FILE_ACTION_COPY_TAIL)
- map->fetch_size += (entry->source_size - entry->target_size);
+ filemap->fetch_size += (entry->source_size - entry->target_size);
if (entry->target_modified_pages.bitmapsize > 0)
{
@@ -533,7 +487,7 @@ calculate_totals(void)
iter = datapagemap_iterate(&entry->target_modified_pages);
while (datapagemap_next(iter, &blk))
- map->fetch_size += BLCKSZ;
+ filemap->fetch_size += BLCKSZ;
pg_free(iter);
}
@@ -541,15 +495,14 @@ calculate_totals(void)
}
void
-print_filemap(void)
+print_filemap(filemap_t *filemap)
{
- filemap_t *map = filemap;
file_entry_t *entry;
int i;
- for (i = 0; i < map->narray; i++)
+ for (i = 0; i < filemap->nactions; i++)
{
- entry = map->array[i];
+ entry = filemap->actions[i];
if (entry->action != FILE_ACTION_NONE ||
entry->target_modified_pages.bitmapsize > 0)
{
@@ -671,15 +624,6 @@ datasegpath(RelFileNode rnode, ForkNumber forknum, BlockNumber segno)
return path;
}
-static int
-path_cmp(const void *a, const void *b)
-{
- file_entry_t *fa = *((file_entry_t **) a);
- file_entry_t *fb = *((file_entry_t **) b);
-
- return strcmp(fa->path, fb->path);
-}
-
/*
* In the final stage, the filemap is sorted so that removals come last.
* From disk space usage point of view, it would be better to do removals
@@ -710,16 +654,17 @@ final_filemap_cmp(const void *a, const void *b)
/*
* Decide what to do with each file.
*/
-void
+filemap_t *
filemap_finalize()
{
int i;
+ filehash_iterator it;
+ file_entry_t *entry;
+ filemap_t *filemap;
- filemap_list_to_array(filemap);
-
- for (i = 0; i < filemap->narray; i++)
+ filehash_start_iterate(filehash, &it);
+ while ((entry = filehash_iterate(filehash, &it)) != NULL)
{
- file_entry_t *entry = filemap->array[i];
file_action_t action;
if (!entry->target_exists && entry->source_exists)
@@ -825,7 +770,34 @@ filemap_finalize()
entry->action = action;
}
- /* Sort the actions to the order that they should be performed */
- qsort(filemap->array, filemap->narray, sizeof(file_entry_t *),
+ /*
+ * Turn the hash table into an array, sorted in the order that the actions
+ * should be performed.
+ */
+ filemap = pg_malloc(offsetof(filemap_t, actions) +
+ filehash->members * sizeof(file_entry_t *));
+ filemap->nactions = filehash->members;
+ filehash_start_iterate(filehash, &it);
+ i = 0;
+ while ((entry = filehash_iterate(filehash, &it)) != NULL)
+ {
+ filemap->actions[i++] = entry;
+ }
+
+ qsort(&filemap->actions, filemap->nactions, sizeof(file_entry_t *),
final_filemap_cmp);
+
+ return filemap;
+}
+
+
+/*
+ * Helper function for filemap hash table.
+ */
+static uint32
+hash_string_pointer(const char *s)
+{
+ unsigned char *ss = (unsigned char *) s;
+
+ return hash_bytes(ss, strlen(s));
}
diff --git a/src/bin/pg_rewind/filemap.h b/src/bin/pg_rewind/filemap.h
index a5e8df57f40..3660ffe0990 100644
--- a/src/bin/pg_rewind/filemap.h
+++ b/src/bin/pg_rewind/filemap.h
@@ -12,15 +12,6 @@
#include "storage/block.h"
#include "storage/relfilenode.h"
-/*
- * For every file found in the local or remote system, we have a file entry
- * that contains information about the file on both systems. For relation
- * files, there is also a page map that marks pages in the file that were
- * changed in the target after the last common checkpoint. Each entry also
- * contains an 'action' field, which says what we are going to do with the
- * file.
- */
-
/* these enum values are sorted in the order we want actions to be processed */
typedef enum
{
@@ -44,9 +35,21 @@ typedef enum
FILE_TYPE_SYMLINK
} file_type_t;
+/*
+ * For every file found in the local or remote system, we have a file entry
+ * that contains information about the file on both systems. For relation
+ * files, there is also a page map that marks pages in the file that were
+ * changed in the target after the last common checkpoint.
+ *
+ * When gathering information, these are kept in a hash table, private to
+ * filemap.c. filemap_finalize() fills in the 'action' field, sorts all the
+ * entries, and returns them in an array, ready for executing the actions.
+ */
typedef struct file_entry_t
{
- char *path;
+ uint32 status; /* hash status */
+
+ const char *path;
bool isrelfile; /* is it a relation data file? */
/*
@@ -71,44 +74,25 @@ typedef struct file_entry_t
* What will we do to the file?
*/
file_action_t action;
-
- struct file_entry_t *next;
} file_entry_t;
+/*
+ * This represents the final decisions on what to do with each file.
+ * 'actions' array contains an entry for each file, sorted in the order
+ * that their actions should executed.
+ */
typedef struct filemap_t
{
- /*
- * New entries are accumulated to a linked list, in process_source_file
- * and process_target_file.
- */
- file_entry_t *first;
- file_entry_t *last;
- int nlist; /* number of entries currently in list */
-
- /*
- * After processing all the remote files, the entries in the linked list
- * are moved to this array. After processing local files, too, all the
- * local entries are added to the array by filemap_finalize, and sorted in
- * the final order. After filemap_finalize, all the entries are in the
- * array, and the linked list is empty.
- */
- file_entry_t **array;
- int narray; /* current length of array */
-
- /*
- * Summary information.
- */
+ /* Summary information, filled by calculate_totals() */
uint64 total_size; /* total size of the source cluster */
uint64 fetch_size; /* number of bytes that needs to be copied */
-} filemap_t;
-extern filemap_t *filemap;
-
-extern void filemap_create(void);
-extern void calculate_totals(void);
-extern void print_filemap(void);
+ int nactions; /* size of 'actions' array */
+ file_entry_t *actions[FLEXIBLE_ARRAY_MEMBER];
+} filemap_t;
/* Functions for populating the filemap */
+extern void filemap_init(void);
extern void process_source_file(const char *path, file_type_t type,
size_t size, const char *link_target);
extern void process_target_file(const char *path, file_type_t type,
@@ -116,6 +100,9 @@ extern void process_target_file(const char *path, file_type_t type,
extern void process_target_wal_block_change(ForkNumber forknum,
RelFileNode rnode,
BlockNumber blkno);
-extern void filemap_finalize(void);
+
+extern filemap_t *filemap_finalize(void);
+extern void calculate_totals(filemap_t *filemap);
+extern void print_filemap(filemap_t *filemap);
#endif /* FILEMAP_H */
diff --git a/src/bin/pg_rewind/libpq_fetch.c b/src/bin/pg_rewind/libpq_fetch.c
index 7fc9161b8c8..9c541bb73d5 100644
--- a/src/bin/pg_rewind/libpq_fetch.c
+++ b/src/bin/pg_rewind/libpq_fetch.c
@@ -460,9 +460,9 @@ libpq_executeFileMap(filemap_t *map)
PQresultErrorMessage(res));
PQclear(res);
- for (i = 0; i < map->narray; i++)
+ for (i = 0; i < map->nactions; i++)
{
- entry = map->array[i];
+ entry = map->actions[i];
/* If this is a relation file, copy the modified blocks */
execute_pagemap(&entry->target_modified_pages, entry->path);
diff --git a/src/bin/pg_rewind/pg_rewind.c b/src/bin/pg_rewind/pg_rewind.c
index 210984d302b..2bdeed26c53 100644
--- a/src/bin/pg_rewind/pg_rewind.c
+++ b/src/bin/pg_rewind/pg_rewind.c
@@ -129,6 +129,7 @@ main(int argc, char **argv)
TimeLineID endtli;
ControlFileData ControlFile_new;
bool writerecoveryconf = false;
+ filemap_t *filemap;
pg_logging_init(argv[0]);
set_pglocale_pgservice(argv[0], PG_TEXTDOMAIN("pg_rewind"));
@@ -371,10 +372,11 @@ main(int argc, char **argv)
/*
* Collect information about all files in the target and source systems.
*/
- filemap_create();
if (showprogress)
pg_log_info("reading source file list");
+ filemap_init();
fetchSourceFileList();
+
if (showprogress)
pg_log_info("reading target file list");
traverse_datadir(datadir_target, &process_target_file);
@@ -395,13 +397,13 @@ main(int argc, char **argv)
* We have collected all information we need from both systems. Decide
* what to do with each file.
*/
- filemap_finalize();
+ filemap = filemap_finalize();
if (showprogress)
- calculate_totals();
+ calculate_totals(filemap);
/* this is too verbose even for verbose mode */
if (debug)
- print_filemap();
+ print_filemap(filemap);
/*
* Ok, we're ready to start copying things over.
@@ -421,7 +423,7 @@ main(int argc, char **argv)
* modified the target directory and there is no turning back!
*/
- executeFileMap();
+ execute_file_actions(filemap);
progress_report(true);
--
2.20.1
--------------D93EDEBFB124D563B723F4BD
Content-Type: text/x-patch; charset=UTF-8;
name="0004-pg_rewind-Refactor-the-abstraction-to-fetch-from-loc.patch"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename*0="0004-pg_rewind-Refactor-the-abstraction-to-fetch-from-loc.pa";
filename*1="tch"
^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: log_min_messages per backend type
@ 2026-01-15 16:07 Euler Taveira <[email protected]>
2026-01-17 22:02 ` Re: log_min_messages per backend type Alvaro Herrera <[email protected]>
2026-01-21 14:51 ` Re: log_min_messages per backend type Alvaro Herrera <[email protected]>
0 siblings, 2 replies; 4+ messages in thread
From: Euler Taveira @ 2026-01-15 16:07 UTC (permalink / raw)
To: Alvaro Herrera <[email protected]>; +Cc: japin <[email protected]>; Andres Freund <[email protected]>; [email protected]; Chao Li <[email protected]>
On Tue, Dec 9, 2025, at 3:24 PM, Alvaro Herrera wrote:
> On 2025-Dec-09, Alvaro Herrera wrote:
>
>> So here's your v6 again with those fixes as 0003 -- let's see what CI
>> thinks of this. I haven't looked at your doc changes yet.
>
> This passed CI, so I have marked it as Ready for Committer. Further
> comments are still welcome, of course, but if there are none, I intend
> to get it committed in a few days.
>
I took another look after Chao Li comments [1]. I created the 0003 patch
that does the sort as suggested. I think it is good to be consistent but
I'm fine if we decided the additional code is not worth. The 32 in the
MAX_LMM_STR_LEN is arbitrary but it is based on the size of the largest
element in the list ("dead-end client backend:warning"). I didn't take
into account the comma and space between elements but it is not
necessary since other elements are smaller than the largest one.
I didn't implement the 2nd suggestion.
I also merged Alvaro's fix to 0002. The v8 is attached.
I didn't change the commit message but if 0003 is merged into 0001 then
it should mention that
8<--------------------------------------------------------------------8<
The SHOW command presents well-formatted list sorted by process type and
the generic log level is the first element list. It improves readability
and has a clear indentation.
8<--------------------------------------------------------------------8<
Do we really need a different backend type in this case? For background
workers the description is "background worker". Shoundn't it use the
same description for this edge case too?
- backend_type_str = MyBgworkerEntry->bgw_type;
+ {
+ if (MyBgworkerEntry && MyBgworkerEntry->bgw_type[0] != '\0')
+ backend_type_str = MyBgworkerEntry->bgw_type;
+ else
+ backend_type_str = "early bgworker";
+ }
I also noticed that commit 18d67a8d7d30 forgot to add gettext_noop to
the get_backend_type_for_log function. It should be consistent with
GetBackendTypeDesc() return.
[1] https://postgr.es/m/[email protected]
--
Euler Taveira
EDB https://www.enterprisedb.com/
Attachments:
[text/x-patch] v8-0001-log_min_messages-per-process-type.patch (26.3K, ../../[email protected]/2-v8-0001-log_min_messages-per-process-type.patch)
download | inline diff:
From 7b327eacb382c7efe7c7fcab309bb04c0043507d Mon Sep 17 00:00:00 2001
From: Euler Taveira <[email protected]>
Date: Tue, 4 Nov 2025 17:18:20 -0300
Subject: [PATCH v8 1/3] log_min_messages per process type
Change log_min_messages from a single element to a comma-separated list
of elements. Each element is type:level. The types are archiver,
autovacuum (includes launcher and workers), backend, bgworker, bgwriter,
checkpointer, ioworker, postmaster, syslogger, slotsyncworker, startup,
walreceiver, walsender, walsummarizer and walwriter. A single log level
should be part of this list and it is applied as a final step to the
process types that were not informed.
The old syntax (a single log level) is still accepted for backward
compatibility. In this case, it means the informed log level is applied
for all process types. The default log level is the same (WARNING) for
all process types.
---
doc/src/sgml/config.sgml | 42 +++-
src/backend/commands/extension.c | 2 +-
src/backend/commands/variable.c | 208 ++++++++++++++++++
src/backend/postmaster/launch_backend.c | 2 +-
src/backend/utils/error/elog.c | 2 +-
src/backend/utils/init/miscinit.c | 2 +-
src/backend/utils/misc/guc_parameters.dat | 10 +-
src/backend/utils/misc/guc_tables.c | 21 +-
src/backend/utils/misc/postgresql.conf.sample | 2 +
src/include/postmaster/proctypelist.h | 42 ++--
src/include/utils/guc.h | 6 +-
src/include/utils/guc_hooks.h | 2 +
src/test/regress/expected/guc.out | 54 +++++
src/test/regress/sql/guc.sql | 18 ++
14 files changed, 372 insertions(+), 41 deletions(-)
diff --git a/doc/src/sgml/config.sgml b/doc/src/sgml/config.sgml
index 0fad34da6eb..4df87e1bc52 100644
--- a/doc/src/sgml/config.sgml
+++ b/doc/src/sgml/config.sgml
@@ -7083,7 +7083,7 @@ local0.* /var/log/postgresql
<variablelist>
<varlistentry id="guc-log-min-messages" xreflabel="log_min_messages">
- <term><varname>log_min_messages</varname> (<type>enum</type>)
+ <term><varname>log_min_messages</varname> (<type>string</type>)
<indexterm>
<primary><varname>log_min_messages</varname> configuration parameter</primary>
</indexterm>
@@ -7092,14 +7092,38 @@ local0.* /var/log/postgresql
<para>
Controls which <link linkend="runtime-config-severity-levels">message
levels</link> are written to the server log.
- Valid values are <literal>DEBUG5</literal>, <literal>DEBUG4</literal>,
- <literal>DEBUG3</literal>, <literal>DEBUG2</literal>, <literal>DEBUG1</literal>,
- <literal>INFO</literal>, <literal>NOTICE</literal>, <literal>WARNING</literal>,
- <literal>ERROR</literal>, <literal>LOG</literal>, <literal>FATAL</literal>, and
- <literal>PANIC</literal>. Each level includes all the levels that
- follow it. The later the level, the fewer messages are sent
- to the log. The default is <literal>WARNING</literal>. Note that
- <literal>LOG</literal> has a different rank here than in
+ Valid values are a comma-separated list of <literal>type:level</literal>
+ and a single <literal>level</literal>. The list allows it to use
+ different levels per process type. Only the single <literal>level</literal>
+ is mandatory (order does not matter) and it is assigned to the process
+ types that are not specified in the list.
+ Valid process types are listed in the table below, each corresponding to
+ either postmaster, an auxiliary process type or a backend.
+ <simplelist type="vert" columns="4">
+ <member><literal>archiver</literal></member>
+ <member><literal>autovacuum</literal></member>
+ <member><literal>backend</literal></member>
+ <member><literal>bgworker</literal></member>
+ <member><literal>bgwriter</literal></member>
+ <member><literal>checkpointer</literal></member>
+ <member><literal>ioworker</literal></member>
+ <member><literal>postmaster</literal></member>
+ <member><literal>syslogger</literal></member>
+ <member><literal>slotsyncworker</literal></member>
+ <member><literal>startup</literal></member>
+ <member><literal>walreceiver</literal></member>
+ <member><literal>walsender</literal></member>
+ <member><literal>walsummarizer</literal></member>
+ <member><literal>walwriter</literal></member>
+ </simplelist>
+ Valid <literal>level</literal> values are <literal>DEBUG5</literal>,
+ <literal>DEBUG4</literal>, <literal>DEBUG3</literal>, <literal>DEBUG2</literal>,
+ <literal>DEBUG1</literal>, <literal>INFO</literal>, <literal>NOTICE</literal>,
+ <literal>WARNING</literal>, <literal>ERROR</literal>, <literal>LOG</literal>,
+ <literal>FATAL</literal>, and <literal>PANIC</literal>. Each level includes
+ all the levels that follow it. The later the level, the fewer messages are sent
+ to the log. The default is <literal>WARNING</literal> for all process types.
+ Note that <literal>LOG</literal> has a different rank here than in
<xref linkend="guc-client-min-messages"/>.
Only superusers and users with the appropriate <literal>SET</literal>
privilege can change this setting.
diff --git a/src/backend/commands/extension.c b/src/backend/commands/extension.c
index 596105ee078..688f1874f31 100644
--- a/src/backend/commands/extension.c
+++ b/src/backend/commands/extension.c
@@ -1191,7 +1191,7 @@ execute_extension_script(Oid extensionOid, ExtensionControlFile *control,
(void) set_config_option("client_min_messages", "warning",
PGC_USERSET, PGC_S_SESSION,
GUC_ACTION_SAVE, true, 0, false);
- if (log_min_messages < WARNING)
+ if (log_min_messages[MyBackendType] < WARNING)
(void) set_config_option_ext("log_min_messages", "warning",
PGC_SUSET, PGC_S_SESSION,
BOOTSTRAP_SUPERUSERID,
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index bda932d4992..f4f5d4c367c 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -34,6 +34,7 @@
#include "utils/backend_status.h"
#include "utils/datetime.h"
#include "utils/fmgrprotos.h"
+#include "utils/guc.h"
#include "utils/guc_hooks.h"
#include "utils/snapmgr.h"
#include "utils/syscache.h"
@@ -1257,3 +1258,210 @@ check_ssl(bool *newval, void **extra, GucSource source)
#endif
return true;
}
+
+/*
+ * GUC check_hook for log_min_messages
+ *
+ * The parsing consists of a comma-separated list of TYPE:LEVEL elements. TYPE
+ * is log_min_messages_process_types. LEVEL is server_message_level_options. A
+ * single LEVEL element should be part of this list and it is applied as a
+ * final step to the process types that are not specified. For backward
+ * compatibility, the old syntax is still accepted and it means to apply the
+ * LEVEL for all process types.
+ */
+bool
+check_log_min_messages(char **newval, void **extra, GucSource source)
+{
+ char *rawstring;
+ List *elemlist;
+ ListCell *l;
+ int newlevel[BACKEND_NUM_TYPES];
+ bool assigned[BACKEND_NUM_TYPES];
+ int genericlevel = -1; /* -1 means not assigned */
+
+ /* Initialize the array. */
+ for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+ {
+ newlevel[i] = WARNING;
+ assigned[i] = false;
+ }
+
+ /* Need a modifiable copy of string. */
+ rawstring = guc_strdup(LOG, *newval);
+
+ /* Parse string into list of identifiers. */
+ if (!SplitGUCList(rawstring, ',', &elemlist))
+ {
+ /* syntax error in list */
+ GUC_check_errdetail("List syntax is invalid.");
+ list_free(elemlist);
+ guc_free(rawstring);
+ return false;
+ }
+
+ /* Validate and assign log level and process type. */
+ foreach(l, elemlist)
+ {
+ char *tok = (char *) lfirst(l);
+ char *sep;
+ const struct config_enum_entry *entry;
+
+ /*
+ * Check whether there is a process type following the log level. If
+ * there is no separator, it means this is the generic log level. The
+ * generic log level will be assigned to the process types that were
+ * not informed.
+ */
+ sep = strchr(tok, ':');
+ if (sep == NULL)
+ {
+ bool found = false;
+
+ /* Reject duplicates for generic log level. */
+ if (genericlevel != -1)
+ {
+ GUC_check_errdetail("Generic log level was already assigned.");
+ guc_free(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ /* Is the log level valid? */
+ for (entry = server_message_level_options; entry && entry->name; entry++)
+ {
+ if (pg_strcasecmp(entry->name, tok) == 0)
+ {
+ genericlevel = entry->val;
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ GUC_check_errdetail("Unrecognized log level: \"%s\".", tok);
+ guc_free(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ }
+ else
+ {
+ char *loglevel;
+ char *ptype;
+ bool found = false;
+
+ ptype = guc_malloc(LOG, (sep - tok) + 1);
+ if (!ptype)
+ {
+ guc_free(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+ memcpy(ptype, tok, sep - tok);
+ ptype[sep - tok] = '\0';
+ loglevel = sep + 1;
+
+ /* Is the log level valid? */
+ for (entry = server_message_level_options; entry && entry->name; entry++)
+ {
+ if (pg_strcasecmp(entry->name, loglevel) == 0)
+ {
+ found = true;
+ break;
+ }
+ }
+
+ if (!found)
+ {
+ GUC_check_errdetail("Unrecognized log level: \"%s\".", loglevel);
+ guc_free(ptype);
+ guc_free(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ /*
+ * Is the process type name valid? There might be multiple entries
+ * per process type, don't bail out because it can assign the
+ * value for multiple entries.
+ */
+ found = false;
+ for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+ {
+ if (pg_strcasecmp(log_min_messages_process_types[i], ptype) == 0)
+ {
+ /* Reject duplicates for a process type. */
+ if (assigned[i])
+ {
+ GUC_check_errdetail("Process type \"%s\" was already assigned.", ptype);
+ guc_free(ptype);
+ guc_free(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ newlevel[i] = entry->val;
+ assigned[i] = true;
+ found = true;
+ }
+ }
+
+ if (!found)
+ {
+ GUC_check_errdetail("Unrecognized process type: \"%s\".", ptype);
+ guc_free(ptype);
+ guc_free(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ guc_free(ptype);
+ }
+ }
+
+ /*
+ * The generic log level must be specified. It is the fallback value.
+ */
+ if (genericlevel == -1)
+ {
+ GUC_check_errdetail("Generic log level was not defined.");
+ guc_free(rawstring);
+ list_free(elemlist);
+ return false;
+ }
+
+ /*
+ * Apply the generic log level after all of the specific process types
+ * have been assigned. Hence, it doesn't matter the order you specify the
+ * generic log level, the final result will be the same.
+ */
+ for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+ {
+ if (!assigned[i])
+ newlevel[i] = genericlevel;
+ }
+
+ guc_free(rawstring);
+ list_free(elemlist);
+
+ /*
+ * Pass back data for assign_log_min_messages to use.
+ */
+ *extra = guc_malloc(LOG, BACKEND_NUM_TYPES * sizeof(int));
+ if (!*extra)
+ return false;
+ memcpy(*extra, newlevel, BACKEND_NUM_TYPES * sizeof(int));
+
+ return true;
+}
+
+/*
+ * GUC assign_hook for log_min_messages
+ */
+void
+assign_log_min_messages(const char *newval, void *extra)
+{
+ for (int i = 0; i < BACKEND_NUM_TYPES; i++)
+ log_min_messages[i] = ((int *) extra)[i];
+}
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index eb91503522f..57303e1731b 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -179,7 +179,7 @@ typedef struct
} child_process_kind;
static child_process_kind child_process_kinds[] = {
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
[bktype] = {description, main_func, shmem_attach},
#include "postmaster/proctypelist.h"
#undef PG_PROCTYPE
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index aa530d3685e..59975309a4e 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -235,7 +235,7 @@ is_log_level_output(int elevel, int log_min_level)
static inline bool
should_output_to_server(int elevel)
{
- return is_log_level_output(elevel, log_min_messages);
+ return is_log_level_output(elevel, log_min_messages[MyBackendType]);
}
/*
diff --git a/src/backend/utils/init/miscinit.c b/src/backend/utils/init/miscinit.c
index 563f20374ff..efba20fa8b9 100644
--- a/src/backend/utils/init/miscinit.c
+++ b/src/backend/utils/init/miscinit.c
@@ -266,7 +266,7 @@ GetBackendTypeDesc(BackendType backendType)
switch (backendType)
{
-#define PG_PROCTYPE(bktype, description, main_func, shmem_attach) \
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
case bktype: backendDesc = description; break;
#include "postmaster/proctypelist.h"
#undef PG_PROCTYPE
diff --git a/src/backend/utils/misc/guc_parameters.dat b/src/backend/utils/misc/guc_parameters.dat
index 7c60b125564..81d76133ce4 100644
--- a/src/backend/utils/misc/guc_parameters.dat
+++ b/src/backend/utils/misc/guc_parameters.dat
@@ -1692,12 +1692,14 @@
options => 'server_message_level_options',
},
-{ name => 'log_min_messages', type => 'enum', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
+{ name => 'log_min_messages', type => 'string', context => 'PGC_SUSET', group => 'LOGGING_WHEN',
short_desc => 'Sets the message levels that are logged.',
long_desc => 'Each level includes all the levels that follow it. The later the level, the fewer messages are sent.',
- variable => 'log_min_messages',
- boot_val => 'WARNING',
- options => 'server_message_level_options',
+ flags => 'GUC_LIST_INPUT',
+ variable => 'log_min_messages_string',
+ boot_val => '"WARNING"',
+ check_hook => 'check_log_min_messages',
+ assign_hook => 'assign_log_min_messages',
},
{ name => 'log_parameter_max_length', type => 'int', context => 'PGC_SUSET', group => 'LOGGING_WHAT',
diff --git a/src/backend/utils/misc/guc_tables.c b/src/backend/utils/misc/guc_tables.c
index 73ff6ad0a32..d2a98007e9b 100644
--- a/src/backend/utils/misc/guc_tables.c
+++ b/src/backend/utils/misc/guc_tables.c
@@ -146,7 +146,7 @@ static const struct config_enum_entry client_message_level_options[] = {
{NULL, 0, false}
};
-static const struct config_enum_entry server_message_level_options[] = {
+const struct config_enum_entry server_message_level_options[] = {
{"debug5", DEBUG5, false},
{"debug4", DEBUG4, false},
{"debug3", DEBUG3, false},
@@ -537,7 +537,6 @@ static bool default_with_oids = false;
bool current_role_is_superuser;
int log_min_error_statement = ERROR;
-int log_min_messages = WARNING;
int client_min_messages = NOTICE;
int log_min_duration_sample = -1;
int log_min_duration_statement = -1;
@@ -595,6 +594,7 @@ static char *server_version_string;
static int server_version_num;
static char *debug_io_direct_string;
static char *restrict_nonsystem_relation_kind_string;
+static char *log_min_messages_string;
#ifdef HAVE_SYSLOG
#define DEFAULT_SYSLOG_FACILITY LOG_LOCAL0
@@ -647,6 +647,23 @@ char *role_string;
/* should be static, but guc.c needs to get at this */
bool in_hot_standby_guc;
+/*
+ * log_min_messages
+ */
+int log_min_messages[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+ [bktype] = log_min_messages,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
+const char *const log_min_messages_process_types[] = {
+#define PG_PROCTYPE(bktype, bkcategory, description, main_func, shmem_attach, log_min_messages) \
+ [bktype] = bkcategory,
+#include "postmaster/proctypelist.h"
+#undef PG_PROCTYPE
+};
+
/*
* Displayable names for context types (enum GucContext)
diff --git a/src/backend/utils/misc/postgresql.conf.sample b/src/backend/utils/misc/postgresql.conf.sample
index dc9e2255f8a..24b5d983590 100644
--- a/src/backend/utils/misc/postgresql.conf.sample
+++ b/src/backend/utils/misc/postgresql.conf.sample
@@ -541,6 +541,8 @@
# log
# fatal
# panic
+ # and an optional comma-separated list
+ # of type:level
#log_min_error_statement = error # values in order of decreasing detail:
# debug5
diff --git a/src/include/postmaster/proctypelist.h b/src/include/postmaster/proctypelist.h
index 0b99eaabfd0..0ddd805f0e7 100644
--- a/src/include/postmaster/proctypelist.h
+++ b/src/include/postmaster/proctypelist.h
@@ -25,27 +25,27 @@
*/
/*
- * List of process types (symbol, description, Main function, shmem_attach)
- * entries.
+ * List of process types (symbol, category, description, Main function,
+ * shmem_attach, message level) entries.
*/
-/* bktype, description, main_func, shmem_attach */
-PG_PROCTYPE(B_ARCHIVER, gettext_noop("archiver"), PgArchiverMain, true)
-PG_PROCTYPE(B_AUTOVAC_LAUNCHER, gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true)
-PG_PROCTYPE(B_AUTOVAC_WORKER, gettext_noop("autovacuum worker"), AutoVacWorkerMain, true)
-PG_PROCTYPE(B_BACKEND, gettext_noop("client backend"), BackendMain, true)
-PG_PROCTYPE(B_BG_WORKER, gettext_noop("background worker"), BackgroundWorkerMain, true)
-PG_PROCTYPE(B_BG_WRITER, gettext_noop("background writer"), BackgroundWriterMain, true)
-PG_PROCTYPE(B_CHECKPOINTER, gettext_noop("checkpointer"), CheckpointerMain, true)
-PG_PROCTYPE(B_DEAD_END_BACKEND, gettext_noop("dead-end client backend"), BackendMain, true)
-PG_PROCTYPE(B_INVALID, gettext_noop("unrecognized"), NULL, false)
-PG_PROCTYPE(B_IO_WORKER, gettext_noop("io worker"), IoWorkerMain, true)
-PG_PROCTYPE(B_LOGGER, gettext_noop("syslogger"), SysLoggerMain, false)
-PG_PROCTYPE(B_SLOTSYNC_WORKER, gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true)
-PG_PROCTYPE(B_STANDALONE_BACKEND, gettext_noop("standalone backend"), NULL, false)
-PG_PROCTYPE(B_STARTUP, gettext_noop("startup"), StartupProcessMain, true)
-PG_PROCTYPE(B_WAL_RECEIVER, gettext_noop("walreceiver"), WalReceiverMain, true)
-PG_PROCTYPE(B_WAL_SENDER, gettext_noop("walsender"), NULL, true)
-PG_PROCTYPE(B_WAL_SUMMARIZER, gettext_noop("walsummarizer"), WalSummarizerMain, true)
-PG_PROCTYPE(B_WAL_WRITER, gettext_noop("walwriter"), WalWriterMain, true)
+/* bktype, bkcategory, description, main_func, shmem_attach, log_min_messages */
+PG_PROCTYPE(B_ARCHIVER, "archiver", gettext_noop("archiver"), PgArchiverMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_LAUNCHER, "autovacuum", gettext_noop("autovacuum launcher"), AutoVacLauncherMain, true, WARNING)
+PG_PROCTYPE(B_AUTOVAC_WORKER, "autovacuum", gettext_noop("autovacuum worker"), AutoVacWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BACKEND, "backend", gettext_noop("client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_BG_WORKER, "bgworker", gettext_noop("background worker"), BackgroundWorkerMain, true, WARNING)
+PG_PROCTYPE(B_BG_WRITER, "bgwriter", gettext_noop("background writer"), BackgroundWriterMain, true, WARNING)
+PG_PROCTYPE(B_CHECKPOINTER, "checkpointer", gettext_noop("checkpointer"), CheckpointerMain, true, WARNING)
+PG_PROCTYPE(B_DEAD_END_BACKEND, "backend", gettext_noop("dead-end client backend"), BackendMain, true, WARNING)
+PG_PROCTYPE(B_INVALID, "postmaster", gettext_noop("unrecognized"), NULL, false, WARNING)
+PG_PROCTYPE(B_IO_WORKER, "ioworker", gettext_noop("io worker"), IoWorkerMain, true, WARNING)
+PG_PROCTYPE(B_LOGGER, "syslogger", gettext_noop("syslogger"), SysLoggerMain, false, WARNING)
+PG_PROCTYPE(B_SLOTSYNC_WORKER, "slotsyncworker", gettext_noop("slotsync worker"), ReplSlotSyncWorkerMain, true, WARNING)
+PG_PROCTYPE(B_STANDALONE_BACKEND, "backend", gettext_noop("standalone backend"), NULL, false, WARNING)
+PG_PROCTYPE(B_STARTUP, "startup", gettext_noop("startup"), StartupProcessMain, true, WARNING)
+PG_PROCTYPE(B_WAL_RECEIVER, "walreceiver", gettext_noop("walreceiver"), WalReceiverMain, true, WARNING)
+PG_PROCTYPE(B_WAL_SENDER, "walsender", gettext_noop("walsender"), NULL, true, WARNING)
+PG_PROCTYPE(B_WAL_SUMMARIZER, "walsummarizer", gettext_noop("walsummarizer"), WalSummarizerMain, true, WARNING)
+PG_PROCTYPE(B_WAL_WRITER, "walwriter", gettext_noop("walwriter"), WalWriterMain, true, WARNING)
diff --git a/src/include/utils/guc.h b/src/include/utils/guc.h
index bf39878c43e..47cdc72bf02 100644
--- a/src/include/utils/guc.h
+++ b/src/include/utils/guc.h
@@ -295,7 +295,7 @@ extern PGDLLIMPORT bool log_duration;
extern PGDLLIMPORT int log_parameter_max_length;
extern PGDLLIMPORT int log_parameter_max_length_on_error;
extern PGDLLIMPORT int log_min_error_statement;
-extern PGDLLIMPORT int log_min_messages;
+extern PGDLLIMPORT int log_min_messages[];
extern PGDLLIMPORT int client_min_messages;
extern PGDLLIMPORT int log_min_duration_sample;
extern PGDLLIMPORT int log_min_duration_statement;
@@ -329,6 +329,9 @@ extern PGDLLIMPORT bool trace_sort;
extern PGDLLIMPORT bool optimize_bounded_sort;
#endif
+/* log_min_messages */
+extern PGDLLIMPORT const char *const log_min_messages_process_types[];
+
/*
* Declarations for options for enum values
*
@@ -344,6 +347,7 @@ extern PGDLLIMPORT const struct config_enum_entry archive_mode_options[];
extern PGDLLIMPORT const struct config_enum_entry dynamic_shared_memory_options[];
extern PGDLLIMPORT const struct config_enum_entry io_method_options[];
extern PGDLLIMPORT const struct config_enum_entry recovery_target_action_options[];
+extern PGDLLIMPORT const struct config_enum_entry server_message_level_options[];
extern PGDLLIMPORT const struct config_enum_entry wal_level_options[];
extern PGDLLIMPORT const struct config_enum_entry wal_sync_method_options[];
diff --git a/src/include/utils/guc_hooks.h b/src/include/utils/guc_hooks.h
index f723668da9e..ac27737a107 100644
--- a/src/include/utils/guc_hooks.h
+++ b/src/include/utils/guc_hooks.h
@@ -175,5 +175,7 @@ extern void assign_wal_sync_method(int new_wal_sync_method, void *extra);
extern bool check_synchronized_standby_slots(char **newval, void **extra,
GucSource source);
extern void assign_synchronized_standby_slots(const char *newval, void *extra);
+extern bool check_log_min_messages(char **newval, void **extra, GucSource source);
+extern void assign_log_min_messages(const char *newval, void *extra);
#endif /* GUC_HOOKS_H */
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index d6fb879f500..3b8f44566b2 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -935,3 +935,57 @@ SELECT name FROM tab_settings_flags
(0 rows)
DROP TABLE tab_settings_flags;
+-- Test log_min_messages
+SET log_min_messages TO foo; -- fail
+ERROR: invalid value for parameter "log_min_messages": "foo"
+DETAIL: Unrecognized log level: "foo".
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+ log_min_messages
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+ log_min_messages
+------------------
+ fatal
+(1 row)
+
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1'; --fail
+ERROR: invalid value for parameter "log_min_messages": "checkpointer:debug2, autovacuum:debug1"
+DETAIL: Generic log level was not defined.
+SET log_min_messages TO 'debug1, backend:error, fatal'; -- fail
+ERROR: invalid value for parameter "log_min_messages": "debug1, backend:error, fatal"
+DETAIL: Generic log level was already assigned.
+SET log_min_messages TO 'backend:error, debug1, backend:warning'; -- fail
+ERROR: invalid value for parameter "log_min_messages": "backend:error, debug1, backend:warning"
+DETAIL: Process type "backend" was already assigned.
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1'; -- fail
+ERROR: invalid value for parameter "log_min_messages": "backend:error, foo:fatal, archiver:debug1"
+DETAIL: Unrecognized process type: "foo".
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1'; -- fail
+ERROR: invalid value for parameter "log_min_messages": "backend:error, checkpointer:bar, archiver:debug1"
+DETAIL: Unrecognized log level: "bar".
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+ log_min_messages
+-------------------------------------------------------------------------------------------------
+ backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+(1 row)
+
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+ log_min_messages
+----------------------------
+ warning, autovacuum:debug1
+(1 row)
+
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
+ log_min_messages
+----------------------------
+ autovacuum:debug1, warning
+(1 row)
+
diff --git a/src/test/regress/sql/guc.sql b/src/test/regress/sql/guc.sql
index bafaf067e82..f7dfc909b42 100644
--- a/src/test/regress/sql/guc.sql
+++ b/src/test/regress/sql/guc.sql
@@ -377,3 +377,21 @@ SELECT name FROM tab_settings_flags
WHERE no_reset AND NOT no_reset_all
ORDER BY 1;
DROP TABLE tab_settings_flags;
+
+-- Test log_min_messages
+SET log_min_messages TO foo; -- fail
+SET log_min_messages TO fatal;
+SHOW log_min_messages;
+SET log_min_messages TO 'fatal';
+SHOW log_min_messages;
+SET log_min_messages TO 'checkpointer:debug2, autovacuum:debug1'; --fail
+SET log_min_messages TO 'debug1, backend:error, fatal'; -- fail
+SET log_min_messages TO 'backend:error, debug1, backend:warning'; -- fail
+SET log_min_messages TO 'backend:error, foo:fatal, archiver:debug1'; -- fail
+SET log_min_messages TO 'backend:error, checkpointer:bar, archiver:debug1'; -- fail
+SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3';
+SHOW log_min_messages;
+SET log_min_messages TO 'warning, autovacuum:debug1';
+SHOW log_min_messages;
+SET log_min_messages TO 'autovacuum:debug1, warning';
+SHOW log_min_messages;
--
2.39.5
[text/x-patch] v8-0002-Assign-backend-type-earlier.patch (8.6K, ../../[email protected]/3-v8-0002-Assign-backend-type-earlier.patch)
download | inline diff:
From 0c91ca9b3546c3cdc91d1586bf63e247d09efe38 Mon Sep 17 00:00:00 2001
From: Euler Taveira <[email protected]>
Date: Tue, 25 Nov 2025 21:09:11 -0300
Subject: [PATCH v8 2/3] Assign backend type earlier
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Instead of assigning the backend type into the Main function of each
postmaster child, do it earlier (after fork()). The backend type is
already known by postmaster_child_launch() before it calls fork(). This
reduces the time frame that MyBackendType is correct. (This is important
for log_min_messages per backend type that relies on a known
MyBackendType to decide if it writes a log message or not.)
Author: Euler Taveira <[email protected]>
Author: Álvaro Herrera <[email protected]>
Discussion: https://www.postgresql.org/message-id/flat/[email protected]
---
src/backend/postmaster/autovacuum.c | 2 --
src/backend/postmaster/bgworker.c | 1 -
src/backend/postmaster/bgwriter.c | 1 -
src/backend/postmaster/checkpointer.c | 1 -
src/backend/postmaster/launch_backend.c | 5 +++++
src/backend/postmaster/pgarch.c | 1 -
src/backend/postmaster/startup.c | 1 -
src/backend/postmaster/syslogger.c | 1 -
src/backend/postmaster/walsummarizer.c | 1 -
src/backend/postmaster/walwriter.c | 1 -
src/backend/replication/logical/slotsync.c | 2 --
src/backend/replication/walreceiver.c | 1 -
src/backend/storage/aio/method_worker.c | 1 -
src/backend/utils/error/elog.c | 7 ++++++-
14 files changed, 11 insertions(+), 15 deletions(-)
diff --git a/src/backend/postmaster/autovacuum.c b/src/backend/postmaster/autovacuum.c
index 3e507d23cc9..5d1c3fdfb0f 100644
--- a/src/backend/postmaster/autovacuum.c
+++ b/src/backend/postmaster/autovacuum.c
@@ -388,7 +388,6 @@ AutoVacLauncherMain(const void *startup_data, size_t startup_data_len)
PostmasterContext = NULL;
}
- MyBackendType = B_AUTOVAC_LAUNCHER;
init_ps_display(NULL);
ereport(DEBUG1,
@@ -1401,7 +1400,6 @@ AutoVacWorkerMain(const void *startup_data, size_t startup_data_len)
PostmasterContext = NULL;
}
- MyBackendType = B_AUTOVAC_WORKER;
init_ps_display(NULL);
Assert(GetProcessingMode() == InitProcessing);
diff --git a/src/backend/postmaster/bgworker.c b/src/backend/postmaster/bgworker.c
index 65deabe91a7..5144d70095c 100644
--- a/src/backend/postmaster/bgworker.c
+++ b/src/backend/postmaster/bgworker.c
@@ -753,7 +753,6 @@ BackgroundWorkerMain(const void *startup_data, size_t startup_data_len)
}
MyBgworkerEntry = worker;
- MyBackendType = B_BG_WORKER;
init_ps_display(worker->bgw_name);
Assert(GetProcessingMode() == InitProcessing);
diff --git a/src/backend/postmaster/bgwriter.c b/src/backend/postmaster/bgwriter.c
index 80e3088fc7e..0956bd39a85 100644
--- a/src/backend/postmaster/bgwriter.c
+++ b/src/backend/postmaster/bgwriter.c
@@ -94,7 +94,6 @@ BackgroundWriterMain(const void *startup_data, size_t startup_data_len)
Assert(startup_data_len == 0);
- MyBackendType = B_BG_WRITER;
AuxiliaryProcessMainCommon();
/*
diff --git a/src/backend/postmaster/checkpointer.c b/src/backend/postmaster/checkpointer.c
index 6482c21b8f9..e03c19123bc 100644
--- a/src/backend/postmaster/checkpointer.c
+++ b/src/backend/postmaster/checkpointer.c
@@ -199,7 +199,6 @@ CheckpointerMain(const void *startup_data, size_t startup_data_len)
Assert(startup_data_len == 0);
- MyBackendType = B_CHECKPOINTER;
AuxiliaryProcessMainCommon();
CheckpointerShmem->checkpointer_pid = MyProcPid;
diff --git a/src/backend/postmaster/launch_backend.c b/src/backend/postmaster/launch_backend.c
index 57303e1731b..501bad56832 100644
--- a/src/backend/postmaster/launch_backend.c
+++ b/src/backend/postmaster/launch_backend.c
@@ -224,6 +224,8 @@ postmaster_child_launch(BackendType child_type, int child_slot,
pid = fork_process();
if (pid == 0) /* child */
{
+ MyBackendType = child_type;
+
/* Capture and transfer timings that may be needed for logging */
if (IsExternalConnectionBackend(child_type))
{
@@ -586,6 +588,8 @@ SubPostmasterMain(int argc, char *argv[])
IsPostmasterEnvironment = true;
whereToSendOutput = DestNone;
+ MyBackendType = B_BACKEND;
+
/*
* Capture the end of process creation for logging. We don't include the
* time spent copying data from shared memory and setting up the backend.
@@ -609,6 +613,7 @@ SubPostmasterMain(int argc, char *argv[])
if (strcmp(child_process_kinds[idx].name, child_kind) == 0)
{
child_type = (BackendType) idx;
+ MyBackendType = child_type;
found = true;
break;
}
diff --git a/src/backend/postmaster/pgarch.c b/src/backend/postmaster/pgarch.c
index 1a20387c4bd..82731e452fc 100644
--- a/src/backend/postmaster/pgarch.c
+++ b/src/backend/postmaster/pgarch.c
@@ -222,7 +222,6 @@ PgArchiverMain(const void *startup_data, size_t startup_data_len)
{
Assert(startup_data_len == 0);
- MyBackendType = B_ARCHIVER;
AuxiliaryProcessMainCommon();
/*
diff --git a/src/backend/postmaster/startup.c b/src/backend/postmaster/startup.c
index a1a4f65f9a9..cdbe53dd262 100644
--- a/src/backend/postmaster/startup.c
+++ b/src/backend/postmaster/startup.c
@@ -217,7 +217,6 @@ StartupProcessMain(const void *startup_data, size_t startup_data_len)
{
Assert(startup_data_len == 0);
- MyBackendType = B_STARTUP;
AuxiliaryProcessMainCommon();
/* Arrange to clean up at startup process exit */
diff --git a/src/backend/postmaster/syslogger.c b/src/backend/postmaster/syslogger.c
index 1c443b3d126..86c5e376b40 100644
--- a/src/backend/postmaster/syslogger.c
+++ b/src/backend/postmaster/syslogger.c
@@ -206,7 +206,6 @@ SysLoggerMain(const void *startup_data, size_t startup_data_len)
now = MyStartTime;
- MyBackendType = B_LOGGER;
init_ps_display(NULL);
/*
diff --git a/src/backend/postmaster/walsummarizer.c b/src/backend/postmaster/walsummarizer.c
index c3d56c866d3..2d8f57099fd 100644
--- a/src/backend/postmaster/walsummarizer.c
+++ b/src/backend/postmaster/walsummarizer.c
@@ -234,7 +234,6 @@ WalSummarizerMain(const void *startup_data, size_t startup_data_len)
Assert(startup_data_len == 0);
- MyBackendType = B_WAL_SUMMARIZER;
AuxiliaryProcessMainCommon();
ereport(DEBUG1,
diff --git a/src/backend/postmaster/walwriter.c b/src/backend/postmaster/walwriter.c
index 38ec8a4c8c7..23e79a32345 100644
--- a/src/backend/postmaster/walwriter.c
+++ b/src/backend/postmaster/walwriter.c
@@ -94,7 +94,6 @@ WalWriterMain(const void *startup_data, size_t startup_data_len)
Assert(startup_data_len == 0);
- MyBackendType = B_WAL_WRITER;
AuxiliaryProcessMainCommon();
/*
diff --git a/src/backend/replication/logical/slotsync.c b/src/backend/replication/logical/slotsync.c
index 73fc51ea53e..b53f888b804 100644
--- a/src/backend/replication/logical/slotsync.c
+++ b/src/backend/replication/logical/slotsync.c
@@ -1540,8 +1540,6 @@ ReplSlotSyncWorkerMain(const void *startup_data, size_t startup_data_len)
Assert(startup_data_len == 0);
- MyBackendType = B_SLOTSYNC_WORKER;
-
init_ps_display(NULL);
Assert(GetProcessingMode() == InitProcessing);
diff --git a/src/backend/replication/walreceiver.c b/src/backend/replication/walreceiver.c
index a41453530a1..8dbd8cb2009 100644
--- a/src/backend/replication/walreceiver.c
+++ b/src/backend/replication/walreceiver.c
@@ -169,7 +169,6 @@ WalReceiverMain(const void *startup_data, size_t startup_data_len)
Assert(startup_data_len == 0);
- MyBackendType = B_WAL_RECEIVER;
AuxiliaryProcessMainCommon();
/*
diff --git a/src/backend/storage/aio/method_worker.c b/src/backend/storage/aio/method_worker.c
index d7c144cd8f7..d9617c20e76 100644
--- a/src/backend/storage/aio/method_worker.c
+++ b/src/backend/storage/aio/method_worker.c
@@ -390,7 +390,6 @@ IoWorkerMain(const void *startup_data, size_t startup_data_len)
volatile int error_errno = 0;
char cmd[128];
- MyBackendType = B_IO_WORKER;
AuxiliaryProcessMainCommon();
pqsignal(SIGHUP, SignalHandlerForConfigReload);
diff --git a/src/backend/utils/error/elog.c b/src/backend/utils/error/elog.c
index 59975309a4e..f35ec0429b2 100644
--- a/src/backend/utils/error/elog.c
+++ b/src/backend/utils/error/elog.c
@@ -2779,7 +2779,12 @@ get_backend_type_for_log(void)
if (MyProcPid == PostmasterPid)
backend_type_str = "postmaster";
else if (MyBackendType == B_BG_WORKER)
- backend_type_str = MyBgworkerEntry->bgw_type;
+ {
+ if (MyBgworkerEntry && MyBgworkerEntry->bgw_type[0] != '\0')
+ backend_type_str = MyBgworkerEntry->bgw_type;
+ else
+ backend_type_str = "early bgworker";
+ }
else
backend_type_str = GetBackendTypeDesc(MyBackendType);
--
2.39.5
[text/x-patch] v8-0003-fixup-log_min_messages-per-process-type.patch (3.5K, ../../[email protected]/4-v8-0003-fixup-log_min_messages-per-process-type.patch)
download | inline diff:
From 2370da9bbe5757f26505dd45a3ddd2091c621108 Mon Sep 17 00:00:00 2001
From: Euler Taveira <[email protected]>
Date: Wed, 14 Jan 2026 17:29:09 -0300
Subject: [PATCH v8 3/3] fixup! log_min_messages per process type
---
src/backend/commands/variable.c | 55 +++++++++++++++++++++++++++++++
src/test/regress/expected/guc.out | 4 +--
2 files changed, 57 insertions(+), 2 deletions(-)
diff --git a/src/backend/commands/variable.c b/src/backend/commands/variable.c
index f4f5d4c367c..53fcce5ea43 100644
--- a/src/backend/commands/variable.c
+++ b/src/backend/commands/variable.c
@@ -42,6 +42,10 @@
#include "utils/tzparser.h"
#include "utils/varlena.h"
+#define MAX_LMM_STR_LEN (BACKEND_NUM_TYPES * 32)
+
+static int string_cmp(const ListCell *a, const ListCell *b);
+
/*
* DATESTYLE
*/
@@ -1275,10 +1279,16 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
char *rawstring;
List *elemlist;
ListCell *l;
+ char *result;
+ bool first = true;
int newlevel[BACKEND_NUM_TYPES];
bool assigned[BACKEND_NUM_TYPES];
int genericlevel = -1; /* -1 means not assigned */
+ result = (char *) guc_malloc(LOG, MAX_LMM_STR_LEN);
+ if (!result)
+ return false;
+
/* Initialize the array. */
for (int i = 0; i < BACKEND_NUM_TYPES; i++)
{
@@ -1442,6 +1452,42 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
newlevel[i] = genericlevel;
}
+ /*
+ * Use a stable representation of log_min_messages. The generic level is
+ * always the first element and the other elements (type:level) are sorted
+ * by process type.
+ */
+ list_sort(elemlist, string_cmp);
+ foreach(l, elemlist)
+ {
+ char *tok = (char *) lfirst(l);
+
+ if (strchr(tok, ':') == NULL)
+ {
+ elemlist = list_delete(elemlist, tok);
+ elemlist = lcons(tok, elemlist);
+ break;
+ }
+ }
+
+ foreach(l, elemlist)
+ {
+ char *tok = (char *) lfirst(l);
+
+ if (first)
+ {
+ strncpy(result, tok, MAX_LMM_STR_LEN);
+ first = false;
+ }
+ else
+ {
+ strlcat(result, ", ", MAX_LMM_STR_LEN);
+ strlcat(result, tok, MAX_LMM_STR_LEN);
+ }
+ }
+ guc_free(*newval);
+ *newval = result;
+
guc_free(rawstring);
list_free(elemlist);
@@ -1456,6 +1502,15 @@ check_log_min_messages(char **newval, void **extra, GucSource source)
return true;
}
+static int
+string_cmp(const ListCell *a, const ListCell *b)
+{
+ const char *s = lfirst(a);
+ const char *t = lfirst(b);
+
+ return strcmp(s, t);
+}
+
/*
* GUC assign_hook for log_min_messages
*/
diff --git a/src/test/regress/expected/guc.out b/src/test/regress/expected/guc.out
index 3b8f44566b2..9a6fd503009 100644
--- a/src/test/regress/expected/guc.out
+++ b/src/test/regress/expected/guc.out
@@ -972,7 +972,7 @@ SET log_min_messages TO 'backend:error, checkpointer:debug3, fatal, archiver:deb
SHOW log_min_messages;
log_min_messages
-------------------------------------------------------------------------------------------------
- backend:error, checkpointer:debug3, fatal, archiver:debug2, autovacuum:debug1, walsender:debug3
+ fatal, archiver:debug2, autovacuum:debug1, backend:error, checkpointer:debug3, walsender:debug3
(1 row)
SET log_min_messages TO 'warning, autovacuum:debug1';
@@ -986,6 +986,6 @@ SET log_min_messages TO 'autovacuum:debug1, warning';
SHOW log_min_messages;
log_min_messages
----------------------------
- autovacuum:debug1, warning
+ warning, autovacuum:debug1
(1 row)
--
2.39.5
^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: log_min_messages per backend type
2026-01-15 16:07 Re: log_min_messages per backend type Euler Taveira <[email protected]>
@ 2026-01-17 22:02 ` Alvaro Herrera <[email protected]>
1 sibling, 0 replies; 4+ messages in thread
From: Alvaro Herrera @ 2026-01-17 22:02 UTC (permalink / raw)
To: Euler Taveira <[email protected]>; +Cc: japin <[email protected]>; Andres Freund <[email protected]>; [email protected]; Chao Li <[email protected]>
On 2026-Jan-15, Euler Taveira wrote:
> + /*
> + * Use a stable representation of log_min_messages. The generic level is
> + * always the first element and the other elements (type:level) are sorted
> + * by process type.
> + */
> + list_sort(elemlist, string_cmp);
> + foreach(l, elemlist)
> + {
> + char *tok = (char *) lfirst(l);
> +
> + if (strchr(tok, ':') == NULL)
> + {
> + elemlist = list_delete(elemlist, tok);
> + elemlist = lcons(tok, elemlist);
> + break;
> + }
> + }
> +static int
> +string_cmp(const ListCell *a, const ListCell *b)
> +{
> + const char *s = lfirst(a);
> + const char *t = lfirst(b);
> +
> + return strcmp(s, t);
> +}
I'm not opposed to this idea, but I think it should be implemented not
by sorting and then moving the first element to the top of the list, but
instead by modifying the cmp function so that the desired order is
achieved directly. So the cmp() should return -1 if element a has no
colon, or 1 if element b has no colon, otherwise return per strcmp.
That way you can remove the foreach() block above, which is icky.
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"Computing is too important to be left to men." (Karen Spärck Jones)
^ permalink raw reply [nested|flat] 4+ messages in thread
* Re: log_min_messages per backend type
2026-01-15 16:07 Re: log_min_messages per backend type Euler Taveira <[email protected]>
@ 2026-01-21 14:51 ` Alvaro Herrera <[email protected]>
1 sibling, 0 replies; 4+ messages in thread
From: Alvaro Herrera @ 2026-01-21 14:51 UTC (permalink / raw)
To: Euler Taveira <[email protected]>; +Cc: japin <[email protected]>; Andres Freund <[email protected]>; [email protected]; Chao Li <[email protected]>
On 2026-Jan-15, Euler Taveira wrote:
> @@ -586,6 +588,8 @@ SubPostmasterMain(int argc, char *argv[])
> IsPostmasterEnvironment = true;
> whereToSendOutput = DestNone;
>
> + MyBackendType = B_BACKEND;
> +
> /*
> * Capture the end of process creation for logging. We don't include the
> * time spent copying data from shared memory and setting up the backend.
BTW I think SubPostmasterMain() shouldn't do this.
--
Álvaro Herrera 48°01'N 7°57'E — https://www.EnterpriseDB.com/
"Curiosity is the hope that something wondrous waits just out of sight,
for we are surrounded by a garden of miracles" (Namron)
(Marjorie Liu and Sana Takeda, Monstress: Inferno)
^ permalink raw reply [nested|flat] 4+ messages in thread
end of thread, other threads:[~2026-01-21 14:51 UTC | newest]
Thread overview: 4+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2020-08-19 12:34 [PATCH 3/5] pg_rewind: Replace the hybrid list+array data structure with simplehash. Heikki Linnakangas <[email protected]>
2026-01-15 16:07 Re: log_min_messages per backend type Euler Taveira <[email protected]>
2026-01-17 22:02 ` Re: log_min_messages per backend type Alvaro Herrera <[email protected]>
2026-01-21 14:51 ` Re: log_min_messages per backend type Alvaro Herrera <[email protected]>
This inbox is served by agora; see mirroring instructions
for how to clone and mirror all data and code used for this inbox