Re: Extensible Rmgr for Table AMs

From: Andres Freund <andres(at)anarazel(dot)de>
To: Jeff Davis <pgsql(at)j-davis(dot)com>
Cc: Julien Rouhaud <rjuju123(at)gmail(dot)com>, Robert Haas <robertmhaas(at)gmail(dot)com>, "pgsql-hackers(at)postgresql(dot)org" <pgsql-hackers(at)postgresql(dot)org>, Amit Kapila <amit(dot)kapila16(at)gmail(dot)com>, Tom Lane <tgl(at)sss(dot)pgh(dot)pa(dot)us>, Simon Riggs <simon(at)2ndquadrant(dot)com>
Subject: Re: Extensible Rmgr for Table AMs
Date: 2022-03-28 18:00:45
Message-ID: 20220328180045.nz3nuy5ea2xpxuax@alap3.anarazel.de
Views: Raw Message | Whole Thread | Download mbox | Resend email
Thread:
Lists: pgsql-hackers

Hi,

On 2022-03-23 21:43:08 -0700, Jeff Davis wrote:
> /* must be kept in sync with RmgrData definition in xlog_internal.h */
> #define PG_RMGR(symname,name,redo,desc,identify,startup,cleanup,mask,decode) \
> - { name, redo, desc, identify, startup, cleanup, mask, decode },
> + &(struct RmgrData){ name, redo, desc, identify, startup, cleanup, mask, decode },
>
> -const RmgrData RmgrTable[RM_MAX_ID + 1] = {
> +static RmgrData *RmgrTable[RM_MAX_ID + 1] = {
> #include "access/rmgrlist.h"
> };

I think this has been discussed before, but to me it's not obvious that it's a
good idea to change RmgrTable from RmgrData to RmgrData *. That adds an
indirection, without obvious benefit.

> +
> +/*
> + * Start up all resource managers.
> + */
> +void
> +StartupResourceManagers()

(void)

> +void
> +RegisterCustomRmgr(RmgrId rmid, RmgrData *rmgr)
> +{
> + if (rmid < RM_MIN_CUSTOM_ID)
> + ereport(PANIC, errmsg("custom rmgr id %d is out of range", rmid));
> +
> + if (!process_shared_preload_libraries_in_progress)
> + ereport(ERROR,
> + (errmsg("custom rmgr must be registered while initializing modules in shared_preload_libraries")));
> +
> + ereport(LOG,
> + (errmsg("registering custom rmgr \"%s\" with ID %d",
> + rmgr->rm_name, rmid)));
> +
> + if (RmgrTable[rmid] != NULL)
> + ereport(PANIC,
> + (errmsg("custom rmgr ID %d already registered with name \"%s\"",
> + rmid, RmgrTable[rmid]->rm_name)));
> +
> + /* check for existing rmgr with the same name */
> + for (int i = 0; i <= RM_MAX_ID; i++)
> + {
> + const RmgrData *existing_rmgr = RmgrTable[i];
> +
> + if (existing_rmgr == NULL)
> + continue;
> +
> + if (!strcmp(existing_rmgr->rm_name, rmgr->rm_name))
> + ereport(PANIC,
> + (errmsg("custom rmgr \"%s\" has the same name as builtin rmgr",
> + existing_rmgr->rm_name)));
> + }
> +
> + /* register it */
> + RmgrTable[rmid] = rmgr;
> +}

Random idea: Might be worth emitting the id->name mapping just after a redo
location is determined, to make it easier to debug things.

> +RmgrData *
> +GetRmgr(RmgrId rmid)
> +{
> + return RmgrTable[rmid];
> +}

Given this is so simple, why incur the cost of a function call? Rather than
continuing to expose RmgrTable and move GetRmgr() into the header, as a static
inline? As-is this also prevent the compiler from optimizing across repeated
GetRmgr() calls (which often won't be possible anyway, but still)..

> - if (record->xl_rmid > RM_MAX_ID)
> + if (record->xl_rmid > RM_MAX_BUILTIN_ID && record->xl_rmid < RM_MIN_CUSTOM_ID)
> {
> report_invalid_record(state,
> "invalid resource manager ID %u at %X/%X",

Shouldn't this continue to enforce RM_MAX_ID as well?

> @@ -1604,12 +1603,7 @@ PerformWalRecovery(void)
>
> InRedo = true;
>
> - /* Initialize resource managers */
> - for (rmid = 0; rmid <= RM_MAX_ID; rmid++)
> - {
> - if (RmgrTable[rmid].rm_startup != NULL)
> - RmgrTable[rmid].rm_startup();
> - }
> + StartupResourceManagers();

Personally I'd rather name it ResourceManagersStartup() or RmgrStartup().

> @@ -1871,7 +1860,7 @@ ApplyWalRecord(XLogReaderState *xlogreader, XLogRecord *record, TimeLineID *repl
> xlogrecovery_redo(xlogreader, *replayTLI);
>
> /* Now apply the WAL record itself */
> - RmgrTable[record->xl_rmid].rm_redo(xlogreader);
> + GetRmgr(record->xl_rmid)->rm_redo(xlogreader);
>
> /*
> * After redo, check whether the backup pages associated with the WAL

So we have just added one indirect call and one pointer indirection
(previously RmgrTable could be resolved by the linker, now it needs to be
dereferenced again), that's not too bad. I was afraid there'd be multiple
calls.

> @@ -2101,16 +2090,16 @@ xlog_outdesc(StringInfo buf, XLogReaderState *record)
> uint8 info = XLogRecGetInfo(record);
> const char *id;
>
> - appendStringInfoString(buf, RmgrTable[rmid].rm_name);
> + appendStringInfoString(buf, GetRmgr(rmid)->rm_name);
> appendStringInfoChar(buf, '/');
>
> - id = RmgrTable[rmid].rm_identify(info);
> + id = GetRmgr(rmid)->rm_identify(info);
> if (id == NULL)
> appendStringInfo(buf, "UNKNOWN (%X): ", info & ~XLR_INFO_MASK);
> else
> appendStringInfo(buf, "%s: ", id);
>
> - RmgrTable[rmid].rm_desc(buf, record);
> + GetRmgr(rmid)->rm_desc(buf, record);
> }

Like here. It's obviously not as performance critical as replay. But it's
still a shame to add 3 calls to GetRmgr, that each then need to dereference
RmgrTable. The compiler won't be able to optimize any of that away.

> @@ -117,8 +117,8 @@ LogicalDecodingProcessRecord(LogicalDecodingContext *ctx, XLogReaderState *recor
>
> rmid = XLogRecGetRmid(record);
>
> - if (RmgrTable[rmid].rm_decode != NULL)
> - RmgrTable[rmid].rm_decode(ctx, &buf);
> + if (GetRmgr(rmid)->rm_decode != NULL)
> + GetRmgr(rmid)->rm_decode(ctx, &buf);
> else
> {
> /* just deal with xid, and done */

This one might actually matter, overhead wise.

> @@ -473,7 +473,7 @@ static void
> XLogDumpDisplayRecord(XLogDumpConfig *config, XLogReaderState *record)
> {
> const char *id;
> - const RmgrDescData *desc = &RmgrDescTable[XLogRecGetRmid(record)];
> + const RmgrDescData *desc = GetRmgrDesc(XLogRecGetRmid(record));
> uint32 rec_len;
> uint32 fpi_len;
> RelFileNode rnode;
> @@ -658,7 +658,7 @@ XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
> * calculate column totals.
> */
>
> - for (ri = 0; ri < RM_NEXT_ID; ri++)
> + for (ri = 0; ri < RM_MAX_ID; ri++)
> {
> total_count += stats->rmgr_stats[ri].count;
> total_rec_len += stats->rmgr_stats[ri].rec_len;

> @@ -694,6 +694,9 @@ XLogDumpDisplayStats(XLogDumpConfig *config, XLogDumpStats *stats)
> fpi_len = stats->rmgr_stats[ri].fpi_len;
> tot_len = rec_len + fpi_len;
>
> + if (ri > RM_MAX_BUILTIN_ID && count == 0)
> + continue;
> +

Ah, I see. I had written a concerned comment about the previous hunk...

> XLogDumpStatsRow(desc->rm_name,
> count, total_count, rec_len, total_rec_len,
> fpi_len, total_fpi_len, tot_len, total_len);
> @@ -913,16 +916,16 @@ main(int argc, char **argv)
> exit(EXIT_SUCCESS);
> }
>
> - for (i = 0; i <= RM_MAX_ID; i++)
> + for (i = 0; i <= RM_MAX_BUILTIN_ID; i++)
> {
> - if (pg_strcasecmp(optarg, RmgrDescTable[i].rm_name) == 0)
> + if (pg_strcasecmp(optarg, GetRmgrDesc(i)->rm_name) == 0)
> {
> config.filter_by_rmgr[i] = true;
> config.filter_by_rmgr_enabled = true;
> break;
> }
> }
> - if (i > RM_MAX_ID)
> + if (i > RM_MAX_BUILTIN_ID)
> {
> pg_log_error("resource manager \"%s\" does not exist",
> optarg);

So we can't filter by rmgr id for non-builtin rmgr's? That sucks. Maybe add a
custom:<i> syntax? Or generally allow numerical identifiers in addition to the
names?

Greetings,

Andres Freund

In response to

Responses

Browse pgsql-hackers by date

  From Date Subject
Next Message Robert Haas 2022-03-28 18:31:04 Re: role self-revocation
Previous Message Nathan Bossart 2022-03-28 17:35:03 Re: Estimating HugePages Requirements?