{"id":385875,"date":"2024-06-29T06:22:58","date_gmt":"2024-06-29T06:22:58","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=385875"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=385875","title":{"rendered":"<span>PostgreSQL 16: Part 4 or CommitFest 2023-01<\/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\/ek\/of\/sk\/ekofsk_9ng-x5vge4bv5c3xakok.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ek\/of\/sk\/ekofsk_9ng-x5vge4bv5c3xakok.png\"\/><\/p>\n<p>  <\/p>\n<p>We continue to follow the news of the PostgreSQL 16 release, and today, the results of the fourth CommitFest are on the table. Let&#8217;s have a look.<\/p>\n<p>  <\/p>\n<p>If you missed the previous CommitFests, check out our reviews for <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5969676\">2022-07<\/a>, <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5969859\">2022-09<\/a> and <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5969929\">2022-11<\/a>.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<p>Here are the patches I want to talk about this time:<\/p>\n<p>  <\/p>\n<p><a href=\"#commit_38d81760\">New function: random_normal<\/a><br \/>  <a href=\"#commit_6fcda9ab\">Input formats for integer literals<\/a><br \/>  <a href=\"#commit_81266442\">Goodbye, postmaster<\/a><br \/>  <a href=\"#commit_16fd03e9\">Parallel execution for string_agg and array_agg<\/a><br \/>  <a href=\"#commit_4a29eabd\">New parameter: enable_presorted_aggregate<\/a><br \/>  <a href=\"#commit_ed1a88dd\">Planner support function for working with window functions<\/a><br \/>  <a href=\"#commit_8d83a5d0\">Optimized grouping of repeating columns in GROUP BY and DISTINCT<\/a><br \/>  <a href=\"#commit_a46a7011\">VACUUM parameters: SKIP_DATABASE_STATS and ONLY_DATABASE_STATS<\/a><br \/>  <a href=\"#commit_5f53b42c\">pg_dump: lock tables in batches<\/a><br \/>  <a href=\"#commit_d747dc85\">PL\/pgSQL: cursor variable initialization<\/a><br \/>  <a href=\"#commit_cf5eb37c\">Roles with the CREATEROLE attribute<\/a><br \/>  <a href=\"#commit_096dd80f\">Setting parameter values at the database and user level<\/a><br \/>  <a href=\"#commit_6e2775e4\">New parameter: reserved_connections<\/a><br \/>  <a href=\"#commit_8ad51b5f\">postgres_fdw: analyzing foreign tables with TABLESAMPLE<\/a><br \/>  <a href=\"#commit_594f8d37\">postgres_fdw: batch insert records during partition key updates<\/a><br \/>  <a href=\"#commit_efb6f4a4\">pg_ident.conf: new ways to identify users in PostgreSQL<\/a><br \/>  <a href=\"#commit_3db72ebc\">Query jumbling for DDL and utility statements<\/a><br \/>  <a href=\"#commit_1fd3dd20\">New function: bt_multi_page_stats<\/a><br \/>  <a href=\"#commit_cca18634\">New function: pg_split_walfile_name<\/a><br \/>  <a href=\"#commit_c31cf1c0\">pg_walinspect, pg_waldump: collecting page images from WAL<\/a><\/p>\n<p>  <a name=\"commit_38d81760\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4056\/\">New function: random_normal<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/38d81760\">38d81760<\/a><\/p>\n<p>  <\/p>\n<p>The function <em>random<\/em> returns a number between 0 and 1, and any number within the scope has an equal chance to be selected, i.e., the numbers are distributed uniformly. The patch introduces a new function <em>random_normal<\/em> as a way to get random numbers from a <a href=\"https:\/\/en.wikipedia.org\/wiki\/Normal_distribution\">normal distribution<\/a>. The function has two parameters: the mean and the standard deviation.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s generate 1,000,000 numbers and see if the <a href=\"https:\/\/en.wikipedia.org\/wiki\/68%E2%80%9395%E2%80%9399.7_rule\">68-95-99.7 rule<\/a> applies. The rule states that 68% of normally distributed random numbers are within no more than one standard deviation (the second parameter) from the mean (the first parameter), 95% are within two standard deviations, and 99.7% are within three.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">WITH samples AS (     SELECT random_normal(0.0, 1.0) AS x FROM generate_series(1,1000000) ) SELECT count(x) FILTER (WHERE x BETWEEN -1 AND 1)\/1000000.0 AS stddev_1,        count(x) FILTER (WHERE x BETWEEN -2 AND 2)\/1000000.0 AS stddev_2,        count(x) FILTER (WHERE x BETWEEN -3 AND 3)\/1000000.0 AS stddev_3 FROM samples;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">        stddev_1        |        stddev_2        |        stddev_3         ------------------------+------------------------+------------------------  0.68391100000000000000 | 0.95473800000000000000 | 0.99724900000000000000<\/code><\/pre>\n<p>  <\/p>\n<p>We can send the output of psql to gnuplot to display the resulting distribution visually:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">WITH samples AS (     SELECT random_normal(0.0, 1.0) AS x FROM generate_series(1,1000000) ) SELECT round(x::numeric,1) point, count(*) AS density FROM samples GROUP BY point ORDER BY point \\g (format=unaligned tuples_only=on fieldsep='\\t') | gnuplot -e \"set term png; set output 'data.png'; plot '&lt;cat'\"<\/code><\/pre>\n<p>  <\/p>\n<p>I separated the values into groups in increments of 0.1 to get the distribution density. As a result, in the data.png file we get the famous bell curve:<\/p>\n<p>  <\/p>\n<p><img decoding=\"async\" src=\"https:\/\/habrastorage.org\/r\/w1560\/webt\/zz\/jq\/gi\/zzjqgicd70nbxspwioqyzsrnwqc.png\" data-src=\"https:\/\/habrastorage.org\/webt\/zz\/jq\/gi\/zzjqgicd70nbxspwioqyzsrnwqc.png\"\/><\/p>\n<p>  <\/p>\n<p>Note that the <a href=\"https:\/\/www.postgresql.org\/docs\/devel\/tablefunc.html\">tablefunc extension<\/a> already has a function <em>normal_rand<\/em> for obtaining random numbers from a normal distribution. Now, however, the functionality is available out-of-the-box, without the need for any extensions.<\/p>\n<p>  <\/p>\n<p>See also:<br \/>  <a href=\"https:\/\/www.depesz.com\/2023\/01\/11\/waiting-for-postgresql-16-invent-random_normal-to-provide-normally-distributed-random-numbers\/\">Waiting for PostgreSQL 16 \u2013 Invent random_normal() to provide normally-distributed random numbers<\/a> (Hubert &#8216;depesz&#8217; Lubaczewski)<\/p>\n<p>  <a name=\"commit_6fcda9ab\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/3292\/\">Input formats for integer literals<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/6fcda9ab\">6fcda9ab<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/6dfacbf7\">6dfacbf7<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/faff8f8e\">faff8f8e<\/a><\/p>\n<p>  <\/p>\n<p>The latest changes to the SQL standard allow declaring integer literals not only in decimal, but also in hexadecimal, octal and binary formats.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT 0x2A hex_int, 0o52 oct_int, 0b101010 bin_int;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> hex_int | oct_int | bin_int ---------+---------+---------       42 |      42 |      42<\/code><\/pre>\n<p>  <\/p>\n<p>If the resulting number doesn&#8217;t fit into bigint, it is transformed into numeric type (the second commit). In addition, for large integer values, an underscore can be used to visually separate groups of digits (the third commit):<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT count(*) FROM generate_series(1, 1_000_000);<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">  count   ---------  1000000<\/code><\/pre>\n<p>  <\/p>\n<p>See also:<br \/>  <a href=\"https:\/\/www.depesz.com\/2022\/12\/14\/waiting-for-postgresql-16-non-decimal-integer-literals\/\">Waiting for PostgreSQL 16 \u2013 Non-decimal integer literals<\/a> (Hubert &#8216;depesz&#8217; Lubaczewski)<br \/>  <a href=\"https:\/\/www.depesz.com\/2023\/02\/06\/waiting-for-postgresql-16-allow-underscores-in-integer-and-numeric-constants\/\">Waiting for PostgreSQL 16 \u2013 Allow underscores in integer and numeric constants<\/a> (Hubert &#8216;depesz&#8217; Lubaczewski)<br \/>  <a href=\"https:\/\/www.cybertec-postgresql.com\/en\/hex-oct-bin-integers-in-postgresql-16\/\">hex, oct, bin integers in PostgreSQL 16<\/a> (Pavlo Golub)<\/p>\n<p>  <a name=\"commit_81266442\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4030\/\">Goodbye, postmaster<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/81266442\">81266442<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/37e26733\">37e26733<\/a><\/p>\n<p>  <\/p>\n<p><em>On startup, the server starts a process traditionally referred to as postmaster&#8230;<\/em><\/p>\n<p>  <\/p>\n<p>Many a course on PostgreSQL architecture begin with a variation of this sentence. But in today&#8217;s Postgres, postmaster is just a 15-years-old moniker of the postgres process. You can still find the name in the documentation, and the bin catalog contains a postmaster file with a symlink to postgres.<\/p>\n<p>  <\/p>\n<p>The PostgreSQL 16 release finally says goodbye to postmaster, wiping every mention of it.<\/p>\n<p>  <a name=\"commit_16fd03e9\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/3794\/\">Parallel execution for string_agg and array_agg<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/16fd03e9\">16fd03e9<\/a><\/p>\n<p>  <\/p>\n<p>Prior to PostgreSQL 16, all queries containing <em>string_agg<\/em> and <em>array_agg<\/em> always executed sequentially:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# EXPLAIN (costs off) SELECT fare_conditions, array_agg(flight_id), string_agg(ticket_no, ',') FROM ticket_flights GROUP BY fare_conditions;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">            QUERY PLAN             ----------------------------------  HashAggregate    Group Key: fare_conditions    ->  Seq Scan on ticket_flights<\/code><\/pre>\n<p>  <\/p>\n<p>Now, parallel execution is also possible:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# EXPLAIN (costs off) SELECT fare_conditions, array_agg(flight_id), string_agg(ticket_no, ',') FROM ticket_flights GROUP BY fare_conditions;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">                         QUERY PLAN                           -------------------------------------------------------------  Finalize GroupAggregate    Group Key: fare_conditions    ->  Gather Merge          Workers Planned: 2          ->  Sort                Sort Key: fare_conditions                ->  Partial HashAggregate                      Group Key: fare_conditions                      ->  Parallel Seq Scan on ticket_flights<\/code><\/pre>\n<p>  <a name=\"commit_4a29eabd\"><\/a>  <\/p>\n<p><strong>New parameter: enable_presorted_aggregate<\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/4a29eabd\">4a29eabd<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/3226f472\">3226f472<\/a><\/p>\n<p>  <\/p>\n<p>The first commit reduces the incremental sort cost estimate. This makes the planner choose it more often, specifically for <a href=\"https:\/\/commitfest.postgresql.org\/39\/3164\/\">optimizing ORDER BY and DISTINCT aggregates<\/a>.<\/p>\n<p>  <\/p>\n<p>However, <a href=\"https:\/\/www.postgresql.org\/message-id\/CAApHDvr1Sm+g9hbv4REOVuvQKeDWXcKUAhmbK5K+dfun0s9CvA@mail.gmail.com\">in some cases<\/a> of non-uniform distribution of data in groups, ORDER BY and DISTINCT aggregates will perform slower with incremental sort. Disabling the new parameter <em>enable_presorted_aggregate<\/em> (the second commit) will use the old version planning behavior.<\/p>\n<p>  <a name=\"commit_ed1a88dd\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/3994\/\">Planner support functions for working with window functions<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/ed1a88dd\">ed1a88dd<\/a><\/p>\n<p>  <\/p>\n<p>Unless a window function frame is defined explicitly, <em>RANGE<\/em> UNBOUNDED PRECEDING is used by default.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# EXPLAIN (costs off, analyze, timing off) SELECT row_number() OVER (ORDER BY ticket_no) FROM tickets;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">                                    QUERY PLAN                                      -----------------------------------------------------------------------------------  WindowAgg (actual rows=2949857 loops=1)    ->  Index Only Scan using tickets_pkey on tickets (actual rows=2949857 loops=1)          Heap Fetches: 0  Planning Time: 0.098 ms  Execution Time: 682.466 ms<\/code><\/pre>\n<p>  <\/p>\n<p>For this query, another frame can be specified: <em>ROWS<\/em> UNBOUNDED PRECEDING. This will not change the result, but will boost performance:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# EXPLAIN (costs off, analyze, timing off) SELECT row_number() OVER (ORDER BY ticket_no ROWS UNBOUNDED PRECEDING) FROM tickets;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">                                    QUERY PLAN                                      -----------------------------------------------------------------------------------  WindowAgg (actual rows=2949857 loops=1)    ->  Index Only Scan using tickets_pkey on tickets (actual rows=2949857 loops=1)          Heap Fetches: 0  Planning Time: 0.100 ms  Execution Time: 483.560 ms<\/code><\/pre>\n<p>  <\/p>\n<p>It&#8217;s faster because with RANGE you have to check all peer rows while with ROWS you don&#8217;t. Therefore, the default behavior can end up being slower.<\/p>\n<p>  <\/p>\n<p>In the 16th release, <a href=\"https:\/\/www.postgresql.org\/docs\/devel\/xfunc-optimization.html\">planner support functions<\/a> were added for window functions <em>row_number<\/em>, <em>rank<\/em>, <em>dense_rank<\/em>, <em>percent_rank<\/em>, <em>cume_dist<\/em> and <em>ntile<\/em>. With their help, the planner will always use the most optimal way to define a window frame.<\/p>\n<p>  <a name=\"commit_8d83a5d0\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4083\/\">Optimized grouping of repeating columns in GROUP BY and DISTINCT<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/8d83a5d0\">8d83a5d0<\/a><\/p>\n<p>  <\/p>\n<p>If multiple columns containing the same values are put into a group, it is sufficient to group by any one of them.<\/p>\n<p>  <\/p>\n<p>The optimization is illustrated well by the following example:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SET max_parallel_workers_per_gather = 0; SET jit = off;  15=# EXPLAIN (costs off, analyze) SELECT b.seat_no, s.seat_no FROM boarding_passes b      JOIN seats s ON b.seat_no = s.seat_no GROUP BY b.seat_no, s.seat_no;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">                                               QUERY PLAN                                                 ---------------------------------------------------------------------------------------------------------  Group (actual time=3537.559..10758.307 rows=461 loops=1)    Group Key: b.seat_no, s.seat_no ...<\/code><\/pre>\n<p>  <\/p>\n<p>The rest of the plan is omitted as irrelevant.<\/p>\n<p>  <\/p>\n<p>Here are the first two lines of the plan of the same query after the patch:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">                                                QUERY PLAN                                                  -----------------------------------------------------------------------------------------------------------  HashAggregate (actual time=8201.412..8201.440 rows=461 loops=1)    Group Key: b.seat_no ...<\/code><\/pre>\n<p>  <\/p>\n<p>Note the Group Key line. In PostgreSQL 16, the planner knows that grouping by one column is enough and selects one plan based on that. In the older version, the grouping is performed on both columns, with a worse plan overall (see the execution time).<\/p>\n<p>  <\/p>\n<p>The same optimization is done for DISTINCT.<\/p>\n<p>  <\/p>\n<p>The idea of grouping by two columns containing identical values may seem strange at first, but I assume that automatic query generation systems (like various ORMs) may do that and may benefit from this bit of optimization.<\/p>\n<p>  <a name=\"commit_a46a7011\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4087\/\">VACUUM parameters: SKIP_DATABASE_STATS and ONLY_DATABASE_STATS<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/a46a7011\">a46a7011<\/a><\/p>\n<p>  <\/p>\n<p>After each vacuuming, the vacuuming process updates database stats: the frozen transactions counter (<em>pg_database.datfrozenxid<\/em>) and the multitransaction counter (<em>pg_database.datminmxid<\/em>). These operations require a full scan of <em>pg_class<\/em> in search of minimal <em>relfrozenxid<\/em> and <em>relminmxid<\/em> values. If either value is found that is greater than the current database value, the stats are updated. The more there are tables in the database, the longer this step takes.<\/p>\n<p>  <\/p>\n<p>When multiple VACUUM commands for multiple tables are executed in sequence, each one will update the counters, while it would be sufficient to only update them after the last vacuum in the sequence.<\/p>\n<p>  <\/p>\n<p>The <em>vacuumdb<\/em> utility (in PostgreSQL 12 through 15) runs VACUUM for each table in the database. With a high enough table count (thousands, tens of thousands) it becomes extremely inefficient due to the issues mentioned above. Even parallelization (<em>-j<\/em> key) doesn&#8217;t save the day, because only one process can update the database counters at a given time.<\/p>\n<p>  <\/p>\n<p>PostgreSQL 16 supplements the <a href=\"https:\/\/www.postgresql.org\/docs\/devel\/sql-vacuum.html\">VACUUM command<\/a> with two logical parameters:<\/p>\n<p>  <\/p>\n<ul>\n<li>SKIP_DATABASE_STATS \u2013 vacuum as usual but do not update the counters,<\/li>\n<li>ONLY_DATABASE_STATS \u2015 do not vacuum at all, just update the counters.<\/li>\n<\/ul>\n<p>  <\/p>\n<p>Now <em>vacuumdb<\/em> vacuums tables first without updating the stats, and when it&#8217;s done, it runs vacuum once to update the stats:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">VACUUM(ONLY_DATABASE_STATS)<\/code><\/pre>\n<p>  <a name=\"commit_5f53b42c\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4052\/\">pg_dump: lock tables in batches<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/5f53b42c\">5f53b42c<\/a><\/p>\n<p>  <\/p>\n<p>When called, <em>pg_dump<\/em> first creates a list of tables to dump and locks them one by one with LOCK TABLE in ACCESS SHARE mode. With thousands or tens of thousands of tables, and with <em>pg_dump<\/em> running on a remote machine, the total network latency will add up.<\/p>\n<p>  <\/p>\n<p>With the new patch, <em>pg_dump<\/em> locks all tables at once by calling LOCK TABLE with a list of tables. (It will split into multiple commands to fit the limit of 100,000 characters per command, if necessary.)<\/p>\n<p>  <a name=\"commit_d747dc85\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4002\/\">PL\/pgSQL: cursor variable initialization<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/d747dc85\">d747dc85<\/a><\/p>\n<p>  <\/p>\n<p>Bound cursors are initialized by string-type variables that match the cursor names. Unbound cursors (refcursors), on the other hand, remain undefined until opened. When a refcursor is opened, it gets a unique name generated for it.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# DO $$ DECLARE                                    c CURSOR FOR SELECT 1; -- bound cursor variable     rc refcursor; -- unbound cursor variable BEGIN     RAISE NOTICE 'c: %, rc: %', c, rc;     OPEN c;     OPEN rc FOR SELECT 2;     RAISE NOTICE 'c: %, rc: %', c, rc; END;$$;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">NOTICE:  c: c, rc: &lt;NULL> NOTICE:  c: c, rc: &lt;unnamed portal 7> DO<\/code><\/pre>\n<p>  <\/p>\n<p>If multiple nested code blocks or function calls use bound cursors with the same name, the initialization of bound cursors may lead to a name collision.<\/p>\n<p>  <\/p>\n<p>Consider this example. Here, two procedures use the same cursor name.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">CREATE PROCEDURE proc_1() AS $$DECLARE     c CURSOR FOR SELECT 1; BEGIN     FOR r IN c LOOP         RAISE NOTICE 'r: %, c: %', r, c;     END LOOP; END;$$ LANGUAGE plpgsql;  CREATE PROCEDURE proc_2() AS $$DECLARE     c CURSOR FOR SELECT 2; BEGIN     FOR r IN c LOOP         RAISE NOTICE 'r: %, c: %', r, c;         CALL proc_1();     END LOOP; END;$$ LANGUAGE plpgsql;<\/code><\/pre>\n<p>  <\/p>\n<p>Despite the fact that the cursor in both procedures is declared locally, the second procedure call will return an error:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15=# CALL proc_2();<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">NOTICE:  r: (2), c: c ERROR:  cursor \"c\" already in use CONTEXT:  PL\/pgSQL function proc_1() line 4 at FOR over cursor SQL statement \"CALL proc_1()\" PL\/pgSQL function proc_2() line 6 at CALL<\/code><\/pre>\n<p>  <\/p>\n<p>The new patch makes bound cursors behave like refcursors in this sense: they will not initialize until opened and they will have unique names generated for them (unless explicitly named in advance).<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16=# CALL proc_2();<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">NOTICE:  r: (2), c: &lt;unnamed portal 1> NOTICE:  r: (1), c: &lt;unnamed portal 2> CALL<\/code><\/pre>\n<p>  <a name=\"commit_cf5eb37c\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4050\/\">Roles with the CREATEROLE attribute<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/cf5eb37c\">cf5eb37c<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/f1358ca5\">f1358ca5<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/e5b8a4c0\">e5b8a4c0<\/a><\/p>\n<p>  <\/p>\n<p>One common but not very secure way of managing database roles without superuser privileges is using a regular role with the CREATEROLE attribute. This role can perform all necessary tasks but cannot grant the SUPERUSER attribute unless it has it itself.<\/p>\n<p>  <\/p>\n<p>Or, rather, it shouldn&#8217;t be able to, but it can \u2013 with a few extra steps:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-postgres=# CREATE ROLE admin LOGIN CREATEROLE;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">CREATE ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-postgres=# \\c - admin<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">You are now connected to database \"postgres\" as user \"admin\".<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-admin=> CREATE ROLE bob LOGIN;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">CREATE ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">15-admin=> GRANT pg_execute_server_program TO bob;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">GRANT ROLE<\/code><\/pre>\n<p>  <\/p>\n<p>Done. Now bob can run OS programs as the server owner.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s see how the new release handles that.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# CREATE ROLE admin LOGIN CREATEROLE;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">CREATE ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# \\c - admin<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">You are now connected to database \"postgres\" as user \"admin\".<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> CREATE ROLE bob LOGIN;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">CREATE ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> \\du<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">                                   List of roles  Role name |                         Attributes                         | Member of -----------+------------------------------------------------------------+-----------  admin     | Create role                                                | {bob}  bob       |                                                            | {}  postgres  | Superuser, Create role, Create DB, Replication, Bypass RLS | {}<\/code><\/pre>\n<p>  <\/p>\n<p>Note that the admin role is automatically included into the bob role immediately after being created, and that it can manage the role (WITH ADMIN OPTION). This is done so that only the role that created the bob role can manage it. Suppose we have a second administrator with a dedicated role and with the CREATEROLE attribute.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# CREATE ROLE admin2 LOGIN CREATEROLE;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">CREATE ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# \\c - admin2<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">You are now connected to database \"postgres\" as user \"admin2\".<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin2=> ALTER ROLE bob CONNECTION LIMIT 1;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  permission denied<\/code><\/pre>\n<p>  <\/p>\n<p>The CREATEROLE attribute alone no longer grants the ability to manage other roles.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s go back to the first admin and include bob into pg_execute_server_program.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> GRANT pg_execute_server_program TO bob;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  must have admin option on role \"pg_execute_server_program\"<\/code><\/pre>\n<p>  <\/p>\n<p>To execute the GRANT command, the admin role itself must be included in <em>pg_execute_server_program<\/em> and also have the admin option.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# GRANT pg_execute_server_program TO admin 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-admin=> GRANT pg_execute_server_program TO bob;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">GRANT ROLE<\/code><\/pre>\n<p>  <\/p>\n<p>The governing principle here is that you should be able to grant only what you are granted already. These are the changes presented in the first commit.<\/p>\n<p>  <\/p>\n<p>Now, let&#8217;s see if admin can switch to bob.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> SET ROLE bob;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  permission denied to set role \"bob\"<\/code><\/pre>\n<p>  <\/p>\n<p>No, they cannot. SET ROLE requires the <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/708082\/#commit_97da4824\">SET option<\/a> to be enabled when the role is initially granted. Let&#8217;s make sure it&#8217;s not there:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> SELECT roleid::regrole, member::regrole, grantor::regrole,        admin_option, inherit_option, set_option FROM pg_auth_members WHERE roleid = 'bob'::regrole\\gx<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">-[ RECORD 1 ]--+--------- roleid         | bob member         | admin grantor        | postgres admin_option   | t inherit_option | f set_option     | f<\/code><\/pre>\n<p>  <\/p>\n<p>As you can see, only the ADMIN option is enabled, which allows you to manage the role. The disabled INHERIT option means that the admin role will not inherit the privileges of the bob role. And the disabled SET option will not allow the admin role to switch to bob.<\/p>\n<p>  <\/p>\n<p>With that said, admin can grant bob to itself one more time with the necessary options enabled:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> GRANT bob TO admin WITH INHERIT TRUE, SET TRUE;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">GRANT ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> SET ROLE bob;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">SET<\/code><\/pre>\n<p>  <\/p>\n<p>If you want your admin to regularly create new roles with the SET and\/or INHERIT options, then you can automate the process using the new <em>createrole_self_grant<\/em> parameter (the second commit).<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> SET createrole_self_grant = 'INHERIT, SET';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">SET<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> CREATE ROLE alice LOGIN;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">CREATE ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> SELECT roleid::regrole, member::regrole, grantor::regrole,        admin_option, inherit_option, set_option FROM pg_auth_members WHERE roleid = 'alice'::regrole AND grantor = 'admin'::regrole\\gx<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">-[ RECORD 1 ]--+------ roleid         | alice member         | admin grantor        | admin admin_option   | f inherit_option | t set_option     | t<\/code><\/pre>\n<p>  <\/p>\n<p>alice has all the necessary membership options enabled.<\/p>\n<p>  <\/p>\n<p>The third commit allows the REPLICATION, BYPASSRLS and CREATEDB attributes to be set to other roles, provided that the admin role itself has them.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# ALTER ROLE admin CREATEDB;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ALTER ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-postgres=# \\c - admin<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">You are now connected to database \"postgres\" as user \"admin\".<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> ALTER ROLE bob CREATEDB;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ALTER ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">16-admin=> ALTER ROLE bob REPLICATION;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  must have replication privilege to change replication attribute<\/code><\/pre>\n<p>  <\/p>\n<p>See also:<br \/>  <a href=\"http:\/\/rhaas.blogspot.com\/2023\/01\/surviving-without-superuser-coming-to.html\">Surviving Without A Superuser \u2014 Coming to v16<\/a> (Robert Haas)<\/p>\n<p>  <a name=\"commit_096dd80f\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4037\/\">Setting parameter values at the database and user level<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/096dd80f\">096dd80f<\/a><\/p>\n<p>  <\/p>\n<p>In the <a href=\"https:\/\/postgrespro.com\/community\/demodb\">demo database<\/a>, you can switch the language using the custom parameter <em>bookings.lang<\/em>. Suppose that a user Bob creates a role called bob with no superuser privileges. He then connects to the database and decides that he wants to use the English version.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">postgres=# \\c demo bob<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">You are now connected to database \"demo\" as user \"bob\".<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">bob=> ALTER ROLE bob IN DATABASE demo SET bookings.lang = 'en';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ERROR:  permission denied to set parameter \"bookings.lang\"<\/code><\/pre>\n<p>  <\/p>\n<p>To set parameters with the ALTER ROLE|DATABASE\u2026 SET command, bob has to be a superuser. (In PostgreSQL 15 and higher, you can <a href=\"https:\/\/commitfest.postgresql.org\/37\/3413\/\">grant specific parameters<\/a>, including custom ones, with the GRANT\u2026 ON PARAMETER command, but this doesn&#8217;t seem like the intended use here.)<\/p>\n<p>  <\/p>\n<p>In PostgreSQL 16, it became possible to explicitly specify that the parameter should be granted on behalf of a regular role without superuser rights:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">bob=> ALTER ROLE bob IN DATABASE demo SET bookings.lang = 'en' USER SET;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ALTER ROLE<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">bob=> \\c demo bob<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">You are now connected to database \"demo\" as user \"bob\".<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">bob=> SHOW bookings.lang;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> bookings.lang ---------------  en<\/code><\/pre>\n<p>  <\/p>\n<p>The <em>pg_db_role_setting<\/em> has a new flag to represent that. We can use the <em>\\drds<\/em> command to have a look at it:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">bob=> \\drds<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">                     List of settings  Role | Database |           Settings           | User set ------+----------+------------------------------+----------  bob  | demo     | bookings.lang=en             | t       | demo     | search_path=bookings, public+| f       +       |          | bookings.lang=ru             | f<\/code><\/pre>\n<p>  <a name=\"commit_6e2775e4\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/www.postgresql.org\/message-id\/20230119194601.GA4105788@nathanxps13\">New parameter: reserved_connections<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/6e2775e4\">6e2775e4<\/a><\/p>\n<p>  <\/p>\n<p>The parameter <em>superuser_reserved_connections<\/em> defines a number of connection slots (3 by default) out of total <em>max_connections<\/em> that are kept in reserve so that superusers could always connect to the server.<\/p>\n<p>  <\/p>\n<p>However, many maintenance, monitoring, and backup tasks are performed by regular, non-superuser roles. PostgreSQL 16 provides a way to secure connection slots for these tasks in the form of a new parameter <em>reserved_connections<\/em> (0 by default) and a new predefined role <em>pg_use_reserved_connections<\/em>. You can set a number of <em>reserved_connections<\/em> and grant <em>pg_use_reserved_connections<\/em> to maintenance roles, restart the server, and have a number of connections reserved for these roles.<\/p>\n<p>  <\/p>\n<p>See also:<br \/>  <a href=\"https:\/\/www.cybertec-postgresql.com\/en\/reserve-connections-in-postgresql-16\/\">Reserve connections for the pg_use_reserved_connections group in PostgreSQL 16<\/a> (Pavlo Golub)<\/p>\n<p>  <a name=\"commit_8ad51b5f\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/3535\/\">postgres_fdw: analyzing foreign tables with TABLESAMPLE<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/8ad51b5f\">8ad51b5f<\/a><\/p>\n<p>  <\/p>\n<p>Collecting statistics on large foreign tables isn&#8217;t done very efficiently. The ANALYZE command gets all the rows from a table and immediately discards most of them, because it only needs a small sample to produce statistics (300 * default_statistics_target).<\/p>\n<p>  <\/p>\n<p>In the new release, the TABLESAMPLE clause will be used by default to fetch the desired amount of rows. For older server versions that do not support TABLESAMPLE (9.5 and prior), the <em>random<\/em> function will be used to limit the number of rows instead.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s test it. First, in the <em>postgres<\/em> database create a foreign table <em>tickets<\/em> (~3 million rows) for the <em>tickets<\/em> table from the <em>demo<\/em> database within the same cluster.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">CREATE EXTENSION postgres_fdw;  CREATE SERVER srv     FOREIGN DATA WRAPPER postgres_fdw     OPTIONS (dbname 'demo');  CREATE USER MAPPING FOR postgres     SERVER srv     OPTIONS (user 'postgres');  IMPORT FOREIGN SCHEMA bookings     LIMIT TO (tickets)     FROM SERVER srv     INTO public;<\/code><\/pre>\n<p>  <\/p>\n<p>Sampling for statistics collection is enabled by default:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">\\timing on  ANALYZE tickets;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ANALYZE Time: 322,737 ms<\/code><\/pre>\n<p>  <\/p>\n<p>By disabling sampling, we can see how much time it takes to collect statistics in previous PostgreSQL versions.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">ALTER FOREIGN TABLE tickets OPTIONS (analyze_sampling 'off');  ANALYZE tickets;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">ANALYZE Time: 2068,078 ms (00:02,068)<\/code><\/pre>\n<p>  <\/p>\n<p>And this is for databases in the same cluster. If the foreign table was located on a remote server, the collection time would&#8217;ve included network latency, and the overall time would&#8217;ve been even greater.<\/p>\n<p>  <a name=\"commit_594f8d37\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/2992\/\">postgres_fdw: batch insert records during partition key updates<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/594f8d37\">594f8d37<\/a><\/p>\n<p>  <\/p>\n<p>The <em>postgres_fdw<\/em> extension supports batch insertion of records since PostgreSQL 14. In the last CommitFest review, we <a href=\"https:\/\/habr.com\/en\/company\/postgrespro\/blog\/708082\/#commit_97da4824\">mentioned<\/a> that one of the restrictions on using batch mode for the COPY command is being removed. But there are others.<\/p>\n<p>  <\/p>\n<p>Batch mode is also prohibited if records are inserted with commands other than INSERT and COPY. What are other ways to insert records? One is using UPDATE on a partitioned table key. In this case, records are deleted from one partition and inserted into another. If the target partition is a foreign table, then batch mode is not used for it.<\/p>\n<p>  <\/p>\n<p>In the new release, this restriction is removed.<\/p>\n<p>  <\/p>\n<p>Note that so far we are talking only about inserting records. Batch mode for updating and deleting records has not yet been implemented.<\/p>\n<p>  <a name=\"commit_efb6f4a4\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4081\/\">pg_ident.conf: new ways to identify users in PostgreSQL<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/efb6f4a4\">efb6f4a4<\/a><\/p>\n<p>  <\/p>\n<p>In the <em>pg_ident.conf<\/em> file, the PostgreSQL username (PG-USERNAME field) is now processed according to the same rules as in <em>pg_hba.conf<\/em>. Allowed keywords are:<\/p>\n<p>  <\/p>\n<ul>\n<li>the special &#171;all&#187; keyword (corresponds to any user),<\/li>\n<li>keywords starting with the &#171;\/&#187; character (considered as regular expressions),<\/li>\n<li>keywords starting with the &#171;+&#187; character (initiate a role membership check).<\/li>\n<\/ul>\n<p>  <a name=\"commit_3db72ebc\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4047\/\">Query jumbling for DDL and utility statements<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/3db72ebc\">3db72ebc<\/a><\/p>\n<p>  <\/p>\n<p>Query jumbling is performed when calculating the hash code that the <em>pg_stat_statements<\/em> extension uses to group identical queries. For DML commands, the hash code is calculated based on the parsed query, not the command text itself. This lets the system ignore things like character case and whitespaces and replace constants with parameters.<\/p>\n<p>  <\/p>\n<p>But besides DML, there are also DDL commands and utility statements, and for those the hash code was calculated based on the exact query text. For example, the following two commands are identical in every way except for the case of characters and the number of spaces between words, but in PostgreSQL 15, <em>pg_stat_statements<\/em> will consider them to be different statements:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">DROP TABLE IF EXISTS t; drop   table   if   exists   t;<\/code><\/pre>\n<p>  <\/p>\n<p>Thanks to the new patch, this is no longer the case, and <em>pg_stat_statements<\/em> will read them as identical:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT queryid, query, calls FROM pg_stat_statements WHERE query ILIKE 'drop table%';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">       queryid       |         query          | calls ---------------------+------------------------+-------  8953406224830875875 | DROP TABLE IF EXISTS t |     2<\/code><\/pre>\n<p>  <\/p>\n<p>There is room for improvement yet, though. Jumbling still cannot group together CALLs of procedures with different parameter values, nor can it group multiple uses of the SET command setting different values of the same parameter.<\/p>\n<p>  <a name=\"commit_1fd3dd20\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4007\/\">New function: bt_multi_page_stats<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/1fd3dd20\">1fd3dd20<\/a><\/p>\n<p>  <\/p>\n<p>The <em>pageinspect<\/em> extension now features a new function <em>bt_multi_page_stats<\/em>, an easy way to collect statistics for multiple B-tree index pages at once. Stats for a single page were already obtainable with the <em>bt_page_stats<\/em> function. The new function accepts the number of pages as an argument and returns information about that number of pages. If the number is negative, it scans and returns stats for all the pages up until the end of the index.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT blkno, type, live_items FROM bt_multi_page_stats('pg_class_oid_index', 2, -1);<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> blkno | type | live_items -------+------+------------      2 | l    |        145      3 | r    |          2 (2 rows)<\/code><\/pre>\n<p>  <a name=\"commit_cca18634\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/3909\/\">New function: pg_split_walfile_name<\/a><\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/cca18634\">cca18634<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/13e0d7a6\">13e0d7a6<\/a><\/p>\n<p>  <\/p>\n<p>Some WAL file read\/write errors include the file name and an offset. The new function <em>pg_split_walfile_name<\/em> can turn that information into an LSN position.<\/p>\n<p>  <\/p>\n<p>To illustrate, let&#8217;s take the current wal file and an offset of 42 bytes.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT pg_walfile_name(pg_current_wal_lsn()) AS walfile,        42 AS offset \\gset<\/code><\/pre>\n<p>  <\/p>\n<p><em>pg_split_walfile_name<\/em> returns the WAL segment number and the timeline ID.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT :'walfile' AS walfile, * FROM pg_split_walfile_name(:'walfile');<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">         walfile          | segment_number | timeline_id --------------------------+----------------+-------------  000000010000000100000040 |            320 |           1<\/code><\/pre>\n<p>  <\/p>\n<p>The segment number and its size and the offset can be transformed into the LSN.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">SELECT :'walfile' AS walfile,                                                      :offset AS offset,        '0\/0'::pg_lsn + w.segment_number*s.setting::int + :offset AS lsn FROM pg_split_walfile_name(:'walfile') w, pg_settings s WHERE s.name = 'wal_segment_size';<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">         walfile          | offset |    lsn      --------------------------+--------+------------  000000010000000100000040 |     42 | 1\/4000002A<\/code><\/pre>\n<p>  <a name=\"commit_c31cf1c0\"><\/a>  <\/p>\n<p><strong><a href=\"https:\/\/commitfest.postgresql.org\/41\/4084\/\">pg_walinspect<\/a>, <a href=\"https:\/\/commitfest.postgresql.org\/41\/3628\/\">pg_waldump<\/a>: collecting page images from WAL<\/strong><br \/>  commit: <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/c31cf1c0\">c31cf1c0<\/a>, <a href=\"https:\/\/github.com\/postgres\/postgres\/commit\/d497093c\">d497093c<\/a><\/p>\n<p>  <\/p>\n<p>Both the WAL inspection tools can now extract images of pages from WAL.<\/p>\n<p>  <\/p>\n<p>Page images get into into WAL whenever a page is changed compared to its last checkpoint value.<\/p>\n<p>  <\/p>\n<p>Let&#8217;s note down the WAL positions before and after the UPDATE command:<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">CREATE TABLE t AS SELECT x FROM generate_series(1,100) x; CHECKPOINT;  SELECT pg_current_wal_lsn() AS start_lsn;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\"> start_lsn   ------------  1\/6DF4D250<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"pgsql\">UPDATE t SET x = x + 1;  SELECT pg_current_wal_lsn() AS end_lsn;<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">  end_lsn    ------------  1\/6DF51A18<\/code><\/pre>\n<p>  <\/p>\n<p>Let&#8217;s use the new function <em>pg_get_wal_fpi_info<\/em> from <em>pg_walinspect<\/em> (the first commit) to get all the page images written into WAL between these two LSNs.<\/p>\n<p>  <\/p>\n<pre><code class=\"pgsql\">CREATE EXTENSION pg_walinspect;  SELECT lsn, reltablespace, reldatabase, relfilenode, relblocknumber, forkname,        substr(fpi, 1, 8) AS fpi_trimmed FROM pg_get_wal_fpi_info('1\/6DF4D250', '1\/6DF51A18');<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">    lsn     | reltablespace | reldatabase | relfilenode | relblocknumber | forkname |    fpi_trimmed      ------------+---------------+-------------+-------------+----------------+----------+--------------------  1\/6DF4D250 |          1663 |       16384 |        1259 |              1 | main     | \\x01000000002ef46d  1\/6DF4E9A8 |          1663 |       16384 |        1249 |             60 | main     | \\x01000000c0b6f46d  1\/6DF4EF58 |          1663 |       16384 |       16678 |              0 | main     | \\x0100000008d0f46d (3 rows)<\/code><\/pre>\n<p>  <\/p>\n<p>The same can be done with <em>pg_waldump<\/em>. The new parameter <em>save-fullpage<\/em> (the second commit) takes a directory name and creates a separate file for each page there.<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">$ pg_waldump --start=1\/6DF4D250 --end=1\/6DF51A18 --quiet --save-fullpage=.\/waldump $ ls -1 -s -h .\/waldump<\/code><\/pre>\n<p>  <\/p>\n<pre><code class=\"plaintext\">total 24K 8,0K 00000001-6DF4D250.1663.16384.1259.1_main 8,0K 00000001-6DF4E9A8.1663.16384.1249.60_main 8,0K 00000001-6DF4EF58.1663.16384.16678.0_main<\/code><\/pre>\n<p>  <\/p>\n<hr\/>\n<p>  <\/p>\n<p>That&#8217;s all for today. Now, let&#8217;s look forward to the results of the <a href=\"https:\/\/commitfest.postgresql.org\/42\/\">March CommitFest<\/a>, the last one for the 16th release.<\/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\/721586\/\"> https:\/\/habr.com\/ru\/articles\/721586\/<\/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\/ek\/of\/sk\/ekofsk_9ng-x5vge4bv5c3xakok.png\" data-src=\"https:\/\/habrastorage.org\/webt\/ek\/of\/sk\/ekofsk_9ng-x5vge4bv5c3xakok.png\"\/><\/p>\n<p>  <\/p>\n<p>We continue to follow the news of the PostgreSQL 16 release, and today, the results of the fourth CommitFest are on the table. Let&#8217;s have a look.<\/p>\n<p>  <\/p>\n<p>If you missed the previous CommitFests, check out our reviews for <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5969676\">2022-07<\/a>, <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5969859\">2022-09<\/a> and <a href=\"https:\/\/postgrespro.com\/blog\/pgsql\/5969929\">2022-11<\/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-385875","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/385875","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=385875"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/385875\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=385875"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=385875"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=385875"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}