PostgreSQL 18 (2025)

Z PostgreSQL
Verze z 17. 4. 2025, 05:01, kterou vytvořil PavelStehule (diskuse | příspěvky) (založena nová stránka s textem „<p>Začátkem dubna byly zmrazen vývoj PostgreSQL 18. Od té doby do vydání (na přelomu září října) se do této verze dostanou pouze opravy, takže už nyní je jasné, co v nové verzi Postgresu bude. V osmnáctce se finalizují práce na podpoře tzv index skip skanu nebo větší schopnosti optimalizátoru zredukovat prováděcí plán o některé zbytečné operace. Nová verze také přináší částečnou podporu asynchronních IO oper…“)
(rozdíl) ← Starší verze | zobrazit aktuální verzi (rozdíl) | Novější verze → (rozdíl)
Skočit na navigaci Skočit na vyhledávání

Začátkem dubna byly zmrazen vývoj PostgreSQL 18. Od té doby do vydání (na přelomu září října) se do této verze dostanou pouze opravy, takže už nyní je jasné, co v nové verzi Postgresu bude. V osmnáctce se finalizují práce na podpoře tzv index skip skanu nebo větší schopnosti optimalizátoru zredukovat prováděcí plán o některé zbytečné operace. Nová verze také přináší částečnou podporu asynchronních IO operací.

PostgreSQL 18 (30 let vývoje)

Třicetiny ještě asi letos komunita slavit nebude - u software je někdy problém s identitou, nicméně verze PostgreSQL 1.0 byla uvolněna 5. září 1995. První záznam v historii je ale z roku 1996:

Author: Marc G. Fournier <scrappy@hub.org>
Date:   Tue Jul 9 06:22:35 1996 +0000

    Postgres95 1.01 Distribution - Virgin Sources

diff --git a/src/Makefile b/src/Makefile
new file mode 100644

Takže je možné, že třicetiny se budou oslavovat až příští rok. V každém případě je to úspěch, kam se tento projekt dostal, kde je dnes, a že vůbec přežil. Databáze PostgreSQL navazovala na starší POSTGRES z roku 1986, která ideově vycházela z databáze INGRES, na které se začalo pracovat v roce 1973.

Práce na verzi 18 pokračovaly víceméně v poklidném a zažitém tempu. Neotevírají se nová větší témata, pokračuje se v rozdělané práci a sem tam se docela nahodile přidá pár drobnosti. Podpora AIO, která před třemi roky vypadala jako vzdálená fantazie, je v osmnáctce skutečností. Zrovna tak i index skip scan a další optimalizace.

Z pohledu komunity asi největší změnou je nástup Microsoftu coby významného zaměstnavatele vývojářů Postgresu (a tudíž i sponzora vývoje Postgresu). Padla i další lokální meta - poprvé velká banka (Komerční banka) anoncovala nasazení PostgreSQL ve svém core systému. V bankovnictví se PostgreSQL používá delší dobu - ale žádná větší banka působící v ČR se k tomu extra nehlásila a stejně tak se nejednalo o core systém (naopak fintech používá Postgres běžně).

Prostředí uložených procedur a extenzí

Při změně obsahu proměnné (v PL/pgSQL) dochází k náhradě původního obsahu novým obsahem. U atomických datových typů není co řešit. U neatomických datových typů (např. pole, kompozity, …) takový přístup není moc efektivní, protože náhrada jednoho prvku pole, jednoho atributu kompozitu vede k nutnosti vytvořit kompletní novou hodnotu. U polí je to spojené často s alokací a překopírováním velkého bloku paměti (vyřešeno v 9.5). Kompozitní typy vetšinou nejsou tak velké, ale zase mají mnohem komplikovanější formát (interně proto PL/pgSQL rozlišoval mezi ROW a RECORD typy, pro které měl různé implementace). Dnes PL/pgSQL podporuje tzv in-place update hodnot pro vybrané třídy typů a některé operace. Tato podpora musela být natvrdo zadrátována do kódu PL/pgSQL. Nově lze podporu in-place update napsat i pro vlastní datové typy (extenze).

Pro uživatele neviditelnou (ale vítanou) změnou bude použití implicitní plan cache i pro funkce napsané v jazyku SQL (tělo funkce obsahuje posloupnost (většinou pouze jeden) SQL příkazů). Jazyk SQL je pro psaní funkcí zajímavý tím, že umožňuje v některých případech inlining (místo volání funkce se nahradí tělem funkce). Pokud dojde k inliningu, tak volání funkce má nulovou režii (a funkce je jakoby rychlejší než napsaná v PL/pgSQL). Pokud ale k inliningu nedošlo, tak se SQL příkazy uvnitř funkce překládaly (do prováděcích plánů) vždy pro každé volání funkce, a tehdy byly SQL funkce výrazně pomalejší než funkce v PL/pgSQL. Od verze 18 už i SQL funkce budou používat implicitní cache plánů a funkcí (stejně jako v PL/pgSQL). Touto změnou se trochu zmenší magie (proč je něco rychlé, a proč je něco pomalé) pro uživatele bez hlubších znalostí Postgresu (kterých není málo).

V posledních dvou letech finišovala podpora buildu pomocí nového nástroje <a href="https://mesonbuild.com/">meson</a>. Jelikož mám už odladěné skripty pro moje extenze orafce, plpgsql_check a pager pspg postavené nad configure, tak jsem meson neřešil a nechtěl jsem se jím zabývat. Bohužel párkrát do roka musím ve Visual Studiu připravit dllka orafce a plpgsql_check, ruční konfigurace (bez hlubších znalostí VS) je nepříjemná opičí práce, takže chca nechca jsem napsal build skripty pro zmíněné extenze pro meson. Je to docela šikovný nástroj, a nyní je build extenzí ve windows čajíček, učící křivka mesonu je ale hodně strmá. Zvlášť pro vývojáře zvyklé na configure a make. Nicméně je to použitelné, a je to dobře použitelné. Nový build Postgresu pro AIX poběží čistě přes meson.

Instalace extenzí doposud vyžadovala zápis do systémového adresáře Postgresu. V některých prostředích to byl problém (ať už bezpečnostní nebo kvůli požadavku rebuildu image). S novou konfigurační proměnnou extension_control_path by to už být problém neměl, a extenze se nyní mohou kompletně instalovat mimo systémové adresáře PostgreSQL (nutností může být nastavení proměnné dynamic_library_path). Kromě jiného to může znamenat o něco více komfortu pro autory extenzí (testy bude možné startovat příkazem make check a nikoliv speciálním příkazem make installcheck, který vyžaduje běžící instanci Postgresu spolu s přístupy).

Funkce pro práci s datovými typy

Agregační funkce min a max lze nově použít i pro kompozitní typy.

U regulárních funkcí lze použít pojmenované parametry (což dává smysl hlavně u funkce regexp_replace). Pojmenované parametry dostaly i funkce substring a substr.

