The purpose of MultiXactMemberFreezeThreshold() is to make the effective autovacuum_multixact_freeze_max_age smaller, if the multixact members SLRU is approaching wraparound. Per comment there:

 * To prevent that, if more than a threshold portion of the members space is
 * used, we effectively reduce autovacuum_multixact_freeze_max_age and
 * to a value just less than the number of multixacts in use.  We hope that
 * this will quickly trigger autovacuuming on the table or tables with the
 * oldest relminmxid, thus allowing datminmxid values to advance and removing
 * some members.

However, the value that the function calculates can sometimes be *greater* than autovacuum_multixact_freeze_max_age. To get an overview of how it behaves, I wrote the attached stand-alone C program to test it with different inputs:

If members < MULTIXACT_MEMBER_SAFE_THRESHOLD, it just returns autovacuum_multixact_freeze_max_age, which is 200 million by default:

multixacts:    1000000, members 1000000000 ->  200000000
multixacts:    1000000, members 2000000000 ->  200000000
multixacts:    1000000, members 2100000000 ->  200000000

Above MULTIXACT_MEMBER_SAFE_THRESHOLD, the members-based calculated kicks in:

multixacts:    1000000, members 2200000000 ->     951091
multixacts:    1000000, members 2300000000 ->     857959
multixacts:    1000000, members 2500000000 ->     671694
multixacts:    1000000, members 3000000000 ->     206033
multixacts:    1000000, members 3100000000 ->     112901
multixacts:    1000000, members 3500000000 ->          0
multixacts:    1000000, members 4000000000 ->          0

However, if multixacts is also large the returned value is also quite large:

multixacts: 1000000000, members 2200000000 ->  951090335

That's larger than the default autovacuum_multixact_freeze_max_age! If you had set it to a lower non-default value, it's even worse.

I noticed this after I used pg_resetwal to reset next-multixid and next-mxoffset to a high value for testing purposes. Not sure how easy it is to reach that situation normally. In any case, I think the function should clamp the result to autovacuum_multixact_freeze_max_age, per attached.

--
Heikki Linnakangas
Neon (https://neon.tech)
#include <stdint.h>
#include <stdio.h>

typedef uint32_t uint32;
typedef uint32 MultiXactOffset;

#define MaxMultiXactOffset	((MultiXactOffset) 0xFFFFFFFF)

#define MULTIXACT_MEMBER_SAFE_THRESHOLD		(MaxMultiXactOffset / 2)
#define MULTIXACT_MEMBER_DANGER_THRESHOLD	\
	(MaxMultiXactOffset - MaxMultiXactOffset / 4)

#define autovacuum_multixact_freeze_max_age 200000000

static int
test_MultiXactMemberFreezeThreshold(uint32 multixacts, MultiXactOffset members)
{
	uint32		victim_multixacts;
	double		fraction;

	/* If member space utilization is low, no special action is required. */
	if (members <= MULTIXACT_MEMBER_SAFE_THRESHOLD)
		return autovacuum_multixact_freeze_max_age;

	/*
	 * Compute a target for relminmxid advancement.  The number of multixacts
	 * we try to eliminate from the system is based on how far we are past
	 * MULTIXACT_MEMBER_SAFE_THRESHOLD.
	 */
	fraction = (double) (members - MULTIXACT_MEMBER_SAFE_THRESHOLD) /
		(MULTIXACT_MEMBER_DANGER_THRESHOLD - MULTIXACT_MEMBER_SAFE_THRESHOLD);
	victim_multixacts = multixacts * fraction;

	/* fraction could be > 1.0, but lowest possible freeze age is zero */
	if (victim_multixacts > multixacts)
		return 0;
	return multixacts - victim_multixacts;
}

static int
testit(uint32 multixacts, MultiXactOffset members)
{
  int result = test_MultiXactMemberFreezeThreshold(multixacts, members);

  printf("multixacts: %10u, members %10u -> %10d\n", multixacts, members, result);

  return result;
}


int main(int argc, char **argv)
{
  testit(   1000000UL, 1000000000UL);
  testit(   1000000UL, 2000000000UL);
  testit(   1000000UL, 2100000000UL);
  testit(   1000000UL, 2200000000UL);
  testit(   1000000UL, 2300000000UL);
  testit(   1000000UL, 2500000000UL);
  testit(   1000000UL, 3000000000UL);
  testit(   1000000UL, 3100000000UL);
  testit(   1000000UL, 3500000000UL);
  testit(   1000000UL, 4000000000UL);

  testit(1000000000UL, 2200000000UL);
}
From 25c18f6918c064e36865e8969fda8300a2864eca Mon Sep 17 00:00:00 2001
From: Heikki Linnakangas <heikki.linnakan...@iki.fi>
Date: Wed, 12 Jun 2024 23:38:41 +0300
Subject: [PATCH 1/1] Clamp result of MultiXactMemberFreezeThreshold

The purpose of the function is to reduce the effective
autovacuum_multixact_freeze_max_age, to make multixid freezing more
aggressive, if the multixact members SLRU is approaching
wraparound. The returned value should thereore never be greater than
plain autovacuum_multixact_freeze_max_age is.

Discussion: xxx
---
 src/backend/access/transam/multixact.c | 9 ++++++++-
 1 file changed, 8 insertions(+), 1 deletion(-)

diff --git a/src/backend/access/transam/multixact.c b/src/backend/access/transam/multixact.c
index 54c916e034..a4e732a3f5 100644
--- a/src/backend/access/transam/multixact.c
+++ b/src/backend/access/transam/multixact.c
@@ -2932,6 +2932,7 @@ MultiXactMemberFreezeThreshold(void)
 	uint32		multixacts;
 	uint32		victim_multixacts;
 	double		fraction;
+	int			result;
 
 	/* If we can't determine member space utilization, assume the worst. */
 	if (!ReadMultiXactCounts(&multixacts, &members))
@@ -2953,7 +2954,13 @@ MultiXactMemberFreezeThreshold(void)
 	/* fraction could be > 1.0, but lowest possible freeze age is zero */
 	if (victim_multixacts > multixacts)
 		return 0;
-	return multixacts - victim_multixacts;
+	result = multixacts - victim_multixacts;
+
+	/*
+	 * Clamp to autovacuum_multixact_freeze_max_age, so that we never make
+	 * autovacuum less aggressive than it would otherwise be.
+	 */
+	return Min(result, autovacuum_multixact_freeze_max_age);
 }
 
 typedef struct mxtruncinfo
-- 
2.39.2

Reply via email to