Goldfields Pipeline, od SeanMac (Wikimedia Commons)
Pokud se snažíte optimalizovat výkon své aplikace založené na PostgreSQL, pravděpodobně se zaměřujete na obvyklé nástroje:EXPLAIN (BUFFERS, ANALYZE) , pg_stat_statements , auto_explain , log_statement_min_duration , atd.Možná se díváte na spor o zámek s log_lock_waits , sledování výkonu vašeho kontrolního bodu atd.
Ale přemýšleli jste o latenci sítě? ? Hráči vědí o latenci sítě, ale mysleli jste si, že je to důležité pro váš aplikační server?
Na latenci záleží
Typické zpoždění sítě klient/server se může pohybovat od 0,01 ms (lokální hostitel) až po ~0,5 ms přepínané sítě, 5 ms WiFi, 20 ms ADSL, 300 ms mezikontinentálního směrování a ještě více pro věci jako satelitní a WWAN spojení. .
Triviální SELECT spuštění na straně serveru může trvat řádově 0,1 ms. Triviální INSERT může trvat 0,5 ms.
Pokaždé, když vaše aplikace spustí dotaz, musí čekat na odpověď serveru s úspěšnou/neúspěšnou odpovědí a případně se sadou výsledků, metadaty dotazu atd. To způsobí nejméně jedno síťové zpoždění.
Když pracujete s malými, jednoduchými dotazy, latence sítě může být významná vzhledem k době provádění vašich dotazů, pokud vaše databáze není na stejném hostiteli jako vaše aplikace.
Mnoho aplikací, zejména ORM, je velmi náchylných ke spuštění
Podobně, pokud naplňujete databázi z ORM, pravděpodobně děláte statisíce triviálních INSERT s… a po každé počkejte, až server potvrdí, že je to v pořádku.
Je snadné pokusit se zaměřit na dobu provádění dotazu a pokusit se ji optimalizovat, ale s triviálním INSERT INTO ...VALUES ... toho můžete udělat jen tolik. . Zrušte některé indexy a omezení, ujistěte se, že je to dávkové do transakce, a máte hotovo.
Co takhle zbavit se všech čekání na síti? Dokonce i na LAN začnou sčítat přes tisíce dotazů.
KOPÍROVAT
Jedním ze způsobů, jak se vyhnout latenci, je použít COPY . Chcete-li použít podporu COPY PostgreSQL, vaše aplikace nebo ovladač musí vytvořit sadu řádků podobnou CSV a streamovat je na server v nepřetržitém pořadí. Nebo může být server požádán, aby vaší aplikaci odeslal stream podobný CSV.
Ať tak či onak, aplikace nemůže prokládat COPY s jinými dotazy a kopie-vložení musí být načteno přímo do cílové tabulky. Běžným přístupem je KOPÍROVAT do dočasné tabulky a odtud proveďte INSERT INTO ... SELECT ... , AKTUALIZACE ... OD .... , SMAZAT Z ... POUŽÍVÁNÍM... , atd. k použití zkopírovaných dat k úpravě hlavních tabulek v jediné operaci.
To je užitečné, pokud píšete své vlastní SQL přímo, ale mnoho aplikačních frameworků a ORM to nepodporuje a navíc může přímo nahradit jednoduché INSERT . Vaše aplikace, framework nebo klientský ovladač se musí vypořádat s konverzí pro speciální reprezentaci potřebnou pro COPY , vyhledejte všechna požadovaná metadata typu sama o sobě atd.
(Významné ovladače, které dělají podpora KOPÍROVAT zahrnují libpq, PgJDBC, psycopg2 a klenot Pg... ale ne nutně rámce a ORM postavené na nich.)
PgJDBC – dávkový režim
Ovladač JDBC PostgreSQL má řešení tohoto problému. Spoléhá na podporu na serverech PostgreSQL od verze 8.4 a na dávkové funkce rozhraní JDBC API pro odeslání dávky dotazů na server pak počkejte pouze jednou na potvrzení, že celá dávka proběhla v pořádku.
No, teoreticky. Ve skutečnosti to některé implementační problémy omezují, takže dávky lze provádět pouze v kouscích několika stovek dotazů v nejlepším případě. Ovladač může také spouštět dotazy, které vracejí řádky výsledků v dávkových blocích, pokud dokáže předem zjistit, jak velké budou výsledky. Navzdory těmto omezením použijte Statement.executeBatch() může nabídnout obrovské zvýšení výkonu aplikacím, které provádějí úkoly, jako je hromadné načítání dat vzdálené instance databáze.
Protože se jedná o standardní API, mohou jej používat aplikace, které pracují s více databázovými stroji. Hibernate například může používat dávkování JDBC, ačkoli to ve výchozím nastavení nedělá.
libpq a dávkování
Většina (všechny?) ostatních ovladačů PostgreSQL nemá podporu pro dávkování. PgJDBC implementuje protokol PostgreSQL zcela nezávisle, zatímco většina ostatních ovladačů interně používá knihovnu C libpq který je dodáván jako součást PostgreSQL.
libpq nepodporuje dávkování. Má asynchronní neblokující API, ale klient může mít stále pouze jeden dotaz „za letu“. Musí počkat, až obdrží výsledky tohoto dotazu, než bude moci odeslat další.
PostgreSQL server podporuje dávkování v pohodě a PgJDBC to již používá. Napsal jsem tedy dávkovou podporu pro libpq a předložil jej jako kandidáta na další verzi PostgreSQL. Vzhledem k tomu, že změní pouze klienta, pokud bude přijat, stále to zrychlí věci při připojování ke starším serverům.
Opravdu by mě zajímala zpětná vazba od autorů a pokročilých uživatelů libpq klientské ovladače a vývojáři libpq -založené aplikace. Pokud si ji chcete vyzkoušet, oprava se v pořádku aplikuje na PostgreSQL 9.6beta1. Dokumentace je podrobná a obsahuje komplexní ukázkový program.
Výkon
Myslel jsem, že hostovaná databázová služba, jako je RDS nebo Heroku Postgres, by mohla být dobrým příkladem toho, kde by byla tato funkce užitečná. Zejména přístup k nim z naší vlastní sítě skutečně ukazuje, jak velká latence může bolet.
Při latenci sítě ~320 ms:
- 500 příloh bez dávkování:
167,0 s - 500 příloh s dávkováním:
1,2 s
… což je více než 120x rychlejší.
Aplikaci obvykle nebudete spouštět přes mezikontinentální propojení mezi aplikačním serverem a databází, ale to slouží ke zdůraznění dopadu latence. Dokonce i přes unixový socket na localhost jsem viděl více než 50% zlepšení výkonu pro 10 000 insertů.
Dávkování ve stávajících aplikacích
U stávajících aplikací bohužel není možné automaticky povolit dávkování. Aplikace musí používat trochu jiné rozhraní, kde odesílají řadu dotazů a teprve poté žádají o výsledky.
Mělo by být poměrně jednoduché přizpůsobit aplikace, které již používají asynchronní rozhraní libpq, zvláště pokud používají neblokovací režim a select() /poll() /epoll() /WaitForMultipleObjectsEx smyčka. Aplikace, které používají synchronní libpq rozhraní budou vyžadovat více změn.
Dávkování v jiných klientských ovladačích
Podobně klientské ovladače, rámce a ORM budou obecně potřebovat rozhraní a interní změny, aby bylo možné používat dávkování. Pokud již používají smyčku událostí a neblokující I/O, mělo by být poměrně snadné je upravit.
Rád bych viděl, aby uživatelé Pythonu, Ruby atd. měli přístup k této funkci, takže jsem zvědavý, kdo má zájem. Představte si, že byste mohli udělat toto:
import psycopg2 conn = psycopg2.connect(...) cur = conn.cursor() # this is just an idea, this code does not work with psycopg2: futures = [ cur.async_execute(sql) for sql in my_queries ] for future in futures: result = future.result # waits if result not ready yet ... process the result ... conn.commit()
Asynchronní dávkové spouštění nemusí být na úrovni klienta složité.
KOPÍROVÁNÍ je nejrychlejší
Tam, kde by praktičtí klienti měli stále upřednostňovat COPY . Zde jsou některé výsledky z mého notebooku:
inserting 1000000 rows batched, unbatched and with COPY batch insert elapsed: 23.715315s sequential insert elapsed: 36.150162s COPY elapsed: 1.743593s Done.
Dávkování práce poskytuje překvapivě velké zvýšení výkonu i při připojení místního unixového socketu…. ale KOPÍROVAT nechává oba jednotlivé přístupy vložek daleko za sebou v prachu.
Použijte COPY .
Obrázek
Obrázek pro tento příspěvek je z ropovodu Goldfields Water Supply Scheme z Mundaring Weir poblíž Perthu v západní Austrálii do vnitrozemských (pouštních) zlatých polí. Je relevantní, protože jeho dokončení trvalo tak dlouho a bylo vystaveno tak intenzivní kritice, že jeho návrhář a hlavní zastánce C. Y. O’Connor spáchal sebevraždu 12 měsíců před uvedením do provozu. Místní lidé často (nesprávně) říkají, že zemřel