Celočíselné hodnoty lze nyní přetypovat do typu bytea:

postgres=# SELECT '10'::smallint::bytea;
┌────────┐
│ bytea  │
╞════════╡
│ \x000a │
└────────┘
(1 row)

postgres=# SELECT '\x000a'::bytea::smallint;
┌──────┐
│ int2 │
╞══════╡
│   10 │
└──────┘
(1 row)

Funkci reverse můžeme použít i pro typ bytea:

postgres=# SELECT reverse('\x000a'::bytea);
┌─────────┐
│ reverse │
╞═════════╡
│ \x0a00  │
└─────────┘
(1 row)

Používání uuid považuji za zlozvyk, který může mít nepříjemný dopad na výkon databáze (větší objem zápisů do transakčního logu). Objektivní problémy s uuid by měl řešit nový generátor uuidéček verze sedm. V této verzi generované hodnoty vytváří monotónní vzestupnou řadu. Navíc lze z vygenerované hodnoty lze získat čas, kdy byla hodnota vygenerována:

(2025-03-18 08:54:00) postgres=# SELECT uuid_extract_timestamp(uuidv7());
┌────────────────────────────┐
│   uuid_extract_timestamp   │
╞════════════════════════════╡
│ 2025-03-18 08:54:43.346+01 │
└────────────────────────────┘
(1 row)

Případně lze uuid vygenerovat zpětně:

(2025-03-18 08:57:06) postgres=# SELECT uuid_extract_timestamp(uuidv7('-1year'));
┌────────────────────────────┐
│   uuid_extract_timestamp   │
╞════════════════════════════╡
│ 2024-03-18 08:57:18.809+01 │
└────────────────────────────┘
(1 row)

Nová funkce array_reverse prohodí pořadí prvků v poli:

postgres=# SELECT array_reverse(ARRAY[1,2,3,4]);
┌───────────────┐
│ array_reverse │
╞═══════════════╡
│ {4,3,2,1}     │
└───────────────┘
(1 row)

postgres=# SELECT array_reverse(ARRAY[[1,8],[3,4]]);
┌───────────────┐
│ array_reverse │
╞═══════════════╡
│ {{3,4},{1,8}} │
└───────────────┘
(1 row)

Nová funkce array_sort řadí pole (pole mohou být více dimenzionální podobně jako u array_reverse):

postgres=# SELECT array_sort(ARRAY[2,1,4,3]);
┌────────────┐
│ array_sort │
╞════════════╡
│ {1,2,3,4}  │
└────────────┘
(1 row)

postgres=# SELECT array_sort(ARRAY[1,2,3,4], descending=>true);
┌────────────┐
│ array_sort │
╞════════════╡
│ {4,3,2,1}  │
└────────────┘
(1 row)

K dispozici jsou dvě nové funkce pro výpočet CRC - crc32 a crc32c (argumentem je hodnota typu bytea).

Spíš pro úplnost (konzistenci s to_char) a kompatibilitu s Oraclem funkce to_number nově podporuje konverzi z římských číslic:

postgres=# SELECT to_number('MMXX  ', 'RN');
┌───────────┐
│ to_number │
╞═══════════╡
│      2020 │
└───────────┘
(1 row)

Funkce json_strip_nulls má nový volitelně druhý parametr s výchozí hodnotou false. S hodnotou true se odstraní null hodnoty i z JSON polí:

postgres=# SELECT json_strip_nulls('[10,20,null,10]', true);
┌──────────────────┐
│ json_strip_nulls │
╞══════════════════╡
│ [10,20,10]       │
└──────────────────┘
(1 row)

Collations

Postgres sám (bez použití externí knihovny) nyní dokáže interně pracovat se speciálními víceznakovými symboly a dokáže správně provést jejich konverzi mezi malými a velkými znaky (např. “ß” je zkonvertováno do “SS”, “dž” na “DŽ” nebo “Dž” (podle kontextu) atd). To je důležité pro možnost implementace interního collation pg_unicode_fast. Výhodou tohoto collation je rychlé řazení, nezávislost na externích knihovnách a plně korektní převody mezi malými a velkými písmeny (funkce lower, upper a initcap) (oproti dřívě přidanému pg_c_utf8, které pro výše uvedené funkce používá zjednodušené mapování simple case mapping). Pozor, stejné řazení je u těchto collations garantované pouze v rámci major verze, a tudíž je možné, že po upgrade bude nutný rebuild indexů (z důvodu aktualizace Unicode).

Nově se zrušila některá omezení pro nedeterministická collations (s dopadem na výkon protože není možné použít určité optimalizace).

postgres=# CREATE COLLATION ignore_accent_case (provider = icu,
                                                deterministic = false, locale = 'und-u-ks-level1');
CREATE COLLATION
postgres=# CREATE TABLE foo(a varchar);
CREATE TABLE
postgres=# INSERT INTO foo VALUES('Stěhule');
INSERT 0 1
postgres=# SELECT * FROM foo WHERE a = 'stehule' collate "ignore_accent_case";
┌─────────┐
│    a    │
╞═════════╡
│ Stěhule │
└─────────┘
(1 row)

postgres=# SELECT * FROM foo WHERE a like 'ste%' collate "ignore_accent_case"
┌─────────┐
│    a    │
╞═════════╡
│ Stěhule │
└─────────┘
(1 row)

Výchozí collation databáze stále musí být deterministické.

Pro case insensitive porovnání se dost často porovnávané řetězce převedou na velká nebo malá písmena a pak teprve porovnají. Ve speciálních případech tento postup nebude fungovat. Nová funkce casefold je navržena speciálně pro transformaci řetězců tak, aby následné case insensitive porovnání bylo korektní (a až na zmíněné speciální případy se tato funkce chová podobně jako funkce lower):

postgres=# SELECT casefold('Ahojẞ' COLLATE "tr-x-icu"), lower('Ahojẞ' COLLATE "tr-x-icu");
┌──────────┬───────┐
│ casefold │ lower │
╞══════════╪═══════╡
│ ahojss   │ ahojß │
└──────────┴───────┘
(1 row)

SQL

DML

Nově můžeme v klauzuli RETURNING v příkazech INSERT, UPDATE, DELETE a MERGE použít aliasy NEW a OLD:

postgres=# UPDATE foo SET a = 100, b = 1000 RETURNING NEW.*, OLD.a AS old_a, OLD.b AS old_b;
┌─────┬──────┬───────┬───────┐
│  a  │  b   │ old_a │ old_b │
╞═════╪══════╪═══════╪═══════╡
│ 100 │ 1000 │   100 │  1000 │
└─────┴──────┴───────┴───────┘
(1 row)

DDL

V nové verzi máme možnost používat generované virtuální sloupce (už dříve bylo možné používat materializované virtuální sloupce - PostgreSQL 12):

