{"id":421105,"date":"2024-06-30T03:53:20","date_gmt":"2024-06-30T03:53:20","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=421105"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=421105","title":{"rendered":"<span>PostgreSQL 16: Part 2 or CommitFest 2022-09<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/z7\/fj\/pt\/z7fjptxnt0jbcvmfpejym20gfu0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/z7\/fj\/pt\/z7fjptxnt0jbcvmfpejym20gfu0.png\"\/><\/p>\n<p>  <\/p>\n<p>It&#8217;s official! PostgreSQL 15 is out, and the community is abuzz discussing all the new features of the fresh release.<\/p>\n<p>  <\/p>\n<p>Meanwhile, the October CommitFest for PostgreSQL 16 had come and gone, with its own notable additions to the code.<\/p>\n<p>  <\/p>\n<p>If you missed the July CommitFest, <a href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/696816\/\">our previous article<\/a> will get you up to speed in no time.<\/p>\n<p>  <\/p>\n<p>Here are the patches I want to talk about:<\/p>\n<p>  <\/p>\n<p><a href=\"#commit_0823d061\">SYSTEM_USER function<\/a><br \/>  <a href=\"#commit_d977ffd9\">Frozen pages\/tuples information in autovacuum&#8217;s server log<\/a><br \/>  <a href=\"#commit_d7e39d72\">pg_stat_get_backend_idset returns the actual backend ID<\/a><br \/>  <a href=\"#commit_1349d279\">Improved performance of ORDER BY \/ DISTINCT aggregates<\/a><br \/>  <a href=\"#commit_3592e0ff\">Faster bulk-loading into partitioned tables<\/a><br \/>  <a href=\"#commit_37a6e5df\">Optimized lookups in snapshots<\/a><br \/>  <a href=\"#commit_36628396\">Bidirectional logical replication<\/a><br \/>  <a href=\"#commit_ce6b672e\">pg_auth_members: pg_auth_members: role membership granting management<\/a><br \/>  <a href=\"#commit_e3ce2de0\">pg_auth_members: role membership and privilege inheritance<\/a><br \/>  <a href=\"#commit_8b60db77\">pg_receivewal and pg_recvlogical can now handle SIGTERM<\/a><\/p>\n<p><a name=\"habracut\"><\/a><br \/>  <a name=\"commit_0823d061\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3703\/\">SYSTEM_USER function<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/0823d061\">0823d061<\/a><\/p>\n<p>  <\/p>\n<p>The <code>SYSTEM_USER<\/code> function is a part of the SQL standard which has not been implemented in PostgreSQL so far. It displays information about the system user connected to the database server and the authentification method used:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT session_user, system_user;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> session_user | system_user   --------------+--------------  alice        | peer:student<\/code><\/pre>\n<p>  <\/p>\n<p>The example shows that <code>alice<\/code> has connected to the database as <code>student<\/code> (OS username) using peer authentification. For trust authentification, the function always returns NULL.<\/p>\n<p>  <\/p>\n<p>These connection details are also obtainable from the server log (if the parameter <code>log_connections<\/code> is on).<\/p>\n<p>  <a name=\"commit_d977ffd9\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3827\/\">Frozen pages\/tuples information in autovacuum&#8217;s server log<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/d977ffd9\">d977ffd9<\/a><\/p>\n<p>  <\/p>\n<p>The autovacuum log and the <code>VACUUM (verbose)<\/code> output now display additional information on the number of frozen pages and tuples in a given table.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">CREATE TEMP TABLE t AS SELECT 42;  VACUUM (freeze, verbose) t;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">INFO:  aggressively vacuuming \"demo.pg_temp_3.t\" INFO:  finished vacuuming \"demo.pg_temp_3.t\": index scans: 0 pages: 0 removed, 1 remain, 1 scanned (100.00% of total) tuples: 0 removed, 1 remain, 0 are dead but not yet removable removable cutoff: 945, which was 0 XIDs old when operation ended new relfrozenxid: 945, which is 1 XIDs ahead of previous value frozen: 1 pages from table (100.00% of total) had 1 tuples frozen index scan not needed: 0 pages from table (0.00% of total) had 0 dead item identifiers removed avg read rate: 91.912 MB\/s, avg write rate: 0.000 MB\/s buffer usage: 5 hits, 4 misses, 0 dirtied WAL usage: 1 records, 0 full page images, 188 bytes system usage: CPU: user: 0.00 s, system: 0.00 s, elapsed: 0.00 s VACUUM<\/code><\/pre>\n<p>  <\/p>\n<p>See the line that starts with <code>frozen:<\/code>.<\/p>\n<p>  <a name=\"commit_d7e39d72\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3816\/\">pg_stat_get_backend_idset returns the actual backend ID<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/d7e39d72\">d7e39d72<\/a><\/p>\n<p>  <\/p>\n<p>Consider a system that runs several concurrent sessions. The sessions work with temporary tables, which are stored in temporary schemas. The list of temporary schemas:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT n.nspname,        regexp_replace(n.nspname, '^pg_temp_', '')::int AS backend_id FROM   pg_namespace n                                                     WHERE  n.nspname ~ '^pg_temp_';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">  nspname  | backend_id -----------+------------  pg_temp_5 |        5  pg_temp_3 |        3  pg_temp_4 |        4 (3 rows)<\/code><\/pre>\n<p>  <\/p>\n<p>Let me explain this query. The <code>backend_id<\/code> column lists backend IDs. The system uses them to name temporary schemas. Backend IDs may be reused, and a new backend may get the same name as a recently terminated one. When a backend terminates, its temporary schemas don&#8217;t get deleted, but simply remain unused until a new backend with the same ID is created, at which point they are assigned to it.<\/p>\n<p>  <\/p>\n<p>But how do you draw a line between a temporary schema and a backend process? You may need to do that, for example, to terminate a hung up session when objects in a temporary schema prevent freezing, potentially leading to transaction ID wraparound.<\/p>\n<p>  <\/p>\n<p>The updated function <code>pg_stat_get_backend_idset<\/code> and related <a href=\"https:\/\/www.postgresql.org\/docs\/devel\/monitoring-stats.html#MONITORING-STATS-BACKEND-FUNCS-TABLE\">statistics functions<\/a> can help you out with that. Before, the function returned indexes from the array of open sessions as the backend IDs. However, an index value for a session may change after opening or terminating a session and rebuilding statistics. The new <code>pg_stat_get_backend_idset<\/code> now reliably returns actual backend session IDs, which can be linked to backend_id from the query above.<\/p>\n<p>  <\/p>\n<p>This allows us to retrieve a list of backend IDs and temporary schemas:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">WITH tmp AS (     SELECT n.nspname,                                                        regexp_replace(n.nspname, '^pg_temp_', '')::int AS backend_id     FROM   pg_namespace n     WHERE  n.nspname ~ '^pg_temp_' ) SELECT pg_stat_get_backend_pid(backend_id) AS pid,        nspname FROM   tmp        LEFT JOIN pg_stat_get_backend_idset() AS b(backend_id)            USING (backend_id);<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">  pid   |  nspname   --------+-----------  336661 | pg_temp_3  349872 | pg_temp_4         | pg_temp_5 (3 rows)<\/code><\/pre>\n<p>  <\/p>\n<p>Also note that the <code>pg_temp_5<\/code> schema is not currently used.<\/p>\n<p>  <a name=\"commit_1349d279\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3164\/\">Improved performance of ORDER BY \/ DISTINCT aggregates<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/1349d279\">1349d279<\/a><\/p>\n<p>  <\/p>\n<p>Aggregates with <code>ORDER BY<\/code> and <code>DISTINCT<\/code> can now use indexes to skip sorting tuples when the inputs are known to be pre-sorted.<\/p>\n<p>  <\/p>\n<p>Example:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT flight_no,        array_agg(status ORDER BY status),        array_agg(DISTINCT status) FROM flights GROUP BY flight_no;<\/code><\/pre>\n<p>  <\/p>\n<p>In both cases, <code>array_agg<\/code> has to sort the statuses for each flight. Now let&#8217;s create an index:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">CREATE INDEX ON flights (flight_no, status);<\/code><\/pre>\n<p>  <\/p>\n<p>The PostgreSQL 16 planner knows that the input from the index comes in pre-sorted. Therefore, the statuses from each group can be passed to the transition function right away, without the need to wait for them all to be fetched and sorted.<\/p>\n<p>  <\/p>\n<p>As a result, the operation executes on my laptop 1.5 times faster in PostgreSQL 16 than an identical operation in PostgreSQL 15.<\/p>\n<p>  <a name=\"commit_3592e0ff\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3270\/\">Faster bulk-loading into partitioned tables<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/3592e0ff\">3592e0ff<\/a><\/p>\n<p>  <\/p>\n<p>The patch increases bulk-loading and update speeds for partitioned tables. Operations that load records into the same partition will spend less time looking for specific partitions for each record. When 16 (the value is hard-coded) records in a row are loaded into the same partition, the partition&#8217;s name is cached, and if the following records must go to the same partition, they are processed faster.<\/p>\n<p>  <\/p>\n<p>The optimization works for RANGE and LIST partitions, but it&#8217;s of no use for HASH partitioned tables.<\/p>\n<p>  <\/p>\n<p>Developer testing shows a 15-20% decrease in execution time.<\/p>\n<p>  <a name=\"commit_37a6e5df\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3768\/\">Optimized lookups in snapshots<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/37a6e5df\">37a6e5df<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/b6ef1675\">b6ef1675<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/82739d4a\">82739d4a<\/a><\/p>\n<p>  <\/p>\n<p>The patch optimizes linear searches of arrays (first commit) for x86-64 (second commit) and ARM (third commit). The new algorithm using SIMD instructions was applied to <code>snapshot->xip<\/code> array search. At a large number (hundreds) of concurrent writers, it significantly increases the visibility check speed, which is an overall performance boost.<\/p>\n<p>  <a name=\"commit_36628396\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3610\/\">Bidirectional logical replication<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/36628396\">36628396<\/a><\/p>\n<p>  <\/p>\n<p>Bidirectional replication occurs when server A subscribes to table changes from server B, and server B subscribes to the same table changes from server A. As a result, all changes made on either server will be applied to both.<\/p>\n<p>  <\/p>\n<p>Previous PostgreSQL releases didn&#8217;t support this replication setup. Originally, a subscriber replicated all changes made to the publisher, be it SQL commands or replicated changes. When server A runs an SQL command, server B receives and runs the command as well. The same changes, then, are sent to the first server, resulting in an infinite replication loop. To avoid it, PostgreSQL returns an error when attempting to create a second subscription that will result in a loop.<\/p>\n<p>  <\/p>\n<p>To implement bidirectional replication, the developers needed a way to apply only changes made by SQL commands but not those from replication. And they found one!<\/p>\n<p>  <\/p>\n<p>PostgreSQL can distinguish between the two sources of changes, as all logical replication changes are written into WAL together with their origins. Now, when you create a subscription, you can set the publisher to not publish any changes with replication origins.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s set up bidirectional replication between <code>node_1<\/code> and <code>node_2<\/code>, both located on the same server, on ports 5401 and 5402.<\/p>\n<p>  <\/p>\n<p><code>wal_level<\/code> must allow logical replication on both nodes:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_1=# SHOW wal_level;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">wal_level  -----------  logical (1 row)<\/code><\/pre>\n<p>  <\/p>\n<p>Let&#8217;s create a table and a publication. The node column in the primary key may look redundant, but it&#8217;s helpful for avoiding replication conflicts.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_1=# CREATE TABLE test (              node text DEFAULT 'node_1',              id int,              PRIMARY KEY (node, id)          ); node_1=# CREATE PUBLICATION pub1 FOR TABLE test;<\/code><\/pre>\n<p>  <\/p>\n<p>Next, let&#8217;s create a table with an identical structure on the second node and a publication for it:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_2=# CREATE TABLE test (              node text DEFAULT 'node_2',              id int,              PRIMARY KEY (node, id)          ); node_2=# CREATE PUBLICATION pub2 FOR TABLE test;<\/code><\/pre>\n<p>  <\/p>\n<p>Subscribe <code>node_1<\/code> to the <code>node_2<\/code> publication, and vice versa.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_1=# CREATE SUBSCRIPTION sub1_pub2              CONNECTION 'port=5402 user=postgres dbname=postgres'              PUBLICATION pub2              WITH (copy_data = false, origin = none); node_2=# CREATE SUBSCRIPTION sub2_pub1              CONNECTION 'port=5401 user=postgres dbname=postgres'              PUBLICATION pub1              WITH (copy_data = false, origin = none);<\/code><\/pre>\n<p>  <\/p>\n<p>A few things to pay attention to:<\/p>\n<p>  <\/p>\n<ul>\n<li>\n<p><code>origin = none<\/code> \u2015 this is a new subscription parameter that handles replication cycles. It tells the server to only send out the changes that don&#8217;t originate from replication (with a nonempty origin), i.e. changes made by SQL commands. The second possible parameter value, <code>any<\/code>, is the default. It enables the behavior as seen in the previous versions.<\/p>\n<p>  <\/li>\n<li>\n<p><code>copy_data = false<\/code> \u2015 disable automatic synchronization. Also set when both tables are empty, which is the case in our example.<\/p>\n<p>  <\/li>\n<li>\n<p>The tables must not be modified until we complete the setup.<\/p>\n<p>  <\/li>\n<\/ul>\n<p>  <\/p>\n<p>Let&#8217;s add a row into each table:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_1=# INSERT INTO test (id) VALUES (1); node_2=# INSERT INTO test (id) VALUES (1);<\/code><\/pre>\n<p>  <\/p>\n<p>Verify the contents.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_1=# SELECT * FROM test ORDER BY 1,2;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> node   | id  --------+----  node_1 |  1  node_2 |  1 (2 rows)<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_2=# SELECT * FROM test ORDER BY 1,2;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> node   | id  --------+----  node_1 |  1  node_2 |  1 (2 rows)<\/code><\/pre>\n<p>  <\/p>\n<p>The replication works!<\/p>\n<p>  <\/p>\n<p>Let&#8217;s add another node at port 5403. The tables must remain unchanged during setup. <\/p>\n<p>  <\/p>\n<p>Create a table and a publication:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_3=# CREATE TABLE test (              node text DEFAULT 'node_3',              id int,              PRIMARY KEY (node, id)          ); node_3=# CREATE PUBLICATION pub3 FOR TABLE test;<\/code><\/pre>\n<p>  <\/p>\n<p>Subscribe the first two nodes to the publication.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_1=# CREATE SUBSCRIPTION sub1_pub3              CONNECTION 'port=5403 user=postgres dbname=postgres'              PUBLICATION pub3              WITH (copy_data = false, origin = none); node_2=# CREATE SUBSCRIPTION sub2_pub3              CONNECTION 'port=5403 user=postgres dbname=postgres'              PUBLICATION pub3              WITH (copy_data = false, origin = none);<\/code><\/pre>\n<p>  <\/p>\n<p>Subscribe the third node to the first two.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_3=# CREATE SUBSCRIPTION sub3_pub1              CONNECTION 'port=5401 user=postgres dbname=postgres'              PUBLICATION pub1              WITH (copy_data = true, origin = none);<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">WARNING:  subscription \"sub3_pub1\" requested copy_data with origin = NONE but might copy data that had a different origin DETAIL:  Subscribed publication \"pub1\" is subscribing to other publications. HINT:  Verify that initial data copied from the publisher tables did not come from other origins. NOTICE:  created replication slot \"sub3_pub1\" on publisher CREATE SUBSCRIPTION<\/code><\/pre>\n<p>  <\/p>\n<p>Note the subscription parameters: <code>copy_data = true<\/code>, <code>origin = none<\/code>. <code>CREATE SUBSCRIPTION<\/code> returns a warning that the third node is going to copy all data from the first one, including the changes with the replication origin. This is exactly what we want for the initial synchronization of the third node: to copy the two records.<\/p>\n<p>  <\/p>\n<p>There is no need to repeat the initial synchronization when subscribing to the second node.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_3=# CREATE SUBSCRIPTION sub3_pub2              CONNECTION 'port=5402 user=postgres dbname=postgres'              PUBLICATION pub2              WITH (copy_data = false, origin = none);<\/code><\/pre>\n<p>  <\/p>\n<p>The setup is complete. Let&#8217;s add a record to the third node:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_3=# INSERT INTO test (id) VALUES (1);<\/code><\/pre>\n<p>  <\/p>\n<p>Verify the contents on all nodes.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_1=# SELECT * FROM test ORDER BY 1,2;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> node   | id  --------+----  node_1 |  1  node_2 |  1  node_3 |  1 (3 rows)<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_2=# SELECT * FROM test ORDER BY 1,2;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">  node   | id  --------+----  node_1 |  1  node_2 |  1  node_3 |  1 (3 rows)<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">node_3=# SELECT * FROM test ORDER BY 1,2;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">  node   | id  --------+----  node_1 |  1  node_2 |  1  node_3 |  1 (3 rows)<\/code><\/pre>\n<p>  <\/p>\n<p>A multimaster out of the box!<\/p>\n<p>  <a name=\"commit_ce6b672e\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3745\/\">pg_auth_members: role membership granting management<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/ce6b672e\">ce6b672e<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/6566133c\">6566133c<\/a><\/p>\n<p>  <\/p>\n<p>To demonstrate this, let&#8217;s take three basic roles with <code>LOGIN<\/code> privileges:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">CREATE ROLE alice LOGIN; CREATE ROLE bob LOGIN; CREATE ROLE charlie LOGIN;<\/code><\/pre>\n<p>  <\/p>\n<p>First, let&#8217;s observe how the &#171;old&#187; PostgreSQL 15 behaves, then switch to 16.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s grant <code>alice<\/code> membership in <code>bob<\/code>. The <code>GRANTED BY<\/code> clause of the <code>GRANT<\/code> command allows to specify which role grants the membership. This information is recorded in the system catalog:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# GRANT bob TO alice GRANTED BY charlie;  15=# SELECT roleid::regrole,             member::regrole,             grantor::regrole,             * FROM pg_auth_members WHERE roleid::regrole::text !~ '^pg_';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> roleid | member | grantor | roleid | member | grantor | admin_option --------+--------+---------+--------+--------+---------+--------------  bob    | alice  | charlie |  24590 |  24589 |   24591 | f<\/code><\/pre>\n<p>  <\/p>\n<p>Seeing the <code>pg_auth_members<\/code> record alone, one may wonder how it was possible for <code>charlie<\/code> to grant <code>alice<\/code> membership in <code>bob<\/code>. To do that, <code>charlie<\/code> must be a member of <code>bob<\/code> and have the privilege that allows granting the membership to other roles (<code>WITH ADMIN OPTION<\/code>). And <code>charlie<\/code> isn&#8217;t even a member of <code>bob<\/code>.<\/p>\n<p>  <\/p>\n<p>We know that <code>GRANT<\/code> was run by superuser (<code>postgres<\/code>), which skips these sorts of privilege checks. But the system catalog record shows that <code>charlie<\/code> granted <code>alice<\/code> membership in <code>bob<\/code> while not having the appropriate privileges, which is not what happened.<\/p>\n<p>  <\/p>\n<p>But that is not all. What if we drop the role <code>charlie<\/code> altogether?<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# DROP ROLE charlie;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">DROP ROLE<\/code><\/pre>\n<p>  <\/p>\n<p>The role is no more. But what about <code>alice<\/code>&#8216;s membership in <code>bob<\/code>, which <code>charlie<\/code> has granted?<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# SELECT roleid::regrole,             member::regrole,             grantor::regrole,             * FROM pg_auth_members WHERE roleid::regrole::text !~ '^pg_';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> roleid | member | grantor | roleid | member | grantor | admin_option --------+--------+---------+--------+--------+---------+--------------  bob    | alice  | 24591   |  24590 |  24589 |   24591 | f<\/code><\/pre>\n<p>  <\/p>\n<p>The membership is still there, and so is grantor info! 24591 is the OID of the dropped <code>charlie<\/code> role. Now we have nonsense in <code>pg_auth_members<\/code>, and, what is worse, the OID may now be reused when creating another role. It is improbable, but not impossible to end up with a record that shows some random new role as the grantor for <code>alice<\/code>&#8216;s membership in <code>bob<\/code>.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s move on to PostgreSQL 16.<\/p>\n<p>  <\/p>\n<p>After creating the three roles, let&#8217;s try to grant <code>alice<\/code> membership in <code>bob<\/code> with <code>charlie<\/code> as the grantor:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# GRANT bob TO alice GRANTED BY charlie;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  grantor must have ADMIN OPTION on \"bob\"<\/code><\/pre>\n<p>  <\/p>\n<p>The command returns an error because <code>charlie<\/code> must be a member of <code>bob<\/code> and must have the privileges necessary to grant the membership. All as it should be.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# GRANT bob TO charlie WITH ADMIN OPTION;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">GRANT ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# GRANT bob TO alice GRANTED BY charlie;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">GRANT ROLE<\/code><\/pre>\n<p>  <\/p>\n<p>Now <code>alice<\/code> is successfully granted the membership.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s try to drop the role <code>charlie<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# DROP ROLE charlie;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  role \"charlie\" cannot be dropped because some objects depend on it DETAIL:  privileges for membership of role alice in role bob<\/code><\/pre>\n<p>  <\/p>\n<p>Or try to revoke <code>ADMIN OPTION<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# REVOKE ADMIN OPTION FOR bob FROM charlie ;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  dependent privileges exist HINT:  Use CASCADE to revoke them too.<\/code><\/pre>\n<p>  <\/p>\n<p>You can only revoke <code>ADMIN OPTION<\/code> if you add <code>CASCADE<\/code>, and you can&#8217;t drop the role. The role <code>charlie<\/code> now has a dependency because it has granted a role membership. The dependency uses the new OID column in <code>pg_auth_members<\/code>, and the dependency itself is recorded in <code>pg_shdepend<\/code>. Let&#8217;s have a look at it:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# SELECT oid,             roleid::regrole,             member::regrole,             grantor::regrole FROM pg_auth_members WHERE oid = 16572;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">  oid  | roleid | member | grantor -------+--------+--------+---------  16572 | bob    | alice  | charlie<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# SELECT d.classid::regclass,             (SELECT m FROM pg_auth_members m WHERE m.oid = d.objid) AS objid,             d.refclassid::regclass,             d.refobjid::regrole FROM pg_shdepend d\\gx<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">-[ RECORD 1 ]----------------------------- classid    | pg_auth_members objid      | (16572,16569,16568,16570,f,t) refclassid | pg_authid refobjid   | charlie<\/code><\/pre>\n<p>  <\/p>\n<p>Granting membership on behalf of third-party roles may not be the most demanded feature, but nevertheless PostgreSQL 16 will make the behavior more logical and predictable.<\/p>\n<p>  <a name=\"commit_e3ce2de0\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3744\/\">pg_auth_members: role membership and privilege inheritance<\/a><\/strong><\/p>\n<p>  <\/p>\n<p>commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/e3ce2de0\">e3ce2de0<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/48a257d4\">48a257d4<\/a><\/p>\n<p>  <\/p>\n<p>While the previous patch dealt with membership granters, this one concerns grantees.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s look at PostgreSQL 15 first:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-postgres=# CREATE ROLE alice LOGIN; 15-postgres=# CREATE ROLE bob LOGIN;  15-postgres=# GRANT bob TO alice;<\/code><\/pre>\n<p>  <\/p>\n<p><code>alice<\/code> is granted membership in <code>bob<\/code> so that it can use <code>bob<\/code>&#8216;s privileges.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-postgres=# CREATE TABLE t AS SELECT 42 AS col; 15-postgres=# GRANT SELECT ON t TO bob;<\/code><\/pre>\n<p>  <\/p>\n<p>Both <em>bob<\/em> and <em>alice<\/em> can see the table contents:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-alice=> SELECT * FROM t;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> col -----   42 (1 row)<\/code><\/pre>\n<p>  <\/p>\n<p>This works because the role <code>alice<\/code> has the <code>INHERIT<\/code> attribute (all roles have it by default), which makes <code>alice<\/code> inherit all <code>bob<\/code>&#8216;s privileges automatically.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-alice=> SELECT rolname, rolinherit FROM pg_roles WHERE rolname = 'alice';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> rolname | rolinherit ---------+------------  alice   | t<\/code><\/pre>\n<p>  <\/p>\n<p>If this attribute is disabled, <code>alice<\/code> will have to switch roles in order to access the table:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-postgres=# ALTER ROLE alice NOINHERIT;  15-alice=> SELECT * FROM t;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  permission denied for table t<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-alice=> SET ROLE bob; 15-alice=> SELECT * FROM t;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> col -----   42 (1 row)<\/code><\/pre>\n<p>  <\/p>\n<p>Let&#8217;s say that this is the desired behavior.<\/p>\n<p>  <\/p>\n<p>Now, consider that we want to grant <code>alice<\/code> some system monitoring capabilities. There&#8217;s a predefined role <code>pg_read_all_settings<\/code>, which has the privileges required to view system configuration parameters. We grant <code>alice<\/code> membership in it.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-postgres=# GRANT pg_read_all_settings TO alice;  15-alice=> SHOW data_directory;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  must be superuser or have privileges of pg_read_all_settings to examine \"data_directory\"<\/code><\/pre>\n<p>  <\/p>\n<p>The lack of the <code>INHERIT<\/code> attribute makes <code>alice<\/code> unable to view the parameters without explicitly switching roles.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-alice=> SET ROLE pg_read_all_settings; 15-alice=> SHOW data_directory;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">   data_directory     ---------------------  \/home\/pal\/pg15\/data<\/code><\/pre>\n<p>  <\/p>\n<p>But switching into the role <code>pg_read_all_settings<\/code> is a major hassle. We want <code>alice<\/code> to have all <code>pg_read_all_settings<\/code> privileges by default, but we also want to have them switch to <code>bob<\/code> explicitly when <code>bob<\/code>&#8216;s privileges are required.<\/p>\n<p>  <\/p>\n<p>And the new patch allows us to achieve that!<\/p>\n<p>  <\/p>\n<p>Let&#8217;s reproduce the situation in PostgreSQL 16:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# CREATE ROLE alice LOGIN; 16-postgres=# CREATE ROLE bob LOGIN;  16-postgres=# CREATE TABLE t AS SELECT 42 AS col; 16-postgres=# GRANT SELECT ON t TO bob;<\/code><\/pre>\n<p>  <\/p>\n<p>The <code>GRANT<\/code> syntax has been extended. Now, when granting a role membership, you can specify whether or not the grantee inherits the role&#8217;s privileges:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# GRANT bob TO alice WITH INHERIT FALSE; 16-postgres=# GRANT pg_read_all_settings TO alice WITH INHERIT TRUE;<\/code><\/pre>\n<p>  <\/p>\n<p>Here, <code>alice<\/code> may use <code>pg_read_all_settings<\/code> privileges right away but needs to switch to <code>bob<\/code> to run the <code>get_ultimate_answer<\/code> function:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-alice=> SHOW data_directory;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">   data_directory     ---------------------  \/home\/pal\/pg16\/data<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-alice=> SELECT * FROM t;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  permission denied for table t<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-alice=> SET ROLE bob; 16-alice=> SELECT * FROM t;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> col -----   42 (1 row)<\/code><\/pre>\n<p>  <\/p>\n<p><code>GRANT<\/code> data is stored in <code>pg_auth_members<\/code>. Now it also includes the <code>inherit_option<\/code> value:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# SELECT roleid::regrole, member::regrole, grantor::regrole,                      admin_option, inherit_option FROM pg_auth_members WHERE member = 'alice'::regrole;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">        roleid        | member | grantor  | admin_option | inherit_option ----------------------+--------+----------+--------------+----------------  bob                  | alice  | postgres | f            | f  pg_read_all_settings | alice  | postgres | f            | t<\/code><\/pre>\n<p>  <\/p>\n<p>But what about the <code>INHERIT<\/code> attribute? Now it is only used in <code>GRANT<\/code> whenever the <code>WITH INHERIT<\/code> clause is not present. In this example, <code>alice<\/code> has the <code>INHERIT<\/code> attribute set, which means that the following command will record it in <code>pg_auth_members<\/code>:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# GRANT pg_read_all_data TO alice;<\/code><\/pre>\n<p>  <\/p>\n<p>The default value will change if the attribute is revoked.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# ALTER ROLE alice NOINHERIT; 16-postgres=# GRANT pg_write_all_data TO alice;  16-postgres=# SELECT roleid::regrole, member::regrole, grantor::regrole,                      admin_option, inherit_option FROM pg_auth_members WHERE member = 'alice'::regrole;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">        roleid        | member | grantor  | admin_option | inherit_option ----------------------+--------+----------+--------------+----------------  bob                  | alice  | postgres | f            | f  pg_read_all_settings | alice  | postgres | f            | t  pg_read_all_data     | alice  | postgres | f            | t  pg_write_all_data    | alice  | postgres | f            | f<\/code><\/pre>\n<p>  <\/p>\n<p>Important takeaway: the privilege inheritance behavior in PostgreSQL 16 changes. The <code>INHERIT<\/code> no longer affects privilege inheritance and only affects subsequent <code>GRANT<\/code> commands without <code>WITH INHERIT<\/code>.<\/p>\n<p>  <\/p>\n<p>The other commit is for the <code>ALTER DEFAULT PRIVILEGES<\/code> command. Now you need the <code>INHERIT<\/code> attribute to be able to modify other roles&#8217; privileges. In our example, <code>alice<\/code> can&#8217;t use <code>ALTER DEFAULT PRIVILEGES<\/code> to manage privileges for objects within <code>bob<\/code>&#8216;s schema.<\/p>\n<p>  <a name=\"commit_8b60db77\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/39\/3825\/\">pg_receivewal and pg_recvlogical now can handle SIGTERM<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/8b60db77\">8b60db77<\/a><\/p>\n<p>  <\/p>\n<p><code>pg_receivewal<\/code> and <code>pg_recvlogical<\/code> can run as daemons. When they do, systemd can only send <code>SIGTERM<\/code> to stop them, but both tools expect <code>SIGINT<\/code> to stop cleanly and just terminate on <code>SIGTERM<\/code>. This is not an issue in itself, but when compression for <code>pg_receivewal<\/code> is enabled, it only dumps data on disk on a clean exit.<\/p>\n<p>  <\/p>\n<p>So the developers ended up teaching the tools to recognize <code>SIGTERM<\/code> properly.<\/p>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p>This is all for now. Looking forward to the results of the <a href=\"https:\/\/commitfest.postgresql.org\/40\/\">November CommitFest<\/a>!<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/699610\/\"> https:\/\/habr.com\/ru\/articles\/699610\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/z7\/fj\/pt\/z7fjptxnt0jbcvmfpejym20gfu0.png\" data-src=\"https:\/\/habrastorage.org\/webt\/z7\/fj\/pt\/z7fjptxnt0jbcvmfpejym20gfu0.png\"\/><\/p>\n<p>  <\/p>\n<p>It&#8217;s official! PostgreSQL 15 is out, and the community is abuzz discussing all the new features of the fresh release.<\/p>\n<p>  <\/p>\n<p>Meanwhile, the October CommitFest for PostgreSQL 16 had come and gone, with its own notable additions to the code.<\/p>\n<p>  <\/p>\n<p>If you missed the July CommitFest, <a href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/696816\/\">our previous article<\/a> will get you up to speed in no time.<\/p>\n<p>  <\/p>\n<p>Here are the patches I want to talk about:<\/p>\n<p>  <\/p>\n<p><a href=\"#commit_0823d061\">SYSTEM_USER function<\/a><br \/>  <a href=\"#commit_d977ffd9\">Frozen pages\/tuples information in autovacuum&#8217;s server log<\/a><br \/>  <a href=\"#commit_d7e39d72\">pg_stat_get_backend_idset returns the actual backend ID<\/a><br \/>  <a href=\"#commit_1349d279\">Improved performance of ORDER BY \/ DISTINCT aggregates<\/a><br \/>  <a href=\"#commit_3592e0ff\">Faster bulk-loading into partitioned tables<\/a><br \/>  <a href=\"#commit_37a6e5df\">Optimized lookups in snapshots<\/a><br \/>  <a href=\"#commit_36628396\">Bidirectional logical replication<\/a><br \/>  <a href=\"#commit_ce6b672e\">pg_auth_members: pg_auth_members: role membership granting management<\/a><br \/>  <a href=\"#commit_e3ce2de0\">pg_auth_members: role membership and privilege inheritance<\/a><br \/>  <a href=\"#commit_8b60db77\">pg_receivewal and pg_recvlogical can now handle SIGTERM<\/a><\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-421105","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/421105","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=421105"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/421105\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=421105"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=421105"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=421105"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}