{"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\/pt\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","title":{"rendered":"DBA: organizando de forma eficaz sincronia e importa\u00e7\u00f5es","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p>Ao lidar com grandes conjuntos de dados complexos (diferentes <noindex><a rel=\"nofollow\" href=\"https:\/\/ru.wikipedia.org\/wiki\/ETL\">Processos ETL<\/a><\/noindex>: importa\u00e7\u00f5es, convers\u00f5es e sincroniza\u00e7\u00f5es com fontes externas), muitas vezes surge a necessidade <b>de \"lembrar\" temporariamente e processar rapidamente<\/b> algo volumoso.<\/p>\n<p>A tarefa t\u00edpica desse tipo geralmente soa mais ou menos assim: <i>\"Aqui, <noindex><a rel=\"nofollow\" href=\"https:\/\/sbis.ru\/accounting\">a contabilidade exportou do banco de clientes<\/a><\/noindex> os \u00faltimos pagamentos recebidos, precisamos carreg\u00e1-los rapidamente no site e vincul\u00e1-los \u00e0s contas\"<\/i><\/p>\n<p>Mas quando o volume desse \"algo\" come\u00e7a a ser medido em centenas de megabytes, enquanto o servi\u00e7o deve continuar operando com a base em modo 24\/7, surgem muitos efeitos colaterais que podem atrapalhar sua vida.<br \/>\n<img decoding=\"async\" alt=\"DBA: organizando de forma eficaz sincronia e importa\u00e7\u00f5es\" src=\"\/wp-content\/uploads\/2020\/03\/f74afb2cd6f5f8de26a0932166933c95.jpg\" style=\"display:block;margin: 0 auto;\" \/><br \/>\nPara lidar com eles no PostgreSQL (e n\u00e3o apenas nele), podemos usar alguns recursos de otimiza\u00e7\u00e3o que permitir\u00e3o processar tudo mais rapidamente e com menor consumo de recursos.<br \/>\n<noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<h2>1. Onde carregar?<\/h2>\n<p>\nPrimeiro, vamos definir onde podemos carregar os dados que queremos \"processar\".<\/p>\n<h3>1.1. Tabelas tempor\u00e1rias (TEMPORARY TABLE)<\/h3>\n<p>\nBasicamente, para o PostgreSQL, tempor\u00e1rios s\u00e3o tabelas como qualquer outra. Portanto, n\u00e3o \u00e9 verdade que existam supersti\u00e7\u00f5es do tipo <i><b>\"tudo \u00e9 armazenado apenas na mem\u00f3ria, e ela pode acabar\".<\/b><\/i>Mas existem algumas diferen\u00e7as significativas.<\/p>\n<h4>Seu \"namespace\" para cada conex\u00e3o ao BD<\/h4>\n<p>\nSe duas conex\u00f5es tentarem executar ao mesmo tempo <code>CREATE TABLE x<\/code>, ent\u00e3o algu\u00e9m definitivamente receber\u00e1 <b>um erro de n\u00e3o unicidade<\/b> de objetos BD.<\/p>\n<p>Mas se ambos tentarem executar <code>CREATE <b>TEMPOR\u00c1RIO<\/b> TABELA x<\/code>, ambos far\u00e3o isso normalmente, e cada um receber\u00e1 <b>sua pr\u00f3pria inst\u00e2ncia<\/b> da tabela. E nada em comum entre elas.<\/p>\n<h4>\"Auto-destrui\u00e7\u00e3o\" ao desconectar<\/h4>\n<p>\nAo fechar a conex\u00e3o, todas as tabelas tempor\u00e1rias s\u00e3o automaticamente removidas, ent\u00e3o n\u00e3o faz sentido executar manualmente <code>DROP TABLE x<\/code> , exceto por\u2026<\/p>\n<p>Se voc\u00ea estiver trabalhando atrav\u00e9s <b>do pgbouncer no modo de transa\u00e7\u00e3o<\/b>, o banco ainda acredita que essa conex\u00e3o ainda est\u00e1 ativa, e nela essa tabela tempor\u00e1ria ainda existe.<\/p>\n<p>Portanto, a tentativa de cri\u00e1-la novamente, j\u00e1 de outra conex\u00e3o ao pgbouncer, resultar\u00e1 em erro. Mas isso pode ser contornado, usando <code>CRIE UMA TABELA TEMPOR\u00c1RIA <b>SE N\u00c3O EXISTIR<\/b> x<\/code>.<\/p>\n<p>Embora seja melhor n\u00e3o fazer isso, porque voc\u00ea pode \"repentinamente\" descobrir dados restantes do \"propriet\u00e1rio anterior\". Em vez disso, \u00e9 muito melhor ler o manual e verificar que ao criar a tabela h\u00e1 uma op\u00e7\u00e3o para adicionar <code>AO COMMIT <b>DROP<\/b><\/code> \u2014 ou seja, ao final da transa\u00e7\u00e3o, a tabela ser\u00e1 deletada automaticamente.<\/p>\n<h4>N\u00e3o-replica\u00e7\u00e3o<\/h4>\n<p>\nDevido \u00e0 sua associa\u00e7\u00e3o a uma conex\u00e3o espec\u00edfica, tabelas tempor\u00e1rias n\u00e3o s\u00e3o replicadas. No entanto, <b>isso elimina a necessidade de gravar os dados duas vezes<\/b> em heap + WAL, tornando as opera\u00e7\u00f5es de INSERT\/UPDATE\/DELETE muito mais r\u00e1pidas.<\/p>\n<p>Mas como uma tabela tempor\u00e1ria \u00e9, na verdade, uma tabela \"quase normal\", ela tamb\u00e9m n\u00e3o pode ser criada na r\u00e9plica. Pelo menos, n\u00e3o ainda, embora o patch correspondente j\u00e1 esteja em circula\u00e7\u00e3o h\u00e1 muito tempo.<\/p>\n<h3>1.2. Tabelas n\u00e3o registradas (UNLOGGED TABLE)<\/h3>\n<p>\nMas o que fazer, por exemplo, se voc\u00ea tem algum processo ETL complexo que n\u00e3o consegue ser realizado dentro de uma \u00fanica transa\u00e7\u00e3o, e voc\u00ea realmente <b>do pgbouncer no modo de transa\u00e7\u00e3o<\/b>?..<\/p>\n<p>Ou se o fluxo de dados \u00e9 t\u00e3o grande que <b>n\u00e3o h\u00e1 largura de banda suficiente de uma \u00fanica conex\u00e3o<\/b> com o banco de dados (leia-se, um \u00fanico processo no CPU)?..<\/p>\n<p>Ou se parte das opera\u00e7\u00f5es ocorre <b>ass\u00edncronamente<\/b> em diferentes conex\u00f5es?..<\/p>\n<p>Aqui a \u00fanica op\u00e7\u00e3o \u00e9 \u2014 <b>criar temporariamente uma tabela n\u00e3o tempor\u00e1ria<\/b>. Um trocadilho, sim. Ou seja:<\/p>\n<ul>\n<li>criei \"minhas\" tabelas com nomes o mais aleat\u00f3rios poss\u00edvel, para n\u00e3o me confundir com ningu\u00e9m<\/li>\n<li><b>Extrair<\/b>: carreguei os dados de uma fonte externa<\/li>\n<li><b>Transformar<\/b>: processei, preenchi os campos-chave de liga\u00e7\u00e3o<\/li>\n<li><b>Carga<\/b>: transferi os dados prontos para as tabelas de destino<\/li>\n<li>deletei \"minhas\" tabelas<\/li>\n<\/ul>\n<p>\nE agora \u2014 uma colher de destrui\u00e7\u00e3o. Na verdade, <b>toda grava\u00e7\u00e3o no PostgreSQL ocorre duas vezes<\/b> \u2014 <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/postgrespro\/blog\/461523\/\">primeiro no WAL<\/a><\/noindex>, depois nos corpos das tabelas\/\u00edndices. Tudo isso \u00e9 feito para suportar ACID e garantir a visibilidade correta dos dados entre <code>COMMIT<\/code>e <code>ROLLBACK<\/code>transa\u00e7\u00f5es.<\/p>\n<p>Mas n\u00f3s n\u00e3o precisamos disso! Todo o nosso processo <b>ou foi conclu\u00eddo com sucesso como um todo, ou n\u00e3o foi.<\/b>N\u00e3o importa quantas transa\u00e7\u00f5es intermedi\u00e1rias existam \u2014 n\u00e3o nos importa \"continuar o processo do meio\", especialmente quando n\u00e3o est\u00e1 claro onde ele parou.<\/p>\n<p>Para isso, os desenvolvedores do PostgreSQL, desde a vers\u00e3o 9.1, implementaram algo como <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable#SQL-CREATETABLE-UNLOGGED\">tabelas n\u00e3o registradas (UNLOGGED)<\/a><\/noindex>:<\/p>\n<blockquote><p>Com essa indica\u00e7\u00e3o, a tabela \u00e9 criada como n\u00e3o registrada. Os dados gravados em tabelas n\u00e3o registradas n\u00e3o passam pelo di\u00e1rio de pr\u00e9-grava\u00e7\u00e3o (veja o Cap\u00edtulo 29), resultando em tabelas que <b>funcionam muito mais r\u00e1pido que as normais.<\/b>No entanto, elas n\u00e3o est\u00e3o protegidas contra falhas; em caso de falha ou desligamento abrupto do servidor, a tabela n\u00e3o registrada <b>\u00e9 automaticamente truncada.<\/b>Al\u00e9m disso, o conte\u00fado da tabela n\u00e3o registrada <b>n\u00e3o \u00e9 replicado.<\/b> em servidores secund\u00e1rios. Qualquer \u00edndice criado para uma tabela n\u00e3o registrada se torna automaticamente n\u00e3o registrado.<\/p><\/blockquote>\n<p>Em resumo, <b>ser\u00e1 muito mais r\u00e1pido<\/b>, mas se o servidor de banco de dados falhar, isso ser\u00e1 desagrad\u00e1vel. Mas com que frequ\u00eancia isso acontece, e seu processo ETL consegue corrigir isso corretamente \"do meio\" ap\u00f3s a \"ressurrei\u00e7\u00e3o\" do banco de dados?..<\/p>\n<p>Se n\u00e3o, e o caso acima se parece com o seu, use <code>UNLOGGED<\/code>, mas nunca <b>ative esse atributo em tabelas reais<\/b>, os dados das quais s\u00e3o valiosos para voc\u00ea.<\/p>\n<h3>1.3. ON COMMIT { DELETE ROWS | DROP }<\/h3>\n<p>\nEssa constru\u00e7\u00e3o permite definir o comportamento autom\u00e1tico ao concluir a transa\u00e7\u00e3o ao criar a tabela.<\/p>\n<p>Sobre <code>AO COMMIT <b>DROP<\/b><\/code> Eu j\u00e1 escrevi acima, ele gera <code>DROP TABLE<\/code>, mas aqui <code>AO COMMIT <b>EXCLUIR LINHAS<\/b><\/code> a situa\u00e7\u00e3o \u00e9 mais interessante \u2014 aqui \u00e9 gerado <code>TRUNCATE TABLE<\/code>.<\/p>\n<p>Uma vez que toda a infraestrutura de armazenamento da metadescri\u00e7\u00e3o da tabela tempor\u00e1ria \u00e9 exatamente a mesma que a de uma tabela normal, <b>a cria\u00e7\u00e3o e exclus\u00e3o constantes de tabelas tempor\u00e1rias levam a uma grande \"expans\u00e3o\" das tabelas do sistema<\/b> pg_class, pg_attribute, pg_attrdef, pg_depend,\u2026<\/p>\n<p>Agora imagine que voc\u00ea tem um trabalhador em uma conex\u00e3o direta com o banco de dados, que a cada segundo abre uma nova transa\u00e7\u00e3o, cria, preenche, processa e exclui uma tabela tempor\u00e1ria\u2026 Haver\u00e1 um ac\u00famulo excessivo de lixo nas tabelas do sistema, o que causa lentid\u00e3o em cada opera\u00e7\u00e3o.<\/p>\n<p>Em geral, n\u00e3o fa\u00e7a isso! Nesse caso, \u00e9 muito mais eficaz <code>CREATE TEMPORARY TABLE x ... ON COMMIT DELETE ROWS<\/code> extrair para fora do ciclo de transa\u00e7\u00f5es \u2014 assim, no in\u00edcio de cada nova transa\u00e7\u00e3o, a tabela j\u00e1 <b>existir\u00e1<\/b> (economizamos chamadas <code>CREATE<\/code>), mas <b>estar\u00e1 vazia<\/b>, gra\u00e7as a <code>TRUNCATE<\/code> (tamb\u00e9m economizamos essa chamada) ao t\u00e9rmino da transa\u00e7\u00e3o anterior.<\/p>\n<h3>1.4. COMO\u2026 INCLUINDO\u2026<\/h3>\n<p>\nMencionei no in\u00edcio que um dos casos t\u00edpicos para tabelas tempor\u00e1rias \u00e9 a importa\u00e7\u00e3o de dados \u2014 e o desenvolvedor cansativamente copia e cola a lista de campos da tabela de destino na declara\u00e7\u00e3o de sua tabela tempor\u00e1ria\u2026<\/p>\n<p>Mas a pregui\u00e7a \u00e9 o motor do progresso! Portanto, <b>criar uma nova tabela \"por modelo\"<\/b> pode ser muito mais simples:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE import_table(\n  LIKE target_table\n);<\/code><\/pre>\n<p>\nUma vez que \u00e9 poss\u00edvel gerar muitos dados nessa tabela posteriormente, as buscas nela se tornar\u00e3o muito lentas. Mas h\u00e1 uma solu\u00e7\u00e3o tradicional contra isso \u2014 \u00edndices! E sim, <b>uma tabela tempor\u00e1ria tamb\u00e9m pode ter \u00edndices<\/b>.<\/p>\n<p>Uma vez que, muitas vezes, os \u00edndices necess\u00e1rios coincidem com os \u00edndices da tabela de destino, basta escrever <code>SEMELHANTE a target_table <b>INCLUINDO \u00cdNDICES<\/b><\/code>.<\/p>\n<p>Se voc\u00ea tamb\u00e9m precisa de <code>DEFAULT<\/code>-valores (por exemplo, para preencher os valores da chave prim\u00e1ria), voc\u00ea pode usar <code>SEMELHANTE a target_table <b>INCLUINDO PADR\u00d5ES<\/b><\/code>. Ou simplesmente \u2014 <code>SEMELHANTE a target_table <b>INCLUINDO TODOS<\/b><\/code> \u2014 copia os padr\u00f5es, \u00edndices, restri\u00e7\u00f5es,\u2026<\/p>\n<p>Mas aqui j\u00e1 \u00e9 preciso entender que se voc\u00ea criou <b>a tabela de importa\u00e7\u00e3o j\u00e1 com \u00edndices, os dados ser\u00e3o carregados mais lentamente<\/b>, do que se voc\u00ea carreg\u00e1-los primeiro e s\u00f3 depois aplicar os \u00edndices \u2014 veja como isso \u00e9 feito, como exemplo, no <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/app-pgdump\">pg_dump<\/a><\/noindex>.<\/p>\n<p>Em geral, <noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-createtable\">RTFM<\/a><\/noindex>!<\/p>\n<h2>2. Como escrever?<\/h2>\n<p>\nVou dizer simplesmente \u2014 use <code><noindex><a rel=\"nofollow\" href=\"https:\/\/postgrespro.ru\/docs\/postgresql\/12\/sql-copy\">COPY<\/a><\/noindex><\/code>-fluxo em vez de \"lote\" <code>INSERT<\/code>, <noindex><a rel=\"nofollow\" href=\"https:\/\/www.citusdata.com\/blog\/2017\/11\/08\/faster-bulk-loading-in-postgresql-with-copy\/\">acelera\u00e7\u00e3o m\u00faltipla<\/a><\/noindex>. Voc\u00ea pode at\u00e9 fazer isso diretamente de um arquivo previamente formado.<\/p>\n<h2>3. Como processar?<\/h2>\n<p>\nEnt\u00e3o, vamos supor que nossa entrada seja algo assim:<\/p>\n<ul>\n<li>voc\u00ea tem uma tabela em seu banco de dados com os dados dos clientes com <b>1M registros<\/b><\/li>\n<li>todos os dias o cliente lhe envia um novo <b>\u2018imagem\u2019 completa<\/b><\/li>\n<li>pela experi\u00eancia voc\u00ea sabe que de uma vez para outra <b>mudam n\u00e3o mais de 10K registros<\/b><\/li>\n<\/ul>\n<p>\nUm exemplo cl\u00e1ssico dessa situa\u00e7\u00e3o \u00e9 <noindex><a rel=\"nofollow\" href=\"https:\/\/www.gnivc.ru\/technical_support\/classifiers_reference\/kladr\/\">a base KLAADR<\/a><\/noindex> \u2014 s\u00e3o muitos endere\u00e7os no total, mas em cada exporta\u00e7\u00e3o semanal de mudan\u00e7as (mudan\u00e7as nos nomes das cidades, fus\u00f5es de ruas, aparecimento de novos edif\u00edcios) h\u00e1 bem poucos, mesmo em escala nacional.<\/p>\n<h3>3.1. Algoritmo de sincroniza\u00e7\u00e3o total<\/h3>\n<p>\nPara simplificar, vamos supor que voc\u00ea n\u00e3o precise nem reestruturar os dados \u2014 apenas formatar a tabela conforme necess\u00e1rio, isto \u00e9:<\/p>\n<ul>\n<li><b>remover<\/b> tudo o que j\u00e1 n\u00e3o existe<\/li>\n<li><b>atualizar<\/b> tudo o que j\u00e1 existia e precisa ser atualizado<\/li>\n<li><b>inserir<\/b> tudo o que ainda n\u00e3o existe<\/li>\n<\/ul>\n<p>\nPor que as opera\u00e7\u00f5es devem ser feitas exatamente nesta ordem? Porque assim o tamanho da tabela crescer\u00e1 ao m\u00ednimo (<noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/491366\/\">lembre-se do MVCC!<\/a><\/noindex>).<\/p>\n<h4>DELETE FROM dst<\/h4>\n<p>\nN\u00e3o, claro que \u00e9 poss\u00edvel fazer tudo com apenas duas opera\u00e7\u00f5es:<\/p>\n<ul>\n<li><b>remover<\/b> (<code>DELETE<\/code>) tudo em geral<\/li>\n<li><b>inserir<\/b> tudo a partir da nova imagem<\/li>\n<\/ul>\n<p>\nMas, nesse caso, gra\u00e7as ao MVCC, <b>o tamanho da tabela aumentar\u00e1 exatamente ao dobro<\/b>! Obter +1M imagens de registros na tabela devido \u00e0 atualiza\u00e7\u00e3o de 10K \u2014 uma redund\u00e2ncia nada agrad\u00e1vel\u2026<\/p>\n<h4>TRUNCATE dst<\/h4>\n<p>\nUm desenvolvedor mais experiente sabe que toda a tabela pode ser limpa de forma bastante barata:<\/p>\n<ul>\n<li><b>limpar<\/b> (<code>TRUNCATE<\/code>) toda a tabela<\/li>\n<li><b>inserir<\/b> tudo a partir da nova imagem<\/li>\n<\/ul>\n<p>\nO m\u00e9todo \u00e9 eficaz, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/481866\/\">\u00e0s vezes \u00e9 bastante aplic\u00e1vel<\/a><\/noindex>, mas h\u00e1 um problema\u2026 Injetar 1M de registros levar\u00e1 muito tempo, ent\u00e3o n\u00e3o podemos nos dar ao luxo de deixar a tabela vazia todo esse tempo (o que acontece sem envolver em uma \u00fanica transa\u00e7\u00e3o).<\/p>\n<p>E significa que:<\/p>\n<ul>\n<li>come\u00e7amos <b>uma transa\u00e7\u00e3o longa<\/b><\/li>\n<li><code>TRUNCATE<\/code> imp\u00f5e <b>AccessExclusive<\/b>-bloqueio<\/li>\n<li>estamos demorando para inserir, enquanto todos os outros nesse tempo <b>n\u00e3o podem sequer <code>SELECT<\/code><\/b><\/li>\n<\/ul>\n<p>\nAlgo n\u00e3o parece certo...<\/p>\n<h4>ALTER TABLE\u2026 RENOMEAR\u2026 \/ DROPAR TABELA\u2026<\/h4>\n<p>\nUma op\u00e7\u00e3o \u00e9 carregar tudo em uma nova tabela separada e depois simplesmente renome\u00e1-la para o lugar da antiga. Algumas pequenas complica\u00e7\u00f5es:<\/p>\n<ul>\n<li>tamb\u00e9m <b>AccessExclusive<\/b>, embora substancialmente menos em tempo<\/li>\n<li>todas as estat\u00edsticas de consultas\/tabela dessa tabela s\u00e3o redefinidas, <noindex><a rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/company\/tensor\/blog\/479656\/\">\u00e9 necess\u00e1rio executar ANALYZE<\/a><\/noindex><\/li>\n<li><b>todas as chaves estrangeiras<\/b> (FK) na tabela<\/li>\n<\/ul>\n<p>\nHavia um patch WIP de Simon Riggs, que prop\u00f4s fazer <code>ALTER<\/code>-opera\u00e7\u00e3o para substituir o corpo da tabela em n\u00edvel de arquivo, sem tocar nas estat\u00edsticas e FK, mas n\u00e3o conseguiu reunir qu\u00f3rum.<\/p>\n<h4>DELETE, UPDATE, INSERT<\/h4>\n<p>\nEnt\u00e3o, ficamos com a op\u00e7\u00e3o n\u00e3o bloqueante de tr\u00eas opera\u00e7\u00f5es. Quase tr\u00eas\u2026 Como fazer isso da maneira mais eficiente?<\/p>\n<pre><code class=\"sql\">-- tudo devemos fazer dentro da transa\u00e7\u00e3o, para que ningu\u00e9m veja os \"estados intermedi\u00e1rios\"\nBEGIN;\n\n-- criaremos uma tabela tempor\u00e1ria com os dados importados\nCREATE TEMPORARY TABLE tmp(\n  LIKE dst INCLUDING INDEXES -- semelhante, incluindo \u00edndices\n) ON COMMIT DROP; -- fora da transa\u00e7\u00e3o, n\u00e3o precisamos dela\n\n-- rapidamente inserimos a nova imagem atrav\u00e9s de COPY\nCOPY tmp FROM STDIN;\n-- ...\n-- .\n\n-- removemos os ausentes\nDELETE FROM\n  dst D\nUSING\n  dst X\nLEFT JOIN\n  tmp Y\n    USING(pk1, pk2) -- campos da chave prim\u00e1ria\nWHERE\n  (D.pk1, D.pk2) = (X.pk1, X.pk2) AND\n  Y IS NOT DISTINCT FROM NULL; -- \"anti-join\"\n\n-- atualizamos os restantes\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); -- n\u00e3o h\u00e1 necessidade de atualizar os correspondentes\n\n-- inserimos os ausentes\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. P\u00f3s-processamento da importa\u00e7\u00e3o<\/h3>\n<p>\nNas mesmas entradas do KAD, todos os registros alterados precisam ser processados novamente \u2014 normalizados, palavras-chave extra\u00eddas, estruturados conforme necess\u00e1rio. Mas como saber \u2014 <b>o que exatamente foi alterado<\/b>, sem complicar o c\u00f3digo de sincroniza\u00e7\u00e3o, idealmente, n\u00e3o tocando nele de forma alguma?<\/p>\n<p>Se o acesso para grava\u00e7\u00e3o no momento da sincroniza\u00e7\u00e3o est\u00e1 dispon\u00edvel apenas para seu processo, voc\u00ea pode usar um gatilho que coletar\u00e1 todas as altera\u00e7\u00f5es:<\/p>\n<pre><code class=\"sql\">-- tabelas de destino\nCREATE TABLE kladr(...);\nCREATE TABLE kladr_house(...);\n\n-- tabelas com hist\u00f3rico de modifica\u00e7\u00f5es\nCREATE TABLE kladr$log(\n  ro kladr, -- aqui est\u00e3o os registros antigos\/novos\n  rn kladr\n);\n\nCREATE TABLE kladr_house$log(\n  ro kladr_house,\n  rn kladr_house\n);\n\n-- fun\u00e7\u00e3o geral para registrar mudan\u00e7as\nCREATE OR REPLACE FUNCTION diff$log() RETURNS trigger AS $$\nDECLARE\n  dst varchar = TG_TABLE_NAME || '$log';\n  stmt text = '';\nBEGIN\n  -- verificando a necessidade de registrar ao atualizar\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  -- criando o registro de log\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>\nAgora podemos, antes de iniciar a sincroniza\u00e7\u00e3o, aplicar os gatilhos (ou ativ\u00e1-los atrav\u00e9s de <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>\nE, a partir das tabelas de log, extra\u00edmos todas as mudan\u00e7as necess\u00e1rias e processamos com manipuladores adicionais.<\/p>\n<h3>3.3. Importa\u00e7\u00e3o de conjuntos de dados relacionados<\/h3>\n<p>\nAcima, discutimos os casos em que as estruturas de dados da fonte e do receptor coincidem. Mas o que fazer se a exporta\u00e7\u00e3o de um sistema externo estiver em um formato diferente da estrutura de armazenamento em nosso banco de dados?<\/p>\n<p>Tomemos como exemplo o armazenamento de clientes e suas faturas, uma variante cl\u00e1ssica de \"muitos para um\":<\/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>\nMas a exporta\u00e7\u00e3o de uma fonte externa chega para n\u00f3s em forma de \"tudo em um\":<\/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>\n\u00c9 \u00f3bvio que os dados dos clientes podem se duplicar nessa variante, e o registro principal \u00e9 a \"fatura\":<\/p>\n<pre><code class=\"plaintext\">0123456789;Vasya;A-01;2020-03-16;1000.00\n9876543210;Petya;A-02;2020-03-16;666.00\n0123456789;Vasya;B-03;2020-03-16;9999.00\n<\/code><\/pre>\n<p>\nPara o modelo, vamos simplesmente inserir nossos dados de teste, mas lembramos \u2014 <code>COPY<\/code> \u00e9 mais eficiente!<\/p>\n<pre><code class=\"sql\">INSERT INTO invoice_import\nVALUES\n  ('0123456789', 'Vasya', 'A-01', '2020-03-16', 1000.00)\n, ('9876543210', 'Petya', 'A-02', '2020-03-16', 666.00)\n, ('0123456789', 'Vasya', 'B-03', '2020-03-16', 9999.00);<\/code><\/pre>\n<p>\nPrimeiro, destacaremos aqueles \"recortes\" aos quais nossos \"fatos\" se referem. No nosso caso, as faturas se referem aos clientes:<\/p>\n<pre><code class=\"sql\">CREATE TEMPORARY TABLE client_import AS\nSELECT DISTINCT ON(client_inn)\n-- pode simplesmente ser SELECT DISTINCT, se os dados forem definitivamente consistentes\n  client_inn inn\n, client_name \"name\"\nFROM\n  invoice_import;<\/code><\/pre>\n<p>\nPara vincular corretamente as faturas aos IDs dos clientes, precisamos primeiro descobrir ou gerar esses identificadores. Adicionaremos campos para eles:<\/p>\n<pre><code class=\"sql\">ALTER TABLE invoice_import ADD COLUMN client_id integer;\nALTER TABLE client_import ADD COLUMN client_id integer;<\/code><\/pre>\n<p>\nUsaremos o m\u00e9todo de sincroniza\u00e7\u00e3o de tabelas descrito acima com uma pequena altera\u00e7\u00e3o \u2014 n\u00e3o iremos atualizar ou excluir nada na tabela de destino, uma vez que a importa\u00e7\u00e3o de clientes \u00e9 'apenas adi\u00e7\u00e3o':<\/p>\n<pre><code class=\"sql\">-- inserindo na tabela de importa\u00e7\u00e3o os IDs das grava\u00e7\u00f5es j\u00e1 existentes\nATUALIZAR\n  client_import T\nSET\n  client_id = D.client_id\nDE\n  client D\nONDE\n  T.inn = D.inn; -- chave \u00fanica\n\n-- inserindo registros ausentes e atribuindo seus IDs\nCOM ins AS (\n  INSERIR EM client(\n    inn\n  , name\n  )\n  SELECIONE\n    inn\n  , name\n  DE\n    client_import\n  ONDE\n    client_id IS NULL -- se o ID n\u00e3o foi atribu\u00eddo\n  RETORNANDO *\n)\nATUALIZAR\n  client_import T\nSET\n  client_id = D.client_id\nDE\n  ins D\nONDE\n  T.inn = D.inn; -- chave \u00fanica\n\n-- atribuirmos IDs de clientes aos registros das faturas\nATUALIZAR\n  invoice_import T\nSET\n  client_id = D.client_id\nDE\n  client_import D\nONDE\n  T.client_inn = D.inn; -- chave pr\u00e1tica\n<\/code><\/pre>\n<p>\nIsso \u00e9 tudo \u2014 em <code>invoice_import<\/code> agora temos o campo de v\u00ednculo preenchido <code>client_id<\/code>, com o qual inseriremos a fatura.<br \/>\n<br \/>Fonte: <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.3 - 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\/pt\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy\" \/>\n\t\t<meta name=\"generator\" content=\"All in One SEO (AIOSEO) 5.0.3\" \/>\n\t\t<meta property=\"og:locale\" content=\"pt_PT\" \/>\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\/pt\/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: organizamos as sincroniza\u00e7\u00f5es e importa\u00e7\u00f5es de forma eficiente | ProHoster","description":"Em processamentos complexos de grandes conjuntos de dados (diferentes processos ETL: importa\u00e7\u00f5es, convers\u00f5es e sincroniza\u00e7\u00f5es com fontes externas) frequentemente.","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/dba-gramotno-organizovyvaem-sinhronizaczii-i-importy","robots":"max-image-preview:large","keywords":"","webmasterTools":{"miscellaneous":""},"schema":null,"og:locale":"pt_PT","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\/pt\/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\/pt\/wp-json\/wp\/v2\/posts\/74953","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/comments?post=74953"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/74953\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media\/74954"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=74953"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=74953"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=74953"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}