postgres=# CREATE TABLE foo(a int, b int, c int GENERATED ALWAYS AS (a+b) VIRTUAL);
CREATE TABLE
postgres=# INSERT INTO foo VALUES(10,20);
INSERT 0 1
postgres=# SELECT * FROM foo;
┌────┬────┬────┐
│ a  │ b  │ c  │
╞════╪════╪════╡
│ 10 │ 20 │ 30 │
└────┴────┴────┘
(1 row)

Generované virtuální sloupce mají zatím řadu omezení - například nemohou být oindexované nebo nemohou být doménového typu, ale většina omezení by měla být postupně odstraněna - nemožnost indexace je dost nepříjemná. Generované sloupce si patrně najdou svoje uživatele, možná i z důvodu přenositelnosti. Jestli si správně pamatuji, tak v MySQL se pomocí generovaných sloupců implementují funkční indexy.

Pomalu se blíží podpora temporálních databází podle standardu. V temporálních databázích mohou být v databázi uložená data limitovaná časovým úsekem. Ve verzi 18 můžeme definovat temporální primární klíč (je unikátní v daném časovém rozsahu (v Postgresu v libovolném rozsahu (range)) a temporální cizí klíč v ANSI/SQL syntaxi:

CREATE TABLE temporal_fk_rng2rng (
  id integer,
  valid_at daterange,
  parent_id integer,
  CONSTRAINT temporal_fk_rng2rng_pk PRIMARY KEY (id, valid_at WITHOUT OVERLAPS),
  CONSTRAINT temporal_fk_rng2rng_fk FOREIGN KEY (parent_id, PERIOD valid_at)
    REFERENCES temporal_rng (id, PERIOD valid_at)
);

Na plnou implementaci této části standardu SQL:2011 si ještě rok - dva budeme muset počkat. Kdo by si chtěl hrát s touto funkcionalitou, může vyzkoušet implementaci temporálních tabulek v MariaDB 10.4.3 a vyšší.

Administrace

Novinkou, které si určitě hned všimnete, je zobrazení BUFFERS ve výchozím nastavení příkazu EXPLAIN. S touto volbou se zobrazí počty načtených datových stránek (z disku, z cache):

postgres=# EXPLAIN (COSTS OFF, ANALYZE) SELECT * FROM pg_class ;
┌─────────────────────────────────────────────────────────────────────┐
│                             QUERY PLAN                              │
╞═════════════════════════════════════════════════════════════════════╡
│ Seq Scan on pg_class (actual time=0.022..0.112 rows=433.00 loops=1) │
│   Buffers: shared hit=14                                            │
│ Planning Time: 0.118 ms                                             │
│ Execution Time: 0.186 ms                                            │
└─────────────────────────────────────────────────────────────────────┘
(4 rows)

EXPLAIN nyní umí zobrazit zápisy do WAL (transakčního logu):

postgres=# CREATE TABLE foo(a int);
CREATE TABLE
postgres=# EXPLAIN (ANALYZE, WAL, COSTS OFF) INSERT INTO foo VALUES(1);
┌────────────────────────────────────────────────────────────┐
│                         QUERY PLAN                         │
╞════════════════════════════════════════════════════════════╡
│ Insert on foo (actual time=0.027..0.027 rows=0.00 loops=1) │
│   Buffers: shared hit=1                                    │
│   WAL: records=1 bytes=59                                  │
│   ->  Result (actual time=0.002..0.002 rows=1.00 loops=1)  │
│ Planning Time: 0.024 ms                                    │
│ Execution Time: 0.045 ms                                   │
└────────────────────────────────────────────────────────────┘
(6 rows)

Zápisy do WAL zobrazí i příkaz VACUUM VERBOSE. Sledovat lze i časovou náročnost zápisů do transakčního logu nastavením track_wal_io_timing (zobrazí se v pg_stat_io).

Všimněte si, že se počty řádků nyní zobrazují jako desetinné číslo. Může to být trochu neintuitivní. Důvodem pro změnu zobrazení je nested loop. V případě, že část dotazu byla prováděna opakovaně, tak se počty vrácených řádků (i časy) dělily počtem iterací. Tím, že se počty řádků zobrazovaly jako celé číslo, tak samozřejmě docházelo k velkému zkreslení (a zobrazované hodnoty někdy nedávaly smysl).

Nově se v explainu zobrazuje i počet průchodů indexem (od minulé verze Postgres umí na jednou iterací indexu dohledat více hodnot). Čím méně je iterací, tím je samozřejmě vyhledávání rychlejší:

postgres=# EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM pg_class WHERE oid = ANY(ARRAY[2619, 1247]);
┌────────────────────────────────────────────────────────────────────────────────────────────┐
│                                         QUERY PLAN                                         │
╞════════════════════════════════════════════════════════════════════════════════════════════╡
│ Bitmap Heap Scan on pg_class (actual time=0.027..0.028 rows=2.00 loops=1)                  │
│   Recheck Cond: (oid = ANY ('{2619,1247}'::oid[]))                                         │
│   Heap Blocks: exact=1                                                                     │
│   Buffers: shared hit=3                                                                    │
│   ->  Bitmap Index Scan on pg_class_oid_index (actual time=0.013..0.013 rows=2.00 loops=1) │
│         Index Cond: (oid = ANY ('{2619,1247}'::oid[]))                                     │
│         Index Searches: 1                                                                  │
│         Buffers: shared hit=2                                                              │
│ Planning:                                                                                  │
│   Buffers: shared hit=30 read=2                                                            │
│ Planning Time: 0.264 ms                                                                    │
│ Execution Time: 0.051 ms                                                                   │
└────────────────────────────────────────────────────────────────────────────────────────────┘
(12 rows)

postgres=# EXPLAIN (ANALYZE, COSTS OFF) SELECT * FROM pg_class WHERE oid = 2619 OR oid = 1247;
┌────────────────────────────────────────────────────────────────────────────────────────────┐
│                                         QUERY PLAN                                         │
╞════════════════════════════════════════════════════════════════════════════════════════════╡
│ Bitmap Heap Scan on pg_class (actual time=0.047..0.048 rows=2.00 loops=1)                  │
│   Recheck Cond: ((oid = '2619'::oid) OR (oid = '1247'::oid))                               │
│   Heap Blocks: exact=1                                                                     │
│   Buffers: shared hit=3                                                                    │
│   ->  Bitmap Index Scan on pg_class_oid_index (actual time=0.035..0.035 rows=2.00 loops=1) │
│         Index Cond: (oid = ANY ('{2619,1247}'::oid[]))                                     │
│         Index Searches: 1                                                                  │
│         Buffers: shared hit=2                                                              │
│ Planning Time: 0.138 ms                                                                    │
│ Execution Time: 0.078 ms                                                                   │
└────────────────────────────────────────────────────────────────────────────────────────────┘
(10 rows)

Po upgrade databáze pomocí pg_upgrade bylo nutné aktualizovat sloupcové statistiky. Ty se z upgradované instance nepřenesly. I s použitím volby --analyze-in-stages aktualizace statistik prodlužovala dobu odstávky. Nově pg_dump exportuje i sloupcové statistiky:

-- Name: x; Type: TABLE; Schema: public; Owner: pavel
--

CREATE TABLE public.x (
    a integer
);


ALTER TABLE public.x OWNER TO pavel;

--
-- Data for Name: x; Type: TABLE DATA; Schema: public; Owner: pavel
--

COPY public.x (a) FROM stdin;
10
\.


--
-- Statistics for Name: x; Type: STATISTICS DATA; Schema: public; Owner: -
--

SELECT * FROM pg_catalog.pg_restore_relation_stats(
    'version', '180000'::integer,
    'relation', 'public.x'::regclass,
    'relpages', '1'::integer,
    'reltuples', '1'::real,
    'relallvisible', '0'::integer
);
SELECT * FROM pg_catalog.pg_restore_attribute_stats(
    'version', '180000'::integer,
    'relation', 'public.x'::regclass,
    'attname', 'a'::name,
    'inherited', 'f'::boolean,
    'null_frac', '0'::real,
    'avg_width', '4'::integer,
    'n_distinct', '-1'::real
);


--
-- PostgreSQL database dump complete
--

pg_upgrade interně používá pg_dump a tudíž se přenesou i statistiky. vacuumdb má novou volbu --missing-stats-only, která zajistí, že se budou přepočítávat pouze chybějící statistiky (lze ji kombinovat s --analyze-only a --analyze-in-stages. Chybějící aktualizace statistik byl asi nejčastější problém upgradů Postgresu, a z většího by měl být odstraněn).

pg_upgrade nyní podporuje novou metodu - swap (aktivováno volboy --swap). Při této metodě se přesunou celé adresáře databází do nového clusteru, a následně se upraví. Mělo by se jednat o nejrychlejší způsob aktualizace, zejména pokud cluster (instance) obsahuje velké množství tabulek. Nevýhodou je destruktivní přístup k původní instanci. Po zahájení upgrade je originální instance zničená, a v případě chyby během upgrade je nutné se vrátit k záloze.

S novým přepínačem --no-policies nebude pg_dump exportovat politiky row level security (RLS).

autovacuum_max_workers lze změnit bez nutnosti restartu. Z minulých verzí známe parametr autovacuum_vacuum_threshold, který udává minimální počet změněných řádků, aby se automaticky provedl příkaz VACUUM nad tabulkou (u malých tabulek blokuje zbytečně časté vakuování). Nový parametr autovacuum_vacuum_max_threshold specifikuje maximální počet změněných řádků, který vynutí vakuování tabulky. Tento parametr dokáže vynutit častější vakuování u velkých tabulek, kde výchozích 20% (autovacuum_vacuum_scale_factor) je příliš. Výchozí nastavení je sto miliónů řádků.

Pár menších i větších vylepšení doznala extenze pg_stat_statements. V sloupci parallel_workers_to_launch je vidět počet naplánovaných workerů paralelní exekuce. V sloupci parallel_workers_launched pak skutečný počet workerů použitý pro exekuci dotazu. Nově lze v pg_stat_statements vidět i příkaz SET, který je také normalizován (hodnota parametru je nahrazena symbolem $n). Po nastavení query_id_squash_values na true bude normalizace dotazu normalizovat i seznamy konstant:

-- stávající a výchozí zobrazení
                                        query                                         | calls 
--------------------------------------------------------------------------------------+-------
 SELECT * FROM test_squash WHERE id IN ($1, $2, $3, $4, $5, $6, $7, $8, $9)           |     1
 SELECT * FROM test_squash WHERE id IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10)      |     1
 SELECT * FROM test_squash WHERE id IN ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11) |     1

