pg.ddx.io pgsql-hackers@postgresql.org mailing list archive
help / color / mirror / Atom feedInit connection time grows quadratically
18+ messages / 7 participants
[nested] [flat]
* Init connection time grows quadratically
@ 2025-05-27 10:45 Потапов Александр <a.potapov@postgrespro.ru>
0 siblings, 2 replies; 18+ messages in thread
From: Потапов Александр @ 2025-05-27 10:45 UTC (permalink / raw)
To: pgsql-hackers
Hello!
I ran some experiments with pgbench to measure the initialization time and found that the time increases quadratically with the number of clients. It was surprising to me and I would like to understand a reason of such behavior.
Some details on how it was done:
1) I used the branch REL_16_STABLE (commit 2caa85f4).
2) The default system configuration was modified (CPU speed control, memory control, network, ram disk). Briefly:
sudo cpupower frequency-set -g performance
sudo cpupower idle-set -D0
sudo swapoff -a
sudo sh -c 'echo 16384 >/proc/sys/net/core/somaxconn'
sudo sh -c 'echo 16384 >/proc/sys/net/core/netdev_max_backlog'
sudo sh -c ‘echo 16384 >/proc/sys/net/ipv4/tcp_max_syn_backlog’
numactl --membind=0 bash
sudo mount -t tmpfs -o rw,size=512G tmpfs /mnt/ramdisk
exit
Hyperthreading and cpu boost were disabled:
echo 0 | sudo tee /sys/devices/system/cpu/cpufreq/boost
Please note: When testing on a fast multi-core server with a large number of clients, when the speed of creation of new connections becomes very high, even with such kernel parameters an error may occur:
pgbench:pgbench: error: connection to server on socket "/tmp/.s.PGSQL.5114" failed: Resource temporarily unavailable
In such case you need to apply the patch 0001-Fix-fast-connection-rate-issue.patch (attached).
3) The server was configured as:
./configure --enable-debug --with-perl --with-icu --enable-depend --enable-tap-tests
4) Build and install on ramdrive:
make -j$(nproc) -s && make install
5) DB initialization:
/mnt/ramdisk/bin/initdb -k -D /mnt/ramdisk/data -U postgres
Add to the postgresql.conf:
huge_pages = off #for the sake of test stability and reproducibility
shared_buffers = 1GB
max_connections = 16384
6) Start command:
a) Start server (e.g on the first numa socket)
/mnt/ramdisk/bin/pg_ctl -w -D /mnt/ramdisk/data start
b) create test database and stop the server
/mnt/ramdisk/bin/psql -U postgres -c 'create database bench'
/mnt/ramdisk/bin/pg_ctl -w -D /mnt/ramdisk/data stop
7) pgbench commands:
Perform the single test sequence (I've got a dual socket server, so the server was running on the first socket while the clients were running on the second one):
export PATH=/mnt/ramdisk/bin:$PATH
export NUMACTL_CLIENT="--physcpubind=96-191 --membind=1"
export NUMACTL_SERVER="--physcpubind=0-95 --membind=0"
export CLIENTS=1024
numactl $NUMACTL_SERVER pg_ctl -w -D /mnt/ramdisk/data start
numactl $NUMACTL_CLIENT pgbench -U postgres -i -s100 bench
numactl $NUMACTL_CLIENT psql -U postgres -d bench -c "checkpoint"
numactl $NUMACTL_CLIENT pgbench -U postgres -c$CLIENTS -j$CLIENTS -t100 -S bench
numactl $NUMACTL_SERVER pg_ctl -m smart -w -D /mnt/ramdisk/data stop
8) Measurements & Results
Before the measurements I rebooted host machine and configured the host as described above. After that I ran a script that did 30 measurements of init connection time per a given number of clients, average time and standard deviation were also calculated.
The measurements results are presented as graph an in table form:
Number of clientsAverage init time, ms1024~435 +-202048~1062 +-204096~3284 +-408192~11617 +-12016384~43391 +-230
9) The Question
It turned out that the results correspond to a quadratic dependence like y ~ 0.0002x^2 where x is a number of clients and y is init time (ms).
Here there is a question: is it expected behavior or a bug? What do you think? I appreciate any comments and opinions.
--
Best regards,
Alexander Potapov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[image/png] 3BDBA-68359800-F-249DA480 (33.4K, ../../3bdba-68359800-d-249da480@139845401/3-3BDBA-68359800-F-249DA480)
download | view image
[application/octet-stream] 0001-Fix-fast-connection-rate-issue-for-v16.patch (1.2K, ../../3bdba-68359800-d-249da480@139845401/4-0001-Fix-fast-connection-rate-issue-for-v16.patch)
download | inline diff:
From 9206625663b2721af888553ee467b8d0f980f9c3 Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?=E2=80=9CYourUsername=E2=80=9D?= <“YourEmail”>
Date: Tue, 15 Oct 2024 09:32:12 +0300
Subject: [PATCH] Fix-fast-connection-rate-issue-for-v16
---
src/interfaces/libpq/fe-connect.c | 7 +++++++
1 file changed, 7 insertions(+)
diff --git a/src/interfaces/libpq/fe-connect.c b/src/interfaces/libpq/fe-connect.c
index 6bce6f647b..99cffa06f6 100644
--- a/src/interfaces/libpq/fe-connect.c
+++ b/src/interfaces/libpq/fe-connect.c
@@ -3040,6 +3040,7 @@ keep_going: /* We will come back to here until there is
* Start/make connection. This should not block, since we
* are in nonblock mode. If it does, well, too bad.
*/
+retry_connect:
if (connect(conn->sock, (struct sockaddr *) &addr_cur->addr.addr,
addr_cur->addr.salen) < 0)
{
@@ -3057,6 +3058,12 @@ keep_going: /* We will come back to here until there is
conn->status = CONNECTION_STARTED;
return PGRES_POLLING_WRITING;
}
+
+ if (SOCK_ERRNO == EAGAIN)
+ {
+ pg_usleep(100000L); /* wait 0.1 sec */
+ goto retry_connect;
+ }
/* otherwise, trouble */
}
else
--
2.39.2
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2025-05-27 11:27 Потапов Александр <a.potapov@postgrespro.ru>
parent: Потапов Александр <a.potapov@postgrespro.ru>
1 sibling, 0 replies; 18+ messages in thread
From: Потапов Александр @ 2025-05-27 11:27 UTC (permalink / raw)
To: pgsql-hackers
Sorry, I forgot to add the table and graph for point #8
The graph is attached.
This is the table:
--------------------------------------------------
| Number of clients | Average init time, ms |
--------------------------------------------------
| 1024 | ~435 +-20 |
| 2048 | ~1062 +-20 |
| 4096 | ~3284 +-40 |
| 8192 | ~11617 +-120 |
| 16384 | ~43391 +-230 |
--------------------------------------------------
Best regards,
Alexander Potapov
Postgres Professional: http://www.postgrespro.com
The Russian Postgres Company
Attachments:
[image/png] init_time_2caa85f4aae.png (61.1K, ../../209a6-6835a180-9f-204dda80@158931578/3-init_time_2caa85f4aae.png)
download | view image
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2025-05-27 11:36 Matthias van de Meent <boekewurm+postgres@gmail.com>
parent: Потапов Александр <a.potapov@postgrespro.ru>
1 sibling, 1 reply; 18+ messages in thread
From: Matthias van de Meent @ 2025-05-27 11:36 UTC (permalink / raw)
To: Потапов Александр <a.potapov@postgrespro.ru>; +Cc: pgsql-hackers
On Tue, 27 May 2025 at 12:45, Потапов Александр
<a.potapov@postgrespro.ru> wrote:
>
> Hello!
>
> I ran some experiments with pgbench to measure the initialization time and found that the time increases quadratically with the number of clients. It was surprising to me and I would like to understand a reason of such behavior.
>
> Some details on how it was done:
>
[...]
>
> It turned out that the results correspond to a quadratic dependence like y ~ 0.0002x^2 where x is a number of clients and y is init time (ms).
> Here there is a question: is it expected behavior or a bug? What do you think? I appreciate any comments and opinions.
Note that the value of "initial connection time" is based on the time
it takes from about the start of the pg_bench process until the moment
all N expected connections have been established, *not* the average
time it took pg_bench to connect to PostgreSQL. This does also not
exclude other known measurable delays (like spawning threads,
synchronization, etc), so the actual per-connection connection time is
probably closer to O(n) than O(n^2).
Q: Did you check that pgbench or the OS does not have
O(n_active_connections) or O(n_active_threads) overhead per worker
during thread creation or connection establishment, e.g. by varying
the number of threads used to manage these N clients? I wouldn't be
surprised if there are inefficiencies in e.g. the threading- or
synchronization model that cause O(N) per-thread overhead, or O(N^2)
overall when you have one thread per connection.
Kind regards,
Matthias van de Meent
Neon (https://neon.tech)
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2025-06-10 08:56 Потапов Александр <a.potapov@postgrespro.ru>
parent: Matthias van de Meent <boekewurm+postgres@gmail.com>
0 siblings, 1 reply; 18+ messages in thread
From: Потапов Александр @ 2025-06-10 08:56 UTC (permalink / raw)
To: Matthias van de Meent <boekewurm+postgres@gmail.com>; +Cc: pgsql-hackers
Hi Matthias,
I did additional experiments (changed a number of threads on pgbench side an on server side) but it did find not chanches.
Вторник, Май 27, 2025 14:36 MSK, Matthias van de Meent <boekewurm+postgres@gmail.com> писал(а):
On Tue, 27 May 2025 at 12:45, Потапов Александр
<a.potapov@postgrespro.ru> wrote:
>
> Hello!
>
> I ran some experiments with pgbench to measure the initialization time and found that the time increases quadratically with the number of clients. It was surprising to me and I would like to understand a reason of such behavior.
>
> Some details on how it was done:
>
[...]
>
> It turned out that the results correspond to a quadratic dependence like y ~ 0.0002x^2 where x is a number of clients and y is init time (ms).
> Here there is a question: is it expected behavior or a bug? What do you think? I appreciate any comments and opinions.
Note that the value of "initial connection time" is based on the time
it takes from about the start of the pg_bench process until the moment
all N expected connections have been established, *not* the average
time it took pg_bench to connect to PostgreSQL. This does also not
exclude other known measurable delays (like spawning threads,
synchronization, etc), so the actual per-connection connection time is
probably closer to O(n) than O(n^2).
Q: Did you check that pgbench or the OS does not have
O(n_active_connections) or O(n_active_threads) overhead per worker
during thread creation or connection establishment, e.g. by varying
the number of threads used to manage these N clients? I wouldn't be
surprised if there are inefficiencies in e.g. the threading- or
synchronization model that cause O(N) per-thread overhead, or O(N^2)
overall when you have one thread per connection.
Kind regards,
Matthias van de Meent
Neon (https://neon.tech)
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2025-06-16 08:56 Потапов Александр <a.potapov@postgrespro.ru>
parent: Потапов Александр <a.potapov@postgrespro.ru>
0 siblings, 1 reply; 18+ messages in thread
From: Потапов Александр @ 2025-06-16 08:56 UTC (permalink / raw)
To: Потапов Александр <a.potapov@postgrespro.ru>; +Cc: Matthias van de Meent <boekewurm+postgres@gmail.com>; pgsql-hackers
To be more precise I used constant number of threads (128 and 1024) to compare with previous results. The quadratic dependency exists everywhere, see new graph.
> Q: Did you check that pgbench or the OS does not have
> O(n_active_connections) or O(n_active_threads) overhead per worker
> during thread creation or connection establishment, e.g. by varying
> the number of threads used to manage these N clients? I wouldn't be
> surprised if there are inefficiencies in e.g. the threading- or
> synchronization model that cause O(N) per-thread overhead, or O(N^2)
> overall when you have one thread per connection.
Attachments:
[image/png] init_time_2caa85f4aae_const_threads_2.png (89.1K, ../../3b7e5e-684fdc80-f-48e15c80@193664207/3-init_time_2caa85f4aae_const_threads_2.png)
download | view image
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-06-03 06:33 Maksim.Melnikov <m.melnikov@postgrespro.ru>
parent: Потапов Александр <a.potapov@postgrespro.ru>
0 siblings, 1 reply; 18+ messages in thread
From: Maksim.Melnikov @ 2026-06-03 06:33 UTC (permalink / raw)
To: Потапов Александр <a.potapov@postgrespro.ru>; +Cc: Matthias van de Meent <boekewurm+postgres@gmail.com>; pgsql-hackers
On 6/16/25 11:56, Потапов Александр wrote:
>
> To be more precise I used constant number of threads (128 and 1024) to
> compare with previous results. The quadratic dependency exists
> everywhere, see new graph.
>
> > Q: Did you check that pgbench or the OS does not have
> > O(n_active_connections) or O(n_active_threads) overhead per worker
> > during thread creation or connection establishment, e.g. by varying
> > the number of threads used to manage these N clients? I wouldn't be
> > surprised if there are inefficiencies in e.g. the threading- or
> > synchronization model that cause O(N) per-thread overhead, or O(N^2)
> > overall when you have one thread per connection.
>
Hi, all!
I've investigated slightly different scenario then Alexander and I want
share my thoughts in this thread too.
I found that when we run pgbench scenarios sequantially, without
postgres restart between iterations, initial time degrades from launch
to launch and eventually it stabilizes at the worst values then first
run(ICT_degradation.png attached).
Scenario details:
1. postgres rev 1a51ec16db7, 0001-Fix-fast-connection-rate-issue.patch
was applied
2. numa was disabled, swap was off, ramdisk was used for binaries and
pg_data,Hyperthreading and cpu boost were disabled
3. server was built with -02
4.Add to the postgresql.conf:
huge_pages = off #for the sake of test stability and reproducibility
shared_buffers = 1GB
max_connections = 16384
5. Cycle iteration
psql -U postgres -c 'create database bench'
pgbench -U postgres -i -s100 bench
psql -U postgres -d bench -c "checkpoint"
pgbench -U postgres -c${clients} -t100 -j6 -S bench
psql -U postgres -c "DROP DATABASE bench"
Where ${clients} is one of 512/1024/2048/4096/8192
I paid attention that ICT for the first iteration much better than for
next ones. I investigated this behavior a little bit and found a lot of
minor page fault events in ProcArrayAdd method(perf_without_patch-j6.txt
attached) for code line
allProcs[procno].pgxactoff = index;
So, every proc.pgxactoff access generate page fault, because proc
objects accessed in memory randomly and page replacement can occur. I
have some ideas how to improve this - it seems we can put array
of pgxactoff separately
in shmem to have only few hot pages for them. I've attached appropriate
patch(0001-This-patch-reduce-connection-init-close-time.patch). Perf
with minor faults for updated version also was
attached(perf_with_patch-j6.txt attached),
as we can see, patched version fixes this. I made a series of
measurements for all versions and attached comparison
chart(ICT_degradation_with_patch.png attached). Also I add the table
with results
without_patch_first_iteration, average ICT, ms
without_patch_after_warmup, average ICT, ms with_patch_first_iteration,
average ICT, ms with_patch_after_warmup, average ICT, ms
512 ~480+-15 ~500+-10 ~470+-15 ~215 +-15
1024 ~920+-20 ~1000+-20 ~900+-15 ~920 +-20
2048 ~1780+-30 ~2240+-20 ~1760+-30 ~1800 +-30
4096 ~3570+-50 ~6140+-40 ~3450+-130 ~3740 +-60
8192 ~7440+-90 ~18840+-140 ~7440+-140 ~8100+-80
As we can see from attached charts, from one side there is no
degradation for the first iterations for patched and no patched
versions. From another side patched version is much better for next ones.
I've tried the same tests for different values of pgbench -j parameter
to avoid effects of multithreading pgbench nature and got the same picture.
I hope it will be interesting and helpful.
Best regards,
Maksim Melnikov
# Samples: 1M of event 'minor-faults'
# Event count (approx.): 20314715
#
# Children Self Period Command Shared Object Symbol
# ........ ........ ............ ............... ..................................... ........................................................................
#
99.79% 0.00% 0 postgres postgres [.] _start
|
---_start
__libc_start_main@@GLIBC_2.34
__libc_start_call_main
main
PostmasterMain
|
--99.79%--ServerLoop
|
--99.61%--BackendStartup
|
--99.33%--postmaster_child_launch
|
|--95.12%--BackendInitialize
| |
| --95.08%--PostgresMain
| |
| |--53.39%--InitPostgres
| | |
| | |--35.65%--InitProcessPhase2
| | | |
| | | --35.57%--ProcArrayAdd
Percent | Source code & Disassembly of postgres for minor-faults (103275 samples, percent: local period)
--------------------------------------------------------------------------------------------------------------
:
:
:
: 3 Disassembly of section .text:
:
: 5 00000000006b5720 <ProcArrayAdd>:
: 6 /*
: 7 * Add the specified PGPROC to the shared array.
: 8 */
: 9 void
: 10 ProcArrayAdd(PGPROC *proc)
: 11 {
0.00 : 6b5720: endbr64
0.00 : 6b5724: push %rbp
0.00 : 6b5725: mov %rsp,%rbp
0.00 : 6b5728: sub $0x30,%rsp
0.00 : 6b572c: mov %rdi,-0x28(%rbp)
: 17 int pgprocno = GetNumberFromPGProc(proc);
0.00 : 6b5730: mov 0x686319(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5737: mov (%rax),%rax
0.00 : 6b573a: mov -0x28(%rbp),%rdx
0.00 : 6b573e: sub %rax,%rdx
0.00 : 6b5741: sar $0x7,%rdx
0.00 : 6b5745: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b574f: imul %rdx,%rax
0.00 : 6b5753: mov %eax,-0x18(%rbp)
: 26 ProcArrayStruct *arrayP = procArray;
0.00 : 6b5756: mov 0x684043(%rip),%rax # d397a0 <procArray>
0.00 : 6b575d: mov %rax,-0x8(%rbp)
: 29 int index;
: 30 int movecount;
:
: 32 /* See ProcGlobal comment explaining why both locks are held */
: 33 LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
0.00 : 6b5761: mov 0x6854f8(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5768: add $0x200,%rax
0.00 : 6b576e: mov $0x0,%esi
0.00 : 6b5773: mov %rax,%rdi
0.00 : 6b5776: call 6d454c <LWLockAcquire>
: 39 LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
0.00 : 6b577b: mov 0x6854de(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5782: add $0x180,%rax
0.00 : 6b5788: mov $0x0,%esi
0.00 : 6b578d: mov %rax,%rdi
0.00 : 6b5790: call 6d454c <LWLockAcquire>
:
: 46 if (arrayP->numProcs >= arrayP->maxProcs)
0.00 : 6b5795: mov -0x8(%rbp),%rax
0.11 : 6b5799: mov (%rax),%edx
0.00 : 6b579b: mov -0x8(%rbp),%rax
0.00 : 6b579f: mov 0x4(%rax),%eax
0.00 : 6b57a2: cmp %eax,%edx
0.00 : 6b57a4: jl 6b57f5 <ProcArrayAdd+0xd5>
: 53 /*
: 54 * Oops, no room. (This really shouldn't happen, since there is a
: 55 * fixed supply of PGPROC structs too, and so we should have failed
: 56 * earlier.)
: 57 */
: 58 ereport(FATAL,
0.00 : 6b57a6: mov $0x0,%esi
0.00 : 6b57ab: mov $0x16,%edi
0.00 : 6b57b0: call 8f9a0f <errstart_cold>
0.00 : 6b57b5: test %al,%al
0.00 : 6b57b7: je 6b57f5 <ProcArrayAdd+0xd5>
0.00 : 6b57b9: mov $0x30c5,%edi
0.00 : 6b57be: call 8fa2a6 <errcode>
0.00 : 6b57c3: lea 0x4496ce(%rip),%rax # afee98 <__func__.0+0x58>
0.00 : 6b57ca: mov %rax,%rdi
0.00 : 6b57cd: mov $0x0,%eax
0.00 : 6b57d2: call 8fa559 <errmsg>
0.00 : 6b57d7: lea 0x449b4a(%rip),%rax # aff328 <__func__.19>
0.00 : 6b57de: mov %rax,%rdx
0.00 : 6b57e1: mov $0x1e2,%esi
0.00 : 6b57e6: lea 0x4496cb(%rip),%rax # afeeb8 <__func__.0+0x78>
0.00 : 6b57ed: mov %rax,%rdi
0.00 : 6b57f0: call 8f9c10 <errfinish>
: 76 * PGPROC structure in the cache.
: 77 *
: 78 * Since the occurrence of adding/removing a proc is much lower than the
: 79 * access to the ProcArray itself, the overhead should be marginal
: 80 */
: 81 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b57f5: movl $0x0,-0x1c(%rbp)
0.00 : 6b57fc: jmp 6b581f <ProcArrayAdd+0xff>
: 84 {
: 85 int this_procno = arrayP->pgprocnos[index];
0.00 : 6b57fe: mov -0x8(%rbp),%rax
0.00 : 6b5802: mov -0x1c(%rbp),%edx
0.00 : 6b5805: movslq %edx,%rdx
0.00 : 6b5808: add $0x8,%rdx
0.07 : 6b580c: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b5810: mov %eax,-0x14(%rbp)
:
: 93 Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 94 Assert(allProcs[this_procno].pgxactoff == index);
:
: 96 /* If we have found our right position in the array, break */
: 97 if (this_procno > pgprocno)
0.00 : 6b5813: mov -0x14(%rbp),%eax
0.00 : 6b5816: cmp -0x18(%rbp),%eax
0.00 : 6b5819: jg 6b582c <ProcArrayAdd+0x10c>
: 101 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b581b: addl $0x1,-0x1c(%rbp)
0.00 : 6b581f: mov -0x8(%rbp),%rax
0.00 : 6b5823: mov (%rax),%eax
0.00 : 6b5825: cmp %eax,-0x1c(%rbp)
0.00 : 6b5828: jl 6b57fe <ProcArrayAdd+0xde>
0.00 : 6b582a: jmp 6b582d <ProcArrayAdd+0x10d>
: 108 break;
0.00 : 6b582c: nop
: 110 }
:
: 112 movecount = arrayP->numProcs - index;
0.00 : 6b582d: mov -0x8(%rbp),%rax
0.00 : 6b5831: mov (%rax),%eax
0.00 : 6b5833: sub -0x1c(%rbp),%eax
0.00 : 6b5836: mov %eax,-0x10(%rbp)
: 117 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b5839: mov -0x10(%rbp),%eax
0.00 : 6b583c: cltq
0.00 : 6b583e: lea 0x0(,%rax,4),%rdx
: 121 &arrayP->pgprocnos[index],
0.00 : 6b5846: mov -0x1c(%rbp),%eax
0.00 : 6b5849: cltq
0.00 : 6b584b: add $0x8,%rax
0.00 : 6b584f: lea 0x0(,%rax,4),%rcx
0.00 : 6b5857: mov -0x8(%rbp),%rax
0.00 : 6b585b: add %rcx,%rax
0.00 : 6b585e: lea 0x4(%rax),%rcx
: 129 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b5862: mov -0x1c(%rbp),%eax
0.00 : 6b5865: add $0x1,%eax
0.00 : 6b5868: cltq
0.00 : 6b586a: add $0x8,%rax
0.00 : 6b586e: lea 0x0(,%rax,4),%rsi
0.00 : 6b5876: mov -0x8(%rbp),%rax
0.00 : 6b587a: add %rsi,%rax
0.00 : 6b587d: add $0x4,%rax
0.00 : 6b5881: mov %rcx,%rsi
0.00 : 6b5884: mov %rax,%rdi
0.00 : 6b5887: call ed240 <memmove@plt>
: 141 movecount * sizeof(*arrayP->pgprocnos));
: 142 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b588c: mov -0x10(%rbp),%eax
0.00 : 6b588f: cltq
0.00 : 6b5891: lea 0x0(,%rax,4),%rdx
: 146 &ProcGlobal->xids[index],
0.00 : 6b5899: mov 0x6861b0(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58a0: mov 0x8(%rax),%rax
0.00 : 6b58a4: mov -0x1c(%rbp),%ecx
0.00 : 6b58a7: movslq %ecx,%rcx
0.00 : 6b58aa: shl $0x2,%rcx
0.00 : 6b58ae: add %rax,%rcx
: 153 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b58b1: mov 0x686198(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58b8: mov 0x8(%rax),%rax
0.00 : 6b58bc: mov -0x1c(%rbp),%esi
0.00 : 6b58bf: add $0x1,%esi
0.00 : 6b58c2: movslq %esi,%rsi
0.00 : 6b58c5: shl $0x2,%rsi
0.00 : 6b58c9: add %rsi,%rax
0.00 : 6b58cc: mov %rcx,%rsi
0.00 : 6b58cf: mov %rax,%rdi
0.00 : 6b58d2: call ed240 <memmove@plt>
: 164 movecount * sizeof(*ProcGlobal->xids));
: 165 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b58d7: mov -0x10(%rbp),%eax
0.00 : 6b58da: cltq
0.00 : 6b58dc: lea (%rax,%rax,1),%rdx
: 169 &ProcGlobal->subxidStates[index],
0.00 : 6b58e0: mov 0x686169(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58e7: mov 0x10(%rax),%rax
0.00 : 6b58eb: mov -0x1c(%rbp),%ecx
0.00 : 6b58ee: movslq %ecx,%rcx
0.00 : 6b58f1: add %rcx,%rcx
0.00 : 6b58f4: add %rax,%rcx
: 176 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b58f7: mov 0x686152(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58fe: mov 0x10(%rax),%rax
0.00 : 6b5902: mov -0x1c(%rbp),%esi
0.00 : 6b5905: add $0x1,%esi
0.00 : 6b5908: movslq %esi,%rsi
0.00 : 6b590b: add %rsi,%rsi
0.00 : 6b590e: add %rsi,%rax
0.00 : 6b5911: mov %rcx,%rsi
0.00 : 6b5914: mov %rax,%rdi
0.00 : 6b5917: call ed240 <memmove@plt>
: 187 movecount * sizeof(*ProcGlobal->subxidStates));
: 188 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b591c: mov -0x10(%rbp),%eax
0.00 : 6b591f: cltq
: 191 &ProcGlobal->statusFlags[index],
0.00 : 6b5921: mov 0x686128(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b5928: mov 0x18(%rdx),%rcx
: 194 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b592c: mov -0x1c(%rbp),%edx
0.00 : 6b592f: movslq %edx,%rdx
: 197 &ProcGlobal->statusFlags[index],
0.00 : 6b5932: lea (%rcx,%rdx,1),%rsi
: 199 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5936: mov 0x686113(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b593d: mov 0x18(%rdx),%rcx
0.00 : 6b5941: mov -0x1c(%rbp),%edx
0.00 : 6b5944: add $0x1,%edx
0.00 : 6b5947: movslq %edx,%rdx
0.00 : 6b594a: add %rdx,%rcx
0.00 : 6b594d: mov %rax,%rdx
0.00 : 6b5950: mov %rcx,%rdi
0.00 : 6b5953: call ed240 <memmove@plt>
: 209 movecount * sizeof(*ProcGlobal->statusFlags));
:
: 211 arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
0.00 : 6b5958: mov 0x6860f1(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b595f: mov (%rax),%rax
0.00 : 6b5962: mov -0x28(%rbp),%rdx
0.00 : 6b5966: sub %rax,%rdx
0.00 : 6b5969: sar $0x7,%rdx
0.00 : 6b596d: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b5977: imul %rdx,%rax
0.00 : 6b597b: mov %eax,%ecx
0.00 : 6b597d: mov -0x8(%rbp),%rax
0.00 : 6b5981: mov -0x1c(%rbp),%edx
0.00 : 6b5984: movslq %edx,%rdx
0.00 : 6b5987: add $0x8,%rdx
0.00 : 6b598b: mov %ecx,0x4(%rax,%rdx,4)
: 225 proc->pgxactoff = index;
0.00 : 6b598f: mov -0x28(%rbp),%rax
0.00 : 6b5993: mov -0x1c(%rbp),%edx
0.00 : 6b5996: mov %edx,0x2c(%rax)
: 229 ProcGlobal->xids[index] = proc->xid;
0.00 : 6b5999: mov 0x6860b0(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b59a0: mov 0x8(%rax),%rax
0.00 : 6b59a4: mov -0x1c(%rbp),%edx
0.00 : 6b59a7: movslq %edx,%rdx
0.00 : 6b59aa: shl $0x2,%rdx
0.00 : 6b59ae: add %rax,%rdx
0.00 : 6b59b1: mov -0x28(%rbp),%rax
0.00 : 6b59b5: mov 0x3c(%rax),%eax
0.00 : 6b59b8: mov %eax,(%rdx)
: 239 ProcGlobal->subxidStates[index] = proc->subxidStatus;
0.00 : 6b59ba: mov 0x68608f(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b59c1: mov 0x10(%rax),%rax
0.00 : 6b59c5: mov -0x1c(%rbp),%edx
0.00 : 6b59c8: movslq %edx,%rdx
0.00 : 6b59cb: add %rdx,%rdx
0.00 : 6b59ce: add %rax,%rdx
0.00 : 6b59d1: mov -0x28(%rbp),%rax
0.00 : 6b59d5: movzwl 0x44(%rax),%eax
0.00 : 6b59d9: mov %ax,(%rdx)
: 249 ProcGlobal->statusFlags[index] = proc->statusFlags;
0.00 : 6b59dc: mov 0x68606d(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b59e3: mov 0x18(%rax),%rdx
0.00 : 6b59e7: mov -0x1c(%rbp),%eax
0.00 : 6b59ea: cltq
0.00 : 6b59ec: add %rax,%rdx
0.00 : 6b59ef: mov -0x28(%rbp),%rax
0.00 : 6b59f3: movzbl 0x30(%rax),%eax
0.00 : 6b59f7: mov %al,(%rdx)
:
: 259 arrayP->numProcs++;
0.00 : 6b59f9: mov -0x8(%rbp),%rax
0.00 : 6b59fd: mov (%rax),%eax
0.00 : 6b59ff: lea 0x1(%rax),%edx
0.00 : 6b5a02: mov -0x8(%rbp),%rax
0.00 : 6b5a06: mov %edx,(%rax)
:
: 266 /* adjust pgxactoff for all following PGPROCs */
: 267 index++;
0.00 : 6b5a08: addl $0x1,-0x1c(%rbp)
: 269 for (; index < arrayP->numProcs; index++)
0.00 : 6b5a0c: jmp 6b5a4c <ProcArrayAdd+0x32c>
: 271 {
: 272 int procno = arrayP->pgprocnos[index];
0.00 : 6b5a0e: mov -0x8(%rbp),%rax
0.00 : 6b5a12: mov -0x1c(%rbp),%edx
0.00 : 6b5a15: movslq %edx,%rdx
0.00 : 6b5a18: add $0x8,%rdx
0.00 : 6b5a1c: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b5a20: mov %eax,-0xc(%rbp)
:
: 280 Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 281 Assert(allProcs[procno].pgxactoff == index - 1);
:
: 283 allProcs[procno].pgxactoff = index;
0.00 : 6b5a23: mov 0x683d7e(%rip),%rcx # d397a8 <allProcs>
0.00 : 6b5a2a: mov -0xc(%rbp),%eax
0.00 : 6b5a2d: movslq %eax,%rdx
0.00 : 6b5a30: mov %rdx,%rax
0.00 : 6b5a33: shl $0x3,%rax
0.00 : 6b5a37: sub %rdx,%rax
0.00 : 6b5a3a: shl $0x7,%rax
0.00 : 6b5a3e: lea (%rcx,%rax,1),%rdx
0.00 : 6b5a42: mov -0x1c(%rbp),%eax
99.82 : 6b5a45: mov %eax,0x2c(%rdx)
: 294 for (; index < arrayP->numProcs; index++)
0.00 : 6b5a48: addl $0x1,-0x1c(%rbp)
0.00 : 6b5a4c: mov -0x8(%rbp),%rax
0.00 : 6b5a50: mov (%rax),%eax
0.00 : 6b5a52: cmp %eax,-0x1c(%rbp)
0.00 : 6b5a55: jl 6b5a0e <ProcArrayAdd+0x2ee>
:
: 301 /*
: 302 * Release in reversed acquisition order, to reduce frequency of having to
: 303 * wait for XidGenLock while holding ProcArrayLock.
: 304 */
: 305 LWLockRelease(XidGenLock);
0.00 : 6b5a57: mov 0x685202(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5a5e: add $0x180,%rax
0.00 : 6b5a64: mov %rax,%rdi
0.00 : 6b5a67: call 6d4da1 <LWLockRelease>
: 310 LWLockRelease(ProcArrayLock);
0.00 : 6b5a6c: mov 0x6851ed(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5a73: add $0x200,%rax
0.00 : 6b5a79: mov %rax,%rdi
0.00 : 6b5a7c: call 6d4da1 <LWLockRelease>
: 315 }
0.00 : 6b5a81: nop
0.00 : 6b5a82: leave
0.00 : 6b5a83: ret
0.13% 0.10% 11727 postgres postgres [.] ProcArrayAdd
Percent | Source code & Disassembly of postgres for minor-faults (445 samples, percent: local period)
-----------------------------------------------------------------------------------------------------------
:
:
:
: 3 Disassembly of section .text:
:
: 5 00000000006b58ba <ProcArrayAdd>:
: 6 /*
: 7 * Add the specified PGPROC to the shared array.
: 8 */
: 9 void
: 10 ProcArrayAdd(PGPROC *proc)
: 11 {
0.00 : 6b58ba: endbr64
0.00 : 6b58be: push %rbp
0.00 : 6b58bf: mov %rsp,%rbp
0.00 : 6b58c2: sub $0x30,%rsp
0.00 : 6b58c6: mov %rdi,-0x28(%rbp)
: 17 int pgprocno = GetNumberFromPGProc(proc);
0.00 : 6b58ca: mov 0x68617f(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58d1: mov (%rax),%rax
0.00 : 6b58d4: mov -0x28(%rbp),%rdx
0.00 : 6b58d8: sub %rax,%rdx
0.00 : 6b58db: sar $0x7,%rdx
0.00 : 6b58df: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b58e9: imul %rdx,%rax
0.00 : 6b58ed: mov %eax,-0x18(%rbp)
: 26 ProcArrayStruct *arrayP = procArray;
0.00 : 6b58f0: mov 0x683ea9(%rip),%rax # d397a0 <procArray>
0.00 : 6b58f7: mov %rax,-0x8(%rbp)
: 29 int index;
: 30 int movecount;
:
: 32 /* See ProcGlobal comment explaining why both locks are held */
: 33 LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
0.00 : 6b58fb: mov 0x68535e(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5902: add $0x200,%rax
0.00 : 6b5908: mov $0x0,%esi
0.00 : 6b590d: mov %rax,%rdi
0.00 : 6b5910: call 6d48a7 <LWLockAcquire>
: 39 LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
0.00 : 6b5915: mov 0x685344(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b591c: add $0x180,%rax
0.00 : 6b5922: mov $0x0,%esi
0.00 : 6b5927: mov %rax,%rdi
0.00 : 6b592a: call 6d48a7 <LWLockAcquire>
:
: 46 if (arrayP->numProcs >= arrayP->maxProcs)
0.00 : 6b592f: mov -0x8(%rbp),%rax
65.30 : 6b5933: mov (%rax),%edx
0.00 : 6b5935: mov -0x8(%rbp),%rax
0.00 : 6b5939: mov 0x4(%rax),%eax
0.00 : 6b593c: cmp %eax,%edx
0.00 : 6b593e: jl 6b598f <ProcArrayAdd+0xd5>
: 53 /*
: 54 * Oops, no room. (This really shouldn't happen, since there is a
: 55 * fixed supply of PGPROC structs too, and so we should have failed
: 56 * earlier.)
: 57 */
: 58 ereport(FATAL,
0.00 : 6b5940: mov $0x0,%esi
0.00 : 6b5945: mov $0x16,%edi
0.00 : 6b594a: call 8f9e02 <errstart_cold>
0.00 : 6b594f: test %al,%al
0.00 : 6b5951: je 6b598f <ProcArrayAdd+0xd5>
0.00 : 6b5953: mov $0x30c5,%edi
0.00 : 6b5958: call 8fa699 <errcode>
0.00 : 6b595d: lea 0x449534(%rip),%rax # afee98 <__func__.0+0x58>
0.00 : 6b5964: mov %rax,%rdi
0.00 : 6b5967: mov $0x0,%eax
0.00 : 6b596c: call 8fa94c <errmsg>
0.00 : 6b5971: lea 0x4499b0(%rip),%rax # aff328 <__func__.19>
0.00 : 6b5978: mov %rax,%rdx
0.00 : 6b597b: mov $0x1e4,%esi
0.00 : 6b5980: lea 0x449531(%rip),%rax # afeeb8 <__func__.0+0x78>
0.00 : 6b5987: mov %rax,%rdi
0.00 : 6b598a: call 8fa003 <errfinish>
: 76 * PGPROC structure in the cache.
: 77 *
: 78 * Since the occurrence of adding/removing a proc is much lower than the
: 79 * access to the ProcArray itself, the overhead should be marginal
: 80 */
: 81 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b598f: movl $0x0,-0x1c(%rbp)
0.00 : 6b5996: jmp 6b59b9 <ProcArrayAdd+0xff>
: 84 {
: 85 int this_procno = arrayP->pgprocnos[index];
0.00 : 6b5998: mov -0x8(%rbp),%rax
0.00 : 6b599c: mov -0x1c(%rbp),%edx
0.00 : 6b599f: movslq %edx,%rdx
0.00 : 6b59a2: add $0x8,%rdx
34.70 : 6b59a6: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b59aa: mov %eax,-0x14(%rbp)
:
: 93 Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 94 Assert(pgxactoffs[this_procno] == index);
:
: 96 /* If we have found our right position in the array, break */
: 97 if (this_procno > pgprocno)
0.00 : 6b59ad: mov -0x14(%rbp),%eax
0.00 : 6b59b0: cmp -0x18(%rbp),%eax
0.00 : 6b59b3: jg 6b59c6 <ProcArrayAdd+0x10c>
: 101 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b59b5: addl $0x1,-0x1c(%rbp)
0.00 : 6b59b9: mov -0x8(%rbp),%rax
0.00 : 6b59bd: mov (%rax),%eax
0.00 : 6b59bf: cmp %eax,-0x1c(%rbp)
0.00 : 6b59c2: jl 6b5998 <ProcArrayAdd+0xde>
0.00 : 6b59c4: jmp 6b59c7 <ProcArrayAdd+0x10d>
: 108 break;
0.00 : 6b59c6: nop
: 110 }
:
: 112 movecount = arrayP->numProcs - index;
0.00 : 6b59c7: mov -0x8(%rbp),%rax
0.00 : 6b59cb: mov (%rax),%eax
0.00 : 6b59cd: sub -0x1c(%rbp),%eax
0.00 : 6b59d0: mov %eax,-0x10(%rbp)
: 117 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b59d3: mov -0x10(%rbp),%eax
0.00 : 6b59d6: cltq
0.00 : 6b59d8: lea 0x0(,%rax,4),%rdx
: 121 &arrayP->pgprocnos[index],
0.00 : 6b59e0: mov -0x1c(%rbp),%eax
0.00 : 6b59e3: cltq
0.00 : 6b59e5: add $0x8,%rax
0.00 : 6b59e9: lea 0x0(,%rax,4),%rcx
0.00 : 6b59f1: mov -0x8(%rbp),%rax
0.00 : 6b59f5: add %rcx,%rax
0.00 : 6b59f8: lea 0x4(%rax),%rcx
: 129 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b59fc: mov -0x1c(%rbp),%eax
0.00 : 6b59ff: add $0x1,%eax
0.00 : 6b5a02: cltq
0.00 : 6b5a04: add $0x8,%rax
0.00 : 6b5a08: lea 0x0(,%rax,4),%rsi
0.00 : 6b5a10: mov -0x8(%rbp),%rax
0.00 : 6b5a14: add %rsi,%rax
0.00 : 6b5a17: add $0x4,%rax
0.00 : 6b5a1b: mov %rcx,%rsi
0.00 : 6b5a1e: mov %rax,%rdi
0.00 : 6b5a21: call ed240 <memmove@plt>
: 141 movecount * sizeof(*arrayP->pgprocnos));
: 142 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b5a26: mov -0x10(%rbp),%eax
0.00 : 6b5a29: cltq
0.00 : 6b5a2b: lea 0x0(,%rax,4),%rdx
: 146 &ProcGlobal->xids[index],
0.00 : 6b5a33: mov 0x686016(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a3a: mov 0x10(%rax),%rax
0.00 : 6b5a3e: mov -0x1c(%rbp),%ecx
0.00 : 6b5a41: movslq %ecx,%rcx
0.00 : 6b5a44: shl $0x2,%rcx
0.00 : 6b5a48: add %rax,%rcx
: 153 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b5a4b: mov 0x685ffe(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a52: mov 0x10(%rax),%rax
0.00 : 6b5a56: mov -0x1c(%rbp),%esi
0.00 : 6b5a59: add $0x1,%esi
0.00 : 6b5a5c: movslq %esi,%rsi
0.00 : 6b5a5f: shl $0x2,%rsi
0.00 : 6b5a63: add %rsi,%rax
0.00 : 6b5a66: mov %rcx,%rsi
0.00 : 6b5a69: mov %rax,%rdi
0.00 : 6b5a6c: call ed240 <memmove@plt>
: 164 movecount * sizeof(*ProcGlobal->xids));
: 165 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b5a71: mov -0x10(%rbp),%eax
0.00 : 6b5a74: cltq
0.00 : 6b5a76: lea (%rax,%rax,1),%rdx
: 169 &ProcGlobal->subxidStates[index],
0.00 : 6b5a7a: mov 0x685fcf(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a81: mov 0x18(%rax),%rax
0.00 : 6b5a85: mov -0x1c(%rbp),%ecx
0.00 : 6b5a88: movslq %ecx,%rcx
0.00 : 6b5a8b: add %rcx,%rcx
0.00 : 6b5a8e: add %rax,%rcx
: 176 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b5a91: mov 0x685fb8(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a98: mov 0x18(%rax),%rax
0.00 : 6b5a9c: mov -0x1c(%rbp),%esi
0.00 : 6b5a9f: add $0x1,%esi
0.00 : 6b5aa2: movslq %esi,%rsi
0.00 : 6b5aa5: add %rsi,%rsi
0.00 : 6b5aa8: add %rsi,%rax
0.00 : 6b5aab: mov %rcx,%rsi
0.00 : 6b5aae: mov %rax,%rdi
0.00 : 6b5ab1: call ed240 <memmove@plt>
: 187 movecount * sizeof(*ProcGlobal->subxidStates));
: 188 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5ab6: mov -0x10(%rbp),%eax
0.00 : 6b5ab9: cltq
: 191 &ProcGlobal->statusFlags[index],
0.00 : 6b5abb: mov 0x685f8e(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b5ac2: mov 0x20(%rdx),%rcx
: 194 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5ac6: mov -0x1c(%rbp),%edx
0.00 : 6b5ac9: movslq %edx,%rdx
: 197 &ProcGlobal->statusFlags[index],
0.00 : 6b5acc: lea (%rcx,%rdx,1),%rsi
: 199 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5ad0: mov 0x685f79(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b5ad7: mov 0x20(%rdx),%rcx
0.00 : 6b5adb: mov -0x1c(%rbp),%edx
0.00 : 6b5ade: add $0x1,%edx
0.00 : 6b5ae1: movslq %edx,%rdx
0.00 : 6b5ae4: add %rdx,%rcx
0.00 : 6b5ae7: mov %rax,%rdx
0.00 : 6b5aea: mov %rcx,%rdi
0.00 : 6b5aed: call ed240 <memmove@plt>
: 209 movecount * sizeof(*ProcGlobal->statusFlags));
:
: 211 arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
0.00 : 6b5af2: mov 0x685f57(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5af9: mov (%rax),%rax
0.00 : 6b5afc: mov -0x28(%rbp),%rdx
0.00 : 6b5b00: sub %rax,%rdx
0.00 : 6b5b03: sar $0x7,%rdx
0.00 : 6b5b07: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b5b11: imul %rdx,%rax
0.00 : 6b5b15: mov %eax,%ecx
0.00 : 6b5b17: mov -0x8(%rbp),%rax
0.00 : 6b5b1b: mov -0x1c(%rbp),%edx
0.00 : 6b5b1e: movslq %edx,%rdx
0.00 : 6b5b21: add $0x8,%rdx
0.00 : 6b5b25: mov %ecx,0x4(%rax,%rdx,4)
: 225 pgxactoffs[arrayP->pgprocnos[index]] = index;
0.00 : 6b5b29: mov 0x683c80(%rip),%rdx # d397b0 <pgxactoffs>
0.00 : 6b5b30: mov -0x8(%rbp),%rax
0.00 : 6b5b34: mov -0x1c(%rbp),%ecx
0.00 : 6b5b37: movslq %ecx,%rcx
0.00 : 6b5b3a: add $0x8,%rcx
0.00 : 6b5b3e: mov 0x4(%rax,%rcx,4),%eax
0.00 : 6b5b42: cltq
0.00 : 6b5b44: shl $0x2,%rax
0.00 : 6b5b48: add %rax,%rdx
0.00 : 6b5b4b: mov -0x1c(%rbp),%eax
0.00 : 6b5b4e: mov %eax,(%rdx)
: 237 ProcGlobal->xids[index] = proc->xid;
0.00 : 6b5b50: mov 0x685ef9(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5b57: mov 0x10(%rax),%rax
0.00 : 6b5b5b: mov -0x1c(%rbp),%edx
0.00 : 6b5b5e: movslq %edx,%rdx
0.00 : 6b5b61: shl $0x2,%rdx
0.00 : 6b5b65: add %rax,%rdx
0.00 : 6b5b68: mov -0x28(%rbp),%rax
0.00 : 6b5b6c: mov 0x38(%rax),%eax
0.00 : 6b5b6f: mov %eax,(%rdx)
: 247 ProcGlobal->subxidStates[index] = proc->subxidStatus;
0.00 : 6b5b71: mov 0x685ed8(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5b78: mov 0x18(%rax),%rax
0.00 : 6b5b7c: mov -0x1c(%rbp),%edx
0.00 : 6b5b7f: movslq %edx,%rdx
0.00 : 6b5b82: add %rdx,%rdx
0.00 : 6b5b85: add %rax,%rdx
0.00 : 6b5b88: mov -0x28(%rbp),%rax
0.00 : 6b5b8c: movzwl 0x40(%rax),%eax
0.00 : 6b5b90: mov %ax,(%rdx)
: 257 ProcGlobal->statusFlags[index] = proc->statusFlags;
0.00 : 6b5b93: mov 0x685eb6(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5b9a: mov 0x20(%rax),%rdx
0.00 : 6b5b9e: mov -0x1c(%rbp),%eax
0.00 : 6b5ba1: cltq
0.00 : 6b5ba3: add %rax,%rdx
0.00 : 6b5ba6: mov -0x28(%rbp),%rax
0.00 : 6b5baa: movzbl 0x2c(%rax),%eax
0.00 : 6b5bae: mov %al,(%rdx)
:
: 267 arrayP->numProcs++;
0.00 : 6b5bb0: mov -0x8(%rbp),%rax
0.00 : 6b5bb4: mov (%rax),%eax
0.00 : 6b5bb6: lea 0x1(%rax),%edx
0.00 : 6b5bb9: mov -0x8(%rbp),%rax
0.00 : 6b5bbd: mov %edx,(%rax)
:
: 274 /* adjust pgxactoff for all following PGPROCs */
: 275 index++;
0.00 : 6b5bbf: addl $0x1,-0x1c(%rbp)
: 277 for (; index < arrayP->numProcs; index++)
0.00 : 6b5bc3: jmp 6b5bf7 <ProcArrayAdd+0x33d>
: 279 {
: 280 int procno = arrayP->pgprocnos[index];
0.00 : 6b5bc5: mov -0x8(%rbp),%rax
0.00 : 6b5bc9: mov -0x1c(%rbp),%edx
0.00 : 6b5bcc: movslq %edx,%rdx
0.00 : 6b5bcf: add $0x8,%rdx
0.00 : 6b5bd3: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b5bd7: mov %eax,-0xc(%rbp)
:
: 288 Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 289 Assert(pgxactoffs[procno] == index - 1);
:
: 291 pgxactoffs[procno] = index;
0.00 : 6b5bda: mov 0x683bcf(%rip),%rax # d397b0 <pgxactoffs>
0.00 : 6b5be1: mov -0xc(%rbp),%edx
0.00 : 6b5be4: movslq %edx,%rdx
0.00 : 6b5be7: shl $0x2,%rdx
0.00 : 6b5beb: add %rax,%rdx
0.00 : 6b5bee: mov -0x1c(%rbp),%eax
0.00 : 6b5bf1: mov %eax,(%rdx)
: 299 for (; index < arrayP->numProcs; index++)
0.00 : 6b5bf3: addl $0x1,-0x1c(%rbp)
0.00 : 6b5bf7: mov -0x8(%rbp),%rax
0.00 : 6b5bfb: mov (%rax),%eax
0.00 : 6b5bfd: cmp %eax,-0x1c(%rbp)
0.00 : 6b5c00: jl 6b5bc5 <ProcArrayAdd+0x30b>
:
: 306 /*
: 307 * Release in reversed acquisition order, to reduce frequency of having to
: 308 * wait for XidGenLock while holding ProcArrayLock.
: 309 */
: 310 LWLockRelease(XidGenLock);
0.00 : 6b5c02: mov 0x685057(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5c09: add $0x180,%rax
0.00 : 6b5c0f: mov %rax,%rdi
0.00 : 6b5c12: call 6d50fc <LWLockRelease>
: 315 LWLockRelease(ProcArrayLock);
0.00 : 6b5c17: mov 0x685042(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5c1e: add $0x200,%rax
0.00 : 6b5c24: mov %rax,%rdi
0.00 : 6b5c27: call 6d50fc <LWLockRelease>
: 320 }
0.00 : 6b5c2c: nop
0.00 : 6b5c2d: leave
0.00 : 6b5c2e: ret
Attachments:
[image/png] ICT_degradation.png (50.4K, ../../65d0e5b2-2a40-4b12-be04-0804912d2daf@postgrespro.ru/3-ICT_degradation.png)
download | view image
[image/png] ICT_degradation_with_patch.png (63.6K, ../../65d0e5b2-2a40-4b12-be04-0804912d2daf@postgrespro.ru/4-ICT_degradation_with_patch.png)
download | view image
[text/plain] perf_without_patch-j6.txt (16.2K, ../../65d0e5b2-2a40-4b12-be04-0804912d2daf@postgrespro.ru/5-perf_without_patch-j6.txt)
download | inline:
# Samples: 1M of event 'minor-faults'
# Event count (approx.): 20314715
#
# Children Self Period Command Shared Object Symbol
# ........ ........ ............ ............... ..................................... ........................................................................
#
99.79% 0.00% 0 postgres postgres [.] _start
|
---_start
__libc_start_main@@GLIBC_2.34
__libc_start_call_main
main
PostmasterMain
|
--99.79%--ServerLoop
|
--99.61%--BackendStartup
|
--99.33%--postmaster_child_launch
|
|--95.12%--BackendInitialize
| |
| --95.08%--PostgresMain
| |
| |--53.39%--InitPostgres
| | |
| | |--35.65%--InitProcessPhase2
| | | |
| | | --35.57%--ProcArrayAdd
Percent | Source code & Disassembly of postgres for minor-faults (103275 samples, percent: local period)
--------------------------------------------------------------------------------------------------------------
:
:
:
: 3 Disassembly of section .text:
:
: 5 00000000006b5720 <ProcArrayAdd>:
: 6 /*
: 7 * Add the specified PGPROC to the shared array.
: 8 */
: 9 void
: 10 ProcArrayAdd(PGPROC *proc)
: 11 {
0.00 : 6b5720: endbr64
0.00 : 6b5724: push %rbp
0.00 : 6b5725: mov %rsp,%rbp
0.00 : 6b5728: sub $0x30,%rsp
0.00 : 6b572c: mov %rdi,-0x28(%rbp)
: 17 int pgprocno = GetNumberFromPGProc(proc);
0.00 : 6b5730: mov 0x686319(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5737: mov (%rax),%rax
0.00 : 6b573a: mov -0x28(%rbp),%rdx
0.00 : 6b573e: sub %rax,%rdx
0.00 : 6b5741: sar $0x7,%rdx
0.00 : 6b5745: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b574f: imul %rdx,%rax
0.00 : 6b5753: mov %eax,-0x18(%rbp)
: 26 ProcArrayStruct *arrayP = procArray;
0.00 : 6b5756: mov 0x684043(%rip),%rax # d397a0 <procArray>
0.00 : 6b575d: mov %rax,-0x8(%rbp)
: 29 int index;
: 30 int movecount;
:
: 32 /* See ProcGlobal comment explaining why both locks are held */
: 33 LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
0.00 : 6b5761: mov 0x6854f8(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5768: add $0x200,%rax
0.00 : 6b576e: mov $0x0,%esi
0.00 : 6b5773: mov %rax,%rdi
0.00 : 6b5776: call 6d454c <LWLockAcquire>
: 39 LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
0.00 : 6b577b: mov 0x6854de(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5782: add $0x180,%rax
0.00 : 6b5788: mov $0x0,%esi
0.00 : 6b578d: mov %rax,%rdi
0.00 : 6b5790: call 6d454c <LWLockAcquire>
:
: 46 if (arrayP->numProcs >= arrayP->maxProcs)
0.00 : 6b5795: mov -0x8(%rbp),%rax
0.11 : 6b5799: mov (%rax),%edx
0.00 : 6b579b: mov -0x8(%rbp),%rax
0.00 : 6b579f: mov 0x4(%rax),%eax
0.00 : 6b57a2: cmp %eax,%edx
0.00 : 6b57a4: jl 6b57f5 <ProcArrayAdd+0xd5>
: 53 /*
: 54 * Oops, no room. (This really shouldn't happen, since there is a
: 55 * fixed supply of PGPROC structs too, and so we should have failed
: 56 * earlier.)
: 57 */
: 58 ereport(FATAL,
0.00 : 6b57a6: mov $0x0,%esi
0.00 : 6b57ab: mov $0x16,%edi
0.00 : 6b57b0: call 8f9a0f <errstart_cold>
0.00 : 6b57b5: test %al,%al
0.00 : 6b57b7: je 6b57f5 <ProcArrayAdd+0xd5>
0.00 : 6b57b9: mov $0x30c5,%edi
0.00 : 6b57be: call 8fa2a6 <errcode>
0.00 : 6b57c3: lea 0x4496ce(%rip),%rax # afee98 <__func__.0+0x58>
0.00 : 6b57ca: mov %rax,%rdi
0.00 : 6b57cd: mov $0x0,%eax
0.00 : 6b57d2: call 8fa559 <errmsg>
0.00 : 6b57d7: lea 0x449b4a(%rip),%rax # aff328 <__func__.19>
0.00 : 6b57de: mov %rax,%rdx
0.00 : 6b57e1: mov $0x1e2,%esi
0.00 : 6b57e6: lea 0x4496cb(%rip),%rax # afeeb8 <__func__.0+0x78>
0.00 : 6b57ed: mov %rax,%rdi
0.00 : 6b57f0: call 8f9c10 <errfinish>
: 76 * PGPROC structure in the cache.
: 77 *
: 78 * Since the occurrence of adding/removing a proc is much lower than the
: 79 * access to the ProcArray itself, the overhead should be marginal
: 80 */
: 81 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b57f5: movl $0x0,-0x1c(%rbp)
0.00 : 6b57fc: jmp 6b581f <ProcArrayAdd+0xff>
: 84 {
: 85 int this_procno = arrayP->pgprocnos[index];
0.00 : 6b57fe: mov -0x8(%rbp),%rax
0.00 : 6b5802: mov -0x1c(%rbp),%edx
0.00 : 6b5805: movslq %edx,%rdx
0.00 : 6b5808: add $0x8,%rdx
0.07 : 6b580c: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b5810: mov %eax,-0x14(%rbp)
:
: 93 Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 94 Assert(allProcs[this_procno].pgxactoff == index);
:
: 96 /* If we have found our right position in the array, break */
: 97 if (this_procno > pgprocno)
0.00 : 6b5813: mov -0x14(%rbp),%eax
0.00 : 6b5816: cmp -0x18(%rbp),%eax
0.00 : 6b5819: jg 6b582c <ProcArrayAdd+0x10c>
: 101 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b581b: addl $0x1,-0x1c(%rbp)
0.00 : 6b581f: mov -0x8(%rbp),%rax
0.00 : 6b5823: mov (%rax),%eax
0.00 : 6b5825: cmp %eax,-0x1c(%rbp)
0.00 : 6b5828: jl 6b57fe <ProcArrayAdd+0xde>
0.00 : 6b582a: jmp 6b582d <ProcArrayAdd+0x10d>
: 108 break;
0.00 : 6b582c: nop
: 110 }
:
: 112 movecount = arrayP->numProcs - index;
0.00 : 6b582d: mov -0x8(%rbp),%rax
0.00 : 6b5831: mov (%rax),%eax
0.00 : 6b5833: sub -0x1c(%rbp),%eax
0.00 : 6b5836: mov %eax,-0x10(%rbp)
: 117 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b5839: mov -0x10(%rbp),%eax
0.00 : 6b583c: cltq
0.00 : 6b583e: lea 0x0(,%rax,4),%rdx
: 121 &arrayP->pgprocnos[index],
0.00 : 6b5846: mov -0x1c(%rbp),%eax
0.00 : 6b5849: cltq
0.00 : 6b584b: add $0x8,%rax
0.00 : 6b584f: lea 0x0(,%rax,4),%rcx
0.00 : 6b5857: mov -0x8(%rbp),%rax
0.00 : 6b585b: add %rcx,%rax
0.00 : 6b585e: lea 0x4(%rax),%rcx
: 129 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b5862: mov -0x1c(%rbp),%eax
0.00 : 6b5865: add $0x1,%eax
0.00 : 6b5868: cltq
0.00 : 6b586a: add $0x8,%rax
0.00 : 6b586e: lea 0x0(,%rax,4),%rsi
0.00 : 6b5876: mov -0x8(%rbp),%rax
0.00 : 6b587a: add %rsi,%rax
0.00 : 6b587d: add $0x4,%rax
0.00 : 6b5881: mov %rcx,%rsi
0.00 : 6b5884: mov %rax,%rdi
0.00 : 6b5887: call ed240 <memmove@plt>
: 141 movecount * sizeof(*arrayP->pgprocnos));
: 142 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b588c: mov -0x10(%rbp),%eax
0.00 : 6b588f: cltq
0.00 : 6b5891: lea 0x0(,%rax,4),%rdx
: 146 &ProcGlobal->xids[index],
0.00 : 6b5899: mov 0x6861b0(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58a0: mov 0x8(%rax),%rax
0.00 : 6b58a4: mov -0x1c(%rbp),%ecx
0.00 : 6b58a7: movslq %ecx,%rcx
0.00 : 6b58aa: shl $0x2,%rcx
0.00 : 6b58ae: add %rax,%rcx
: 153 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b58b1: mov 0x686198(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58b8: mov 0x8(%rax),%rax
0.00 : 6b58bc: mov -0x1c(%rbp),%esi
0.00 : 6b58bf: add $0x1,%esi
0.00 : 6b58c2: movslq %esi,%rsi
0.00 : 6b58c5: shl $0x2,%rsi
0.00 : 6b58c9: add %rsi,%rax
0.00 : 6b58cc: mov %rcx,%rsi
0.00 : 6b58cf: mov %rax,%rdi
0.00 : 6b58d2: call ed240 <memmove@plt>
: 164 movecount * sizeof(*ProcGlobal->xids));
: 165 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b58d7: mov -0x10(%rbp),%eax
0.00 : 6b58da: cltq
0.00 : 6b58dc: lea (%rax,%rax,1),%rdx
: 169 &ProcGlobal->subxidStates[index],
0.00 : 6b58e0: mov 0x686169(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58e7: mov 0x10(%rax),%rax
0.00 : 6b58eb: mov -0x1c(%rbp),%ecx
0.00 : 6b58ee: movslq %ecx,%rcx
0.00 : 6b58f1: add %rcx,%rcx
0.00 : 6b58f4: add %rax,%rcx
: 176 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b58f7: mov 0x686152(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58fe: mov 0x10(%rax),%rax
0.00 : 6b5902: mov -0x1c(%rbp),%esi
0.00 : 6b5905: add $0x1,%esi
0.00 : 6b5908: movslq %esi,%rsi
0.00 : 6b590b: add %rsi,%rsi
0.00 : 6b590e: add %rsi,%rax
0.00 : 6b5911: mov %rcx,%rsi
0.00 : 6b5914: mov %rax,%rdi
0.00 : 6b5917: call ed240 <memmove@plt>
: 187 movecount * sizeof(*ProcGlobal->subxidStates));
: 188 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b591c: mov -0x10(%rbp),%eax
0.00 : 6b591f: cltq
: 191 &ProcGlobal->statusFlags[index],
0.00 : 6b5921: mov 0x686128(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b5928: mov 0x18(%rdx),%rcx
: 194 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b592c: mov -0x1c(%rbp),%edx
0.00 : 6b592f: movslq %edx,%rdx
: 197 &ProcGlobal->statusFlags[index],
0.00 : 6b5932: lea (%rcx,%rdx,1),%rsi
: 199 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5936: mov 0x686113(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b593d: mov 0x18(%rdx),%rcx
0.00 : 6b5941: mov -0x1c(%rbp),%edx
0.00 : 6b5944: add $0x1,%edx
0.00 : 6b5947: movslq %edx,%rdx
0.00 : 6b594a: add %rdx,%rcx
0.00 : 6b594d: mov %rax,%rdx
0.00 : 6b5950: mov %rcx,%rdi
0.00 : 6b5953: call ed240 <memmove@plt>
: 209 movecount * sizeof(*ProcGlobal->statusFlags));
:
: 211 arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
0.00 : 6b5958: mov 0x6860f1(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b595f: mov (%rax),%rax
0.00 : 6b5962: mov -0x28(%rbp),%rdx
0.00 : 6b5966: sub %rax,%rdx
0.00 : 6b5969: sar $0x7,%rdx
0.00 : 6b596d: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b5977: imul %rdx,%rax
0.00 : 6b597b: mov %eax,%ecx
0.00 : 6b597d: mov -0x8(%rbp),%rax
0.00 : 6b5981: mov -0x1c(%rbp),%edx
0.00 : 6b5984: movslq %edx,%rdx
0.00 : 6b5987: add $0x8,%rdx
0.00 : 6b598b: mov %ecx,0x4(%rax,%rdx,4)
: 225 proc->pgxactoff = index;
0.00 : 6b598f: mov -0x28(%rbp),%rax
0.00 : 6b5993: mov -0x1c(%rbp),%edx
0.00 : 6b5996: mov %edx,0x2c(%rax)
: 229 ProcGlobal->xids[index] = proc->xid;
0.00 : 6b5999: mov 0x6860b0(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b59a0: mov 0x8(%rax),%rax
0.00 : 6b59a4: mov -0x1c(%rbp),%edx
0.00 : 6b59a7: movslq %edx,%rdx
0.00 : 6b59aa: shl $0x2,%rdx
0.00 : 6b59ae: add %rax,%rdx
0.00 : 6b59b1: mov -0x28(%rbp),%rax
0.00 : 6b59b5: mov 0x3c(%rax),%eax
0.00 : 6b59b8: mov %eax,(%rdx)
: 239 ProcGlobal->subxidStates[index] = proc->subxidStatus;
0.00 : 6b59ba: mov 0x68608f(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b59c1: mov 0x10(%rax),%rax
0.00 : 6b59c5: mov -0x1c(%rbp),%edx
0.00 : 6b59c8: movslq %edx,%rdx
0.00 : 6b59cb: add %rdx,%rdx
0.00 : 6b59ce: add %rax,%rdx
0.00 : 6b59d1: mov -0x28(%rbp),%rax
0.00 : 6b59d5: movzwl 0x44(%rax),%eax
0.00 : 6b59d9: mov %ax,(%rdx)
: 249 ProcGlobal->statusFlags[index] = proc->statusFlags;
0.00 : 6b59dc: mov 0x68606d(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b59e3: mov 0x18(%rax),%rdx
0.00 : 6b59e7: mov -0x1c(%rbp),%eax
0.00 : 6b59ea: cltq
0.00 : 6b59ec: add %rax,%rdx
0.00 : 6b59ef: mov -0x28(%rbp),%rax
0.00 : 6b59f3: movzbl 0x30(%rax),%eax
0.00 : 6b59f7: mov %al,(%rdx)
:
: 259 arrayP->numProcs++;
0.00 : 6b59f9: mov -0x8(%rbp),%rax
0.00 : 6b59fd: mov (%rax),%eax
0.00 : 6b59ff: lea 0x1(%rax),%edx
0.00 : 6b5a02: mov -0x8(%rbp),%rax
0.00 : 6b5a06: mov %edx,(%rax)
:
: 266 /* adjust pgxactoff for all following PGPROCs */
: 267 index++;
0.00 : 6b5a08: addl $0x1,-0x1c(%rbp)
: 269 for (; index < arrayP->numProcs; index++)
0.00 : 6b5a0c: jmp 6b5a4c <ProcArrayAdd+0x32c>
: 271 {
: 272 int procno = arrayP->pgprocnos[index];
0.00 : 6b5a0e: mov -0x8(%rbp),%rax
0.00 : 6b5a12: mov -0x1c(%rbp),%edx
0.00 : 6b5a15: movslq %edx,%rdx
0.00 : 6b5a18: add $0x8,%rdx
0.00 : 6b5a1c: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b5a20: mov %eax,-0xc(%rbp)
:
: 280 Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 281 Assert(allProcs[procno].pgxactoff == index - 1);
:
: 283 allProcs[procno].pgxactoff = index;
0.00 : 6b5a23: mov 0x683d7e(%rip),%rcx # d397a8 <allProcs>
0.00 : 6b5a2a: mov -0xc(%rbp),%eax
0.00 : 6b5a2d: movslq %eax,%rdx
0.00 : 6b5a30: mov %rdx,%rax
0.00 : 6b5a33: shl $0x3,%rax
0.00 : 6b5a37: sub %rdx,%rax
0.00 : 6b5a3a: shl $0x7,%rax
0.00 : 6b5a3e: lea (%rcx,%rax,1),%rdx
0.00 : 6b5a42: mov -0x1c(%rbp),%eax
99.82 : 6b5a45: mov %eax,0x2c(%rdx)
: 294 for (; index < arrayP->numProcs; index++)
0.00 : 6b5a48: addl $0x1,-0x1c(%rbp)
0.00 : 6b5a4c: mov -0x8(%rbp),%rax
0.00 : 6b5a50: mov (%rax),%eax
0.00 : 6b5a52: cmp %eax,-0x1c(%rbp)
0.00 : 6b5a55: jl 6b5a0e <ProcArrayAdd+0x2ee>
:
: 301 /*
: 302 * Release in reversed acquisition order, to reduce frequency of having to
: 303 * wait for XidGenLock while holding ProcArrayLock.
: 304 */
: 305 LWLockRelease(XidGenLock);
0.00 : 6b5a57: mov 0x685202(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5a5e: add $0x180,%rax
0.00 : 6b5a64: mov %rax,%rdi
0.00 : 6b5a67: call 6d4da1 <LWLockRelease>
: 310 LWLockRelease(ProcArrayLock);
0.00 : 6b5a6c: mov 0x6851ed(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5a73: add $0x200,%rax
0.00 : 6b5a79: mov %rax,%rdi
0.00 : 6b5a7c: call 6d4da1 <LWLockRelease>
: 315 }
0.00 : 6b5a81: nop
0.00 : 6b5a82: leave
0.00 : 6b5a83: ret
[text/plain] perf_with_patch-j6.txt (14.7K, ../../65d0e5b2-2a40-4b12-be04-0804912d2daf@postgrespro.ru/6-perf_with_patch-j6.txt)
download | inline:
0.13% 0.10% 11727 postgres postgres [.] ProcArrayAdd
Percent | Source code & Disassembly of postgres for minor-faults (445 samples, percent: local period)
-----------------------------------------------------------------------------------------------------------
:
:
:
: 3 Disassembly of section .text:
:
: 5 00000000006b58ba <ProcArrayAdd>:
: 6 /*
: 7 * Add the specified PGPROC to the shared array.
: 8 */
: 9 void
: 10 ProcArrayAdd(PGPROC *proc)
: 11 {
0.00 : 6b58ba: endbr64
0.00 : 6b58be: push %rbp
0.00 : 6b58bf: mov %rsp,%rbp
0.00 : 6b58c2: sub $0x30,%rsp
0.00 : 6b58c6: mov %rdi,-0x28(%rbp)
: 17 int pgprocno = GetNumberFromPGProc(proc);
0.00 : 6b58ca: mov 0x68617f(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b58d1: mov (%rax),%rax
0.00 : 6b58d4: mov -0x28(%rbp),%rdx
0.00 : 6b58d8: sub %rax,%rdx
0.00 : 6b58db: sar $0x7,%rdx
0.00 : 6b58df: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b58e9: imul %rdx,%rax
0.00 : 6b58ed: mov %eax,-0x18(%rbp)
: 26 ProcArrayStruct *arrayP = procArray;
0.00 : 6b58f0: mov 0x683ea9(%rip),%rax # d397a0 <procArray>
0.00 : 6b58f7: mov %rax,-0x8(%rbp)
: 29 int index;
: 30 int movecount;
:
: 32 /* See ProcGlobal comment explaining why both locks are held */
: 33 LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
0.00 : 6b58fb: mov 0x68535e(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5902: add $0x200,%rax
0.00 : 6b5908: mov $0x0,%esi
0.00 : 6b590d: mov %rax,%rdi
0.00 : 6b5910: call 6d48a7 <LWLockAcquire>
: 39 LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
0.00 : 6b5915: mov 0x685344(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b591c: add $0x180,%rax
0.00 : 6b5922: mov $0x0,%esi
0.00 : 6b5927: mov %rax,%rdi
0.00 : 6b592a: call 6d48a7 <LWLockAcquire>
:
: 46 if (arrayP->numProcs >= arrayP->maxProcs)
0.00 : 6b592f: mov -0x8(%rbp),%rax
65.30 : 6b5933: mov (%rax),%edx
0.00 : 6b5935: mov -0x8(%rbp),%rax
0.00 : 6b5939: mov 0x4(%rax),%eax
0.00 : 6b593c: cmp %eax,%edx
0.00 : 6b593e: jl 6b598f <ProcArrayAdd+0xd5>
: 53 /*
: 54 * Oops, no room. (This really shouldn't happen, since there is a
: 55 * fixed supply of PGPROC structs too, and so we should have failed
: 56 * earlier.)
: 57 */
: 58 ereport(FATAL,
0.00 : 6b5940: mov $0x0,%esi
0.00 : 6b5945: mov $0x16,%edi
0.00 : 6b594a: call 8f9e02 <errstart_cold>
0.00 : 6b594f: test %al,%al
0.00 : 6b5951: je 6b598f <ProcArrayAdd+0xd5>
0.00 : 6b5953: mov $0x30c5,%edi
0.00 : 6b5958: call 8fa699 <errcode>
0.00 : 6b595d: lea 0x449534(%rip),%rax # afee98 <__func__.0+0x58>
0.00 : 6b5964: mov %rax,%rdi
0.00 : 6b5967: mov $0x0,%eax
0.00 : 6b596c: call 8fa94c <errmsg>
0.00 : 6b5971: lea 0x4499b0(%rip),%rax # aff328 <__func__.19>
0.00 : 6b5978: mov %rax,%rdx
0.00 : 6b597b: mov $0x1e4,%esi
0.00 : 6b5980: lea 0x449531(%rip),%rax # afeeb8 <__func__.0+0x78>
0.00 : 6b5987: mov %rax,%rdi
0.00 : 6b598a: call 8fa003 <errfinish>
: 76 * PGPROC structure in the cache.
: 77 *
: 78 * Since the occurrence of adding/removing a proc is much lower than the
: 79 * access to the ProcArray itself, the overhead should be marginal
: 80 */
: 81 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b598f: movl $0x0,-0x1c(%rbp)
0.00 : 6b5996: jmp 6b59b9 <ProcArrayAdd+0xff>
: 84 {
: 85 int this_procno = arrayP->pgprocnos[index];
0.00 : 6b5998: mov -0x8(%rbp),%rax
0.00 : 6b599c: mov -0x1c(%rbp),%edx
0.00 : 6b599f: movslq %edx,%rdx
0.00 : 6b59a2: add $0x8,%rdx
34.70 : 6b59a6: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b59aa: mov %eax,-0x14(%rbp)
:
: 93 Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 94 Assert(pgxactoffs[this_procno] == index);
:
: 96 /* If we have found our right position in the array, break */
: 97 if (this_procno > pgprocno)
0.00 : 6b59ad: mov -0x14(%rbp),%eax
0.00 : 6b59b0: cmp -0x18(%rbp),%eax
0.00 : 6b59b3: jg 6b59c6 <ProcArrayAdd+0x10c>
: 101 for (index = 0; index < arrayP->numProcs; index++)
0.00 : 6b59b5: addl $0x1,-0x1c(%rbp)
0.00 : 6b59b9: mov -0x8(%rbp),%rax
0.00 : 6b59bd: mov (%rax),%eax
0.00 : 6b59bf: cmp %eax,-0x1c(%rbp)
0.00 : 6b59c2: jl 6b5998 <ProcArrayAdd+0xde>
0.00 : 6b59c4: jmp 6b59c7 <ProcArrayAdd+0x10d>
: 108 break;
0.00 : 6b59c6: nop
: 110 }
:
: 112 movecount = arrayP->numProcs - index;
0.00 : 6b59c7: mov -0x8(%rbp),%rax
0.00 : 6b59cb: mov (%rax),%eax
0.00 : 6b59cd: sub -0x1c(%rbp),%eax
0.00 : 6b59d0: mov %eax,-0x10(%rbp)
: 117 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b59d3: mov -0x10(%rbp),%eax
0.00 : 6b59d6: cltq
0.00 : 6b59d8: lea 0x0(,%rax,4),%rdx
: 121 &arrayP->pgprocnos[index],
0.00 : 6b59e0: mov -0x1c(%rbp),%eax
0.00 : 6b59e3: cltq
0.00 : 6b59e5: add $0x8,%rax
0.00 : 6b59e9: lea 0x0(,%rax,4),%rcx
0.00 : 6b59f1: mov -0x8(%rbp),%rax
0.00 : 6b59f5: add %rcx,%rax
0.00 : 6b59f8: lea 0x4(%rax),%rcx
: 129 memmove(&arrayP->pgprocnos[index + 1],
0.00 : 6b59fc: mov -0x1c(%rbp),%eax
0.00 : 6b59ff: add $0x1,%eax
0.00 : 6b5a02: cltq
0.00 : 6b5a04: add $0x8,%rax
0.00 : 6b5a08: lea 0x0(,%rax,4),%rsi
0.00 : 6b5a10: mov -0x8(%rbp),%rax
0.00 : 6b5a14: add %rsi,%rax
0.00 : 6b5a17: add $0x4,%rax
0.00 : 6b5a1b: mov %rcx,%rsi
0.00 : 6b5a1e: mov %rax,%rdi
0.00 : 6b5a21: call ed240 <memmove@plt>
: 141 movecount * sizeof(*arrayP->pgprocnos));
: 142 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b5a26: mov -0x10(%rbp),%eax
0.00 : 6b5a29: cltq
0.00 : 6b5a2b: lea 0x0(,%rax,4),%rdx
: 146 &ProcGlobal->xids[index],
0.00 : 6b5a33: mov 0x686016(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a3a: mov 0x10(%rax),%rax
0.00 : 6b5a3e: mov -0x1c(%rbp),%ecx
0.00 : 6b5a41: movslq %ecx,%rcx
0.00 : 6b5a44: shl $0x2,%rcx
0.00 : 6b5a48: add %rax,%rcx
: 153 memmove(&ProcGlobal->xids[index + 1],
0.00 : 6b5a4b: mov 0x685ffe(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a52: mov 0x10(%rax),%rax
0.00 : 6b5a56: mov -0x1c(%rbp),%esi
0.00 : 6b5a59: add $0x1,%esi
0.00 : 6b5a5c: movslq %esi,%rsi
0.00 : 6b5a5f: shl $0x2,%rsi
0.00 : 6b5a63: add %rsi,%rax
0.00 : 6b5a66: mov %rcx,%rsi
0.00 : 6b5a69: mov %rax,%rdi
0.00 : 6b5a6c: call ed240 <memmove@plt>
: 164 movecount * sizeof(*ProcGlobal->xids));
: 165 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b5a71: mov -0x10(%rbp),%eax
0.00 : 6b5a74: cltq
0.00 : 6b5a76: lea (%rax,%rax,1),%rdx
: 169 &ProcGlobal->subxidStates[index],
0.00 : 6b5a7a: mov 0x685fcf(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a81: mov 0x18(%rax),%rax
0.00 : 6b5a85: mov -0x1c(%rbp),%ecx
0.00 : 6b5a88: movslq %ecx,%rcx
0.00 : 6b5a8b: add %rcx,%rcx
0.00 : 6b5a8e: add %rax,%rcx
: 176 memmove(&ProcGlobal->subxidStates[index + 1],
0.00 : 6b5a91: mov 0x685fb8(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5a98: mov 0x18(%rax),%rax
0.00 : 6b5a9c: mov -0x1c(%rbp),%esi
0.00 : 6b5a9f: add $0x1,%esi
0.00 : 6b5aa2: movslq %esi,%rsi
0.00 : 6b5aa5: add %rsi,%rsi
0.00 : 6b5aa8: add %rsi,%rax
0.00 : 6b5aab: mov %rcx,%rsi
0.00 : 6b5aae: mov %rax,%rdi
0.00 : 6b5ab1: call ed240 <memmove@plt>
: 187 movecount * sizeof(*ProcGlobal->subxidStates));
: 188 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5ab6: mov -0x10(%rbp),%eax
0.00 : 6b5ab9: cltq
: 191 &ProcGlobal->statusFlags[index],
0.00 : 6b5abb: mov 0x685f8e(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b5ac2: mov 0x20(%rdx),%rcx
: 194 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5ac6: mov -0x1c(%rbp),%edx
0.00 : 6b5ac9: movslq %edx,%rdx
: 197 &ProcGlobal->statusFlags[index],
0.00 : 6b5acc: lea (%rcx,%rdx,1),%rsi
: 199 memmove(&ProcGlobal->statusFlags[index + 1],
0.00 : 6b5ad0: mov 0x685f79(%rip),%rdx # d3ba50 <ProcGlobal>
0.00 : 6b5ad7: mov 0x20(%rdx),%rcx
0.00 : 6b5adb: mov -0x1c(%rbp),%edx
0.00 : 6b5ade: add $0x1,%edx
0.00 : 6b5ae1: movslq %edx,%rdx
0.00 : 6b5ae4: add %rdx,%rcx
0.00 : 6b5ae7: mov %rax,%rdx
0.00 : 6b5aea: mov %rcx,%rdi
0.00 : 6b5aed: call ed240 <memmove@plt>
: 209 movecount * sizeof(*ProcGlobal->statusFlags));
:
: 211 arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
0.00 : 6b5af2: mov 0x685f57(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5af9: mov (%rax),%rax
0.00 : 6b5afc: mov -0x28(%rbp),%rdx
0.00 : 6b5b00: sub %rax,%rdx
0.00 : 6b5b03: sar $0x7,%rdx
0.00 : 6b5b07: movabs $0x6db6db6db6db6db7,%rax
0.00 : 6b5b11: imul %rdx,%rax
0.00 : 6b5b15: mov %eax,%ecx
0.00 : 6b5b17: mov -0x8(%rbp),%rax
0.00 : 6b5b1b: mov -0x1c(%rbp),%edx
0.00 : 6b5b1e: movslq %edx,%rdx
0.00 : 6b5b21: add $0x8,%rdx
0.00 : 6b5b25: mov %ecx,0x4(%rax,%rdx,4)
: 225 pgxactoffs[arrayP->pgprocnos[index]] = index;
0.00 : 6b5b29: mov 0x683c80(%rip),%rdx # d397b0 <pgxactoffs>
0.00 : 6b5b30: mov -0x8(%rbp),%rax
0.00 : 6b5b34: mov -0x1c(%rbp),%ecx
0.00 : 6b5b37: movslq %ecx,%rcx
0.00 : 6b5b3a: add $0x8,%rcx
0.00 : 6b5b3e: mov 0x4(%rax,%rcx,4),%eax
0.00 : 6b5b42: cltq
0.00 : 6b5b44: shl $0x2,%rax
0.00 : 6b5b48: add %rax,%rdx
0.00 : 6b5b4b: mov -0x1c(%rbp),%eax
0.00 : 6b5b4e: mov %eax,(%rdx)
: 237 ProcGlobal->xids[index] = proc->xid;
0.00 : 6b5b50: mov 0x685ef9(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5b57: mov 0x10(%rax),%rax
0.00 : 6b5b5b: mov -0x1c(%rbp),%edx
0.00 : 6b5b5e: movslq %edx,%rdx
0.00 : 6b5b61: shl $0x2,%rdx
0.00 : 6b5b65: add %rax,%rdx
0.00 : 6b5b68: mov -0x28(%rbp),%rax
0.00 : 6b5b6c: mov 0x38(%rax),%eax
0.00 : 6b5b6f: mov %eax,(%rdx)
: 247 ProcGlobal->subxidStates[index] = proc->subxidStatus;
0.00 : 6b5b71: mov 0x685ed8(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5b78: mov 0x18(%rax),%rax
0.00 : 6b5b7c: mov -0x1c(%rbp),%edx
0.00 : 6b5b7f: movslq %edx,%rdx
0.00 : 6b5b82: add %rdx,%rdx
0.00 : 6b5b85: add %rax,%rdx
0.00 : 6b5b88: mov -0x28(%rbp),%rax
0.00 : 6b5b8c: movzwl 0x40(%rax),%eax
0.00 : 6b5b90: mov %ax,(%rdx)
: 257 ProcGlobal->statusFlags[index] = proc->statusFlags;
0.00 : 6b5b93: mov 0x685eb6(%rip),%rax # d3ba50 <ProcGlobal>
0.00 : 6b5b9a: mov 0x20(%rax),%rdx
0.00 : 6b5b9e: mov -0x1c(%rbp),%eax
0.00 : 6b5ba1: cltq
0.00 : 6b5ba3: add %rax,%rdx
0.00 : 6b5ba6: mov -0x28(%rbp),%rax
0.00 : 6b5baa: movzbl 0x2c(%rax),%eax
0.00 : 6b5bae: mov %al,(%rdx)
:
: 267 arrayP->numProcs++;
0.00 : 6b5bb0: mov -0x8(%rbp),%rax
0.00 : 6b5bb4: mov (%rax),%eax
0.00 : 6b5bb6: lea 0x1(%rax),%edx
0.00 : 6b5bb9: mov -0x8(%rbp),%rax
0.00 : 6b5bbd: mov %edx,(%rax)
:
: 274 /* adjust pgxactoff for all following PGPROCs */
: 275 index++;
0.00 : 6b5bbf: addl $0x1,-0x1c(%rbp)
: 277 for (; index < arrayP->numProcs; index++)
0.00 : 6b5bc3: jmp 6b5bf7 <ProcArrayAdd+0x33d>
: 279 {
: 280 int procno = arrayP->pgprocnos[index];
0.00 : 6b5bc5: mov -0x8(%rbp),%rax
0.00 : 6b5bc9: mov -0x1c(%rbp),%edx
0.00 : 6b5bcc: movslq %edx,%rdx
0.00 : 6b5bcf: add $0x8,%rdx
0.00 : 6b5bd3: mov 0x4(%rax,%rdx,4),%eax
0.00 : 6b5bd7: mov %eax,-0xc(%rbp)
:
: 288 Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
: 289 Assert(pgxactoffs[procno] == index - 1);
:
: 291 pgxactoffs[procno] = index;
0.00 : 6b5bda: mov 0x683bcf(%rip),%rax # d397b0 <pgxactoffs>
0.00 : 6b5be1: mov -0xc(%rbp),%edx
0.00 : 6b5be4: movslq %edx,%rdx
0.00 : 6b5be7: shl $0x2,%rdx
0.00 : 6b5beb: add %rax,%rdx
0.00 : 6b5bee: mov -0x1c(%rbp),%eax
0.00 : 6b5bf1: mov %eax,(%rdx)
: 299 for (; index < arrayP->numProcs; index++)
0.00 : 6b5bf3: addl $0x1,-0x1c(%rbp)
0.00 : 6b5bf7: mov -0x8(%rbp),%rax
0.00 : 6b5bfb: mov (%rax),%eax
0.00 : 6b5bfd: cmp %eax,-0x1c(%rbp)
0.00 : 6b5c00: jl 6b5bc5 <ProcArrayAdd+0x30b>
:
: 306 /*
: 307 * Release in reversed acquisition order, to reduce frequency of having to
: 308 * wait for XidGenLock while holding ProcArrayLock.
: 309 */
: 310 LWLockRelease(XidGenLock);
0.00 : 6b5c02: mov 0x685057(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5c09: add $0x180,%rax
0.00 : 6b5c0f: mov %rax,%rdi
0.00 : 6b5c12: call 6d50fc <LWLockRelease>
: 315 LWLockRelease(ProcArrayLock);
0.00 : 6b5c17: mov 0x685042(%rip),%rax # d3ac60 <MainLWLockArray>
0.00 : 6b5c1e: add $0x200,%rax
0.00 : 6b5c24: mov %rax,%rdi
0.00 : 6b5c27: call 6d50fc <LWLockRelease>
: 320 }
0.00 : 6b5c2c: nop
0.00 : 6b5c2d: leave
0.00 : 6b5c2e: ret
[text/x-patch] 0001-This-patch-reduce-connection-init-close-time.patch (14.0K, ../../65d0e5b2-2a40-4b12-be04-0804912d2daf@postgrespro.ru/7-0001-This-patch-reduce-connection-init-close-time.patch)
download | inline diff:
From 437b48759126a4d8289fd10eb3a885392cf96a47 Mon Sep 17 00:00:00 2001
From: Maksim Melnikov <m.melnikov@postgrespro.ru>
Date: Tue, 18 Nov 2025 17:20:09 +0300
Subject: [PATCH] This patch reduce connection init/close time.
ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
field in PGPROC, because its are placed on different pages and it the
reason of page_faults occurence. Now one of the use case with pgxactoff
is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
allocate ~1KB and it quite enough to have page_faults in case of such
accessing.
So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
the Nth element in pgxactoffs refer to Nth PGPROC.
Exentually it helps to avoid extra page_faults and reduce connection
init/close time.
Tags: lwlock_scale
---
src/backend/access/transam/varsup.c | 10 +++---
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/vacuum.c | 2 +-
src/backend/replication/logical/logical.c | 2 +-
src/backend/replication/slot.c | 2 +-
src/backend/replication/walsender.c | 2 +-
src/backend/storage/ipc/procarray.c | 38 ++++++++++++-----------
src/backend/storage/lmgr/proc.c | 8 +++--
src/include/storage/proc.h | 6 ++--
9 files changed, 39 insertions(+), 33 deletions(-)
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..6c941de5c99 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -85,7 +85,7 @@ GetNewTransactionId(bool isSubXact)
{
Assert(!isSubXact);
MyProc->xid = BootstrapTransactionId;
- ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
+ ProcGlobal->xids[GetMyXactOffPGProc()] = BootstrapTransactionId;
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
}
@@ -244,18 +244,18 @@ GetNewTransactionId(bool isSubXact)
*/
if (!isSubXact)
{
- Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0);
- Assert(!ProcGlobal->subxidStates[MyProc->pgxactoff].overflowed);
+ Assert(ProcGlobal->subxidStates[GetMyXactOffPGProc()].count == 0);
+ Assert(!ProcGlobal->subxidStates[GetMyXactOffPGProc()].overflowed);
Assert(MyProc->subxidStatus.count == 0);
Assert(!MyProc->subxidStatus.overflowed);
/* LWLockRelease acts as barrier */
MyProc->xid = xid;
- ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ ProcGlobal->xids[GetMyXactOffPGProc()] = xid;
}
else
{
- XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ XidCacheStatus *substat = &ProcGlobal->subxidStates[GetMyXactOffPGProc()];
int nxids = MyProc->subxidStatus.count;
Assert(substat->count == MyProc->subxidStatus.count);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..03721601070 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4654,6 +4654,6 @@ set_indexsafe_procflags(void)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_SAFE_IC;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 99d0db82ed7..10b712c8e78 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2054,7 +2054,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
MyProc->statusFlags |= PROC_IN_VACUUM;
if (params.is_wraparound)
MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index a33a685dcc6..91d6d83a5d8 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -188,7 +188,7 @@ StartupDecodingContext(List *output_plugin_options,
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 83fcde74718..180cc800825 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -839,7 +839,7 @@ ReplicationSlotRelease(void)
/* might not have been set when we've been a plain slot */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
if (am_walsender)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 3d4ab929f91..38c935e09a0 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -351,7 +351,7 @@ InitWalSender(void)
Assert(MyProc->xmin == InvalidTransactionId);
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_AFFECTS_ALL_HORIZONS;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 9299bcebbda..35578e7222c 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -283,6 +283,7 @@ typedef enum KAXCompressReason
} KAXCompressReason;
static PGPROC *allProcs;
+static int *pgxactoffs;
/*
* Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
@@ -449,6 +450,7 @@ ProcArrayShmemInit(void *arg)
TransamVariables->xactCompletionCount = 1;
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
static void
@@ -498,7 +500,7 @@ ProcArrayAdd(PGPROC *proc)
int this_procno = arrayP->pgprocnos[index];
Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[this_procno].pgxactoff == index);
+ Assert(pgxactoffs[this_procno] == index);
/* If we have found our right position in the array, break */
if (this_procno > pgprocno)
@@ -520,7 +522,7 @@ ProcArrayAdd(PGPROC *proc)
movecount * sizeof(*ProcGlobal->statusFlags));
arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
- proc->pgxactoff = index;
+ pgxactoffs[arrayP->pgprocnos[index]] = index;
ProcGlobal->xids[index] = proc->xid;
ProcGlobal->subxidStates[index] = proc->subxidStatus;
ProcGlobal->statusFlags[index] = proc->statusFlags;
@@ -534,9 +536,9 @@ ProcArrayAdd(PGPROC *proc)
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff == index - 1);
+ Assert(pgxactoffs[procno] == index - 1);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -574,10 +576,10 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
- myoff = proc->pgxactoff;
+ myoff = GetXactOffPGProc(proc);
Assert(myoff >= 0 && myoff < arrayP->numProcs);
- Assert(ProcGlobal->allProcs[arrayP->pgprocnos[myoff]].pgxactoff == myoff);
+ Assert(ProcGlobal->pgxactoffs[arrayP->pgprocnos[myoff]] == myoff);
if (TransactionIdIsValid(latestXid))
{
@@ -632,9 +634,9 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff - 1 == index);
+ Assert(pgxactoffs[procno] - 1 == index);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -708,9 +710,9 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
{
Assert(!LWLockHeldByMe(ProcArrayLock));
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- Assert(proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]);
+ Assert(proc->statusFlags == ProcGlobal->statusFlags[GetXactOffPGProc(proc)]);
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[GetXactOffPGProc(proc)] = proc->statusFlags;
LWLockRelease(ProcArrayLock);
}
}
@@ -724,7 +726,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
{
- int pgxactoff = proc->pgxactoff;
+ int pgxactoff = GetXactOffPGProc(proc);
/*
* Note: we need exclusive lock here because we're going to change other
@@ -747,7 +749,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[GetXactOffPGProc(proc)] = proc->statusFlags;
}
/* Clear the subtransaction-XID cache too while holding the lock */
@@ -916,7 +918,7 @@ ProcArrayClearTransaction(PGPROC *proc)
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- pgxactoff = proc->pgxactoff;
+ pgxactoff = GetXactOffPGProc(proc);
ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
proc->xid = InvalidTransactionId;
@@ -1475,7 +1477,7 @@ TransactionIdIsInProgress(TransactionId xid)
}
/* No shortcuts, gotta grovel through the array */
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = GetMyXactOffPGProc();
numProcs = arrayP->numProcs;
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
{
@@ -2176,7 +2178,7 @@ GetSnapshotData(Snapshot snapshot)
}
latest_completed = TransamVariables->latestCompletedXid;
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = GetMyXactOffPGProc();
myxid = other_xids[mypgxactoff];
Assert(myxid == MyProc->xid);
@@ -2215,7 +2217,7 @@ GetSnapshotData(Snapshot snapshot)
TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
uint8 statusFlags;
- Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+ Assert(pgxactoffs[arrayP->pgprocnos[pgxactoff]] == pgxactoff);
/*
* If the transaction has no XID assigned, we can skip it; it
@@ -2583,7 +2585,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
MyProc->xmin = TransactionXmin = xmin;
MyProc->statusFlags = (MyProc->statusFlags & ~PROC_XMIN_FLAGS) |
(proc->statusFlags & PROC_XMIN_FLAGS);
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[GetMyXactOffPGProc()] = MyProc->statusFlags;
result = true;
}
@@ -4032,7 +4034,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- mysubxidstat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ mysubxidstat = &ProcGlobal->subxidStates[GetMyXactOffPGProc()];
/*
* Under normal circumstances xid and xids[] will be in increasing order,
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 1ac25068d62..5ae4922128e 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -165,6 +165,7 @@ ProcGlobalShmemRequest(void *arg)
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
+ size = add_size(size, mul_size(TotalProcs, sizeof(int)));
ProcGlobalAllProcsShmemSize = size;
ShmemRequestStruct(.name = "PGPROC structures",
.size = ProcGlobalAllProcsShmemSize,
@@ -273,7 +274,10 @@ ProcGlobalShmemInit(void *arg)
ProcGlobal->statusFlags = (uint8 *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->statusFlags));
- /* make sure we didn't overflow */
+ ProcGlobal->pgxactoffs = (int *) ptr;
+ ptr = (char *) ptr + TotalProcs * sizeof(int);
+
+ /* make sure wer didn't overflow */
Assert((ptr > (char *) procs) && (ptr <= (char *) procs + requestSize));
/*
@@ -1498,7 +1502,7 @@ ProcSleep(LOCALLOCK *locallock)
* the lock held, which is much more undesirable.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- statusFlags = ProcGlobal->statusFlags[autovac->pgxactoff];
+ statusFlags = ProcGlobal->statusFlags[GetXactOffPGProc(autovac)];
lockmethod_copy = lock->tag.locktag_lockmethodid;
locktag_copy = lock->tag;
LWLockRelease(ProcArrayLock);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 3e1d1fad5f9..fee9deb8f3a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -210,9 +210,6 @@ typedef struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
- int pgxactoff; /* offset into various ProcGlobal->arrays with
- * data mirrored from this PGPROC */
-
uint8 statusFlags; /* this backend's status flags, see PROC_*
* above. mirrored in
* ProcGlobal->statusFlags[pgxactoff] */
@@ -445,6 +442,7 @@ typedef struct PROC_HDR
{
/* Array of PGPROC structures (not including dummies for prepared txns) */
PGPROC *allProcs;
+ int *pgxactoffs;
/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
TransactionId *xids;
@@ -509,6 +507,8 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
*/
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
#define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
+#define GetXactOffPGProc(proc) (ProcGlobal->pgxactoffs[(proc) - &ProcGlobal->allProcs[0]])
+#define GetMyXactOffPGProc() (GetXactOffPGProc(MyProc))
/*
* We set aside some extra PGPROC structures for "special worker" processes,
--
2.43.0
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-06-03 13:35 Matthias van de Meent <boekewurm+postgres@gmail.com>
parent: Maksim.Melnikov <m.melnikov@postgrespro.ru>
0 siblings, 1 reply; 18+ messages in thread
From: Matthias van de Meent @ 2026-06-03 13:35 UTC (permalink / raw)
To: Maksim.Melnikov <m.melnikov@postgrespro.ru>; +Cc: Потапов Александр <a.potapov@postgrespro.ru>; pgsql-hackers
On Wed, 3 Jun 2026 at 08:33, Maksim.Melnikov <m.melnikov@postgrespro.ru> wrote:
>
> On 6/16/25 11:56, Потапов Александр wrote:
>
> > To be more precise I used constant number of threads (128 and 1024) to compare with previous results. The quadratic dependency exists everywhere, see new graph.
> >
> > > Q: Did you check that pgbench or the OS does not have
> > > O(n_active_connections) or O(n_active_threads) overhead per worker
> > > during thread creation or connection establishment, e.g. by varying
> > > the number of threads used to manage these N clients? I wouldn't be
> > > surprised if there are inefficiencies in e.g. the threading- or
> > > synchronization model that cause O(N) per-thread overhead, or O(N^2)
> > > overall when you have one thread per connection.
>
>
> Hi, all!
>
> I've investigated slightly different scenario then Alexander and I want share my thoughts in this thread too.
>
> I found that when we run pgbench scenarios sequantially, without postgres restart between iterations, initial time degrades from launch to launch and eventually it stabilizes at the worst values then first run(ICT_degradation.png attached).
>
> Scenario details:
[...]
> 4.Add to the postgresql.conf:
> huge_pages = off #for the sake of test stability and reproducibility
I think this is the main culprit of the extreme slowdown -- without
huge pages, you're effectively guaranteed to get many minor page
faults, and with it the relevant TLB miss rates. With huge pages
enabled, the proc array should fit on one (or just a few) memory
pages.
We're not generally in the business for optimizing workloads that have
huge_pages=off.
> I paid attention that ICT for the first iteration much better than for next ones. I investigated this behavior a little bit and found a lot of minor page fault events in ProcArrayAdd method(perf_without_patch-j6.txt attached) for code line
>
> allProcs[procno].pgxactoff = index;
>
> So, every proc.pgxactoff access generate page fault, because proc objects accessed in memory randomly and page replacement can occur. I have some ideas how to improve this - it seems we can put array of pgxactoff separately
> page replacement can occur
I doubt that this is an issue. Page tables are not removed until the
mapping is removed, and it is highly unlikely that hot shmem areas
(like the PGPROC array) are ever swapped out. It's just that with
smaller memory pages the OS will have to create more page mappings for
the same amount of shared memory, and that'll take more resources
(cpu, memory, time) than it would with large (or huge) memory pages.
> in shmem to have only few hot pages for them. I've attached appropriate patch(0001-This-patch-reduce-connection-init-close-time.patch). Perf with minor faults for updated version also was attached(perf_with_patch-j6.txt attached),
I see. Despite your argument hinging on small pages, I think there is
still some benefit to using a dense array instead of PGPROC.pgxactoff:
With a dense array, ProcArrayAdd/ProcArrayRemove need to touch fewer
cache lines, which are also less likely to be recently dirtied by
unrelated shared proc updates.
However, I'm now a bit more concerned about the number of indirections
required for other operations. Before, accessing pgxactoff was an
offset off of the PgProc pointer, but with this patch getting its
value is a bit more involved.
> as we can see, patched version fixes this. I made a series of measurements for all versions and attached comparison chart(ICT_degradation_with_patch.png attached). Also I add the table with results
Do you happen to have data with huge_pages enabled?
> I hope it will be interesting and helpful.
Definitely interesting. I'm not so sure it's as effective on a
production configuration (with huge pages enabled), but I'm definitely
interested in seeing test results.
----
Some comments on the patch:
> +++ b/src/backend/storage/lmgr/proc.c
> + size = add_size(size, mul_size(TotalProcs, sizeof(int)));
Let's use the following, to fit the surrounding pattern:
+ size = add_size(size, mul_size(TotalProcs,
sizeof(*ProcGlobal->pgxactoffs)));
> @@ -273,7 +274,10 @@ ProcGlobalShmemInit(void *arg)
> ProcGlobal->statusFlags = (uint8 *) ptr;
> ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->statusFlags));
>
> - /* make sure we didn't overflow */
> + ProcGlobal->pgxactoffs = (int *) ptr;
> + ptr = (char *) ptr + TotalProcs * sizeof(int);
> +
> + /* make sure wer didn't overflow */
> Assert((ptr > (char *) procs) && (ptr <= (char *) procs + requestSize));
This needs to be updated, because right now it fails to account for
alignment when (TotalProcs * sizeof(statusFlags)) is not a multiple of
sizeof(int). The other fields take care to be correctly aligned, but
your code doesn't do that yet. It's probably best to allocate and
assign *pgxactoffs just ahead of statusFlags.
> + /* make sure wer didn't overflow */
New typo introduced.
> +++ b/src/include/storage/proc.h
> +#define GetXactOffPGProc(proc) (ProcGlobal->pgxactoffs[(proc) - &ProcGlobal->allProcs[0]])
> +#define GetMyXactOffPGProc() (GetXactOffPGProc(MyProc))
I'd replace this with
+#define ProcGetXactOff(procno) (ProcGlobal->pgxactoffs[(procno)])
+#define ProcGetMyXactOff() (GetXactOffPGProc(MyProcNo))
So that callers can use GetNumberFromPGProc() manually if they need
to, but the offset-based calculations of Proc-to-Number are avoided
when that is possible.
Kind regards,
Matthias van de Meent
Databricks (https://www.databricks.com)
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-06-11 14:31 Maksim.Melnikov <m.melnikov@postgrespro.ru>
parent: Matthias van de Meent <boekewurm+postgres@gmail.com>
0 siblings, 1 reply; 18+ messages in thread
From: Maksim.Melnikov @ 2026-06-11 14:31 UTC (permalink / raw)
To: Matthias van de Meent <boekewurm+postgres@gmail.com>; +Cc: Потапов Александр <a.potapov@postgrespro.ru>; pgsql-hackers
On 6/3/26 16:35, Matthias van de Meent wrote:
> On Wed, 3 Jun 2026 at 08:33, Maksim.Melnikov<m.melnikov@postgrespro.ru> wrote:
>> On 6/16/25 11:56, Потапов Александр wrote:
>>
>>> To be more precise I used constant number of threads (128 and 1024) to compare with previous results. The quadratic dependency exists everywhere, see new graph.
>>>
>>>> Q: Did you check that pgbench or the OS does not have
>>>> O(n_active_connections) or O(n_active_threads) overhead per worker
>>>> during thread creation or connection establishment, e.g. by varying
>>>> the number of threads used to manage these N clients? I wouldn't be
>>>> surprised if there are inefficiencies in e.g. the threading- or
>>>> synchronization model that cause O(N) per-thread overhead, or O(N^2)
>>>> overall when you have one thread per connection.
>> Hi, all!
>>
>> I've investigated slightly different scenario then Alexander and I want share my thoughts in this thread too.
>>
>> I found that when we run pgbench scenarios sequantially, without postgres restart between iterations, initial time degrades from launch to launch and eventually it stabilizes at the worst values then first run(ICT_degradation.png attached).
>>
>> Scenario details:
> [...]
>> 4.Add to the postgresql.conf:
>> huge_pages = off #for the sake of test stability and reproducibility
> I think this is the main culprit of the extreme slowdown -- without
> huge pages, you're effectively guaranteed to get many minor page
> faults, and with it the relevant TLB miss rates. With huge pages
> enabled, the proc array should fit on one (or just a few) memory
> pages.
>
> We're not generally in the business for optimizing workloads that have
> huge_pages=off.
Yes, I agree, huge_pages=off is not common setup now. My motivation was
that even if some configuration isn't commonly used, it does not mean
that it isn't interesting for someone else at all and, as a consequence,
it can be optimized without degradation for basic scenarios . Moreover,
huge_pages = try is the default value, so with huge_pages set to try,
the server will try to request huge pages, but fall back to the
huge_page=off if that fails. As I know on linux default value
for vm.nr_hugepages = 0, this means that by default, the os does not
use HugeTLB pages. Of course, DBA should setup this, but on practice
they can miss this. Anyway, if community isn't interested in such kinds
of optimizations, it is ok. It was interesting and educational
investigation for me, thanks for your help.
> .....
>
>
>> as we can see, patched version fixes this. I made a series of measurements for all versions and attached comparison chart(ICT_degradation_with_patch.png attached). Also I add the table with results
> Do you happen to have data with huge_pages enabled?
>
>> I hope it will be interesting and helpful.
> Definitely interesting. I'm not so sure it's as effective on a
> production configuration (with huge pages enabled), but I'm definitely
> interested in seeing test results.
I've made comparative measurements for configurations with huge_pages =
on/off. Please, you can check results below.
Clients number *Huge-pages-off-with-patch*
Huge-pages-off-without-patch Huge-pages-on-with-patch
Huge-pages-on-without-patch
512 ~480 +- 3.5% ms ~490 +- 3% ms ~420 +- 3.5% ms ~420+-3.5% ms
1024 ~910 +- 1.3% ms ~990 +- 2% ms ~790 +- 1.7% ms ~800+-1.8% ms
2048 ~1810 +- 1.4% ms ~2230 +- 0.9% ms ~1540 +- 0.7% ms ~1530 +-
1.4% ms
4096 ~3690 +- 1.9% ms ~6060 +- 0.8% ms ~3070 +- 0.6% ms ~3070 +-
0.9% ms
8192 ~9900 +- 0.6% ms ~18530 +- 0.4% ms ~6220 +- 0.7% ms ~6230 +-
0.7% ms
Also comparison chart is attached.
As we can see the measurements prove patch efficiency for configuration
with huge_page=off(the same result as in previous message), but for
huge_pages=on I've got the same results for both versions, no
improvement and no degradation.
> ----
>
> Some comments on the patch:
>
Patch with fixes was attached. Thanks for review.
Best regards,
Maksim Melnikov
Attachments:
[image/png] huge_pages_comparison.png (49.7K, ../../08e7c338-e175-4474-be53-6538bb67d4b6@postgrespro.ru/3-huge_pages_comparison.png)
download | view image
[text/x-patch] v2-0001-This-patch-reduce-connection-init-close-time.patch (14.2K, ../../08e7c338-e175-4474-be53-6538bb67d4b6@postgrespro.ru/4-v2-0001-This-patch-reduce-connection-init-close-time.patch)
download | inline diff:
From 5e8b84fcfe4ecccc1847ed8d70d428e1fc5bc59f Mon Sep 17 00:00:00 2001
From: Maksim Melnikov <m.melnikov@postgrespro.ru>
Date: Tue, 18 Nov 2025 17:20:09 +0300
Subject: [PATCH v2] This patch reduce connection init/close time.
ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
field in PGPROC, because its are placed on different pages and it the
reason of page_faults occurence. Now one of the use case with pgxactoff
is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
allocate ~1KB and it quite enough to have page_faults in case of such
accessing.
So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
the Nth element in pgxactoffs refer to Nth PGPROC.
Eventually it helps to avoid extra page_faults and reduce connection
init/close time.
---
src/backend/access/transam/varsup.c | 10 +++---
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/vacuum.c | 2 +-
src/backend/replication/logical/logical.c | 2 +-
src/backend/replication/slot.c | 2 +-
src/backend/replication/walsender.c | 2 +-
src/backend/storage/ipc/procarray.c | 40 +++++++++++++----------
src/backend/storage/lmgr/proc.c | 6 +++-
src/include/storage/proc.h | 6 ++--
9 files changed, 40 insertions(+), 32 deletions(-)
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..1bbc1bcd665 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -85,7 +85,7 @@ GetNewTransactionId(bool isSubXact)
{
Assert(!isSubXact);
MyProc->xid = BootstrapTransactionId;
- ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
+ ProcGlobal->xids[ProcGetMyXactOff()] = BootstrapTransactionId;
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
}
@@ -244,18 +244,18 @@ GetNewTransactionId(bool isSubXact)
*/
if (!isSubXact)
{
- Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0);
- Assert(!ProcGlobal->subxidStates[MyProc->pgxactoff].overflowed);
+ Assert(ProcGlobal->subxidStates[ProcGetMyXactOff()].count == 0);
+ Assert(!ProcGlobal->subxidStates[ProcGetMyXactOff()].overflowed);
Assert(MyProc->subxidStatus.count == 0);
Assert(!MyProc->subxidStatus.overflowed);
/* LWLockRelease acts as barrier */
MyProc->xid = xid;
- ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ ProcGlobal->xids[ProcGetMyXactOff()] = xid;
}
else
{
- XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ XidCacheStatus *substat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
int nxids = MyProc->subxidStatus.count;
Assert(substat->count == MyProc->subxidStatus.count);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 9ab74c8df0a..d9e83e26c78 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4654,6 +4654,6 @@ set_indexsafe_procflags(void)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_SAFE_IC;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index a4abb29cf64..884d18fad4e 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2059,7 +2059,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
MyProc->statusFlags |= PROC_IN_VACUUM;
if (params.is_wraparound)
MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 3541fc793e4..c2c96a70c92 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -188,7 +188,7 @@ StartupDecodingContext(List *output_plugin_options,
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index d7fb9f5a67f..1bd3b8f902b 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -832,7 +832,7 @@ ReplicationSlotRelease(void)
/* might not have been set when we've been a plain slot */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
if (am_walsender)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 04aa770d981..748b47a15bf 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -358,7 +358,7 @@ InitWalSender(void)
Assert(MyProc->xmin == InvalidTransactionId);
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_AFFECTS_ALL_HORIZONS;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index f540bb6b23f..585ed6d3677 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -283,6 +283,7 @@ typedef enum KAXCompressReason
} KAXCompressReason;
static PGPROC *allProcs;
+static int *pgxactoffs;
/*
* Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
@@ -449,6 +450,7 @@ ProcArrayShmemInit(void *arg)
TransamVariables->xactCompletionCount = 1;
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
static void
@@ -498,7 +500,7 @@ ProcArrayAdd(PGPROC *proc)
int this_procno = arrayP->pgprocnos[index];
Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[this_procno].pgxactoff == index);
+ Assert(pgxactoffs[this_procno] == index);
/* If we have found our right position in the array, break */
if (this_procno > pgprocno)
@@ -520,7 +522,7 @@ ProcArrayAdd(PGPROC *proc)
movecount * sizeof(*ProcGlobal->statusFlags));
arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
- proc->pgxactoff = index;
+ pgxactoffs[arrayP->pgprocnos[index]] = index;
ProcGlobal->xids[index] = proc->xid;
ProcGlobal->subxidStates[index] = proc->subxidStatus;
ProcGlobal->statusFlags[index] = proc->statusFlags;
@@ -534,9 +536,9 @@ ProcArrayAdd(PGPROC *proc)
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff == index - 1);
+ Assert(pgxactoffs[procno] == index - 1);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -574,10 +576,10 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
- myoff = proc->pgxactoff;
+ myoff = ProcGetXactOff(GetNumberFromPGProc(proc));
Assert(myoff >= 0 && myoff < arrayP->numProcs);
- Assert(ProcGlobal->allProcs[arrayP->pgprocnos[myoff]].pgxactoff == myoff);
+ Assert(ProcGlobal->pgxactoffs[arrayP->pgprocnos[myoff]] == myoff);
if (TransactionIdIsValid(latestXid))
{
@@ -632,9 +634,9 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff - 1 == index);
+ Assert(pgxactoffs[procno] - 1 == index);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -706,11 +708,13 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
/* avoid unnecessarily dirtying shared cachelines */
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
+
Assert(!LWLockHeldByMe(ProcArrayLock));
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- Assert(proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]);
+ Assert(proc->statusFlags == ProcGlobal->statusFlags[pgxactoff]);
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
LWLockRelease(ProcArrayLock);
}
}
@@ -724,7 +728,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
{
- int pgxactoff = proc->pgxactoff;
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
/*
* Note: we need exclusive lock here because we're going to change other
@@ -747,7 +751,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
}
/* Clear the subtransaction-XID cache too while holding the lock */
@@ -916,7 +920,7 @@ ProcArrayClearTransaction(PGPROC *proc)
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- pgxactoff = proc->pgxactoff;
+ pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
proc->xid = InvalidTransactionId;
@@ -1475,7 +1479,7 @@ TransactionIdIsInProgress(TransactionId xid)
}
/* No shortcuts, gotta grovel through the array */
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
numProcs = arrayP->numProcs;
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
{
@@ -2176,7 +2180,7 @@ GetSnapshotData(Snapshot snapshot)
}
latest_completed = TransamVariables->latestCompletedXid;
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
myxid = other_xids[mypgxactoff];
Assert(myxid == MyProc->xid);
@@ -2215,7 +2219,7 @@ GetSnapshotData(Snapshot snapshot)
TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
uint8 statusFlags;
- Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+ Assert(pgxactoffs[arrayP->pgprocnos[pgxactoff]] == pgxactoff);
/*
* If the transaction has no XID assigned, we can skip it; it
@@ -2583,7 +2587,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
MyProc->xmin = TransactionXmin = xmin;
MyProc->statusFlags = (MyProc->statusFlags & ~PROC_XMIN_FLAGS) |
(proc->statusFlags & PROC_XMIN_FLAGS);
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
result = true;
}
@@ -4011,7 +4015,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- mysubxidstat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ mysubxidstat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
/*
* Under normal circumstances xid and xids[] will be in increasing order,
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 6fa9de33e1c..1a52bdb8b88 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -164,6 +164,7 @@ ProcGlobalShmemRequest(void *arg)
size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
+ size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->pgxactoffs)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
ProcGlobalAllProcsShmemSize = size;
ShmemRequestStruct(.name = "PGPROC structures",
@@ -270,6 +271,9 @@ ProcGlobalShmemInit(void *arg)
ProcGlobal->subxidStates = (XidCacheStatus *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->subxidStates));
+ ProcGlobal->pgxactoffs = (int *) ptr;
+ ptr = (char *) ptr + TotalProcs * sizeof(*ProcGlobal->pgxactoffs);
+
ProcGlobal->statusFlags = (uint8 *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->statusFlags));
@@ -1531,7 +1535,7 @@ ProcSleep(LOCALLOCK *locallock)
* the lock held, which is much more undesirable.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- statusFlags = ProcGlobal->statusFlags[autovac->pgxactoff];
+ statusFlags = ProcGlobal->statusFlags[ProcGetXactOff(GetNumberFromPGProc(autovac))];
lockmethod_copy = lock->tag.locktag_lockmethodid;
locktag_copy = lock->tag;
LWLockRelease(ProcArrayLock);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 3e1d1fad5f9..2c4d23cb116 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -210,9 +210,6 @@ typedef struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
- int pgxactoff; /* offset into various ProcGlobal->arrays with
- * data mirrored from this PGPROC */
-
uint8 statusFlags; /* this backend's status flags, see PROC_*
* above. mirrored in
* ProcGlobal->statusFlags[pgxactoff] */
@@ -445,6 +442,7 @@ typedef struct PROC_HDR
{
/* Array of PGPROC structures (not including dummies for prepared txns) */
PGPROC *allProcs;
+ int *pgxactoffs;
/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
TransactionId *xids;
@@ -509,6 +507,8 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
*/
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
#define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
+#define ProcGetXactOff(procno) (ProcGlobal->pgxactoffs[(procno)])
+#define ProcGetMyXactOff() (ProcGetXactOff(GetNumberFromPGProc(MyProc)))
/*
* We set aside some extra PGPROC structures for "special worker" processes,
--
2.43.0
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-07-17 08:54 Matthias van de Meent <boekewurm+postgres@gmail.com>
parent: Maksim.Melnikov <m.melnikov@postgrespro.ru>
0 siblings, 1 reply; 18+ messages in thread
From: Matthias van de Meent @ 2026-07-17 08:54 UTC (permalink / raw)
To: Maksim.Melnikov <m.melnikov@postgrespro.ru>; +Cc: Потапов Александр <a.potapov@postgrespro.ru>; pgsql-hackers
On Thu, 11 Jun 2026 at 16:31, Maksim.Melnikov <m.melnikov@postgrespro.ru> wrote:
>
> Patch with fixes was attached. Thanks for review.
You removed the pgxactoff field with its comment from PGPROC, but
haven't yet added a comment on PROC_HDR's pgxactoffs. Please add a
comment to the pgxactoffs array that describes its contents. A simple
adaptation of the comment removed with PGPROC's pgxactoff field should
be sufficient.
The comments on PROC_HDR and PGPROC also reference the deleted
PGPROC->pgxactoff field, so that also must be adjusted; there may be
several other places, as I didn't do a full check on the codebase.
Once the relevant comments are added and adjusted I think this is
ready for a committer.
Kind regards,
Matthias van de Meent
Databricks (https://www.databricks.com)
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-08-07 14:05 Maksim.Melnikov <m.melnikov@postgrespro.ru>
parent: Matthias van de Meent <boekewurm+postgres@gmail.com>
0 siblings, 1 reply; 18+ messages in thread
From: Maksim.Melnikov @ 2026-08-07 14:05 UTC (permalink / raw)
To: Matthias van de Meent <boekewurm+postgres@gmail.com>; +Cc: Потапов Александр <a.potapov@postgrespro.ru>; pgsql-hackers
On 7/17/26 11:54, Matthias van de Meent wrote:
> On Thu, 11 Jun 2026 at 16:31, Maksim.Melnikov <m.melnikov@postgrespro.ru> wrote:
>> Patch with fixes was attached. Thanks for review.
> You removed the pgxactoff field with its comment from PGPROC, but
> haven't yet added a comment on PROC_HDR's pgxactoffs. Please add a
> comment to the pgxactoffs array that describes its contents. A simple
> adaptation of the comment removed with PGPROC's pgxactoff field should
> be sufficient.
>
> The comments on PROC_HDR and PGPROC also reference the deleted
> PGPROC->pgxactoff field, so that also must be adjusted; there may be
> several other places, as I didn't do a full check on the codebase.
>
> Once the relevant comments are added and adjusted I think this is
> ready for a committer.
>
>
> Kind regards,
>
> Matthias van de Meent
> Databricks (https://www.databricks.com)
>
>
Sorry for the delay, was unavailable for several weeks.
Thanks for review, patch was updated.
Best regards,
Maksim Melnikov
Attachments:
[text/x-patch] v3-0001-This-patch-reduce-connection-init-close-time.patch (16.9K, ../../ac72087f-0df9-4fbb-bb66-6b51ac9d1c08@postgrespro.ru/2-v3-0001-This-patch-reduce-connection-init-close-time.patch)
download | inline diff:
From 142b29615475aeec750b9cc92c6f41ddb6d68041 Mon Sep 17 00:00:00 2001
From: Maksim Melnikov <m.melnikov@postgrespro.ru>
Date: Tue, 18 Nov 2025 17:20:09 +0300
Subject: [PATCH v3] This patch reduce connection init/close time.
ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
field in PGPROC, because its are placed on different pages and it the
reason of page_faults occurence. Now one of the use case with pgxactoff
is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
allocate ~1KB and it quite enough to have page_faults in case of such
accessing.
So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
the Nth element in pgxactoffs refer to Nth PGPROC.
Eventually it helps to avoid extra page_faults and reduce connection
init/close time. The main benefit is seen on configurations without huge
pages.
---
src/backend/access/transam/varsup.c | 10 ++---
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/vacuum.c | 2 +-
src/backend/replication/logical/logical.c | 2 +-
src/backend/replication/slot.c | 2 +-
src/backend/replication/walsender.c | 2 +-
src/backend/storage/ipc/procarray.c | 53 +++++++++++++----------
src/backend/storage/lmgr/proc.c | 6 ++-
src/include/storage/proc.h | 22 +++++++---
9 files changed, 61 insertions(+), 40 deletions(-)
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index dc5e32d86f3..1bbc1bcd665 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -85,7 +85,7 @@ GetNewTransactionId(bool isSubXact)
{
Assert(!isSubXact);
MyProc->xid = BootstrapTransactionId;
- ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
+ ProcGlobal->xids[ProcGetMyXactOff()] = BootstrapTransactionId;
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
}
@@ -244,18 +244,18 @@ GetNewTransactionId(bool isSubXact)
*/
if (!isSubXact)
{
- Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0);
- Assert(!ProcGlobal->subxidStates[MyProc->pgxactoff].overflowed);
+ Assert(ProcGlobal->subxidStates[ProcGetMyXactOff()].count == 0);
+ Assert(!ProcGlobal->subxidStates[ProcGetMyXactOff()].overflowed);
Assert(MyProc->subxidStatus.count == 0);
Assert(!MyProc->subxidStatus.overflowed);
/* LWLockRelease acts as barrier */
MyProc->xid = xid;
- ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ ProcGlobal->xids[ProcGetMyXactOff()] = xid;
}
else
{
- XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ XidCacheStatus *substat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
int nxids = MyProc->subxidStatus.count;
Assert(substat->count == MyProc->subxidStatus.count);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 713bb5d10f1..046c28a10e7 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4779,6 +4779,6 @@ set_indexsafe_procflags(void)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_SAFE_IC;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 38539a6fd3d..fc49b8e14d4 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2059,7 +2059,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
MyProc->statusFlags |= PROC_IN_VACUUM;
if (params.is_wraparound)
MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 3541fc793e4..c2c96a70c92 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -188,7 +188,7 @@ StartupDecodingContext(List *output_plugin_options,
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index c7ab82a28e1..da4e01f0caf 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -832,7 +832,7 @@ ReplicationSlotRelease(void)
/* might not have been set when we've been a plain slot */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
if (am_walsender)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index 35ebc7e61c8..6b84de1bcfb 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -358,7 +358,7 @@ InitWalSender(void)
Assert(MyProc->xmin == InvalidTransactionId);
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_AFFECTS_ALL_HORIZONS;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 60336b31803..e6075dafe9f 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -284,6 +284,12 @@ typedef enum KAXCompressReason
static PGPROC *allProcs;
+/*
+ * offsets into various ProcGlobal->arrays with data mirrored from appropriate PGPROCs,
+ * procno define offset index in pgxactoffs array(See PROC_HDR for details).
+ */
+static int *pgxactoffs;
+
/*
* Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
*/
@@ -449,6 +455,7 @@ ProcArrayShmemInit(void *arg)
TransamVariables->xactCompletionCount = 1;
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
static void
@@ -498,7 +505,7 @@ ProcArrayAdd(PGPROC *proc)
int this_procno = arrayP->pgprocnos[index];
Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[this_procno].pgxactoff == index);
+ Assert(pgxactoffs[this_procno] == index);
/* If we have found our right position in the array, break */
if (this_procno > pgprocno)
@@ -520,23 +527,23 @@ ProcArrayAdd(PGPROC *proc)
movecount * sizeof(*ProcGlobal->statusFlags));
arrayP->pgprocnos[index] = GetNumberFromPGProc(proc);
- proc->pgxactoff = index;
+ pgxactoffs[arrayP->pgprocnos[index]] = index;
ProcGlobal->xids[index] = proc->xid;
ProcGlobal->subxidStates[index] = proc->subxidStatus;
ProcGlobal->statusFlags[index] = proc->statusFlags;
arrayP->numProcs++;
- /* adjust pgxactoff for all following PGPROCs */
+ /* adjust appropriate pgxactoff for all following PGPROCs */
index++;
for (; index < arrayP->numProcs; index++)
{
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff == index - 1);
+ Assert(pgxactoffs[procno] == index - 1);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -574,10 +581,10 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
- myoff = proc->pgxactoff;
+ myoff = ProcGetXactOff(GetNumberFromPGProc(proc));
Assert(myoff >= 0 && myoff < arrayP->numProcs);
- Assert(ProcGlobal->allProcs[arrayP->pgprocnos[myoff]].pgxactoff == myoff);
+ Assert(ProcGlobal->pgxactoffs[arrayP->pgprocnos[myoff]] == myoff);
if (TransactionIdIsValid(latestXid))
{
@@ -624,17 +631,17 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
arrayP->numProcs--;
/*
- * Adjust pgxactoff of following procs for removed PGPROC (note that
- * numProcs already has been decremented).
+ * Adjust appropriate pgxactoff of following procs for removed PGPROC
+ * (note that numProcs already has been decremented).
*/
for (int index = myoff; index < arrayP->numProcs; index++)
{
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff - 1 == index);
+ Assert(pgxactoffs[procno] - 1 == index);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -706,11 +713,13 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
/* avoid unnecessarily dirtying shared cachelines */
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
+
Assert(!LWLockHeldByMe(ProcArrayLock));
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- Assert(proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]);
+ Assert(proc->statusFlags == ProcGlobal->statusFlags[pgxactoff]);
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
LWLockRelease(ProcArrayLock);
}
}
@@ -724,7 +733,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
{
- int pgxactoff = proc->pgxactoff;
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
/*
* Note: we need exclusive lock here because we're going to change other
@@ -747,7 +756,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
}
/* Clear the subtransaction-XID cache too while holding the lock */
@@ -903,7 +912,7 @@ ProcArrayClearTransaction(PGPROC *proc)
/*
* Currently we need to lock ProcArrayLock exclusively here, as we
* increment xactCompletionCount below. We also need it at least in shared
- * mode for pgproc->pgxactoff to stay the same below.
+ * mode for proc's pgxactoff to stay the same below.
*
* We could however, as this action does not actually change anyone's view
* of the set of running XIDs (our entry is duplicate with the gxact that
@@ -916,7 +925,7 @@ ProcArrayClearTransaction(PGPROC *proc)
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- pgxactoff = proc->pgxactoff;
+ pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
proc->xid = InvalidTransactionId;
@@ -1475,7 +1484,7 @@ TransactionIdIsInProgress(TransactionId xid)
}
/* No shortcuts, gotta grovel through the array */
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
numProcs = arrayP->numProcs;
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
{
@@ -2184,7 +2193,7 @@ GetSnapshotData(Snapshot snapshot)
}
latest_completed = TransamVariables->latestCompletedXid;
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
myxid = other_xids[mypgxactoff];
Assert(myxid == MyProc->xid);
@@ -2223,7 +2232,7 @@ GetSnapshotData(Snapshot snapshot)
TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
uint8 statusFlags;
- Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+ Assert(pgxactoffs[arrayP->pgprocnos[pgxactoff]] == pgxactoff);
/*
* If the transaction has no XID assigned, we can skip it; it
@@ -2591,7 +2600,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
MyProc->xmin = TransactionXmin = xmin;
MyProc->statusFlags = (MyProc->statusFlags & ~PROC_XMIN_FLAGS) |
(proc->statusFlags & PROC_XMIN_FLAGS);
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
result = true;
}
@@ -4019,7 +4028,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- mysubxidstat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ mysubxidstat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
/*
* Under normal circumstances xid and xids[] will be in increasing order,
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 9d6e69175a5..e64ab6e597c 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -164,6 +164,7 @@ ProcGlobalShmemRequest(void *arg)
size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
+ size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->pgxactoffs)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
ProcGlobalAllProcsShmemSize = size;
ShmemRequestStruct(.name = "PGPROC structures",
@@ -271,6 +272,9 @@ ProcGlobalShmemInit(void *arg)
ProcGlobal->subxidStates = (XidCacheStatus *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->subxidStates));
+ ProcGlobal->pgxactoffs = (int *) ptr;
+ ptr = (char *) ptr + TotalProcs * sizeof(*ProcGlobal->pgxactoffs);
+
ProcGlobal->statusFlags = (uint8 *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->statusFlags));
@@ -1561,7 +1565,7 @@ ProcSleep(LOCALLOCK *locallock)
* the lock held, which is much more undesirable.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- statusFlags = ProcGlobal->statusFlags[autovac->pgxactoff];
+ statusFlags = ProcGlobal->statusFlags[ProcGetXactOff(GetNumberFromPGProc(autovac))];
lockmethod_copy = lock->tag.locktag_lockmethodid;
locktag_copy = lock->tag;
LWLockRelease(ProcArrayLock);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 03a1a466fa8..e08d8dd8a4a 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -174,7 +174,8 @@ typedef enum
*
* Some fields in PGPROC (see "mirrored in ..." comment) are mirrored into an
* element of more densely packed ProcGlobal arrays. These arrays are indexed
- * by PGPROC->pgxactoff. Both copies need to be maintained coherently.
+ * by PROC_HDR->pgxactoffs array elements. Both copies need to be maintained
+ * coherently.
*
* NB: The pgxactoff indexed value can *never* be accessed without holding
* locks.
@@ -210,9 +211,6 @@ typedef struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
- int pgxactoff; /* offset into various ProcGlobal->arrays with
- * data mirrored from this PGPROC */
-
uint8 statusFlags; /* this backend's status flags, see PROC_*
* above. mirrored in
* ProcGlobal->statusFlags[pgxactoff] */
@@ -402,10 +400,11 @@ extern PGDLLIMPORT PGPROC *MyProc;
* for PGPROCs that have been added to the shared array with ProcArrayAdd()
* (in contrast to PGPROC array which has unused PGPROCs interspersed).
*
- * The dense arrays are indexed by PGPROC->pgxactoff. Any concurrent
+ * The dense arrays are indexed by PROC_HDR->pgxactoffs array elements, procno
+ * define position of appropriate offset in pgxactoffs array. Any concurrent
* ProcArrayAdd() / ProcArrayRemove() can lead to pgxactoff of a procarray
- * member to change. Therefore it is only safe to use PGPROC->pgxactoff to
- * access the dense array while holding either ProcArrayLock or XidGenLock.
+ * member to change. Therefore it is only safe to use appropriate proc's pgxactoff
+ * to access the dense array while holding either ProcArrayLock or XidGenLock.
*
* As long as a PGPROC is in the procarray, the mirrored values need to be
* maintained in both places in a coherent manner.
@@ -446,6 +445,13 @@ typedef struct PROC_HDR
/* Array of PGPROC structures (not including dummies for prepared txns) */
PGPROC *allProcs;
+ /*
+ * offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs. The PGPROC's appropriate pgxactoff element index
+ * is the same as PGPROC index in allProcs(check ProcGetMyXactOff).
+ */
+ int *pgxactoffs;
+
/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
TransactionId *xids;
@@ -511,6 +517,8 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
*/
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
#define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
+#define ProcGetXactOff(procno) (ProcGlobal->pgxactoffs[(procno)])
+#define ProcGetMyXactOff() (ProcGetXactOff(GetNumberFromPGProc(MyProc)))
/*
* We set aside some extra PGPROC structures for "special worker" processes,
--
2.43.0
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-08-12 20:19 Matthias van de Meent <boekewurm+postgres@gmail.com>
parent: Maksim.Melnikov <m.melnikov@postgrespro.ru>
0 siblings, 1 reply; 18+ messages in thread
From: Matthias van de Meent @ 2026-08-12 20:19 UTC (permalink / raw)
To: Maksim.Melnikov <m.melnikov@postgrespro.ru>; +Cc: Matthias van de Meent <boekewurm+postgres@gmail.com>; Потапов Александр <a.potapov@postgrespro.ru>; pgsql-hackers
On Fri, 7 Aug 2026 at 16:05, Maksim.Melnikov <m.melnikov@postgrespro.ru> wrote:
>
>
> On 7/17/26 11:54, Matthias van de Meent wrote:
> > On Thu, 11 Jun 2026 at 16:31, Maksim.Melnikov <m.melnikov@postgrespro.ru> wrote:
> >> Patch with fixes was attached. Thanks for review.
> > You removed the pgxactoff field with its comment from PGPROC, but
> > haven't yet added a comment on PROC_HDR's pgxactoffs. Please add a
> > comment to the pgxactoffs array that describes its contents. A simple
> > adaptation of the comment removed with PGPROC's pgxactoff field should
> > be sufficient.
> >
> > The comments on PROC_HDR and PGPROC also reference the deleted
> > PGPROC->pgxactoff field, so that also must be adjusted; there may be
> > several other places, as I didn't do a full check on the codebase.
> >
> > Once the relevant comments are added and adjusted I think this is
> > ready for a committer.
> >
> >
> > Kind regards,
> >
> > Matthias van de Meent
> > Databricks (https://www.databricks.com)
> >
> >
> Sorry for the delay, was unavailable for several weeks.
>
> Thanks for review, patch was updated.
I noticed this patch wasn't registered at the commitfest yet, so I
took the liberty to do that for you. It's registered at [0].
The CFBot then noticed crashes caused by the patch in various tests,
which I think can be caused by a lack of initialization of pgxactoffs
in ProcArrayShmemAttach, but I haven't worked on a fix.
Attached are some copy-edits of comments on top of your v3, and a
missed replacement of "GetNumberFromPGProc(MyProc)" with
"MyProcNumber".
This is an incremental patch on your v3, and doesn't fix the CFBot failures.
Kind regards,
Matthias van de Meent
Databricks (https://www.databricks.com)
[0]: https://commitfest.postgresql.org/patch/7139/
From 51619642f910f19788c55b06778629fffa3aa382 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Wed, 12 Aug 2026 22:13:38 +0200
Subject: [PATCH v4] Minor copy-edits
- Adjusted wording in various comments
- Adjust ProcGetMyXactOff to use MyProcNumber, not GetNumberFromPGProc(MyProc)
---
src/backend/storage/ipc/procarray.c | 5 +++--
src/include/storage/proc.h | 19 +++++++++----------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3dd46d91811..e00a255fa6d 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -285,8 +285,9 @@ typedef enum KAXCompressReason
static PGPROC *allProcs;
/*
- * offsets into various ProcGlobal->arrays with data mirrored from appropriate PGPROCs,
- * procno define offset index in pgxactoffs array(See PROC_HDR for details).
+ * offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs, procno define offset index in pgxactoffs array(See
+ * PROC_HDR for details).
*/
static int *pgxactoffs;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index e08d8dd8a4a..8cbc8928dd5 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -174,7 +174,7 @@ typedef enum
*
* Some fields in PGPROC (see "mirrored in ..." comment) are mirrored into an
* element of more densely packed ProcGlobal arrays. These arrays are indexed
- * by PROC_HDR->pgxactoffs array elements. Both copies need to be maintained
+ * by a PGPROC's pgxactoffs entry. Both copies need to be maintained
* coherently.
*
* NB: The pgxactoff indexed value can *never* be accessed without holding
@@ -400,11 +400,11 @@ extern PGDLLIMPORT PGPROC *MyProc;
* for PGPROCs that have been added to the shared array with ProcArrayAdd()
* (in contrast to PGPROC array which has unused PGPROCs interspersed).
*
- * The dense arrays are indexed by PROC_HDR->pgxactoffs array elements, procno
- * define position of appropriate offset in pgxactoffs array. Any concurrent
- * ProcArrayAdd() / ProcArrayRemove() can lead to pgxactoff of a procarray
- * member to change. Therefore it is only safe to use appropriate proc's pgxactoff
- * to access the dense array while holding either ProcArrayLock or XidGenLock.
+ * The dense arrays are indexed by the PGPROC's corresponding offset in
+ * pgxactoff. Any concurrent ProcArrayAdd() / ProcArrayRemove() can cause
+ * the pgxactoff of a procarray member to change. Therefore it is only safe
+ * to use a proc's pgxactoff to access the dense array while holding either
+ * ProcArrayLock or XidGenLock.
*
* As long as a PGPROC is in the procarray, the mirrored values need to be
* maintained in both places in a coherent manner.
@@ -446,9 +446,8 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/*
- * offsets into various ProcGlobal->arrays with data mirrored from
- * appropriate PGPROCs. The PGPROC's appropriate pgxactoff element index
- * is the same as PGPROC index in allProcs(check ProcGetMyXactOff).
+ * Dense offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs. Like allProcs, values are indexed by ProcNumber.
*/
int *pgxactoffs;
@@ -518,7 +517,7 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
#define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
#define ProcGetXactOff(procno) (ProcGlobal->pgxactoffs[(procno)])
-#define ProcGetMyXactOff() (ProcGetXactOff(GetNumberFromPGProc(MyProc)))
+#define ProcGetMyXactOff() (ProcGetXactOff(MyProcNumber))
/*
* We set aside some extra PGPROC structures for "special worker" processes,
--
2.50.1 (Apple Git-155)
Attachments:
[text/plain] v4-0001-Minor-copy-edits.patch.txt (3.7K, ../../CAEze2WhLayiyA_3edX1PeEb0mJcHT_mGfF78Go=TySSb9gwUYA@mail.gmail.com/2-v4-0001-Minor-copy-edits.patch.txt)
download | inline diff:
From 51619642f910f19788c55b06778629fffa3aa382 Mon Sep 17 00:00:00 2001
From: Matthias van de Meent <boekewurm+postgres@gmail.com>
Date: Wed, 12 Aug 2026 22:13:38 +0200
Subject: [PATCH v4] Minor copy-edits
- Adjusted wording in various comments
- Adjust ProcGetMyXactOff to use MyProcNumber, not GetNumberFromPGProc(MyProc)
---
src/backend/storage/ipc/procarray.c | 5 +++--
src/include/storage/proc.h | 19 +++++++++----------
2 files changed, 12 insertions(+), 12 deletions(-)
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index 3dd46d91811..e00a255fa6d 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -285,8 +285,9 @@ typedef enum KAXCompressReason
static PGPROC *allProcs;
/*
- * offsets into various ProcGlobal->arrays with data mirrored from appropriate PGPROCs,
- * procno define offset index in pgxactoffs array(See PROC_HDR for details).
+ * offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs, procno define offset index in pgxactoffs array(See
+ * PROC_HDR for details).
*/
static int *pgxactoffs;
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index e08d8dd8a4a..8cbc8928dd5 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -174,7 +174,7 @@ typedef enum
*
* Some fields in PGPROC (see "mirrored in ..." comment) are mirrored into an
* element of more densely packed ProcGlobal arrays. These arrays are indexed
- * by PROC_HDR->pgxactoffs array elements. Both copies need to be maintained
+ * by a PGPROC's pgxactoffs entry. Both copies need to be maintained
* coherently.
*
* NB: The pgxactoff indexed value can *never* be accessed without holding
@@ -400,11 +400,11 @@ extern PGDLLIMPORT PGPROC *MyProc;
* for PGPROCs that have been added to the shared array with ProcArrayAdd()
* (in contrast to PGPROC array which has unused PGPROCs interspersed).
*
- * The dense arrays are indexed by PROC_HDR->pgxactoffs array elements, procno
- * define position of appropriate offset in pgxactoffs array. Any concurrent
- * ProcArrayAdd() / ProcArrayRemove() can lead to pgxactoff of a procarray
- * member to change. Therefore it is only safe to use appropriate proc's pgxactoff
- * to access the dense array while holding either ProcArrayLock or XidGenLock.
+ * The dense arrays are indexed by the PGPROC's corresponding offset in
+ * pgxactoff. Any concurrent ProcArrayAdd() / ProcArrayRemove() can cause
+ * the pgxactoff of a procarray member to change. Therefore it is only safe
+ * to use a proc's pgxactoff to access the dense array while holding either
+ * ProcArrayLock or XidGenLock.
*
* As long as a PGPROC is in the procarray, the mirrored values need to be
* maintained in both places in a coherent manner.
@@ -446,9 +446,8 @@ typedef struct PROC_HDR
PGPROC *allProcs;
/*
- * offsets into various ProcGlobal->arrays with data mirrored from
- * appropriate PGPROCs. The PGPROC's appropriate pgxactoff element index
- * is the same as PGPROC index in allProcs(check ProcGetMyXactOff).
+ * Dense offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs. Like allProcs, values are indexed by ProcNumber.
*/
int *pgxactoffs;
@@ -518,7 +517,7 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
#define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
#define ProcGetXactOff(procno) (ProcGlobal->pgxactoffs[(procno)])
-#define ProcGetMyXactOff() (ProcGetXactOff(GetNumberFromPGProc(MyProc)))
+#define ProcGetMyXactOff() (ProcGetXactOff(MyProcNumber))
/*
* We set aside some extra PGPROC structures for "special worker" processes,
--
2.50.1 (Apple Git-155)
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-08-25 06:45 Maksim.Melnikov <m.melnikov@postgrespro.ru>
parent: Matthias van de Meent <boekewurm+postgres@gmail.com>
0 siblings, 1 reply; 18+ messages in thread
From: Maksim.Melnikov @ 2026-08-25 06:45 UTC (permalink / raw)
To: Matthias van de Meent <boekewurm+postgres@gmail.com>; +Cc: pgsql-hackers
On 8/12/26 23:19, Matthias van de Meent wrote:
> On Fri, 7 Aug 2026 at 16:05, Maksim.Melnikov<m.melnikov@postgrespro.ru> wrote:
>> On 7/17/26 11:54, Matthias van de Meent wrote:
>>> On Thu, 11 Jun 2026 at 16:31, Maksim.Melnikov<m.melnikov@postgrespro.ru> wrote:
>>>> Patch with fixes was attached. Thanks for review.
>>> You removed the pgxactoff field with its comment from PGPROC, but
>>> haven't yet added a comment on PROC_HDR's pgxactoffs. Please add a
>>> comment to the pgxactoffs array that describes its contents. A simple
>>> adaptation of the comment removed with PGPROC's pgxactoff field should
>>> be sufficient.
>>>
>>> The comments on PROC_HDR and PGPROC also reference the deleted
>>> PGPROC->pgxactoff field, so that also must be adjusted; there may be
>>> several other places, as I didn't do a full check on the codebase.
>>>
>>> Once the relevant comments are added and adjusted I think this is
>>> ready for a committer.
>>>
>>>
>>> Kind regards,
>>>
>>> Matthias van de Meent
>>> Databricks (https://www.databricks.com)
>>>
>>>
>> Sorry for the delay, was unavailable for several weeks.
>>
>> Thanks for review, patch was updated.
> I noticed this patch wasn't registered at the commitfest yet, so I
> took the liberty to do that for you. It's registered at [0].
Thanks!
> The CFBot then noticed crashes caused by the patch in various tests,
> which I think can be caused by a lack of initialization of pgxactoffs
> in ProcArrayShmemAttach, but I haven't worked on a fix.
Yes, thanks, there was the problem with alignment of pgxactoff array on
shmem, I've fixed it with re-ordering of
elements. Also I've rebased master and noticed new commit c6d55714ba4,
that introduced new shmem alloc functions, so I've added minor fix.
I've attached incremental patch for ease of review of last CI fixes.
> Attached are some copy-edits of comments on top of your v3, and a
> missed replacement of "GetNumberFromPGProc(MyProc)" with
> "MyProcNumber".
>
> This is an incremental patch on your v3, and doesn't fix the CFBot failures.
Thanks. I've attached overall patch with your edits for commitfest too.
Kind regards
Melnikov Maksim
Attachments:
[text/x-patch] incremental-CI-fixes-with-rebase.patch (1.1K, ../../a46c4fce-cfb2-470b-87ca-5cc01d6f0901@postgrespro.ru/2-incremental-CI-fixes-with-rebase.patch)
download | inline diff:
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index e6075dafe9..7fef120726 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -462,6 +462,7 @@ static void
ProcArrayShmemAttach(void *arg)
{
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
/*
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index e64ab6e597..be0ca07161 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -269,12 +269,12 @@ ProcGlobalShmemInit(void *arg)
ProcGlobal->xids = (TransactionId *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->xids));
+ ProcGlobal->pgxactoffs = (int *) ptr;
+ ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->pgxactoffs));
+
ProcGlobal->subxidStates = (XidCacheStatus *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->subxidStates));
- ProcGlobal->pgxactoffs = (int *) ptr;
- ptr = (char *) ptr + TotalProcs * sizeof(*ProcGlobal->pgxactoffs);
-
ProcGlobal->statusFlags = (uint8 *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->statusFlags));
[text/x-patch] v5-0001-This-patch-reduce-connection-init-close-time.patch (17.0K, ../../a46c4fce-cfb2-470b-87ca-5cc01d6f0901@postgrespro.ru/3-v5-0001-This-patch-reduce-connection-init-close-time.patch)
download | inline diff:
From a62922a732b261ccfe5c7623aa4fe0e9b18c4aeb Mon Sep 17 00:00:00 2001
From: Maksim Melnikov <m.melnikov@postgrespro.ru>
Date: Tue, 18 Nov 2025 17:20:09 +0300
Subject: [PATCH v5] This patch reduce connection init/close time.
ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
field in PGPROC, because its are placed on different pages and it the
reason of page_faults occurence. Now one of the use case with pgxactoff
is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
allocate ~1KB and it quite enough to have page_faults in case of such
accessing.
So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
the Nth element in pgxactoffs refer to Nth PGPROC.
Eventually it helps to avoid extra page_faults and reduce connection
init/close time. The main benefit is seen on configurations without huge
pages.
---
src/backend/access/transam/varsup.c | 10 ++---
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/vacuum.c | 2 +-
src/backend/replication/logical/logical.c | 2 +-
src/backend/replication/slot.c | 2 +-
src/backend/replication/walsender.c | 2 +-
src/backend/storage/ipc/procarray.c | 55 ++++++++++++++---------
src/backend/storage/lmgr/proc.c | 6 ++-
src/include/storage/proc.h | 23 ++++++----
9 files changed, 63 insertions(+), 41 deletions(-)
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index cb68d8fa974..d67c00b35c8 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -85,7 +85,7 @@ GetNewTransactionId(bool isSubXact)
{
Assert(!isSubXact);
MyProc->xid = BootstrapTransactionId;
- ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
+ ProcGlobal->xids[ProcGetMyXactOff()] = BootstrapTransactionId;
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
}
@@ -244,18 +244,18 @@ GetNewTransactionId(bool isSubXact)
*/
if (!isSubXact)
{
- Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0);
- Assert(!ProcGlobal->subxidStates[MyProc->pgxactoff].overflowed);
+ Assert(ProcGlobal->subxidStates[ProcGetMyXactOff()].count == 0);
+ Assert(!ProcGlobal->subxidStates[ProcGetMyXactOff()].overflowed);
Assert(MyProc->subxidStatus.count == 0);
Assert(!MyProc->subxidStatus.overflowed);
/* LWLockRelease acts as barrier */
MyProc->xid = xid;
- ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ ProcGlobal->xids[ProcGetMyXactOff()] = xid;
}
else
{
- XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ XidCacheStatus *substat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
int nxids = MyProc->subxidStatus.count;
Assert(substat->count == MyProc->subxidStatus.count);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 5a0312fe772..4c66e7bb917 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4811,6 +4811,6 @@ set_indexsafe_procflags(void)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_SAFE_IC;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 72c7f680a84..a2b853e85a4 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2081,7 +2081,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
MyProc->statusFlags |= PROC_IN_VACUUM;
if (params.is_wraparound)
MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 98e5f1dd8f9..53c521f129f 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -274,7 +274,7 @@ StartupDecodingContext(List *output_plugin_options,
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 63ce6d27885..9c2a2cb9888 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -834,7 +834,7 @@ ReplicationSlotRelease(void)
/* might not have been set when we've been a plain slot */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
if (am_walsender)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index c65dd324325..98217943dfa 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -358,7 +358,7 @@ InitWalSender(void)
Assert(MyProc->xmin == InvalidTransactionId);
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_AFFECTS_ALL_HORIZONS;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index b7e03134ed8..090338fb63b 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -284,6 +284,13 @@ typedef enum KAXCompressReason
static PGPROC *allProcs;
+/*
+ * offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs, procno define offset index in pgxactoffs array(See
+ * PROC_HDR for details).
+ */
+static int *pgxactoffs;
+
/*
* Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
*/
@@ -449,12 +456,14 @@ ProcArrayShmemInit(void *arg)
TransamVariables->xactCompletionCount = 1;
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
static void
ProcArrayShmemAttach(void *arg)
{
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
/*
@@ -498,7 +507,7 @@ ProcArrayAdd(PGPROC *proc)
int this_procno = arrayP->pgprocnos[index];
Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[this_procno].pgxactoff == index);
+ Assert(pgxactoffs[this_procno] == index);
/* If we have found our right position in the array, break */
if (this_procno > pgprocno)
@@ -520,23 +529,23 @@ ProcArrayAdd(PGPROC *proc)
movecount * sizeof(*ProcGlobal->statusFlags));
arrayP->pgprocnos[index] = pgprocno;
- proc->pgxactoff = index;
+ pgxactoffs[arrayP->pgprocnos[index]] = index;
ProcGlobal->xids[index] = proc->xid;
ProcGlobal->subxidStates[index] = proc->subxidStatus;
ProcGlobal->statusFlags[index] = proc->statusFlags;
arrayP->numProcs++;
- /* adjust pgxactoff for all following PGPROCs */
+ /* adjust appropriate pgxactoff for all following PGPROCs */
index++;
for (; index < arrayP->numProcs; index++)
{
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff == index - 1);
+ Assert(pgxactoffs[procno] == index - 1);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -574,10 +583,10 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
- myoff = proc->pgxactoff;
+ myoff = ProcGetXactOff(GetNumberFromPGProc(proc));
Assert(myoff >= 0 && myoff < arrayP->numProcs);
- Assert(ProcGlobal->allProcs[arrayP->pgprocnos[myoff]].pgxactoff == myoff);
+ Assert(ProcGlobal->pgxactoffs[arrayP->pgprocnos[myoff]] == myoff);
if (TransactionIdIsValid(latestXid))
{
@@ -624,17 +633,17 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
arrayP->numProcs--;
/*
- * Adjust pgxactoff of following procs for removed PGPROC (note that
- * numProcs already has been decremented).
+ * Adjust appropriate pgxactoff of following procs for removed PGPROC
+ * (note that numProcs already has been decremented).
*/
for (int index = myoff; index < arrayP->numProcs; index++)
{
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff - 1 == index);
+ Assert(pgxactoffs[procno] - 1 == index);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -706,11 +715,13 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
/* avoid unnecessarily dirtying shared cachelines */
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
+
Assert(!LWLockHeldByMe(ProcArrayLock));
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- Assert(proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]);
+ Assert(proc->statusFlags == ProcGlobal->statusFlags[pgxactoff]);
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
LWLockRelease(ProcArrayLock);
}
}
@@ -724,7 +735,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
{
- int pgxactoff = proc->pgxactoff;
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
/*
* Note: we need exclusive lock here because we're going to change other
@@ -747,7 +758,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
}
/* Clear the subtransaction-XID cache too while holding the lock */
@@ -903,7 +914,7 @@ ProcArrayClearTransaction(PGPROC *proc)
/*
* Currently we need to lock ProcArrayLock exclusively here, as we
* increment xactCompletionCount below. We also need it at least in shared
- * mode for pgproc->pgxactoff to stay the same below.
+ * mode for proc's pgxactoff to stay the same below.
*
* We could however, as this action does not actually change anyone's view
* of the set of running XIDs (our entry is duplicate with the gxact that
@@ -916,7 +927,7 @@ ProcArrayClearTransaction(PGPROC *proc)
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- pgxactoff = proc->pgxactoff;
+ pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
proc->xid = InvalidTransactionId;
@@ -1475,7 +1486,7 @@ TransactionIdIsInProgress(TransactionId xid)
}
/* No shortcuts, gotta grovel through the array */
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
numProcs = arrayP->numProcs;
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
{
@@ -2184,7 +2195,7 @@ GetSnapshotData(Snapshot snapshot)
}
latest_completed = TransamVariables->latestCompletedXid;
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
myxid = other_xids[mypgxactoff];
Assert(myxid == MyProc->xid);
@@ -2223,7 +2234,7 @@ GetSnapshotData(Snapshot snapshot)
TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
uint8 statusFlags;
- Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+ Assert(pgxactoffs[arrayP->pgprocnos[pgxactoff]] == pgxactoff);
/*
* If the transaction has no XID assigned, we can skip it; it
@@ -2596,7 +2607,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
MyProc->xmin = TransactionXmin = xmin;
MyProc->statusFlags = (MyProc->statusFlags & ~PROC_XMIN_FLAGS) |
(proc->statusFlags & PROC_XMIN_FLAGS);
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
result = true;
}
@@ -4024,7 +4035,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- mysubxidstat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ mysubxidstat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
/*
* Under normal circumstances xid and xids[] will be in increasing order,
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 624cecc6bc5..053538ccebd 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -163,6 +163,7 @@ ProcGlobalShmemRequest(void *arg)
size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
+ size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->pgxactoffs)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
ProcGlobalAllProcsShmemSize = size;
ShmemRequestStruct(.name = "PGPROC structures",
@@ -267,6 +268,9 @@ ProcGlobalShmemInit(void *arg)
ProcGlobal->xids = (TransactionId *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->xids));
+ ProcGlobal->pgxactoffs = (int *) ptr;
+ ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->pgxactoffs));
+
ProcGlobal->subxidStates = (XidCacheStatus *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->subxidStates));
@@ -1555,7 +1559,7 @@ ProcSleep(LOCALLOCK *locallock)
* the lock held, which is much more undesirable.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- statusFlags = ProcGlobal->statusFlags[autovac->pgxactoff];
+ statusFlags = ProcGlobal->statusFlags[ProcGetXactOff(GetNumberFromPGProc(autovac))];
lockmethod_copy = lock->tag.locktag_lockmethodid;
locktag_copy = lock->tag;
LWLockRelease(ProcArrayLock);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 03a1a466fa8..8cbc8928dd5 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -174,7 +174,8 @@ typedef enum
*
* Some fields in PGPROC (see "mirrored in ..." comment) are mirrored into an
* element of more densely packed ProcGlobal arrays. These arrays are indexed
- * by PGPROC->pgxactoff. Both copies need to be maintained coherently.
+ * by a PGPROC's pgxactoffs entry. Both copies need to be maintained
+ * coherently.
*
* NB: The pgxactoff indexed value can *never* be accessed without holding
* locks.
@@ -210,9 +211,6 @@ typedef struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
- int pgxactoff; /* offset into various ProcGlobal->arrays with
- * data mirrored from this PGPROC */
-
uint8 statusFlags; /* this backend's status flags, see PROC_*
* above. mirrored in
* ProcGlobal->statusFlags[pgxactoff] */
@@ -402,10 +400,11 @@ extern PGDLLIMPORT PGPROC *MyProc;
* for PGPROCs that have been added to the shared array with ProcArrayAdd()
* (in contrast to PGPROC array which has unused PGPROCs interspersed).
*
- * The dense arrays are indexed by PGPROC->pgxactoff. Any concurrent
- * ProcArrayAdd() / ProcArrayRemove() can lead to pgxactoff of a procarray
- * member to change. Therefore it is only safe to use PGPROC->pgxactoff to
- * access the dense array while holding either ProcArrayLock or XidGenLock.
+ * The dense arrays are indexed by the PGPROC's corresponding offset in
+ * pgxactoff. Any concurrent ProcArrayAdd() / ProcArrayRemove() can cause
+ * the pgxactoff of a procarray member to change. Therefore it is only safe
+ * to use a proc's pgxactoff to access the dense array while holding either
+ * ProcArrayLock or XidGenLock.
*
* As long as a PGPROC is in the procarray, the mirrored values need to be
* maintained in both places in a coherent manner.
@@ -446,6 +445,12 @@ typedef struct PROC_HDR
/* Array of PGPROC structures (not including dummies for prepared txns) */
PGPROC *allProcs;
+ /*
+ * Dense offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs. Like allProcs, values are indexed by ProcNumber.
+ */
+ int *pgxactoffs;
+
/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
TransactionId *xids;
@@ -511,6 +516,8 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
*/
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
#define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
+#define ProcGetXactOff(procno) (ProcGlobal->pgxactoffs[(procno)])
+#define ProcGetMyXactOff() (ProcGetXactOff(MyProcNumber))
/*
* We set aside some extra PGPROC structures for "special worker" processes,
--
2.43.0
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-09-09 13:04 Matthias van de Meent <boekewurm+postgres@gmail.com>
parent: Maksim.Melnikov <m.melnikov@postgrespro.ru>
0 siblings, 2 replies; 18+ messages in thread
From: Matthias van de Meent @ 2026-09-09 13:04 UTC (permalink / raw)
To: Maksim.Melnikov <m.melnikov@postgrespro.ru>; +Cc: pgsql-hackers
On Tue, 25 Aug 2026 at 08:45, Maksim.Melnikov <m.melnikov@postgrespro.ru> wrote:
> On 8/12/26 23:19, Matthias van de Meent wrote:
> > Attached are some copy-edits of comments on top of your v3, and a
> > missed replacement of "GetNumberFromPGProc(MyProc)" with
> > "MyProcNumber".
> >
> > This is an incremental patch on your v3, and doesn't fix the CFBot failures.
>
> Thanks. I've attached overall patch with your edits for commitfest too.
Thanks!
I think this is ready for a committer to take a look at, so I'll
update the CF entry accordingly.
Kind regards,
Matthias van de Meent
Databricks (https://www.databricks.com)
PS. I attached v5 again, so that CFbot will pick up the patch, without
the incremental patch that contains changes that conflict with those
in the main patch of v5.
In the future, it's useful to mark patches that you want the CFBot to
ignore with 'nocfbot' in their file name, so that the bot can test the
patches that you want it to include in automated testing.
Attachments:
[application/octet-stream] v5-0001-This-patch-reduce-connection-init-close-time.patch (17.0K, ../../CAEze2WgECLPxazj5v3GQQoZHGe_qg9-4BfjbWVxmQhUp_y_akA@mail.gmail.com/2-v5-0001-This-patch-reduce-connection-init-close-time.patch)
download | inline diff:
From a62922a732b261ccfe5c7623aa4fe0e9b18c4aeb Mon Sep 17 00:00:00 2001
From: Maksim Melnikov <m.melnikov@postgrespro.ru>
Date: Tue, 18 Nov 2025 17:20:09 +0300
Subject: [PATCH v5] This patch reduce connection init/close time.
ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
field in PGPROC, because its are placed on different pages and it the
reason of page_faults occurence. Now one of the use case with pgxactoff
is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
allocate ~1KB and it quite enough to have page_faults in case of such
accessing.
So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
the Nth element in pgxactoffs refer to Nth PGPROC.
Eventually it helps to avoid extra page_faults and reduce connection
init/close time. The main benefit is seen on configurations without huge
pages.
---
src/backend/access/transam/varsup.c | 10 ++---
src/backend/commands/indexcmds.c | 2 +-
src/backend/commands/vacuum.c | 2 +-
src/backend/replication/logical/logical.c | 2 +-
src/backend/replication/slot.c | 2 +-
src/backend/replication/walsender.c | 2 +-
src/backend/storage/ipc/procarray.c | 55 ++++++++++++++---------
src/backend/storage/lmgr/proc.c | 6 ++-
src/include/storage/proc.h | 23 ++++++----
9 files changed, 63 insertions(+), 41 deletions(-)
diff --git a/src/backend/access/transam/varsup.c b/src/backend/access/transam/varsup.c
index cb68d8fa974..d67c00b35c8 100644
--- a/src/backend/access/transam/varsup.c
+++ b/src/backend/access/transam/varsup.c
@@ -85,7 +85,7 @@ GetNewTransactionId(bool isSubXact)
{
Assert(!isSubXact);
MyProc->xid = BootstrapTransactionId;
- ProcGlobal->xids[MyProc->pgxactoff] = BootstrapTransactionId;
+ ProcGlobal->xids[ProcGetMyXactOff()] = BootstrapTransactionId;
return FullTransactionIdFromEpochAndXid(0, BootstrapTransactionId);
}
@@ -244,18 +244,18 @@ GetNewTransactionId(bool isSubXact)
*/
if (!isSubXact)
{
- Assert(ProcGlobal->subxidStates[MyProc->pgxactoff].count == 0);
- Assert(!ProcGlobal->subxidStates[MyProc->pgxactoff].overflowed);
+ Assert(ProcGlobal->subxidStates[ProcGetMyXactOff()].count == 0);
+ Assert(!ProcGlobal->subxidStates[ProcGetMyXactOff()].overflowed);
Assert(MyProc->subxidStatus.count == 0);
Assert(!MyProc->subxidStatus.overflowed);
/* LWLockRelease acts as barrier */
MyProc->xid = xid;
- ProcGlobal->xids[MyProc->pgxactoff] = xid;
+ ProcGlobal->xids[ProcGetMyXactOff()] = xid;
}
else
{
- XidCacheStatus *substat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ XidCacheStatus *substat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
int nxids = MyProc->subxidStatus.count;
Assert(substat->count == MyProc->subxidStatus.count);
diff --git a/src/backend/commands/indexcmds.c b/src/backend/commands/indexcmds.c
index 5a0312fe772..4c66e7bb917 100644
--- a/src/backend/commands/indexcmds.c
+++ b/src/backend/commands/indexcmds.c
@@ -4811,6 +4811,6 @@ set_indexsafe_procflags(void)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_SAFE_IC;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/commands/vacuum.c b/src/backend/commands/vacuum.c
index 72c7f680a84..a2b853e85a4 100644
--- a/src/backend/commands/vacuum.c
+++ b/src/backend/commands/vacuum.c
@@ -2081,7 +2081,7 @@ vacuum_rel(Oid relid, RangeVar *relation, VacuumParams params,
MyProc->statusFlags |= PROC_IN_VACUUM;
if (params.is_wraparound)
MyProc->statusFlags |= PROC_VACUUM_FOR_WRAPAROUND;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/logical/logical.c b/src/backend/replication/logical/logical.c
index 98e5f1dd8f9..53c521f129f 100644
--- a/src/backend/replication/logical/logical.c
+++ b/src/backend/replication/logical/logical.c
@@ -274,7 +274,7 @@ StartupDecodingContext(List *output_plugin_options,
{
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/replication/slot.c b/src/backend/replication/slot.c
index 63ce6d27885..9c2a2cb9888 100644
--- a/src/backend/replication/slot.c
+++ b/src/backend/replication/slot.c
@@ -834,7 +834,7 @@ ReplicationSlotRelease(void)
/* might not have been set when we've been a plain slot */
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags &= ~PROC_IN_LOGICAL_DECODING;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
if (am_walsender)
diff --git a/src/backend/replication/walsender.c b/src/backend/replication/walsender.c
index c65dd324325..98217943dfa 100644
--- a/src/backend/replication/walsender.c
+++ b/src/backend/replication/walsender.c
@@ -358,7 +358,7 @@ InitWalSender(void)
Assert(MyProc->xmin == InvalidTransactionId);
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
MyProc->statusFlags |= PROC_AFFECTS_ALL_HORIZONS;
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
LWLockRelease(ProcArrayLock);
}
diff --git a/src/backend/storage/ipc/procarray.c b/src/backend/storage/ipc/procarray.c
index b7e03134ed8..090338fb63b 100644
--- a/src/backend/storage/ipc/procarray.c
+++ b/src/backend/storage/ipc/procarray.c
@@ -284,6 +284,13 @@ typedef enum KAXCompressReason
static PGPROC *allProcs;
+/*
+ * offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs, procno define offset index in pgxactoffs array(See
+ * PROC_HDR for details).
+ */
+static int *pgxactoffs;
+
/*
* Cache to reduce overhead of repeated calls to TransactionIdIsInProgress()
*/
@@ -449,12 +456,14 @@ ProcArrayShmemInit(void *arg)
TransamVariables->xactCompletionCount = 1;
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
static void
ProcArrayShmemAttach(void *arg)
{
allProcs = ProcGlobal->allProcs;
+ pgxactoffs = ProcGlobal->pgxactoffs;
}
/*
@@ -498,7 +507,7 @@ ProcArrayAdd(PGPROC *proc)
int this_procno = arrayP->pgprocnos[index];
Assert(this_procno >= 0 && this_procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[this_procno].pgxactoff == index);
+ Assert(pgxactoffs[this_procno] == index);
/* If we have found our right position in the array, break */
if (this_procno > pgprocno)
@@ -520,23 +529,23 @@ ProcArrayAdd(PGPROC *proc)
movecount * sizeof(*ProcGlobal->statusFlags));
arrayP->pgprocnos[index] = pgprocno;
- proc->pgxactoff = index;
+ pgxactoffs[arrayP->pgprocnos[index]] = index;
ProcGlobal->xids[index] = proc->xid;
ProcGlobal->subxidStates[index] = proc->subxidStatus;
ProcGlobal->statusFlags[index] = proc->statusFlags;
arrayP->numProcs++;
- /* adjust pgxactoff for all following PGPROCs */
+ /* adjust appropriate pgxactoff for all following PGPROCs */
index++;
for (; index < arrayP->numProcs; index++)
{
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff == index - 1);
+ Assert(pgxactoffs[procno] == index - 1);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -574,10 +583,10 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
LWLockAcquire(XidGenLock, LW_EXCLUSIVE);
- myoff = proc->pgxactoff;
+ myoff = ProcGetXactOff(GetNumberFromPGProc(proc));
Assert(myoff >= 0 && myoff < arrayP->numProcs);
- Assert(ProcGlobal->allProcs[arrayP->pgprocnos[myoff]].pgxactoff == myoff);
+ Assert(ProcGlobal->pgxactoffs[arrayP->pgprocnos[myoff]] == myoff);
if (TransactionIdIsValid(latestXid))
{
@@ -624,17 +633,17 @@ ProcArrayRemove(PGPROC *proc, TransactionId latestXid)
arrayP->numProcs--;
/*
- * Adjust pgxactoff of following procs for removed PGPROC (note that
- * numProcs already has been decremented).
+ * Adjust appropriate pgxactoff of following procs for removed PGPROC
+ * (note that numProcs already has been decremented).
*/
for (int index = myoff; index < arrayP->numProcs; index++)
{
int procno = arrayP->pgprocnos[index];
Assert(procno >= 0 && procno < (arrayP->maxProcs + NUM_AUXILIARY_PROCS));
- Assert(allProcs[procno].pgxactoff - 1 == index);
+ Assert(pgxactoffs[procno] - 1 == index);
- allProcs[procno].pgxactoff = index;
+ pgxactoffs[procno] = index;
}
/*
@@ -706,11 +715,13 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
/* avoid unnecessarily dirtying shared cachelines */
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
+
Assert(!LWLockHeldByMe(ProcArrayLock));
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- Assert(proc->statusFlags == ProcGlobal->statusFlags[proc->pgxactoff]);
+ Assert(proc->statusFlags == ProcGlobal->statusFlags[pgxactoff]);
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
LWLockRelease(ProcArrayLock);
}
}
@@ -724,7 +735,7 @@ ProcArrayEndTransaction(PGPROC *proc, TransactionId latestXid)
static inline void
ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
{
- int pgxactoff = proc->pgxactoff;
+ int pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
/*
* Note: we need exclusive lock here because we're going to change other
@@ -747,7 +758,7 @@ ProcArrayEndTransactionInternal(PGPROC *proc, TransactionId latestXid)
if (proc->statusFlags & PROC_VACUUM_STATE_MASK)
{
proc->statusFlags &= ~PROC_VACUUM_STATE_MASK;
- ProcGlobal->statusFlags[proc->pgxactoff] = proc->statusFlags;
+ ProcGlobal->statusFlags[pgxactoff] = proc->statusFlags;
}
/* Clear the subtransaction-XID cache too while holding the lock */
@@ -903,7 +914,7 @@ ProcArrayClearTransaction(PGPROC *proc)
/*
* Currently we need to lock ProcArrayLock exclusively here, as we
* increment xactCompletionCount below. We also need it at least in shared
- * mode for pgproc->pgxactoff to stay the same below.
+ * mode for proc's pgxactoff to stay the same below.
*
* We could however, as this action does not actually change anyone's view
* of the set of running XIDs (our entry is duplicate with the gxact that
@@ -916,7 +927,7 @@ ProcArrayClearTransaction(PGPROC *proc)
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- pgxactoff = proc->pgxactoff;
+ pgxactoff = ProcGetXactOff(GetNumberFromPGProc(proc));
ProcGlobal->xids[pgxactoff] = InvalidTransactionId;
proc->xid = InvalidTransactionId;
@@ -1475,7 +1486,7 @@ TransactionIdIsInProgress(TransactionId xid)
}
/* No shortcuts, gotta grovel through the array */
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
numProcs = arrayP->numProcs;
for (int pgxactoff = 0; pgxactoff < numProcs; pgxactoff++)
{
@@ -2184,7 +2195,7 @@ GetSnapshotData(Snapshot snapshot)
}
latest_completed = TransamVariables->latestCompletedXid;
- mypgxactoff = MyProc->pgxactoff;
+ mypgxactoff = ProcGetMyXactOff();
myxid = other_xids[mypgxactoff];
Assert(myxid == MyProc->xid);
@@ -2223,7 +2234,7 @@ GetSnapshotData(Snapshot snapshot)
TransactionId xid = UINT32_ACCESS_ONCE(other_xids[pgxactoff]);
uint8 statusFlags;
- Assert(allProcs[arrayP->pgprocnos[pgxactoff]].pgxactoff == pgxactoff);
+ Assert(pgxactoffs[arrayP->pgprocnos[pgxactoff]] == pgxactoff);
/*
* If the transaction has no XID assigned, we can skip it; it
@@ -2596,7 +2607,7 @@ ProcArrayInstallRestoredXmin(TransactionId xmin, PGPROC *proc)
MyProc->xmin = TransactionXmin = xmin;
MyProc->statusFlags = (MyProc->statusFlags & ~PROC_XMIN_FLAGS) |
(proc->statusFlags & PROC_XMIN_FLAGS);
- ProcGlobal->statusFlags[MyProc->pgxactoff] = MyProc->statusFlags;
+ ProcGlobal->statusFlags[ProcGetMyXactOff()] = MyProc->statusFlags;
result = true;
}
@@ -4024,7 +4035,7 @@ XidCacheRemoveRunningXids(TransactionId xid,
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- mysubxidstat = &ProcGlobal->subxidStates[MyProc->pgxactoff];
+ mysubxidstat = &ProcGlobal->subxidStates[ProcGetMyXactOff()];
/*
* Under normal circumstances xid and xids[] will be in increasing order,
diff --git a/src/backend/storage/lmgr/proc.c b/src/backend/storage/lmgr/proc.c
index 624cecc6bc5..053538ccebd 100644
--- a/src/backend/storage/lmgr/proc.c
+++ b/src/backend/storage/lmgr/proc.c
@@ -163,6 +163,7 @@ ProcGlobalShmemRequest(void *arg)
size = add_size(size, mul_size(TotalProcs, sizeof(PGPROC)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->xids)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->subxidStates)));
+ size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->pgxactoffs)));
size = add_size(size, mul_size(TotalProcs, sizeof(*ProcGlobal->statusFlags)));
ProcGlobalAllProcsShmemSize = size;
ShmemRequestStruct(.name = "PGPROC structures",
@@ -267,6 +268,9 @@ ProcGlobalShmemInit(void *arg)
ProcGlobal->xids = (TransactionId *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->xids));
+ ProcGlobal->pgxactoffs = (int *) ptr;
+ ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->pgxactoffs));
+
ProcGlobal->subxidStates = (XidCacheStatus *) ptr;
ptr = ptr + (TotalProcs * sizeof(*ProcGlobal->subxidStates));
@@ -1555,7 +1559,7 @@ ProcSleep(LOCALLOCK *locallock)
* the lock held, which is much more undesirable.
*/
LWLockAcquire(ProcArrayLock, LW_EXCLUSIVE);
- statusFlags = ProcGlobal->statusFlags[autovac->pgxactoff];
+ statusFlags = ProcGlobal->statusFlags[ProcGetXactOff(GetNumberFromPGProc(autovac))];
lockmethod_copy = lock->tag.locktag_lockmethodid;
locktag_copy = lock->tag;
LWLockRelease(ProcArrayLock);
diff --git a/src/include/storage/proc.h b/src/include/storage/proc.h
index 03a1a466fa8..8cbc8928dd5 100644
--- a/src/include/storage/proc.h
+++ b/src/include/storage/proc.h
@@ -174,7 +174,8 @@ typedef enum
*
* Some fields in PGPROC (see "mirrored in ..." comment) are mirrored into an
* element of more densely packed ProcGlobal arrays. These arrays are indexed
- * by PGPROC->pgxactoff. Both copies need to be maintained coherently.
+ * by a PGPROC's pgxactoffs entry. Both copies need to be maintained
+ * coherently.
*
* NB: The pgxactoff indexed value can *never* be accessed without holding
* locks.
@@ -210,9 +211,6 @@ typedef struct PGPROC
Oid tempNamespaceId; /* OID of temp schema this backend is
* using */
- int pgxactoff; /* offset into various ProcGlobal->arrays with
- * data mirrored from this PGPROC */
-
uint8 statusFlags; /* this backend's status flags, see PROC_*
* above. mirrored in
* ProcGlobal->statusFlags[pgxactoff] */
@@ -402,10 +400,11 @@ extern PGDLLIMPORT PGPROC *MyProc;
* for PGPROCs that have been added to the shared array with ProcArrayAdd()
* (in contrast to PGPROC array which has unused PGPROCs interspersed).
*
- * The dense arrays are indexed by PGPROC->pgxactoff. Any concurrent
- * ProcArrayAdd() / ProcArrayRemove() can lead to pgxactoff of a procarray
- * member to change. Therefore it is only safe to use PGPROC->pgxactoff to
- * access the dense array while holding either ProcArrayLock or XidGenLock.
+ * The dense arrays are indexed by the PGPROC's corresponding offset in
+ * pgxactoff. Any concurrent ProcArrayAdd() / ProcArrayRemove() can cause
+ * the pgxactoff of a procarray member to change. Therefore it is only safe
+ * to use a proc's pgxactoff to access the dense array while holding either
+ * ProcArrayLock or XidGenLock.
*
* As long as a PGPROC is in the procarray, the mirrored values need to be
* maintained in both places in a coherent manner.
@@ -446,6 +445,12 @@ typedef struct PROC_HDR
/* Array of PGPROC structures (not including dummies for prepared txns) */
PGPROC *allProcs;
+ /*
+ * Dense offsets into various ProcGlobal->arrays with data mirrored from
+ * appropriate PGPROCs. Like allProcs, values are indexed by ProcNumber.
+ */
+ int *pgxactoffs;
+
/* Array mirroring PGPROC.xid for each PGPROC currently in the procarray */
TransactionId *xids;
@@ -511,6 +516,8 @@ extern PGDLLIMPORT PGPROC *PreparedXactProcs;
*/
#define GetPGProcByNumber(n) (&ProcGlobal->allProcs[(n)])
#define GetNumberFromPGProc(proc) ((proc) - &ProcGlobal->allProcs[0])
+#define ProcGetXactOff(procno) (ProcGlobal->pgxactoffs[(procno)])
+#define ProcGetMyXactOff() (ProcGetXactOff(MyProcNumber))
/*
* We set aside some extra PGPROC structures for "special worker" processes,
--
2.43.0
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-09-11 19:29 Haibo Yan <tristan.yim@gmail.com>
parent: Matthias van de Meent <boekewurm+postgres@gmail.com>
1 sibling, 0 replies; 18+ messages in thread
From: Haibo Yan @ 2026-09-11 19:29 UTC (permalink / raw)
To: Matthias van de Meent <boekewurm+postgres@gmail.com>; +Cc: Maksim.Melnikov <m.melnikov@postgrespro.ru>; pgsql-hackers
On Wed, Sep 9, 2026 at 6:05 AM Matthias van de Meent
<boekewurm+postgres@gmail.com> wrote:
>
> On Tue, 25 Aug 2026 at 08:45, Maksim.Melnikov <m.melnikov@postgrespro.ru> wrote:
> > On 8/12/26 23:19, Matthias van de Meent wrote:
> > > Attached are some copy-edits of comments on top of your v3, and a
> > > missed replacement of "GetNumberFromPGProc(MyProc)" with
> > > "MyProcNumber".
> > >
> > > This is an incremental patch on your v3, and doesn't fix the CFBot failures.
> >
> > Thanks. I've attached overall patch with your edits for commitfest too.
>
> Thanks!
>
> I think this is ready for a committer to take a look at, so I'll
> update the CF entry accordingly.
>
>
> Kind regards,
>
> Matthias van de Meent
> Databricks (https://www.databricks.com)
>
> PS. I attached v5 again, so that CFbot will pick up the patch, without
> the incremental patch that contains changes that conflict with those
> in the main patch of v5.
> In the future, it's useful to mark patches that you want the CFBot to
> ignore with 'nocfbot' in their file name, so that the bot can test the
> patches that you want it to include in automated testing.
Hi,
I dug a bit further into the startup scaling on Linux. I instrumented the
startup path with help from Codex, and the main O(N) cost I found in the
retained-connection case is:
heap_page_prune_opt()
-> GlobalVisTestIsRemovableXid()
-> GlobalVisUpdate()
-> ComputeXidHorizons()
ComputeXidHorizons() scans the live ProcArray. In my tests the horizon scan
cost about 18.75 ns per live backend.
The visibility check currently runs before the page-full/free-space check,
even when the latter would reject pruning anyway. For the pg_database page
seen during startup, there were about 7620 bytes free with an 819-byte
threshold. Master refreshed the horizon and then decided not to prune; the
pruning worker was never called.
I first tried avoiding the refresh and relying only on cached visibility state.
That removed most of the startup slope, but it also suppressed useful pruning:
HOT rates dropped and some update tests grew the heap by 33-142%. I dropped
that approach.
v6 only changes the order of the existing checks: do the page-local usefulness
check first, and only then do the refresh-capable visibility test.
allow_update = true, the cleanup lock, the locked recheck, and the pruning
worker are unchanged.
Five fresh-postmaster runs gave:
connections master v6
500 0.477s 0.467s
1000 0.958 0.933
2000 1.941 1.883
4000 3.960 3.776
8000 8.344 7.674
16000 18.061 15.509
Fitting the per-connection startup times gives about 21.70 ns/live backend on
master and 2.70 ns/live with v6. The independently measured horizon slope was
18.75 ns/live, close to the 19.00 ns/live reduction with v6.
Across 16000 retained connections, that horizon slope corresponds to about 2.4
seconds of cumulative work, close to the observed 2.55-second reduction in
total startup time.
I couldn’t reproduce the HOT/bloat problem from the cached-only experiment.
Fixed-update and 40000-update/20-VACUUM tests had the same heap sizes and HOT
rates as master, and catalog churn produced the same pruning-worker counts and
relation sizes.
There is a small concurrency difference: the unlocked page-local heuristic is
now evaluated earlier, so a concurrent page change can make v6 miss an
opportunistic prune that master might later have attempted. GlobalVisUpdate
itself does not affect the free-space predicate; the difference is only the
earlier observation relative to concurrent page changes. Pages that pass the
early check still follow the original visibility/cleanup-lock/recheck path.
v5 and this patch appear to address orthogonal costs. This patch removes the
retained-startup horizon scan, while v5 improves ProcArray renumbering locality
under backend churn. I haven’t benchmarked the combination.
For example, at 16000 retained connections in the churn workload, v5 reached
about 484 replacements/sec versus 373 with v6 and 355 on master.
One result remains unexplained: at 8000 connections, v6 reduced replacement
throughput by 4.9% versus master in all five paired runs, although it improved
again at 16000. I checked pruning-worker counts and the HOT/bloat workloads and
found no corresponding loss of useful pruning. I haven’t yet profiled that case
for CPU or lock-wait differences.
The assertion build passes make check and check-world with
xid_wraparound enabled.
Comments welcome, particularly on moving the unlocked usefulness check earlier.
Best,
Haibo
Attachments:
[application/octet-stream] v6-0001-Avoid-unnecessary-horizon-refreshes-during-opportunistic-pruning.patch (2.5K, ../../CABXr29FZwP850=dUYyEbqg5BayOboVw3R4aBgW52Q8Ba4Vc+XA@mail.gmail.com/2-v6-0001-Avoid-unnecessary-horizon-refreshes-during-opportunistic-pruning.patch)
download | inline diff:
From 9b89490054f2984d882c8ff6c3eaf6399167b41f Mon Sep 17 00:00:00 2001
From: Haibo Yan <tristan.yim@gmail.com>
Date: Fri, 11 Sep 2026 11:25:14 -0700
Subject: [PATCH v6] Avoid unnecessary horizon refreshes during opportunistic
pruning
heap_page_prune_opt() currently checks whether the page's prune XID is
globally removable before checking whether pruning would be useful based
on the page's free space.
Determining removability can refresh the global visibility horizons,
which requires scanning the ProcArray. That work is unnecessary when
the existing page-local heuristic would reject the pruning attempt
anyway.
Check the existing page-full/free-space heuristic first, and perform the
visibility test only for pages for which opportunistic pruning is
considered useful.
The refresh-capable visibility test, conditional cleanup lock, and the
usefulness recheck under the cleanup lock remain unchanged.
As before, the unlocked usefulness check is only a heuristic and can miss
an opportunistic pruning opportunity if page state changes concurrently.
---
src/backend/access/heap/pruneheap.c | 20 +++++++++++---------
1 file changed, 11 insertions(+), 9 deletions(-)
diff --git a/src/backend/access/heap/pruneheap.c b/src/backend/access/heap/pruneheap.c
index 29f4722b02d..982462c4f55 100644
--- a/src/backend/access/heap/pruneheap.c
+++ b/src/backend/access/heap/pruneheap.c
@@ -295,15 +295,6 @@ heap_page_prune_opt(Relation relation, Buffer buffer, Buffer *vmbuffer,
if (!TransactionIdIsValid(prune_xid))
return;
- /*
- * Check whether prune_xid indicates that there may be dead rows that can
- * be cleaned up.
- */
- vistest = GlobalVisTestFor(relation);
-
- if (!GlobalVisTestIsRemovableXid(vistest, prune_xid, true))
- return;
-
/*
* We prune when a previous UPDATE failed to find enough space on the page
* for a new tuple version, or when free space falls below the relation's
@@ -325,6 +316,17 @@ heap_page_prune_opt(Relation relation, Buffer buffer, Buffer *vmbuffer,
bool record_free_space = false;
Size freespace = 0;
+ /*
+ * Check whether prune_xid indicates that there may be dead rows that
+ * can be cleaned up. Do this only after deciding that pruning would
+ * be useful, since refreshing the visibility horizon may require
+ * scanning the ProcArray.
+ */
+ vistest = GlobalVisTestFor(relation);
+
+ if (!GlobalVisTestIsRemovableXid(vistest, prune_xid, true))
+ return;
+
/* OK, try to get exclusive buffer lock */
if (!ConditionalLockBufferForCleanup(buffer))
return;
--
2.43.0
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-09-11 19:51 Andres Freund <andres@anarazel.de>
parent: Matthias van de Meent <boekewurm+postgres@gmail.com>
1 sibling, 2 replies; 18+ messages in thread
From: Andres Freund @ 2026-09-11 19:51 UTC (permalink / raw)
To: Matthias van de Meent <boekewurm+postgres@gmail.com>; +Cc: Maksim.Melnikov <m.melnikov@postgrespro.ru>; pgsql-hackers
Hi,
On 2026-09-09 15:04:53 +0200, Matthias van de Meent wrote:
> From a62922a732b261ccfe5c7623aa4fe0e9b18c4aeb Mon Sep 17 00:00:00 2001
> From: Maksim Melnikov <m.melnikov@postgrespro.ru>
> Date: Tue, 18 Nov 2025 17:20:09 +0300
> Subject: [PATCH v5] This patch reduce connection init/close time.
>
> ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
> field in PGPROC, because its are placed on different pages and it the
> reason of page_faults occurence. Now one of the use case with pgxactoff
> is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
> allocate ~1KB and it quite enough to have page_faults in case of such
> accessing.
>
> So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
> pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
> the Nth element in pgxactoffs refer to Nth PGPROC.
>
> Eventually it helps to avoid extra page_faults and reduce connection
> init/close time. The main benefit is seen on configurations without huge
> pages.
Maybe I am just missing something, but is this ever a benefit outside of
completely arbitrary scenarios? Who has >= 8k concurrent connections doing
nothing but connecting/disconnecting? Even if one were to agree that it's
useful to optimize large huge_pages=off workloads, surely that's an absurd
enough workload that nobody cares?
This isn't entirely free, needing to look at a separate cachelines that are
frequently modified (and thus commonly won't be in the cpu-local cache), in
reasonably common codepaths like ProcArrayEndTransactionInternal(),
TransactionIdIsInProgress(), GetSnapshotData() is far from free.
Greetings,
Andres Freund
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-09-18 01:54 shihao zhong <zhong950419@gmail.com>
parent: Andres Freund <andres@anarazel.de>
1 sibling, 1 reply; 18+ messages in thread
From: shihao zhong @ 2026-09-18 01:54 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Matthias van de Meent <boekewurm+postgres@gmail.com>; Maksim.Melnikov <m.melnikov@postgrespro.ru>; pgsql-hackers
Hi,
As CF manager, I moved this entry from Ready for Committer to Waiting on
Author.
> Maybe I am just missing something, but is this ever a benefit outside of
> completely arbitrary scenarios?
On Andres's question, the closest workload I have seen is an AI app
behind pgbouncer. Some transactions stay open while they wait for an LLM
response, and background loading causes transactionid and tuple lock
waits. In spikes, pgbouncer grew to about 4000 server connections, then
shrank again.
The real problem there is locks and long transactions. This patch might help
the connection storm, but may not fix it.
In addition, CFBot now tests v6, not v5. Haibo, please post v6 separately.
Thanks,
Shihao
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-09-18 07:49 Jakub Wartak <jakub.wartak@enterprisedb.com>
parent: shihao zhong <zhong950419@gmail.com>
0 siblings, 0 replies; 18+ messages in thread
From: Jakub Wartak @ 2026-09-18 07:49 UTC (permalink / raw)
To: shihao zhong <zhong950419@gmail.com>; +Cc: Andres Freund <andres@anarazel.de>; Matthias van de Meent <boekewurm+postgres@gmail.com>; Maksim.Melnikov <m.melnikov@postgrespro.ru>; pgsql-hackers
On Fri, Sep 18, 2026 at 3:55 AM shihao zhong <zhong950419@gmail.com> wrote:
>
> > Maybe I am just missing something, but is this ever a benefit outside of
> > completely arbitrary scenarios?
>
> On Andres's question, the closest workload I have seen is an AI app
> behind pgbouncer. Some transactions stay open while they wait for an LLM
> response, and background loading causes transactionid and tuple lock
> waits. In spikes, pgbouncer grew to about 4000 server connections, then
> shrank again.
>
> The real problem there is locks and long transactions. This patch might help
> the connection storm, but may not fix it.
>
I'm afraid PostgreSQL is not ready to deterministically handle those kind of
connection counts, but that doesnt mean people don't run such setups anyway.
I'm afraid nothing will help the connection storm until the issue is diagnosed
(root cause of what is causing it) and then stuff is (re)configured properly.
But... kind it's unrelated to this $patch itself that I saw stuff like that
happening from time to time in the field/production systems, but we are often
completley blind to connection rates and/or saturating postmaster in the first
place, so rarely people solve those issues until it's way too late. I've tried
to propose some damage control to that in [1] so at least users would know
this - at least partially for postmaster saturation cases, v3 just logs:
> postmaster potentially overloaded, stats: avg 2905.00 conns/sec; 2904.00 disconns/sec; 0.00 parallel workers started/sec; CPU: user: 0.01 s, system: 0.97 s, elapsed: 1.00
Kind of related is that I've also tried to have PoC for throtthing rates
of incoming connections, but had some issues there (basically be able to
throttle to like e.g. 30 TCP new conns per sec per single IP or /24 network;
that exactly assumes you are DBA and trying to damage-control some rouge
user), but the issue there in how we throw error to the client (which AFAIR
libpq interprets as some kind of generic error and there is no way to explictly
say to the client *you've been throttled, slow down* something like HTTP/429,
because the way interpret every error as some security issue there, I can dig
more details on this if someone is interested).
-J.
[1] - https://www.postgresql.org/message-id/flat/CAKZiRmxeTNAe_VZ9m%3Drt%3D5155mvL%3DCFsQ4ENJWhuUdgMe%3D_U...
^ permalink raw reply [nested|flat] 18+ messages in thread
* Re: Init connection time grows quadratically
@ 2026-09-18 08:01 Matthias van de Meent <boekewurm+postgres@gmail.com>
parent: Andres Freund <andres@anarazel.de>
1 sibling, 0 replies; 18+ messages in thread
From: Matthias van de Meent @ 2026-09-18 08:01 UTC (permalink / raw)
To: Andres Freund <andres@anarazel.de>; +Cc: Maksim.Melnikov <m.melnikov@postgrespro.ru>; pgsql-hackers
On Fri, 11 Sept 2026, 21:51 Andres Freund, <andres@anarazel.de> wrote:
>
> Hi,
>
> On 2026-09-09 15:04:53 +0200, Matthias van de Meent wrote:
> > From a62922a732b261ccfe5c7623aa4fe0e9b18c4aeb Mon Sep 17 00:00:00 2001
> > From: Maksim Melnikov <m.melnikov@postgrespro.ru>
> > Date: Tue, 18 Nov 2025 17:20:09 +0300
> > Subject: [PATCH v5] This patch reduce connection init/close time.
> >
> > ProcArrayRemove/ProcArrayAdd are expensive in terms of accessing pgxactoff
> > field in PGPROC, because its are placed on different pages and it the
> > reason of page_faults occurence. Now one of the use case with pgxactoff
> > is iteration with gaps over PGPROCs and read/write pgxactoff, PGPROC
> > allocate ~1KB and it quite enough to have page_faults in case of such
> > accessing.
> >
> > So we placed all pgxactoff in the separate array pgxactoffs to have adjacent
> > pages for them all. Indexes in allProcs aligned with pgxactoffs array, so
> > the Nth element in pgxactoffs refer to Nth PGPROC.
> >
> > Eventually it helps to avoid extra page_faults and reduce connection
> > init/close time. The main benefit is seen on configurations without huge
> > pages.
>
> Maybe I am just missing something, but is this ever a benefit outside of
> completely arbitrary scenarios? Who has >= 8k concurrent connections doing
> nothing but connecting/disconnecting? Even if one were to agree that it's
> useful to optimize large huge_pages=off workloads, surely that's an absurd
> enough workload that nobody cares?
Prepared transactions are also a workload that go through
ProcArrayAdd+Remove, and I don't think that such a workload (with 100s
or even 1000s of prepared xacts) is completely absurd. Uncommon,
sure, but unlike systems with multiple 1000s of connections I would
not call it absurd.
> This isn't entirely free, needing to look at a separate cachelines that are
> frequently modified (and thus commonly won't be in the cpu-local cache), in
> reasonably common codepaths like ProcArrayEndTransactionInternal(),
> TransactionIdIsInProgress(), GetSnapshotData() is far from free.
This is true, but if it is likely that the new pgxactoff cache line is
modified, then it's more likely than not that on HEAD our Proc entry's
xmin/xid cache line would've been modified instead (depending on cache
line size etc). If we're already going to look at nonlocally dirtied
cache lines, why not make sure those lines are densely packed with
dirtied information, so that the modifying backend can be done with it
sooner, and with fewer false sharing opportunities? Note that
pgxactoff is only dirtied if a proc with procnumber smaller than ours
is added or removed, or (with patch) smaller than the last procnumber
on the cacheline.
Currently, ProcArrayAdd/Remove in HEAD frequently falsely shares a
proc's own xmin/xmax, and I don't believe that that dependency is
free, either. Yes, the offsets array's cache line is more likely to
be modified because more backends' offsets fit on the cache line, and
so you trade false sharing your proc's xmin/xmax with other procs'
offsets, but I think that the overall cost is lower, because fewer
recently modified cache lines total need to be modified and moved
around the NUMA domains -- especially given that these are now only
modified by ProcArrayAdd/Remove's code, when we're already under very
heavy locks.
Kind regards,
Matthias van de Meent
^ permalink raw reply [nested|flat] 18+ messages in thread
end of thread, other threads:[~2026-09-18 08:01 UTC | newest]
Thread overview: 18+ messages (download: mbox mbox.gz follow: Atom feed)
-- links below jump to the message on this page --
2025-05-27 10:45 Init connection time grows quadratically Потапов Александр <a.potapov@postgrespro.ru>
2025-05-27 11:27 ` Потапов Александр <a.potapov@postgrespro.ru>
2025-05-27 11:36 ` Matthias van de Meent <boekewurm+postgres@gmail.com>
2025-06-10 08:56 ` Потапов Александр <a.potapov@postgrespro.ru>
2025-06-16 08:56 ` Потапов Александр <a.potapov@postgrespro.ru>
2026-06-03 06:33 ` Maksim.Melnikov <m.melnikov@postgrespro.ru>
2026-06-03 13:35 ` Matthias van de Meent <boekewurm+postgres@gmail.com>
2026-06-11 14:31 ` Maksim.Melnikov <m.melnikov@postgrespro.ru>
2026-07-17 08:54 ` Matthias van de Meent <boekewurm+postgres@gmail.com>
2026-08-07 14:05 ` Maksim.Melnikov <m.melnikov@postgrespro.ru>
2026-08-12 20:19 ` Matthias van de Meent <boekewurm+postgres@gmail.com>
2026-08-25 06:45 ` Maksim.Melnikov <m.melnikov@postgrespro.ru>
2026-09-09 13:04 ` Matthias van de Meent <boekewurm+postgres@gmail.com>
2026-09-11 19:29 ` Haibo Yan <tristan.yim@gmail.com>
2026-09-11 19:51 ` Andres Freund <andres@anarazel.de>
2026-09-18 01:54 ` shihao zhong <zhong950419@gmail.com>
2026-09-18 07:49 ` Jakub Wartak <jakub.wartak@enterprisedb.com>
2026-09-18 08:01 ` Matthias van de Meent <boekewurm+postgres@gmail.com>
This inbox is served by DDX for PostgreSQL; see mirroring instructions
for how to clone and mirror all data and code used for this inbox