{"id":74953,"date":"2020-03-22T08:42:22","date_gmt":"2020-03-22T05:42:22","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy"},"modified":"2020-03-22T08:42:22","modified_gmt":"2020-03-22T05:42:22","slug":"dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: w\u0142a\u015bciwie organizujemy synchronizacje i importy","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>W przypadku z\u0142o\u017conego przetwarzania du\u017cych zbior\u00f3w danych (r\u00f3\u017cne <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">procesy ETL<\/a><\/noindex>: importy, konwersje i synchronizacje z zewn\u0119trznym \u017ar\u00f3d\u0142em) cz\u0119sto pojawia si\u0119 potrzeba <b>tymczasowego \"zapami\u0119tania\" i szybkiego przetworzenia<\/b> czego\u015b obszernego.<\/p>\n<p>Typowe zadanie tego rodzaju brzmi zazwyczaj mniej wi\u0119cej tak: <i>\"Tu <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">dzia\u0142 ksi\u0119gowo\u015bci wyeksportowa\u0142 z klient-banku<\/a><\/noindex> ostatnie otrzymane p\u0142atno\u015bci, musimy je szybko za\u0142adowa\u0107 na stron\u0119 i powi\u0105za\u0107 z rachunkami\"<\/i><\/p>\n<p>Ale kiedy obj\u0119to\u015b\u0107 tego \u201eczego\u015b\u201d zaczyna by\u0107 mierzona w setkach megabajt\u00f3w, a us\u0142uga musi nadal dzia\u0142a\u0107 z baz\u0105 w trybie 24\/7, pojawia si\u0119 wiele efekt\u00f3w ubocznych, kt\u00f3re b\u0119d\u0105 psu\u0142y wam \u017cycie.<br \/>\n<img decoding=\"async\" alt=\"DBA: w\u0142a\u015bciwie organizujemy synchronizacje i importy\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nAby sobie z nimi poradzi\u0107 w PostgreSQL (i nie tylko tam), mo\u017cna wykorzysta\u0107 pewne mo\u017cliwo\u015bci optymalizacji, kt\u00f3re pozwol\u0105 przetwarza\u0107 wszystko szybciej i przy mniejszym zu\u017cyciu zasob\u00f3w.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. Gdzie \u0142adowa\u0107?<\/h2>\n<p>\nNa pocz\u0105tku ustalmy, gdzie mo\u017cemy za\u0142adowa\u0107 dane, kt\u00f3re chcemy \"przetworzy\u0107\".<\/p>\n<h3>1.1. Tabele tymczasowe (TEMPORARY TABLE)<\/h3>\n<p>\nW zasadzie dla PostgreSQL tabele tymczasowe to te same tabele jak wszystkie inne. Dlatego b\u0142\u0119dne s\u0105 przes\u0105dy typu <i><b>\"wszystko tam jest przechowywane tylko w pami\u0119ci, a ona mo\u017ce si\u0119 sko\u0144czy\u0107\"<\/b><\/i>. Ale s\u0105 r\u00f3wnie\u017c pewne istotne r\u00f3\u017cnice.<\/p>\n<h4>W\u0142asna \"przestrze\u0144 nazw\" dla ka\u017cdego po\u0142\u0105czenia z baz\u0105 danych<\/h4>\n<p>\nJe\u015bli dwa po\u0142\u0105czenia spr\u00f3buj\u0105 jednocze\u015bnie wykona\u0107 <code>CREATE TABLE x<\/code>, to kto\u015b na pewno dostanie <b>b\u0142\u0105d unikalno\u015bci<\/b> obiekt\u00f3w bazy danych.<\/p>\n<p>Je\u015bli jednak obie pr\u00f3by zostan\u0105 wykonane <code>UTW\u00d3RZ <b>Tymczasowa<\/b> Tabela x<\/code>, to obie si\u0119 powiod\u0105, a ka\u017cdy otrzyma <b>sw\u00f3j egzemplarz<\/b> tabeli. Nie b\u0119dzie mi\u0119dzy nimi nic wsp\u00f3lnego.<\/p>\n<h4>\"Autodestrukcja\" przy roz\u0142\u0105czeniu<\/h4>\n<p>\nPo zamkni\u0119ciu po\u0142\u0105czenia wszystkie tabele tymczasowe s\u0105 automatycznie usuwane, dlatego nie ma sensu wykonywa\u0107 <code>DROP TABLE x<\/code> , opr\u00f3cz...<\/p>\n<p>Je\u015bli pracujesz przez <b>pgbouncer w trybie transakcyjnym<\/b>, to baza nadal uwa\u017ca, \u017ce to po\u0142\u0105czenie jest aktywne, a w nim tabela tymczasowa nadal istnieje.<\/p>\n<p>Dlatego pr\u00f3ba ponownego jej utworzenia z innego po\u0142\u0105czenia do pgbouncer spowoduje b\u0142\u0105d. Ale mo\u017cna to obej\u015b\u0107, korzystaj\u0105c z <code>UTW\u00d3RZ TYMCHASOW\u0104 TABEL\u0118 <b>JE\u015aLI NIE ISTNIEJE<\/b> x<\/code>.<\/p>\n<p>Jednak lepiej tego nie robi\u0107, bo p\u00f3\u017aniej mo\u017cna \"nagle\" znale\u017a\u0107 dane pozosta\u0142e po \"poprzednim w\u0142a\u015bcicielu\". Zamiast tego znacznie lepiej przeczyta\u0107 dokumentacj\u0119 i zobaczy\u0107, \u017ce przy tworzeniu tabeli istnieje mo\u017cliwo\u015b\u0107 dodania <code>PRZY ZATWIERDZENIU <b>DROP<\/b><\/code> \u2014 czyli po zako\u0144czeniu transakcji tabela zostanie automatycznie usuni\u0119ta.<\/p>\n<h4>Brak replikacji<\/h4>\n<p>\nZe wzgl\u0119du na to, \u017ce nale\u017cy tylko do okre\u015blonego po\u0142\u0105czenia, tabele tymczasowe nie s\u0105 replikowane. Z kolei <b>pozwala to unikn\u0105\u0107 podw\u00f3jnego zapisu danych<\/b> w heap + WAL, dlatego INSERT\/UPDATE\/DELETE do niej jest znacznie szybsze.<\/p>\n<p>Jednak poniewa\u017c tabela tymczasowa to wci\u0105\u017c \u201eprawie zwyk\u0142a\u201d tabela, nie mo\u017cna jej r\u00f3wnie\u017c utworzy\u0107 na replikacji. Przynajmniej na razie, chocia\u017c odpowiednia \u0142atka jest ju\u017c od dawna przygotowywana.<\/p>\n<h3>1.2. Tabele niezarejestrowane (UNLOGGED TABLE)<\/h3>\n<p>\nAle co zrobi\u0107 na przyk\u0142ad, je\u015bli masz jaki\u015b ci\u0119\u017cki proces ETL, kt\u00f3rego nie da si\u0119 zrealizowa\u0107 w ramach jednej transakcji, a jednak masz <b>pgbouncer w trybie transakcyjnym<\/b>?..<\/p>\n<p>Lub strumie\u0144 danych jest na tyle du\u017cy, \u017ce <b>nie wystarcza przepustowo\u015bci jednego po\u0142\u0105czenia<\/b> z baz\u0105 danych (czytaj, jednego procesu na CPU)?..<\/p>\n<p>Lub cz\u0119\u015b\u0107 operacji przebiega <b>asynchronicznie<\/b> w r\u00f3\u017cnych po\u0142\u0105czeniach?..<\/p>\n<p>Jest tylko jedna opcja \u2014 <b>tymczasowo stworzy\u0107 tabel\u0119 zwyk\u0142\u0105<\/b>. Kalambur, tak. To znaczy:<\/p>\n<ul>\n<li>stw\u00f3rz \u201eswoje\u201d tabele z maksymalnie losowymi nazwami, aby si\u0119 z nikim nie zderzy\u0107<\/li>\n<li><b>Ekspert<\/b>: za\u0142adowa\u0142em w nie dane zewn\u0119trznego \u017ar\u00f3d\u0142a<\/li>\n<li><b>Transformuj<\/b>: przekszta\u0142ci\u0142em, wype\u0142ni\u0142em kluczowe powi\u0105zane pola<\/li>\n<li><b>\u0141adowanie<\/b>: za\u0142adowa\u0142em gotowe dane do docelowych tabel<\/li>\n<li>usun\u0105\u0142em \u201eswoje\u201d tabele<\/li>\n<\/ul>\n<p>\nA teraz \u2014 \u0142y\u017cka dziegciu. W\u0142a\u015bciwie, <b>ca\u0142y zapis w PostgreSQL odbywa si\u0119 dwukrotnie<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">najpierw w WAL<\/a><\/noindex>, a nast\u0119pnie w cia\u0142a tabel\/indeks\u00f3w. Wszystko to zosta\u0142o zrobione w celu wsparcia ACID i poprawnej widoczno\u015bci danych mi\u0119dzy <code>COMMIT<\/code>w\u0142\u0105czonymi i <code>ROLLBACK<\/code>w\u0142\u0105czonymi transakcjami.<\/p>\n<p>Ale to nie jest nam potrzebne! Ca\u0142y proces <b>przeszed\u0142 ca\u0142kowicie pomy\u015blnie lub nie.<\/b>Nie ma znaczenia, ile w nim b\u0119dzie po\u015brednich transakcji \u2014 nie interesuje nas \u201ekontynuowanie procesu z po\u0142owy\u201d, zw\u0142aszcza kiedy nie wiadomo, gdzie to by\u0142o.<\/p>\n<p>W tym celu deweloperzy PostgreSQL ju\u017c w wersji 9.1 wprowadzili co\u015b takiego jak <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">tabele niezarejestrowane (UNLOGGED)<\/a><\/noindex>:<\/p>\n<blockquote><p>Z tym wskazaniem tabela jest tworzona jako niezarejestrowana. Dane zapisywane w tabelach niezarejestrowanych nie przechodz\u0105 przez dziennik przedzapisowy (patrz rozdzia\u0142 29), w wyniku czego takie tabele <b>dzia\u0142aj\u0105 znacznie szybciej ni\u017c zwyk\u0142e<\/b>. Jednak nie s\u0105 chronione przed awari\u0105; w przypadku awarii lub nag\u0142ego wy\u0142\u0105czenia serwera tabela niezarejestrowana <b>zostaje automatycznie uci\u0119ta.<\/b>. Ponadto, zawarto\u015b\u0107 niezarejestrowanej tabeli <b>nie jest replikowana.<\/b> na serwery podrz\u0119dne. Wszelkie indeksy tworzone dla tabeli niezapisanej w dzienniku staj\u0105 si\u0119 automatycznie niezapisane w dzienniku.<\/p><\/blockquote>\n<p>Kr\u00f3tko m\u00f3wi\u0105c, <b>b\u0119dzie znacznie szybsze<\/b>, ale je\u015bli serwer bazy danych \u201epadnie\u201d \u2014 to b\u0119dzie niewygodne. Ale jak cz\u0119sto to si\u0119 zdarza i czy Tw\u00f3j proces ETL umie to poprawnie naprawi\u0107 \u201ez po\u0142owy\u201d po \u201eo\u017cywieniu\u201d bazy danych?..<\/p>\n<p>Je\u015bli nie, a powy\u017cszy przypadek przypomina Tw\u00f3j \u2014 u\u017cyj <code>UNLOGGED<\/code>, ale nigdy <b>nie w\u0142\u0105czaj tego atrybutu w rzeczywistych tabelach<\/b>, z kt\u00f3rych dane s\u0105 dla Ciebie cenne.<\/p>\n<h3>1.3. ON COMMIT { DELETE ROWS | DROP }<\/h3>\n<p>\nTa konstrukcja umo\u017cliwia podczas tworzenia tabeli okre\u015blenie automatycznego zachowania po zako\u0144czeniu transakcji.<\/p>\n<p>O <code>PRZY ZATWIERDZENIU <b>DROP<\/b><\/code> Ju\u017c pisa\u0142em wy\u017cej, generuje <code>DROP TABLE<\/code>, ale w przypadku <code>PRZY ZATWIERDZENIU <b>USU\u0143 WIERSZE<\/b><\/code> sytuacja jest ciekawsza \u2014 tutaj generowane jest <code>TRUNCATE TABLE<\/code>.<\/p>\n<p>Poniewa\u017c ca\u0142a infrastruktura przechowywania metadanych tabeli tymczasowej jest dok\u0142adnie taka sama, jak w przypadku zwyk\u0142ej, to <b>ci\u0105g\u0142e tworzenie i usuwanie tabel tymczasowych prowadzi do znacznego \u201erozrostu\u201d tabel systemowych<\/b> pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/p>\n<p>Wyobra\u017a sobie teraz, \u017ce masz robota na bezpo\u015brednim po\u0142\u0105czeniu z baz\u0105 danych, kt\u00f3ry co sekund\u0119 otwiera now\u0105 transakcj\u0119, tworzy, wype\u0142nia, przetwarza i usuwa tymczasow\u0105 tabel\u0119\u2026 \u015amieci w tabelach systemowych nagromadzi si\u0119 w nadmiarze, a to powoduje dodatkowe op\u00f3\u017anienia przy ka\u017cdej operacji.<\/p>\n<p>Og\u00f3lnie rzecz bior\u0105c, nie r\u00f3b tak! W takim przypadku znacznie skuteczniej <code>CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS<\/code> wyci\u0105gn\u0105\u0107 poza cykl transakcji \u2014 wtedy na pocz\u0105tku ka\u017cdej nowej transakcji tabele ju\u017c <b>b\u0119d\u0105 istnie\u0107<\/b> (oszcz\u0119dzamy wywo\u0142anie <code>UTW\u00d3RZ<\/code>), ale <b>b\u0119d\u0105 puste<\/b>, dzi\u0119ki <code>TRUNCATE<\/code> (jego wywo\u0142anie te\u017c zaoszcz\u0119dzili\u015bmy) po zako\u0144czeniu poprzedniej transakcji.<\/p>\n<h3>1.4. JAK\u2026 W TYM\u00a0\u2026<\/h3>\n<p>\nWspomnia\u0142em na pocz\u0105tku, \u017ce jednym z typowych przypadk\u00f3w u\u017cycia tabel tymczasowych s\u0105 r\u00f3\u017cnego rodzaju importy \u2014 a programista zm\u0119czony kopiowaniem i wklejaniem listy p\u00f3l tabeli docelowej w deklaracji swojej tabeli tymczasowej\u2026<\/p>\n<p>Ale lenistwo jest motorem post\u0119pu! Dlatego <b>utworzenie nowej tabeli \u201ena podstawie\u201d<\/b> mo\u017cna wykona\u0107 znacznie \u0142atwiej:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/code><\/pre>\n<p>\nPoniewa\u017c p\u00f3\u017aniej mo\u017cna wla\u0107 do tej tabeli naprawd\u0119 du\u017co danych, przeszukiwanie jej stanie si\u0119 niezbyt szybkie. Ale jest na to tradycyjne rozwi\u0105zanie \u2014 indeksy! I tak, <b>tabela tymczasowa r\u00f3wnie\u017c mo\u017ce mie\u0107 indeksy<\/b>.<\/p>\n<p>Poniewa\u017c cz\u0119sto wymagane indeksy pokrywaj\u0105 si\u0119 z indeksami tabeli docelowej, mo\u017cna po prostu napisa\u0107 <code>LIKE target_table <b>W tym indeksy<\/b><\/code>.<\/p>\n<p>Je\u015bli potrzebujesz r\u00f3wnie\u017c <code>DOMY\u015aLNY<\/code>-warto\u015bci (np. do wype\u0142nienia warto\u015bci klucza podstawowego), mo\u017cna skorzysta\u0107 <code>LIKE target_table <b>W TYM DOMY\u015aLNE<\/b><\/code>. Albo po prostu \u2014 <code>LIKE target_table <b>W TYM WSZYSTKO<\/b><\/code> \u2014 skopiuje domy\u015blne warto\u015bci, indeksy, constraints,\u2026<\/p>\n<p>Ale tu ju\u017c trzeba zrozumie\u0107, \u017ce je\u015bli tworzy\u0142e\u015b <b>import-tabel\u0119 od razu z indeksami, to \u0142adowanie danych b\u0119dzie trwa\u0142o d\u0142u\u017cej<\/b>, ni\u017c je\u015bli najpierw wszystko za\u0142adujesz, a potem dorobisz indeksy \u2014 zobacz jako przyk\u0142ad, jak to robi <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>Og\u00f3lnie, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Jak pisa\u0107?<\/h2>\n<p>\nPowiem kr\u00f3tko \u2014 u\u017cywaj <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-strumienia zamiast \u201epartii\u201d <code>INSERT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">przyspieszenie w dziesi\u0105tkach razy<\/a><\/noindex>. Mo\u017cna nawet bezpo\u015brednio z wcze\u015bniej przygotowanego pliku.<\/p>\n<h2>3. Jak przetwarza\u0107?<\/h2>\n<p>\nZatem, niech nasz przypadek wygl\u0105da mniej wi\u0119cej tak:<\/p>\n<ul>\n<li>masz w bazie tabel\u0119 z danymi klient\u00f3w na <b>1M rekord\u00f3w<\/b><\/li>\n<li>ka\u017cdego dnia klient przesy\u0142a Ci nowy <b>pe\u0142ny \u201eobraz\u201d<\/b><\/li>\n<li>z do\u015bwiadczenia wiesz, \u017ce z ka\u017cdym razem <b>zmienia si\u0119 nie wi\u0119cej ni\u017c 10K rekord\u00f3w<\/b><\/li>\n<\/ul>\n<p>\nKlasycznym przyk\u0142adem takiej sytuacji jest <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">baza K\u0141ADR<\/a><\/noindex> \u2014 jest ogromna liczba adres\u00f3w, ale w ka\u017cdej cotygodniowej wyeksportowanej wersji zmian (zmiany nazw miejscowo\u015bci, \u0142\u0105czenie ulic, pojawianie si\u0119 nowych dom\u00f3w) jest zaledwie kilka nawet na skal\u0119 ca\u0142ego kraju.<\/p>\n<h3>3.1. Algorytm pe\u0142nej synchronizacji<\/h3>\n<p>\nDla uproszczenia przyjmij, \u017ce nie musisz nawet restrukturyzowa\u0107 danych \u2014 wystarczy przekszta\u0142ci\u0107 tabel\u0119 do odpowiedniego formatu, to znaczy:<\/p>\n<ul>\n<li><b>usun\u0105\u0107<\/b> wszystko, czego ju\u017c nie ma<\/li>\n<li><b>aktualizowa\u0107<\/b> wszystko, co ju\u017c by\u0142o i nale\u017cy zaktualizowa\u0107<\/li>\n<li><b>wstawi\u0107<\/b> wszystko, czego jeszcze nie by\u0142o<\/li>\n<\/ul>\n<p>\nDlaczego w\u0142a\u015bnie w takiej kolejno\u015bci nale\u017cy wykonywa\u0107 operacje? Poniewa\u017c w ten spos\u00f3b rozmiar tabeli wzro\u015bnie minimalnie (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">pami\u0119taj o MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nNie, oczywi\u015bcie mo\u017cna si\u0119 obej\u015b\u0107 tylko dwoma operacjami:<\/p>\n<ul>\n<li><b>usun\u0105\u0107<\/b> (<code>USU\u0143<\/code>) w zasadzie wszystkie<\/li>\n<li><b>wstawi\u0107<\/b> wszystkie z nowego obrazu<\/li>\n<\/ul>\n<p>\nAle przy tym, dzi\u0119ki MVCC, <b>rozmiar tabeli zwi\u0119kszy si\u0119 dok\u0142adnie dwukrotnie<\/b>! Uzyska\u0107 +1M obraz\u00f3w rekord\u00f3w w tabeli z powodu aktualizacji 10K \u2014 to do\u015b\u0107 marnotrawstwo\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nBardziej do\u015bwiadczony programista wie, \u017ce ca\u0142y ca\u0142y st\u00f3\u0142 mo\u017cna wystarczaj\u0105co tanio wyczy\u015bci\u0107:<\/p>\n<ul>\n<li><b>wyczyszczenia<\/b> (<code>TRUNCATE<\/code>) tabel\u0119 ca\u0142kowicie<\/li>\n<li><b>wstawi\u0107<\/b> wszystkie z nowego obrazu<\/li>\n<\/ul>\n<p>\nMetoda skuteczna, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">czasami ca\u0142kiem zastosowalna<\/a><\/noindex>, ale jest pewien problem\u2026 Wlewanie 1M rekord\u00f3w zajmie nam sporo czasu, wi\u0119c nie mo\u017cemy pozwoli\u0107 sobie na umieszczenie tabeli w pustej formie przez ca\u0142y ten czas (jak by si\u0119 sta\u0142o bez opakowania w pojedyncz\u0105 transakcj\u0119).<\/p>\n<p>A zatem:<\/p>\n<ul>\n<li>rozpoczynamy <b>d\u0142ug\u0105 transakcj\u0119<\/b><\/li>\n<li><code>TRUNCATE<\/code> nak\u0142ada <b>AccessExclusive<\/b>-blokad\u0119<\/li>\n<li>d\u0142ugo robimy wstawki, a wszyscy inni w tym czasie <b>nawet nie mog\u0105 <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nCo\u015b nie tak wychodzi\u2026<\/p>\n<h4>ALTER TABLE\u2026 ZMIE\u0143\u2026 \/ USU\u0143 TABEL\u0118 \u2026<\/h4>\n<p>\nMo\u017cna na przyk\u0142ad wgra\u0107 wszystko do osobnej nowej tabeli, a nast\u0119pnie po prostu zmieni\u0107 jej nazw\u0119 na star\u0105. Kilka drobnych, uci\u0105\u017cliwych rzeczy:<\/p>\n<ul>\n<li>tak\u017ce <b>AccessExclusive<\/b>, chocia\u017c znacznie mniej czasoch\u0142onnie<\/li>\n<li>wszystkie plany zapyta\u0144\/statystyki tej tabeli zostan\u0105 zresetowane, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">nale\u017cy uruchomi\u0107 ANALYZE<\/a><\/noindex><\/li>\n<li><b>wszystkie klucze obce<\/b> (FK) do tabeli<\/li>\n<\/ul>\n<p>\nBy\u0142 WIP-patch od Simona Riggsa, kt\u00f3ry proponowa\u0142 <code>ALTER<\/code>-operacj\u0119 do podmiany tre\u015bci tabeli na poziomie plik\u00f3w, bez dotykania statystyk i FK, ale nie uzyska\u0142 wymaganego kworum.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nWi\u0119c decydujemy si\u0119 na nieblokuj\u0105c\u0105 opcj\u0119 z trzech operacji. Prawie trzech\u2026 Jak to zrobi\u0107 w najbardziej efektywny spos\u00f3b?<\/p>\n<pre><code class=\"sql\">-- wszystko robimy w ramach transakcji, aby nikt nie widzia\u0142 \"po\u015brednich\" stan\u00f3w\nBEGIN;\n\n-- tworzymy tymczasow\u0105 tabel\u0119 z importowanymi danymi\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- na wz\u00f3r, razem z indeksami\n) ON COMMIT DROP; -- poza transakcj\u0105 nie jest nam potrzebna\n\n-- szybko wlewamy nowy obraz przez COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- usuwamy brakuj\u0105ce\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- pola klucza podstawowego\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- \"antijoin\"\n\n-- aktualizujemy pozosta\u0142e\nUPDATE\n  dst D\nSET\n  (f1, f2, f3) = (T.f1, T.f2, T.f3)\nFROM\n  tmp T\nWHERE\n  (D.pk1, D.pk2) = (T.pk1, T.pk2) AND\n  (D.f1, D.f2, D.f3) IS DISTINCT FROM (T.f1, T.f2, T.f3); -- nie ma sensu aktualizowa\u0107 dopasowanych\n\n-- wstawiamy brakuj\u0105ce\nINSERT INTO\n  dst\nSELECT\n  T.*\nFROM\n  tmp T\nLEFT JOIN\n  dst D\n    USING(pk1, pk2)\nWHERE\n  D IS NOT DISTINCT FROM NULL;\n\nCOMMIT;\n<\/code><\/pre>\n<p><\/p>\n<h3>3.2. Postprocessing importu<\/h3>\n<p>\nW tym samym KLADe, wszystkie zmienione rekordy nale\u017cy dodatkowo przepu\u015bci\u0107 przez postprocessing \u2014 znormalizowa\u0107, wydoby\u0107 s\u0142owa kluczowe, dostosowa\u0107 do potrzebnych struktur. Ale jak dowiedzie\u0107 si\u0119 \u2014 <b>co dok\u0142adnie si\u0119 zmienia\u0142o<\/b>, nie komplikuj\u0105c przy tym kodu synchronizacji, najlepiej w og\u00f3le go nie dotykaj\u0105c?<\/p>\n<p>Je\u015bli w momencie synchronizacji tylko twoje procesy maj\u0105 dost\u0119p do zapisu, mo\u017cna skorzysta\u0107 z triggera, kt\u00f3ry zbierze dla nas wszystkie zmiany:<\/p>\n<pre><code class=\"sql\">-- tabele docelowe\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- tabele z histori\u0105 zmian\nCREATE TABLE kladr$log(\n  ro kladr, -- tutaj znajduj\u0105 si\u0119 pe\u0142ne obrazy rekord\u00f3w starych\/nowych\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- og\u00f3lna funkcja logowania zmian\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- sprawdzamy konieczno\u015b\u0107 logowania przy aktualizacji rekordu\n  IF TG_OP = 'UPDATE' THEN\n    IF NEW IS NOT DISTINCT FROM OLD THEN\n      RETURN NEW;\n    END IF;\n  END IF;\n  -- tworzymy rekord logu\n  stmt = 'INSERT INTO ' || dst::text || '(ro,rn)VALUES(';\n  CASE TG_OP\n    WHEN 'INSERT' THEN\n      EXECUTE stmt || 'NULL,$1)' USING NEW;\n    WHEN 'UPDATE' THEN\n      EXECUTE stmt || '$1,$2)' USING OLD, NEW;\n    WHEN 'DELETE' THEN\n      EXECUTE stmt || '$1,NULL)' USING OLD;\n  END CASE;\n  RETURN NEW;\nEND;\n$$ LANGUAGE plpgsql;\n<\/code><\/pre>\n<p>\nTeraz mo\u017cemy na\u0142o\u017cy\u0107 (lub w\u0142\u0105czy\u0107 przez) wyzwalacze przed rozpocz\u0119ciem synchronizacji <code>ALTER TABLE ... ENABLE TRIGGER ...<\/code>):<\/p>\n<pre><code class=\"sql\">CREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n\nCREATE TRIGGER log\n  AFTER INSERT OR UPDATE OR DELETE\n  ON kladr_house\n    FOR EACH ROW\n      EXECUTE PROCEDURE diff$log();\n<\/code><\/pre>\n<p>\nA potem spokojnie wyci\u0105gamy wszystkie potrzebne nam zmiany z tabel log, a nast\u0119pnie uruchamiamy dodatkowe przetwarzacze.<\/p>\n<h3>3.3. Import powi\u0105zanych zestaw\u00f3w<\/h3>\n<p>\nPowy\u017cej om\u00f3wili\u015bmy przypadki, w kt\u00f3rych struktury danych \u017ar\u00f3d\u0142a i odbiorcy s\u0105 zgodne. Ale co zrobi\u0107, je\u015bli eksport z zewn\u0119trznego systemu ma format r\u00f3\u017cni\u0105cy si\u0119 od struktury przechowywania w naszej bazie?<\/p>\n<p>We\u017amy za przyk\u0142ad przechowywanie klient\u00f3w i zwi\u0105zanych z nimi faktur, klasyczny przypadek \u201ewiele-do-jednego\u201d:<\/p>\n<pre><code class=\"sql\">CREATE TABLE client(\n  client_id\n    serial\n      PRIMARY KEY\n, inn\n    varchar\n      UNIQUE\n, name\n    varchar\n);\n\nCREATE TABLE invoice(\n  invoice_id\n    serial\n      PRIMARY KEY\n, client_id\n    integer\n      REFERENCES client(client_id)\n, number\n    varchar\n, dt\n    date\n, sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nA oto eksport z zewn\u0119trznego \u017ar\u00f3d\u0142a przychodzi do nas w formie \u201ewszystko w jednym\u201d:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE invoice_import(\n  client_inn\n    varchar\n, client_name\n    varchar\n, invoice_number\n    varchar\n, invoice_dt\n    date\n, invoice_sum\n    numeric(32,2)\n);<\/code><\/pre>\n<p>\nOczywi\u015bcie dane dotycz\u0105ce klient\u00f3w mog\u0105 by\u0107 zdublowane w takim wariancie, a podstawowym rekordem jest \u201efaktura\u201d:<\/p>\n<pre><code class=\"plaintext\">0123456789;Wania;A-01;2020-03-16;1000.00\n9876543210;Petia;A-02;2020-03-16;666.00\n0123456789;Wania;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nDla modelu po prostu wstawimy nasze dane testowe, ale pami\u0119tajmy \u2014 <code>COPY<\/code> efektywniej!<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Wania', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Petia', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Wania', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nNajpierw wyodr\u0119bnijmy te \u201ekategorie\u201d, na kt\u00f3re nasze \u201efakty\u201d si\u0119 odnosz\u0105. W naszym przypadku faktury odnosz\u0105 si\u0119 do klient\u00f3w:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- mo\u017cna po prostu SELECT DISTINCT, je\u015bli dane s\u0105 oczywiste\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nAby poprawnie powi\u0105za\u0107 faktury z identyfikatorami klient\u00f3w, musimy najpierw pozna\u0107 lub wygenerowa\u0107 te identyfikatory. Dodamy dla nich pola:<\/p>\n<pre><code class=\"sql\">ALTER TABLE invoice_import DODAJ KOLUMN\u0118 client_id integer;\nALTER TABLE client_import DODAJ KOLUMN\u0118 client_id integer;<\/code><\/pre>\n<p>\nSkorzystamy z opisanego powy\u017cej sposobu synchronizacji tabel z niewielk\u0105 poprawk\u0105 \u2014 nie b\u0119dziemy nic aktualizowa\u0107 ani usuwa\u0107 w docelowej tabeli, poniewa\u017c import klient\u00f3w jest dla nas \"tylko do dodawania\":<\/p>\n<pre><code class=\"sql\">-- wstawiamy w tabeli importu ID ju\u017c istniej\u0105cych rekord\u00f3w\nUPDATE\n  client_import T\nUSTAW\n  client_id = D.client_id\nZ\n  client D\nGDZIE\n  T.inn = D.inn; -- unikalny klucz\n\n-- wstawiamy brakuj\u0105ce rekordy i ustawiamy ich ID\nWITH ins AS (\n  WSTAW DO client(\n    inn\n  , name\n  )\n  WYBIERZ\n    inn\n  , name\n  Z\n    client_import\n  GDZIE\n    client_id IS NULL -- je\u015bli ID nie by\u0142o ustawione\n  ZWR\u00d3\u0106 *\n)\nUPDATE\n  client_import T\nUSTAW\n  client_id = D.client_id\nZ\n  ins D\nGDZIE\n  T.inn = D.inn; -- unikalny klucz\n\n-- ustawiamy ID klient\u00f3w dla rekord\u00f3w faktur\nUPDATE\n  invoice_import T\nUSTAW\n  client_id = D.client_id\nZ\n  client_import D\nGDZIE\n  T.client_inn = D.inn; -- klucz aplikacyjny\n<\/code><\/pre>\n<p>\nW\u0142a\u015bciwie to wszystko \u2014 w <code>invoice_import<\/code> teraz mamy wype\u0142nione pole powi\u0105zania <code>client_id<\/code>, z kt\u00f3rym wstawimy faktur\u0119.<br \/>\n<br \/>\u0179r\u00f3d\u0142o: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/492464\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e \u0432\u043e\u0437\u043d\u0438\u043a\u0430\u0435\u0442 \u043d\u0435\u043e\u0431\u0445\u043e\u0434\u0438\u043c\u043e\u0441\u0442\u044c \u0432\u0440\u0435\u043c\u0435\u043d\u043d\u043e \u00ab\u0437\u0430\u043f\u043e\u043c\u043d\u0438\u0442\u044c\u00bb, \u0438 \u0441\u0440\u0430\u0437\u0443 \u0431\u044b\u0441\u0442\u0440\u043e \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u0430\u0442\u044c \u0447\u0442\u043e-\u0442\u043e \u043e\u0431\u044a\u0435\u043c\u043d\u043e\u0435. \u0422\u0438\u043f\u043e\u0432\u0430\u044f \u0437\u0430\u0434\u0430\u0447\u0430 \u043f\u043e\u0434\u043e\u0431\u043d\u043e\u0433\u043e \u0440\u043e\u0434\u0430 \u0437\u0432\u0443\u0447\u0438\u0442 \u043e\u0431\u044b\u0447\u043d\u043e \u043f\u0440\u0438\u043c\u0435\u0440\u043d\u043e \u0442\u0430\u043a: \u00ab\u0412\u043e\u0442 \u0442\u0443\u0442 \u0431\u0443\u0445\u0433\u0430\u043b\u0442\u0435\u0440\u0438\u044f \u0432\u044b\u0433\u0440\u0443\u0437\u0438\u043b\u0430 \u0438\u0437 \u043a\u043b\u0438\u0435\u043d\u0442-\u0431\u0430\u043d\u043a\u0430 \u043f\u043e\u0441\u043b\u0435\u0434\u043d\u0438\u0435 \u043f\u043e\u0441\u0442\u0443\u043f\u0438\u0432\u0448\u0438\u0435 \u043e\u043f\u043b\u0430\u0442\u044b, \u043d\u0430\u0434\u043e \u0438\u0445 \u0431\u044b\u0441\u0442\u0440\u0435\u043d\u044c\u043a\u043e \u0432\u043a\u0430\u0447\u0430\u0442\u044c \u043d\u0430 \u0441\u0430\u0439\u0442 \u0438 \u043f\u0440\u0438\u0432\u044f\u0437\u0430\u0442\u044c \u043a \u0441\u0447\u0435\u0442\u0430\u043c\u00bb \u041d\u043e \u043a\u043e\u0433\u0434\u0430 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":74954,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-74953","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-administrirovanie"],"aioseo_notices":[],"aioseo_head":"\n\t\t<!-- All in One SEO 5.0.1.1 - aioseo.com -->\n\t<meta name=\"description\" content=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\n\t<meta name=\"robots\" content=\"max-image-preview:large\" \/>\n\t<meta name=\"author\" content=\"Yuri Gagarin\"\/>\n\t<link rel=\"canonical\" href=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.1.1\" \/>\n\t\t<meta property=\"og:locale\" content=\"pl_PL\" \/>\n\t\t<meta property=\"og:site_name\" content=\"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b\" \/>\n\t\t<meta property=\"og:type\" content=\"article\" \/>\n\t\t<meta property=\"og:title\" content=\"\ud83e\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t\t<meta property=\"og:image\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:secure_url\" content=\"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg\" \/>\n\t\t<meta property=\"og:image:width\" content=\"350\" \/>\n\t\t<meta property=\"og:image:height\" content=\"350\" \/>\n\t\t<meta property=\"article:published_time\" content=\"2020-03-22T05:42:22+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-03-22T05:42:22+00:00\" \/>\n\t\t<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<meta property=\"article:author\" content=\"https:\/\/www.facebook.com\/prohoster\" \/>\n\t\t<!-- All in One SEO -->\n\n","aioseo_head_json":{"title":"\ud83e\udd47DBA: poprawnie organizujemy synchronizacje i importy | ProHoster","description":"Podczas skomplikowanej obr\u00f3bki du\u017cych zbior\u00f3w danych (r\u00f3\u017cne procesy ETL: importy, konwersje i synchronizacje z zewn\u0119trznym \u017ar\u00f3d\u0142em) cz\u0119sto.","canonical_url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pl_PL","og:site_name":"ProHoster | \u041a\u0443\u043f\u0438\u0442\u044c \u043d\u0430\u0434\u0435\u0436\u043d\u044b\u0439 \u0445\u043e\u0441\u0442\u0438\u043d\u0433 \u0434\u043b\u044f \u0441\u0430\u0439\u0442\u043e\u0432 \u0441 \u0437\u0430\u0449\u0438\u0442\u043e\u0439 \u043e\u0442 DDoS, VPS VDS \u0441\u0435\u0440\u0432\u0435\u0440\u044b","og:type":"article","og:title":"\ud83e\udd47DBA: \u0433\u0440\u0430\u043c\u043e\u0442\u043d\u043e \u043e\u0440\u0433\u0430\u043d\u0438\u0437\u043e\u0432\u044b\u0432\u0430\u0435\u043c \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0438 \u0438\u043c\u043f\u043e\u0440\u0442\u044b | ProHoster","og:description":"\u041f\u0440\u0438 \u0441\u043b\u043e\u0436\u043d\u043e\u0439 \u043e\u0431\u0440\u0430\u0431\u043e\u0442\u043a\u0435 \u0431\u043e\u043b\u044c\u0448\u0438\u0445 \u043d\u0430\u0431\u043e\u0440\u043e\u0432 \u0434\u0430\u043d\u043d\u044b\u0445 (\u0440\u0430\u0437\u043d\u044b\u0435 ETL-\u043f\u0440\u043e\u0446\u0435\u0441\u0441\u044b: \u0438\u043c\u043f\u043e\u0440\u0442\u044b, \u043a\u043e\u043d\u0432\u0435\u0440\u0442\u0430\u0446\u0438\u0438 \u0438 \u0441\u0438\u043d\u0445\u0440\u043e\u043d\u0438\u0437\u0430\u0446\u0438\u0438 \u0441 \u0432\u043d\u0435\u0448\u043d\u0438\u043c \u0438\u0441\u0442\u043e\u0447\u043d\u0438\u043a\u043e\u043c) \u0447\u0430\u0441\u0442\u043e.","og:url":"https:\/\/prohoster.info\/pl\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","og:image":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:secure_url":"https:\/\/prohoster.info\/wp-content\/uploads\/2021\/11\/logo-350.jpg","og:image:width":350,"og:image:height":350,"article:published_time":"2020-03-22T05:42:22+00:00","article:modified_time":"2020-03-22T05:42:22+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"74953","title":null,"description":null,"keywords":null,"keyphrases":null,"primary_term":null,"canonical_url":null,"og_title":null,"og_description":null,"og_object_type":"default","og_image_type":"default","og_image_url":null,"og_image_width":null,"og_image_height":null,"og_image_custom_url":null,"og_image_custom_fields":null,"og_video":null,"og_custom_url":null,"og_article_section":null,"og_article_tags":null,"twitter_use_og":false,"twitter_card":"default","twitter_image_type":"default","twitter_image_url":null,"twitter_image_custom_url":null,"twitter_image_custom_fields":null,"twitter_title":null,"twitter_description":null,"schema":{"blockGraphs":[],"customGraphs":[],"default":{"data":{"Article":[],"Course":[],"Dataset":[],"FAQPage":[],"Movie":[],"Person":[],"Product":[],"ProductReview":[],"Car":[],"Recipe":[],"Service":[],"SoftwareApplication":[],"WebPage":[]},"graphName":"","isEnabled":true},"graphs":[]},"schema_type":null,"schema_type_options":null,"pillar_content":false,"robots_default":true,"robots_noindex":false,"robots_noarchive":false,"robots_nosnippet":false,"robots_nofollow":false,"robots_noimageindex":false,"robots_noodp":false,"robots_notranslate":false,"robots_max_snippet":null,"robots_max_videopreview":null,"robots_max_imagepreview":"large","priority":null,"frequency":null,"local_seo":null,"seo_analyzer_scan_date":null,"breadcrumb_settings":null,"limit_modified_date":false,"reviewed_by":null,"ai":null,"created":"2021-02-28 18:04:26","updated":"2022-09-30 13:25:20","focus_keyword":null,"additional_keywords":null,"truseo_locale":null},"gt_translate_keys":[{"key":"link","format":"url"}],"_links":{"self":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pl\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}