-- po zapnutí query_id_squash_values
                                 query                                  | calls 
------------------------------------------------------------------------+-------
 SELECT * FROM test_squash WHERE id IN ($1 /*, ... */)                  |     3

V minulých verzích se tabulka identifikovala svým unikátním identifikačním číslem. Nově se tabulka bude identifikovat pouze nekvalifikovaným jménem (pro normalizaci - výpočet queryid). Na jednu stranu se tím vyřeší problémy s dočasnými tabulkami (které se nyní správně začnou grupovat). Naopak může se ztratit rozdíl mezi stejně pojmenovanými tabulkami v různých schématech (workaroundem může být použití různých aliasů).

S novou konfigurační volbou log_lock_failure lze vynutit zalogování selhání získání zámku. Aktuálně je podporován pouze příkaz SELECT ... NOWAIT.

Nejčastěji používaným indexem v Postgresu bude BTREE. Ostatní typy indexů jako GiST, BRIN nebo GIN se používají určitě mnohem méně, ale používají se. GIN index se používá pro fulltext, vyhledávání prvků v poli nebo pro vyhledávání v JSONB (používá se tam, kde se ptáme, jestli něco obsahuje nějakou hodnotu - článek slovo, polygon bod, dokument v JSONu nějaký klíč). Nepříjemnou vlastností GIN indexu je jeho náročnost při UPDATE. Vytvoření GIN indexu je také pomalejší než vytvoření BTREE indexu a navíc až doposud neumožňoval použít více procesů (více CPU) pro vytvoření indexu. V nové verzi Postgres může použít víc workerů pro vytvoření indexu (navíc některé interní operace jsou optimalizovány, takže vytvoření GIN indexu by mělo být rychlejší i bez paralelizace).

GIN index je nově podporován i v extenzi amcheck (kontrola konzistence).

Příkaz VACUUM (případně příkaz ANALYZE), aktivovaný automatem, běží přiškrceně. S aktivním sledováním volbou track_cost_delay_timing se v progres pohledech pg_stat_progress_vacuum a pg_stat_progress_analyze zobrazí součet časů delay_time (kdy příkaz čekal z důvodu přiškrcení). Tato hodnota se může i zalogovat.

Příkaz pg_combinebackup, který provádí obnovu z inkrementálních záloh, má nový argument -k nebo --link. S tímto argumentem se místo zkopírování souboru vytvoří hardlink.

Příkaz pg_createsubscriber (přidaný v minulé verzi) má nový přepínač --all. S touto volbou se na zdrojové instanci vytvoří publikace pro všechny databáze, a na cílové databázi se zajistí odběr (subskripce) těchto publikací (opět na všech databázích).

Kontroly (check) a omezení referenční integrity lze nyní vypnout příkazem ALTER TABLE ALTER CONSTRAINT ... NOT ENFORCED:

postgres=# CREATE TABLE foo_a(a int primary key);
CREATE TABLE
postgres=# CREATE TABLE foo_b(foo_a int REFERENCES foo_a(a) NOT ENFORCED);
CREATE TABLE
postgres=# INSERT INTO foo_b VALUES(10);
INSERT 0 1
postgres=# INSERT INTO foo_b VALUES(20);
INSERT 0 1
postgres=# \d foo_b
                Table "public.foo_b"
