Dělicí systém v PostgreSQL byl poprvé přidán v PostgreSQL 8.1 zakladatelem 2ndQuadrantu Simonem Riggsem . Byl založen na dědičnosti vztahů a používal novou techniku k vyloučení tabulek ze skenování dotazem, nazývanou „vyloučení omezení“. I když to byl v té době obrovský krok kupředu, v dnešní době je vnímán jako těžkopádný na používání a také pomalý, a proto potřebuje výměnu.
Ve verzi 10 ji díky hrdinskému úsilí nahradilAmit Langote s „deklarativním rozdělením“ v moderním stylu. Tato nová technologie znamenala, že již nemusíte ručně psát kód pro směrování n-tic do jejich správných oddílů a již nemusíte ručně deklarovat správná omezení pro každý oddíl:systém to udělal automaticky za vás.
Bohužel v PostgreSQL 10 je to skoro vše, co udělal. Kvůli naprosté složitosti a časovému omezení bylo v implementaci PostgreSQL 10 mnoho věcí, které chyběly. Robert Haas o tom hovořil na varšavském PGConf.EU.
Mnoho lidí pracovalo na zlepšení situace pro PostgreSQL 11; zde je můj pokus o přepočet. Rozdělil jsem je do tří oblastí:
- Nové funkce rozdělení
- Lepší podpora DDL pro dělené tabulky
- Optimalizace výkonu.
Nové funkce dělení
V PostgreSQL 10 mohou být vaše rozdělené tabulky v RANGE a LIST režimy. Jedná se o výkonné nástroje, na kterých lze založit mnoho databází z reálného světa, ale pro mnoho dalších návrhů potřebujete nový režim přidaný do PostgreSQL 11:HASH rozdělení . Mnoho zákazníků to potřebuje a Amul Sul tvrdě pracovali, aby to bylo možné. Zde je jednoduchý příklad:
CREATE TABLE clients ( client_id INTEGER, name TEXT ) PARTITION BY HASH (client_id); CREATE TABLE clients_0 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 0); CREATE TABLE clients_1 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 1); CREATE TABLE clients_2 PARTITION OF clients FOR VALUES WITH (MODULUS 3, REMAINDER 2);
Není povinné používat stejnou hodnotu modulu pro všechny příčky; to vám umožní později vytvořit více oddílů a v případě potřeby přerozdělit řádky po jednotlivých oddílech.
Další velmi užitečná funkce, kterou napsal Amit Khandekar je schopnost povolit UPDATE pro přesun řádků z jednoho oddílu do druhého — to znamená, že pokud dojde ke změně hodnot ve sloupci rozdělení, řádek se automaticky přesune do správného oddílu. Dříve by tato operace vyvolala chybu.
Další nová funkce, kterou napsal Amit Langote a skutečně vaše , je to INSERT ON CONFLICT UPDATE lze použít na rozdělené tabulky . Dříve tento příkaz selhal, pokud by cílil na dělenou tabulku. Můžete to udělat tak, že budete přesně vědět, ve kterém oddílu by řádek skončil, ale to není příliš pohodlné. Nebudu probírat detaily tohoto příkazu, ale pokud jste si někdy přáli, abyste měli UPSERT v Postgresu to je ono. Jednou výhradou je, že AKTUALIZACE akce nemusí přesunout řádek do jiného oddílu.
Konečně další roztomilá nová funkce v PostgreSQL 11, tentokrát od Jeevana Ladhe, Beeny Emerson, Ashutoshe Bapat, Rahily Syed, a Robert Haas je podpora výchozího oddílu v rozdělené tabulce , tedy oddíl, který přijímá všechny řádky, které se nevejdou do žádného z běžných oddílů. Ačkoli je tato funkce na papíře pěkná, v produkčním nastavení není příliš vhodná, protože některé operace vyžadují těžší zamykání s výchozími oddíly než bez nich. Příklad:vytvoření nového oddílu vyžaduje skenování výchozího oddílu, aby se zjistilo, že žádné existující řádky neodpovídají hranicím nového oddílu. Možná budou v budoucnu tyto požadavky na zámek sníženy, ale mezitím navrhuji nepoužívat jej.
Lepší podpora DDL
V PostgreSQL 10 by určité DDL odmítly fungovat, když byly aplikovány na tabulku s oddíly, a vyžadovaly, abyste zpracovávali každý oddíl samostatně. V PostgreSQL 11 jsme opravili několik z těchto omezení, jak již dříve oznámil Simon Riggs. Nejprve můžete použít CREATE INDEX na rozdělené tabulce , funkce, kterou jste skutečně napsali vy sami. Toto lze považovat za záležitost snížení zdlouhavosti:místo opakování příkazu pro každý oddíl (a ujištění, že nikdy nezapomenete pro každý nový oddíl), můžete to udělat pouze jednou pro nadřazenou dělenou tabulku a automaticky se použije do všech oddílů, stávajících i budoucích.
Jedna skvělá věc, kterou je třeba mít na paměti, je spárování existujících indexů v oddílech. Jak víte, vytvoření indexu je blokování, takže čím méně času to zabere, tím lépe. Tuto funkci jsem napsal proto, aby byly existující indexy v oddílu porovnány s indexy, které se vytvářejí, a pokud existují shody, není nutné skenovat oddíl za účelem vytvoření nových indexů:použijí se stávající indexy.
Společně s tím, i svým skutečně, můžete také vytvořit UNIQUE omezení a také PRIMÁRNÍ KLÍČ omezení . Dvě upozornění:za prvé, klíč oddílu musí být součástí primárního klíče. To umožňuje, aby byly jedinečné kontroly prováděny lokálně na diskový oddíl, aniž by byly použity globální indexy. Za druhé, zatím není možné mít cizí klíče, které odkazují na tyto primární klíče. Pracuji na tom pro PostgreSQL 12.
Další věc, kterou můžete udělat (díky stejné osobě), je vytvořit PRO KAŽDÝ ŘÁDEK spouští na rozdělené tabulce a nechte jej použít na všechny oddíly (stávající i budoucí). Jako vedlejší účinek můžete mít odložený unikátní omezení u dělených tabulek. Jedno upozornění:pouze PO spouštěče jsou povoleny, dokud nezjistíme, jak se vypořádat s PŘED spouštěče, které přesouvají řádky do jiného oddílu.
A konečně rozdělená tabulka může mít CIZI KLÍČ omezení . To je velmi užitečné pro rozdělení velkých tabulek faktů a zároveň se vyhnete visícím odkazům, které všichni nenávidí. Můj kolega Gabriele Bartolini mě popadl za klín, když zjistil, že jsem to napsal a spáchal, a křičel, že to změnilo hru a jak jsem mohl být tak necitlivý a neinformovat ho o tom. Já, pokračuji v hackování kódu pro zábavu.
Výkonová práce
Dříve bylo předběžné zpracování dotazů s cílem zjistit, které diskové oddíly nekontrolovat (vyloučení omezení) spíše zjednodušující a pomalé. To bylo vylepšeno obdivuhodnou týmovou prací, kterou provedli Amit Langote, David Rowley, Beena Emerson, Dilip Kumar, aby nejprve představili „rychlejší prořezávání“ a poté na něm založené „prořezávání za běhu“. Výsledek je mnohem výkonnější a také rychlejší (David Rowley to již bylo popsáno v předchozím článku.) Po vší této snaze se prořezávání oddílů použije ve třech bodech životnosti dotazu:
- V době plánu dotazu
- Když jsou přijaty parametry dotazu,
- V každém bodě, kde jeden dotazovací uzel předává hodnoty jako parametry jinému uzlu.
Toto je pozoruhodné vylepšení původního systému, které bylo možné použít pouze v době plánování dotazů, a věřím, že to mnohé potěší.
Tuto funkci můžete vidět v akci porovnáním výstupu EXPLAIN pro dotaz před a po vypnutí enable_partition_pruning volba. Jako velmi zjednodušený příklad porovnejte tento plán bez prořezávání:
SET enable_partition_pruning TO off; EXPLAIN (ANALYZE, COSTS off) SELECT * FROM clientes WHERE cliente_id = 1234;
QUERY PLAN ------------------------------------------------------------------------- Append (actual time=6.658..10.549 rows=1 loops=1) -> Seq Scan on clientes_1 (actual time=4.724..4.724 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 24978 -> Seq Scan on clientes_00 (actual time=1.914..1.914 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12644 -> Seq Scan on clientes_2 (actual time=0.017..1.021 rows=1 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12570 -> Seq Scan on clientes_3 (actual time=0.746..0.746 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12448 -> Seq Scan on clientes_01 (actual time=0.648..0.648 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12482 -> Seq Scan on clientes_4 (actual time=0.774..0.774 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12400 -> Seq Scan on clientes_5 (actual time=0.717..0.717 rows=0 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12477 Planning Time: 0.375 ms Execution Time: 10.603 ms
s tím, který se vyrábí s prořezáváním:
EXPLAIN (ANALYZE, COSTS off) SELECT * FROM clientes WHERE cliente_id = 1234;
QUERY PLAN ---------------------------------------------------------------------- Append (actual time=0.054..2.787 rows=1 loops=1) -> Seq Scan on clientes_2 (actual time=0.052..2.785 rows=1 loops=1) Filter: (cliente_id = 1234) Rows Removed by Filter: 12570 Planning Time: 0.292 ms Execution Time: 2.822 ms
Jsem si jistý, že to shledáte přesvědčivým. Prostudováním očekávaného souboru regresních testů můžete vidět spoustu sofistikovanějších příkladů.
Dalším bodem bylo zavedení spojování po částech od Ashutoshe Bapata . Myšlenka je taková, že pokud máte dvě rozdělené tabulky a jsou rozděleny identickým způsobem, pak když jsou spojeny, můžete připojit každý oddíl na jedné straně k odpovídajícímu oddílu na druhé straně; je to mnohem lepší než spojovat každý oddíl na straně s každým oddílem na druhé straně. Skutečnost, že schémata oddílů se musí přesně shodovat může se zdát nepravděpodobné, že by to mělo velké využití v reálném světě, ale ve skutečnosti existuje mnoho situací, kdy to platí. Příklad:tabulka objednávek a jí odpovídající tabulka orders_items. Naštěstí je již spousta práce na uvolnění tohoto omezení.
Poslední položkou, kterou chci zmínit, jsou segmentové agregáty od Jeevana Chalkea, Ashutoshe Bapata, a Robert Haas . Tato optimalizace znamená, že agregace, která zahrnuje klíče oddílů v GROUP BY klauzuli lze provést agregací řádků každého oddílu samostatně, což je mnohem rychlejší.
Úvahy na závěr
Po významném vývoji v tomto cyklu má PostgreSQL mnohem působivější příběh o rozdělení. I když je stále potřeba provést mnoho vylepšení, zejména zlepšit výkon a souběžnost různých operací zahrnujících dělené tabulky, nyní jsme v bodě, kdy se deklarativní dělení stalo velmi cenným nástrojem pro mnoho případů použití. Na 2ndQuadrant budeme i nadále přispívat kódem ke zlepšení PostgreSQL v této oblasti a dalších, jako jsme to dělali u každého vydání od 8.0.