┌────────┬─────────┬───────────┬──────────┬─────────┐
│ Column │  Type   │ Collation │ Nullable │ Default │
╞════════╪═════════╪═══════════╪══════════╪═════════╡
│ foo_a  │ integer │           │          │         │
└────────┴─────────┴───────────┴──────────┴─────────┘
Foreign-key constraints:
    "foo_b_foo_a_fkey" FOREIGN KEY (foo_a) REFERENCES foo_a(a) NOT ENFORCED

postgres=# ALTER TABLE foo_b VALIDATE CONSTRAINT foo_b_foo_a_fkey;
ERROR:  cannot validate NOT ENFORCED constraint
postgres=# ALTER TABLE foo_b ALTER CONSTRAINT foo_b_foo_a_fkey ENFORCED;
ERROR:  insert or update on table "foo_b" violates
        foreign key constraint "foo_b_foo_a_fkey"
DETAIL:  Key (foo_a)=(10) is not present in table "foo_a".
postgres=# INSERT INTO foo_a VALUES(10),(20);
INSERT 0 2
postgres=# ALTER TABLE foo_b ALTER CONSTRAINT foo_b_foo_a_fkey ENFORCED;
ALTER TABLE

Nově lze vytvořit nevalidní omezení (constraint) NOT NULL, které lze dodatečně validovat. Důvodem, proč vytvoření omezení rozložit do dvou kroků, jsou zámky. ALTER TABLE ADD CONSTRAINT vyžaduje exkluzivní zámek a pokud má být omezení validní, je nutné přečíst celou tabulku (ve výsledku to znamená u velkých tabulek nutnost odstávky, protože velice dlouho DDL operace drží silný zámek). Pokud je omezení nevalidní, pak nutnost full scanu padá. ALTER TABLE VALIDATE exkluzivní zámek nevyžaduje. Na implementaci se, opět, začalo dělat před několika roky, a předpokladem byl refaktoring uložení metadat omezní typu NOT NULL (některé části implementace byly opakovaně committované a revertované (jestli mne paměť neklame)).

Nově lze přidat nevalidní cizí klíč (s flagem NOT VALID) i na jednotlivé partišny (a později jej zvalidovat).

Alokace sdílené paměti je vidět v pohledu pg_shmem_allocations.

Pomocí funkce pg_stat_get_backend_io(pid) lze získat aktuální IO statistiky jiné než aktivní session. Druhou podobnou funkcí je funkce pg_stat_get_backend_wal(pid), která vrací statistiky zápisu do transakčního logu. S novými statistikami by mělo být možné si vytvořit lepší představu o aktuální zátěži s většími možnostmi filtrování, grupování per databáze, uživatel, (libovolná hodnota z pg_stat_activity). Konečně funkcí pg_get_process_memory_contexts(pid) lze získat popis paměťových kontextů libovolného procesu Postgresu.

Nově také lze nastavit (skrze idle_replication_slot_timeout) invalidaci neaktivního (nepoužívaného) replikačního slotu. Pozor, slot se invaliduje pouze v rámci checkpointu, a tudíž invalidace může proběhnout o něco později, než určuje zadaná hodnota (čeká se na checkpoint).

Export celé instance příkazem pg_dumpall byl možný pouze v textovém formátu. Od verze 18 bude pg_dumpall podporovat všechny formáty, které podporuje pg_dump (v případě jiného než textového formátu pro obnovu (import) použije pg_restore).

Defaultní práva lze nastavit i pro large objects:

GRANT { { SELECT | UPDATE }
    [, ...] | ALL [ PRIVILEGES ] }
    ON LARGE OBJECTS
    TO { [ GROUP ] role_name | PUBLIC } [, ...] [ WITH GRANT OPTION ]

Nový pohled pg_shmem_alloctions_numa zobrazuje alokaci sdílené paměti napříč NUMA uzly. Pro funkčnost tohoto pohledu je nutné přeložit Postgres s podporou NUMA (--with-libnuma (zatím pouze pro Linux)).

psql

Možná většina backslashových příkazů (přidaná v posledních několika verzích) je navržena primárně pro psaní testů (postgresu, protokolu, různých proxy), případně nabízí možnost si jednoduše otestovat (nebo simulovat) specifické vlastnosti komunikačního protokolu (to se dá využít i pro benchmarking).

Komunikační protokol definuje komunikaci mezi klientem a serverem. Základní část protokolu v zásadě řeší připojení, vykonání příkazu a získání výsledku dotazu. Rozšířená část protokolu umožňuje pracovat s před připravenými pojmenovanými parametrizovanými (binding) dotazy. V předchozích verzích bylo možné spustit parametrizované dotazy:

postgres=# CREATE TABLE foo(a int, b int);
CREATE TABLE
INSERT INTO foo VALUES($1,$2) \bind 10 20 \g
INSERT 0 1

Nově lze používat pojmenované (prepared):

postgres=# INSERT INTO foo VALUES($1,$2) \parse stmt
postgres=# \bind_named stmt 10 20 \g
INSERT 0 1

Binding funguje hezky i s psql proměnnými.

Nově také lze simulovat režim pipeline pomocí příkazů \startpipeline, \endpipeline, \sendpipeline, \flush, \flushrequest, \getresult (pipeline režim redukuje komunikaci mezi klientem a serverem a tím zrychluje provádění dávek příkazů v těch případech, kdy hrdlem je síť). Dovedu si představit použití při demonstraci pipeline režimu. Je to jednodušší, názornější a méně pracné než psát byť jednoduchého klienta (musí se použít dedikované API, které prakticky nikdo nezná).

Příkaz \conninfo zobrazí tabulku místo textu. Původně se uvažovalo o pluskovém rozšíření, nakonec se ale rozhodlo o přepisu zobrazení do tabulky v rozšířeném (extended) formátu. To by mělo umožnit snadno přidávat další položky v budoucnu:

(2025-03-16 17:13:36) postgres=# \conninfo 
      Connection Information
┌──────────────────────┬──────────┐
│      Parameter       │  Value   │
╞══════════════════════╪══════════╡
│ Database             │ postgres │
│ Client User          │ pavel    │
│ Socket Directory     │ /tmp     │
│ Server Port          │ 5432     │
│ Options              │          │
│ Protocol Version     │ 3        │
│ Password Used        │ false    │
│ GSSAPI Authenticated │ false    │
│ Backend PID          │ 26657    │
│ TLS Connection       │ false    │
│ Superuser            │ on       │
│ Hot Standby          │ off      │
└──────────────────────┴──────────┘
(12 rows)

U pluskového zobrazení funkcí a operátorů se nově zobrazí hodnota leakproof?. Tento atribut funkce (operátoru) specifikuje, jestli může nebo nebo nemůže dojít k nechtěnému zobrazení argumentů funkce (např. v chybové hlášce). U pohledů vytvořených s volbou security_barrier Postgres garantuje, že nejdříve se vyhodnotí filtry pohledu, a teprve poté funkce, které nemají flag leakproof. Nemůže tak dojít k nechtěnému (nebo i chtěnému) úniku informace. Může to ale znamenat více úrovňový plán, jehož exekuce bude pomalejší.

Příkazem \x přepínáme mezi základním a rozšířeným zobrazením (extended mode). Nově většina deskriptivních příkazů dostala možnost použít x a vynutit si zobrazení v rozšířeném režimu (\dtx, \dix, …):

postgres=# \lx
List of databases
┌─[ RECORD 1 ]──────┬───────────────────────┐
│ Name              │ postgres              │
│ Owner             │ postgres              │
│ Encoding          │ UTF8                  │
│ Locale Provider   │ libc                  │
│ Collate           │ cs_CZ.UTF-8           │
│ Ctype             │ cs_CZ.UTF-8           │
│ Locale            │                       │
│ ICU Rules         │                       │
│ Access privileges │                       │
╞═[ RECORD 2 ]══════╪═══════════════════════╡
│ Name              │ template0             │
│ Owner             │ postgres              │
│ Encoding          │ UTF8                  │
│ Locale Provider   │ libc                  │
│ Collate           │ cs_CZ.UTF-8           │
│ Ctype             │ cs_CZ.UTF-8           │
│ Locale            │                       │
│ ICU Rules         │                       │
│ Access privileges │ =c/postgres          ↵│
│                   │ postgres=CTc/postgres │
╞═[ RECORD 3 ]══════╪═══════════════════════╡
│ Name              │ template1             │
│ Owner             │ postgres              │
│ Encoding          │ UTF8                  │
│ Locale Provider   │ libc                  │
│ Collate           │ cs_CZ.UTF-8           │
│ Ctype             │ cs_CZ.UTF-8           │
│ Locale            │                       │
│ ICU Rules         │                       │
│ Access privileges │ =c/postgres          ↵│
│                   │ postgres=CTc/postgres │
└───────────────────┴───────────────────────┘

Optimalizace dotazů

Optimalizátor nyní může použít hash join pro right semijoin. Pro uživatele to znamená, že v určitých případech má optimalizátor možnost, kde dřív neměl, zvolit pro hashování menší relaci.

Eliminace self joinu (self join elimination (SJE)) je optimalizace, která se snaží odstranit zbytečná vnitřní spojení na tutéž tabulku. Návrh na tuto optimalizaci byl poslán v květnu roku 2018. Patch byl opakovaně commitovaný a revertovaný, nicméně poslední inkarnace patche už vypadá, že bude finální. Původně měli vývojáři Postgresu k optimalizacím tohoto typu dost negativní postoj. Převládal názor, že programátoři nebo ORM mají produkovat SQL příkazy bez zbytečných spojení nebo poddotazů. Jak se ale vývoj Postgresu komercializoval a do Postgresu se víc a víc začaly portovat aplikace z Oracle, tak došlo ke změně názoru. Navíc na moderním hardware je cena za tuto optimalizaci akceptovatelná:

postgres=# EXPLAIN SELECT * FROM t1 m JOIN t1 n ON m.x = n.x JOIN t2 o ON o.x = m.x;
┌──────────────────────────────────────────────────────────────────────────┐
│                                QUERY PLAN                                │
╞══════════════════════════════════════════════════════════════════════════╡
│ Hash Join  (cost=26925.00..43975.01 rows=1000000 width=12)               │
│   Hash Cond: (n.x = o.x)                                                 │
│   ->  Seq Scan on t1 n  (cost=0.00..14425.00 rows=1000000 width=4)       │
│   ->  Hash  (cost=14425.00..14425.00 rows=1000000 width=4)               │
│         ->  Seq Scan on t2 o  (cost=0.00..14425.00 rows=1000000 width=4) │
└──────────────────────────────────────────────────────────────────────────┘
(5 rows)

postgres=# SET enable_self_join_elimination TO off;
SET
postgres=# EXPLAIN SELECT * FROM t1 m JOIN t1 n ON m.x = n.x JOIN t2 o ON o.x = m.x;
┌────────────────────────────────────────────────────────────────────────────────┐
│                                   QUERY PLAN                                   │
╞════════════════════════════════════════════════════════════════════════════════╡
│ Hash Join  (cost=53850.00..73525.02 rows=1000000 width=12)                     │
│   Hash Cond: (m.x = o.x)                                                       │
│   ->  Hash Join  (cost=26925.00..43975.01 rows=1000000 width=8)                │
│         Hash Cond: (m.x = n.x)                                                 │
│         ->  Seq Scan on t1 m  (cost=0.00..14425.00 rows=1000000 width=4)       │
│         ->  Hash  (cost=14425.00..14425.00 rows=1000000 width=4)               │
│               ->  Seq Scan on t1 n  (cost=0.00..14425.00 rows=1000000 width=4) │
│   ->  Hash  (cost=14425.00..14425.00 rows=1000000 width=4)                     │
│         ->  Seq Scan on t2 o  (cost=0.00..14425.00 rows=1000000 width=4)       │
└────────────────────────────────────────────────────────────────────────────────┘
(9 rows)

Je to docela chytré. ORM knihovny občas produkují šílenosti (fakt jsem nikdy ORM nepřišel na chuť), k šíleným plánům se ale lze dostat i kombinací pohledů (takže tato optimalizace mi dává smysl):

postgres=# EXPLAIN SELECT * FROM t1 n JOIN t1 m ON n.id = m.id WHERE n.a = 100 AND m.b = 1000;
┌────────────────────────────────────────────────────────────────────────┐
│                               QUERY PLAN                               │
╞════════════════════════════════════════════════════════════════════════╡
│ Index Scan using t1_a_b_idx on t1 m  (cost=0.42..8.45 rows=1 width=24) │
│   Index Cond: ((a = 100) AND (b = 1000))                               │
└────────────────────────────────────────────────────────────────────────┘
(2 rows)
             
postgres=# SET enable_self_join_elimination TO off;
SET
postgres=# EXPLAIN SELECT * FROM t1 n JOIN t1 m ON n.id = m.id WHERE n.a = 100 AND m.b = 1000;
┌──────────────────────────────────────────────────────────────────────────────────┐
│                                    QUERY PLAN                                    │
╞══════════════════════════════════════════════════════════════════════════════════╡
│ Nested Loop  (cost=20.53..9981.25 rows=1 width=24)                               │
│   ->  Bitmap Heap Scan on t1 n  (cost=20.11..2548.11 rows=991 width=12)          │
│         Recheck Cond: (a = 100)                                                  │
│         ->  Bitmap Index Scan on t1_a_b_idx  (cost=0.00..19.86 rows=991 width=0) │
│               Index Cond: (a = 100)                                              │
│   ->  Index Scan using t1_pkey on t1 m  (cost=0.42..7.50 rows=1 width=12)        │
│         Index Cond: (id = n.id)                                                  │
│         Filter: (b = 1000)                                                       │
└──────────────────────────────────────────────────────────────────────────────────┘
(8 rows)

Optimalizace také nyní dokáže transformovat filtr ve tvaru sl = C1 OR sl = C2 ... do tvaru sl = ANY(ARRAY[C1, C2, ...]). V tomto tvaru dokáže Postgres v rámci jednoho průchodu indexu dohledat víc hodnot, a redukovat použití operace BitmapOR. Opět se jedná o interně komplikovanou funkcionalitu, kdy prototyp byl demonstrován v prosinci 2015. Na historii tohoto patche je vidět, jak je komplikované a pracné pracovat na optimalizaci dotazů. Aby bylo možné patch akceptovat - musel se rozbít na menší kusy (mnohem mnohem menší kousky), které se postupně commitují dva roky ve dvou “samostatných” větvích - planneru a načítání dat z indexu. Napsat patch je legrace, dostat patch do upstreamu je neskutečně práce. Samozřejmě, že nikdo nechce rozbít Postgres. To by dnes už znamenalo velké problémy. Tudíž akceptace patchů, které mají potenciálně větší dopad, je extrémně pečlivá, zoufale pomalá, určitě často úmorná práce. Je to ale vidět pak na kvalitě (a spokojenosti uživatelů).

Ostatní

Indexscan nyní podporuje tzv <a href="https://wiki.postgresql.org/wiki/Loose_indexscan">index skip scan</a>. V tomto případě Postgres dotahuje ostatní databáze, které index skip scan umí dekády (Když jsem pracoval v GoodData, tak na to že Postgres neumí index skip scan, jsme párkrát bolestivě narazili). Mohu se jen domýšlet proč - možná komplikovaná implementace (díky rozšiřitelnosti Postgresu), možná priorita jiných řešení (bitmap index scan, podpora prostorových indexů, atd). Dost možná to opravdu bylo složitostí implementace (implementace se rozdělila do Postgresu 17 a 18). Index skip scan se hodí v těch případech, kdy se používá vícesloupcový index a nefiltruje se podle počátečních sloupců:

postgres=# CREATE TABLE foo(a int, b int);
CREATE TABLE
postgres=# INSERT INTO foo SELECT random()*1000, random()*10000 FROM generate_series(1,1000000);
INSERT 0 1000000
postgres=# CREATE INDEX ON foo(a,b);
CREATE INDEX
postgres=# VACUUM ANALYZE foo;
VACUUM

postgres=# EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) SELECT * FROM foo WHERE b = 100;
┌────────────────────────────────────────────────────────────────────────────────────────┐
│                                       QUERY PLAN                                       │
╞════════════════════════════════════════════════════════════════════════════════════════╡
│ Index Only Scan using foo_a_b_idx on foo (actual time=0.055..6.631 rows=94.00 loops=1) │
│   Index Cond: (b = 100)                                                                │
│   Heap Fetches: 0                                                                      │
│   Index Searches: 1002                                                                 │
│   Buffers: shared hit=3007                                                             │
│ Planning Time: 0.093 ms                                                                │
│ Execution Time: 6.680 ms                                                               │
└────────────────────────────────────────────────────────────────────────────────────────┘
(7 rows)

-- Postgres 17
postgres=# EXPLAIN (ANALYZE, BUFFERS, COSTS OFF) SELECT * FROM foo WHERE b = 100;
┌────────────────────────────────────────────────────────────────────────────┐
│                                 QUERY PLAN                                 │
╞════════════════════════════════════════════════════════════════════════════╡
│ Gather (actual time=0.339..38.282 rows=94 loops=1)                         │
│   Workers Planned: 2                                                       │
│   Workers Launched: 2                                                      │
│   Buffers: shared hit=4432                                                 │
│   ->  Parallel Seq Scan on foo (actual time=0.444..31.454 rows=31 loops=3) │
│         Filter: (b = 100)                                                  │
│         Rows Removed by Filter: 333302                                     │
│         Buffers: shared hit=4432                                           │
│ Planning Time: 0.082 ms                                                    │
│ Execution Time: 38.320 ms                                                  │
└────────────────────────────────────────────────────────────────────────────┘
(10 rows)

Docela zásadní interní univerzální komponentou Postgresu je tzv tuplestore. Tato komponenta je dynamicky se zvětšující paměťový buffer, který po dosažení limitu (work_mem) začne ukládat data do dočasného souboru. Tuplestore v Postgresu zajišťuje efektivní operace nad daty (pokud je to možné v paměti), a garantuje, že tyto operace nevyčerpají operační paměť. Tuplestore nově používá vlastní paměťový kontext s generačním alokátorem. Tento typ alokátoru předpokládá, že paměť bude uvolňována ve stejném pořadí, jako byla alokována nebo bude uvolněna hromadně (zrušením nebo resetem paměťového kontextu).

  • zbytečně neroste paměťový kontext executoru, který má delší životnost,
  • použití generačního alokátoru vede k nižší fragmentaci paměti (sekvenční operace (v paměti) jsou pak o něco málo rychlejší).

Postupně se rozšiřuje použití SIMD instrukcí - nově například pro výstup JSONu. Zrychlit by se také měla transformace do hexakódu (cca o 30%).

Násobení hodnot typu numeric číslem s nízkým počtem číslic (do čtyř) by mělo být rychlejší.

Do mnoha letech experimentování se do Postgresu dostala podpora asynchronního IO (AIO). Použití AIO je řízeno nastavením konfigurační proměnné io_method (sync znamená synchronní IO, worker by měl používat AIO, io-uring používá knihovnu <a href="https://github.com/axboe/liburing">liburing</a> (pouze na Linuxu)). AIO se zatím používá pouze v kódu background writeru a checkpointeru. Výhodou by mělo být snížení latencí IO operací a snížení režie CPU. Do budoucna by to měl být způsob, jak elegantněji řešit direct IO (DIO). Myslím si, že na “typického” uživatele Postresu to bude mít minimální dopad, jelikož běžné problémy, které souvisely se zahlcením file systém cache, by měly být relativně dlouho vyřešené. Postgres tím jak má realizovanou multigenerační architekturu a indexy, tak zapisuje víc než ostatní databáze. Efektivnější zápis může zvednout limity Postgresu, a pomoct uživatelům, kteří mají aplikace, kde se opravdu hodně zapisuje do databáze (což se u extrémů řešilo použitím jiných databází než PostgreSQL).

Došlo ke změně algoritmu pro alokaci paměti hashjoinu. Mělo by se snížit riziko, že se při aktualizaci hash tabulky začne neefektivně ve velkém alokovat paměť. Dříve k tomu docházelo spíš výjimečně a nyní by k takové situaci mělo dojít ještě s menší pravděpodobností.

Nově se oddaluje zamykání tabulek (v tomto případě partišny) až do chvíle, kdy bude zřejmé, které partišny skutečně bude potřeba zamknout (protože budou použité v dotazu). V testech byla režie zámků více než znatelná už při 120 partišen (v testu o více než 50% horší výsledky (Postgres 17)). Nové řešení zvyšuje benchmark u nižšího počtu partišen (cca o 30%) a při vyšším počtu (např. 2000 partišen) je benchmark pouze o 25% nižší než benchmark u Postgresu 17 s nízkým počtem partišen).

Příkaz EXPLAIN je nyní rozšiřitelný - extenze mohou přidávat vlastní volby a zobrazovat jiné než stávající metriky. Zatím ale žádná taková extenze není k dispozici. V diskuzi jako příklady nasazení zazněla možnost návrhů na indexy nebo možnost zobrazit plán vykonávaný na cizím serveru.

Došlo ke změně části vzorce, kterým se řídí autovacuum. autovacuum_vacuum_insert_threshold se porovnával s poměrem nově přidaných řádků vůči celkovému počtu řádků. Nově se porovnává s poměrem nezafrízovaných stránek vůči celkovému počtu stránek. Výhodou by mělo být častější vakuování tabulek do kterých se hodně přidává (a je ještě šance je zachytit ve cache (share buffers)). Pomocí konfigurační volby vacuum_truncate lze vyblokovat poslední fázi vakuování tabulky, kterou je redukce prázdných datových stránek od konce souboru. U zatíženějších serverů popsané chování může působit problémy. Dojde sice k redukci velikosti tabulky (většinou ale zanedbatelné), nicméně krátkodobě, a za cenu exkluzivního zámku nad tabulkou (byť na krátkou dobu).

Změnilo se omezení použití unikátního indexu pro partišnované klíče (a klíče materializovaných pohledů). Místo nuceného použití BTREE indexu je možné použít takový typ unikátního indexu, který podporuje operaci COMPARE_EQ.

Plánování dotazů s větším počtem partitions by mělo být rychlejší.

Na komunikační protokol se nesáhlo fůru let. Z části proto, že nebylo moc důvodu do něj sahat. Z části i proto, že každý zásah do protokolu má většinou dost negativní dopad na uživatele (je nutné aktualizovat všechny klienty a s tím jsou problémy). Návrh protokolu umožňoval jeho rozšíření, ale až doposud se nikdo o rozšíření protokolu nepokusil. V loňském roce se opět začalo víc pracovat na dalších funkcích pgbounceru (proxy), kde by se rozšíření protokolu hodilo, a ukázalo se, že stávající design rozšiřování protokolu není úplně domyšlený (a do určité míry je stávající stav zabetonovaný). V rámci refaktoringu té části protokolu, která řeší jeho rozšiřování, se implementovala možnost použít pro identifikaci procesu obsluhujícího dotaz, který chceme přerušit, delší token (původně 4 bajtový nově až 256 bajtový). Implementace je i jakýmsi vzorem, jak do budoucna rozšiřovat protokol (hlavně se odblokovala diskuze ohledně rozšiřování protokolu).

Co se připravuje

Pro mne asi největší bombou PostgreSQL 19 bude příkaz REPACK. Implementace se jen těsně nedostala do Postgresu 18. Autorem je náš kamarád Tonda Houska z Cybertecu. Jedná se o reimplementaci příkazu VACUUM FULL s volbou CONCURRENTLY. Tonda je autorem extenze pg_squeeze, která je ideový následník extenze pg_repack. Postgres už roky podporuje příkaz REINDEX CONCURRENTLY (případně CREATE INDEX CONCURRENTLY), takže principiálně by nemělo nic bránit tomu, aby existoval příkaz VACUUM FULL CONCURRENTLY. Tonda napsal prototyp (na základě pg_squeeze) a ukázalo se, že největší problém je v pojmenování příkazu. Osobně mi přišlo pojmenování VACUUM FULL CONCURRENTLY v pohodě, nicméně anglicky nativním mluvčím nikoliv. Shoda se našla na názvu REPACK případně REPACK CONCURRENTLY. Když všechno běží jak má, tak VACUUM FULL není potřeba. Občas ale všechno neběží jak má, a pak se možnost jednoduše zredukovat velikost tabulky za provozu hodí.

Chybí dokončit implementaci JSON_TABLE. Patch už je připravený.

Připravuje se podpora <a href="https://www.enterprisedb.com/blog/representing-graphs-postgresql-sqlpgq">SQL/PGQ</a>, což je rozšíření SQL, které umožňuje pracovat s daty v databázi jako s grafy.

Závěr

Určitým motivem řady změn ve verzi 18 je dotahování funkcionalit (třeba i jen do podoby prvotního commitu v upstreamu), na kterých se pracovalo (a které se diskutovaly) posledních pět let (a někdy i déle). Dolaďuje se commitfest aplikace včetně propojení na testovací farmu a mailing list.

Mám pocit, že se už skoro neřeší téma migrací z Oracle a víc se řeší cloud. Samozřejmě, že i v Postgresové komunitě rezonuje hype AI - viz rozšíření extenze <a href="https://github.com/pgvector/pgvector">pgvector</a>. Díky <a href="https://duckdb.org/">DuckDB</a> se snížila motivace o implementaci analytiky do Postgresu (existuje několik různých způsobů jak propojit DuckDB s Postgresem). DuckDB dělá analytiku velice šikovně a dobře (a je to analytický speciál).

Rozšíření Postgresu už dávno překonalo kritickou velikost, a v dnešní turbulentní době je velkou výhodou nezávislost Postgresu na konkrétní firmě, konkrétní zemi nebo společenství. Vývoj Postgresu nejde stopnout - zrovna tak nejde stopnout provoz Postgresu nebo zdražit případně změnit licenci (což se mi osobně i někdy líbit nemusí a v pár lidech jsem se zklamal (i když beru, že doba je opět zlá - nikdy by mne nenapadlo, že budu o Open Source uvažovat v tomto kontextu)). S vývojem Postgresu má dnes zkušenost vyšší stovky vývojářů (a vyšší desítky se aktuálně podílejí na vývoji). Teď se to ukazuje jako neskutečná výhoda vůči MariaDB, MySQL nebo Firebirdu, které měli po celou dobu výrazně uzavřenější vývoj navázaný na jednu firmu nebo jednu organizaci. Všechno má svoje - někdy bývá v Postgresu problém se na něčem domluvit, a některé věci trvají hrozně dlouho. Na druhou stranu u projektu který už existuje skoro 30 let (a kdyby se to počítalo od INGRESu) tak 50 let, pár roků není žádná doba, a výsledek stojí za to (nemluvě o tom, že můžete spolupracovat dost možná s nejlepšími programátory na planetě (a dívat se jim pod prsty)).