{"id":79893,"date":"2020-05-01T13:43:12","date_gmt":"2020-05-01T11:43:12","guid":{"rendered":"https:\/\/prohoster.info\/blog\/administrirovanie\/postgresql-i-jdbc-vyzhimaem-vse-soki-vladimir-sitnikov"},"modified":"2020-05-01T13:43:12","modified_gmt":"2020-05-01T11:43:12","slug":"postgresql-i-jdbc-vyzhimaem-vse-soki-vladimir-sitnikov","status":"publish","type":"post","link":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/postgresql-i-jdbc-vyzhimaem-vse-soki-vladimir-sitnikov","title":{"rendered":"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov","gt_translate_keys":[{"key":"rendered","format":"text"}]},"content":{"rendered":"<p><strong>Convido a conhecer a transcri\u00e7\u00e3o da palestra de in\u00edcio de 2016 de Vladimir Sitnikov \"PostgreSQL e JDBC: Extraindo o m\u00e1ximo de desempenho\"<\/strong><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/9d94c8a024bd2821e431c525aae0127d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><noindex><a rel=\"nofollow\" name=\"habracut\"><\/a><\/noindex><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/030666abda53aa388b1cb1d0c46a7524.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Bom dia! Meu nome \u00e9 Vladimir Sitnikov. Trabalho h\u00e1 10 anos na empresa NetCracker. E, principalmente, me concentro em desempenho. Tudo o que est\u00e1 relacionado a Java, tudo o que diz respeito a SQL \u2013 \u00e9 o que eu amo. <\/p>\n<p><\/p>\n<p>E hoje eu vou falar sobre o que encontramos na empresa quando come\u00e7amos a usar o PostgreSQL como servidor de banco de dados. E n\u00f3s trabalhamos principalmente com Java. Mas o que vou falar hoje n\u00e3o est\u00e1 apenas relacionado ao Java. Como a pr\u00e1tica mostrou, isso ocorre em outras linguagens tamb\u00e9m. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/b01a214d782c7e32797a6c6466457979.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos discutir:<\/p>\n<p><\/p>\n<ul>\n<li>sobre a sele\u00e7\u00e3o de dados. <\/li>\n<li>Sobre a grava\u00e7\u00e3o de dados. <\/li>\n<li>E tamb\u00e9m sobre desempenho. <\/li>\n<li>E sobre obst\u00e1culos ocultos que est\u00e3o embutidos. <\/li>\n<\/ul>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/57da0c6aa4bb62e1beb77160f5aee7e7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos come\u00e7ar com uma pergunta simples. Selecionamos uma linha da tabela pela chave prim\u00e1ria. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/d5fcb1172aef402a87064498bf8a5218.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>O banco de dados est\u00e1 no mesmo host. E todo esse processo leva 20 milissegundos.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/103b7f9736b82a3313491b2f0d2a5824.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Esses 20 milissegundos s\u00e3o muito. Se voc\u00ea tiver 100 dessas solicita\u00e7\u00f5es, voc\u00ea est\u00e1 gastando tempo em segundos para processar essas solicita\u00e7\u00f5es, ou seja, estamos desperdi\u00e7ando tempo desnecessariamente.<\/p>\n<p><\/p>\n<p>N\u00f3s n\u00e3o gostamos de fazer isso e estamos verificando o que o banco de dados nos oferece para isso. O banco de dados nos prop\u00f5e duas op\u00e7\u00f5es para a execu\u00e7\u00e3o das consultas. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/b467e86a0f8c4c32cea182fe27a28e6e.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A primeira op\u00e7\u00e3o \u00e9 uma consulta simples. O que \u00e9 bom nisso? O fato de que a pegamos e enviamos, e nada mais. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/7e333a62ba68feb0781ff96e39486591.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/pgjdbc\/pgjdbc\/pull\/478\">https:\/\/github.com\/pgjdbc\/pgjdbc\/pull\/478<\/a><\/noindex><\/p>\n<p><\/p>\n<p>O banco de dados tamb\u00e9m tem uma consulta expandida, que \u00e9 mais astuta, mas mais funcional. Voc\u00ea pode enviar separadamente uma consulta para an\u00e1lise, execu\u00e7\u00e3o, liga\u00e7\u00e3o de vari\u00e1veis, etc. <\/p>\n<p><\/p>\n<p>A super consulta expandida \u2013 isso \u00e9 algo que n\u00e3o iremos cobrir na apresenta\u00e7\u00e3o atual. Podemos ter algumas necessidades em rela\u00e7\u00e3o ao banco de dados e h\u00e1 essa lista de desejos, que foi formada de alguma maneira, ou seja, isso \u00e9 o que queremos, mas n\u00e3o \u00e9 poss\u00edvel agora e no pr\u00f3ximo ano. Portanto, apenas registramos e iremos procurar as pessoas principais.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/19094729074fd28c5b9f236833a9a266.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quanto ao que podemos fazer, s\u00e3o a consulta simples e a consulta expandida.<\/p>\n<p><\/p>\n<p>Qual \u00e9 a particularidade de cada abordagem? <\/p>\n<p><\/p>\n<p>A consulta simples \u00e9 boa para uma execu\u00e7\u00e3o \u00fanica. Executou uma vez e esqueceu. E o problema \u00e9 que ela n\u00e3o suporta o formato bin\u00e1rio de dados, ou seja, para alguns sistemas de alto desempenho, ela n\u00e3o \u00e9 adequada.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/062c0e45cefd91ece331a306a7651bde.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>A consulta estendida \u2013 permite economizar tempo na an\u00e1lise. Isso \u00e9 o que fizemos e come\u00e7amos a usar. Isso nos ajudou imensamente. N\u00e3o h\u00e1 apenas economia na an\u00e1lise. H\u00e1 tamb\u00e9m economia na transmiss\u00e3o de dados. Transmitir dados em formato bin\u00e1rio \u00e9 muito mais eficiente. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/6df83a2f3736568668cf9d74de5d9758.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos \u00e0 pr\u00e1tica. Veja como \u00e9 um aplicativo t\u00edpico. Pode ser Java, etc. <\/p>\n<p><\/p>\n<p>Criamos um statement. Executamos o comando. Criamos um close. Onde est\u00e1 o erro aqui? Qual \u00e9 o problema? N\u00e3o h\u00e1 problemas. Assim \u00e9 escrito em todos os livros. \u00c9 assim que deve ser. Se voc\u00ea quer a m\u00e1xima performance, escreva assim. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/6923293946dec45b3fd508235728ab95.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Mas a pr\u00e1tica mostrou que isso n\u00e3o funciona. Por qu\u00ea? Porque temos o m\u00e9todo \"close\". E quando fazemos isso, do ponto de vista do banco de dados, \u00e9 como se um fumante estivesse trabalhando com o banco de dados. Dizemos \"PARSE EXECUTE DEALLOCATE\".<\/p>\n<p><\/p>\n<p>Por que essas cria\u00e7\u00f5es desnecess\u00e1rias e descarte de statements? Elas n\u00e3o s\u00e3o necess\u00e1rias. Mas geralmente em PreparedStatement acontece isso; quando os fechamos, eles fecham tudo no banco de dados. Isso n\u00e3o \u00e9 o que queremos. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/5a357a209e414024c437d042f600f251.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Queremos, como pessoas saud\u00e1veis, trabalhar com o banco. Preparamos nosso statement uma vez e depois o executamos muitas vezes. Na verdade, muitas vezes significa uma vez durante toda a vida do aplicativo. E em diferentes REST usamos o mesmo id de statement. Esse \u00e9 o nosso objetivo. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/ac4aed702a624ad9f9addc4362daf760.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Como podemos alcan\u00e7ar isso? <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/0f101d890d1a2af8e99f8a3a8a06fd5a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Muito simples \u2013 n\u00e3o precisamos fechar statements. Escrevemos assim: \"prepare\" \"execute\". <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/0b3fd8fd4f5861d4a76cad5a8421ddad.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/724646bfc55b5e2b611b1584ca8ba6aa.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Se iniciarmos isso, \u00e9 claro que algo ir\u00e1 transbordar em algum lugar. Se n\u00e3o for claro, podemos medir. Vamos escrever um benchmark, no qual teremos um m\u00e9todo simples. Criamos um statement. Executamos em alguma vers\u00e3o do driver e descobrimos que ele cai rapidamente, com perda total da mem\u00f3ria que temos dispon\u00edveis. <\/p>\n<p><\/p>\n<p>\u00c9 claro que esses erros s\u00e3o f\u00e1ceis de corrigir. N\u00e3o vou falar sobre eles. Mas posso dizer que na nova vers\u00e3o funciona muito mais r\u00e1pido. O m\u00e9todo \u00e9 sem objetivo, mas mesmo assim. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/b232a6205e708c70f52be0024bbe9980.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Como trabalhar corretamente? O que precisamos fazer para isso?<\/p>\n<p><\/p>\n<p>Na realidade, os aplicativos sempre fecham os statements. Todos os livros escrevem para fech\u00e1-los, caso contr\u00e1rio haver\u00e1 vazamento de mem\u00f3ria. <\/p>\n<p><\/p>\n<p>E o PostgreSQL n\u00e3o sabe armazenar em cache as consultas. Cada sess\u00e3o precisa criar seu pr\u00f3prio cache. <\/p>\n<p><\/p>\n<p>E tamb\u00e9m n\u00e3o queremos gastar tempo na an\u00e1lise. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/85b9574938aa6e6bf59c956e3728bdc3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>E como sempre temos duas op\u00e7\u00f5es. <\/p>\n<p><\/p>\n<p>A primeira op\u00e7\u00e3o \u00e9 pegarmos e dizermos que vamos compactar tudo em PgSQL. L\u00e1 h\u00e1 cache. Ele cacheia tudo. Ficar\u00e1 maravilhoso. Observamos isso. Temos 100500 consultas. N\u00e3o funciona. N\u00e3o concordamos em transformar as consultas manualmente em procedimentos. N\u00e3o, n\u00e3o. <\/p>\n<p><\/p>\n<p>Temos uma segunda op\u00e7\u00e3o \u2013 pegar e fazemos n\u00f3s mesmos. Abrimos o c\u00f3digo fonte e come\u00e7amos a trabalhar. Trabalhamos, trabalhamos. Descobrimos que n\u00e3o \u00e9 t\u00e3o dif\u00edcil assim. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/501620b014800406167d20668baef709.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/pgjdbc\/pgjdbc\/pull\/319\">https:\/\/github.com\/pgjdbc\/pgjdbc\/pull\/319<\/a><\/noindex><\/p>\n<p><\/p>\n<p>Isso apareceu em agosto de 2015. Agora temos uma vers\u00e3o mais moderna. E tudo est\u00e1 \u00f3timo. Funciona t\u00e3o bem que n\u00e3o mudamos nada no aplicativo. E at\u00e9 paramos de considerar o PgSQL, ou seja, nos serviu o suficiente para reduzir praticamente a zero todas as despesas gerais. <\/p>\n<p><\/p>\n<p>Assim, as instru\u00e7\u00f5es preparadas pelo servidor s\u00e3o ativadas na quinta execu\u00e7\u00e3o para n\u00e3o gastar mem\u00f3ria no banco de dados com cada consulta \u00fanica. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/8ee95e71f92187941989ad0c18ece0c0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Pode-se perguntar \u2013 onde est\u00e3o os n\u00fameros? O que voc\u00ea obt\u00e9m? E aqui n\u00e3o fornecerei n\u00fameros, porque a cada consulta eles s\u00e3o diferentes.<\/p>\n<p><\/p>\n<p>Nossas consultas eram tais que gast\u00e1vamos cerca de 20 milissegundos na an\u00e1lise em consultas OLTP. Ali havia 0,5 milissegundos na execu\u00e7\u00e3o, 20 milissegundos na an\u00e1lise. A consulta \u2013 10 KiB de texto, 170 linhas de plano. Esta \u00e9 uma consulta OLTP. Ela solicita 1, 5, 10 linhas, \u00e0s vezes mais. <\/p>\n<p><\/p>\n<p>Mas n\u00e3o quer\u00edamos gastar 20 milissegundos. N\u00f3s reduzimos isso a zero. Tudo \u00f3timo. <\/p>\n<p><\/p>\n<p>O que voc\u00ea pode levar disso? Se voc\u00ea tem Java, ent\u00e3o voc\u00ea pega a vers\u00e3o moderna do driver e fica feliz. <\/p>\n<p><\/p>\n<p>Se voc\u00ea tem alguma outra linguagem, ent\u00e3o pense \u2013 talvez voc\u00ea tamb\u00e9m precise disso? Porque, do ponto de vista da linguagem final, por exemplo, se PL 8 ou voc\u00ea tem LibPQ, pode n\u00e3o ser \u00f3bvio que voc\u00ea est\u00e1 gastando tempo n\u00e3o na execu\u00e7\u00e3o, mas na an\u00e1lise e isso vale a pena checar. Como? Tudo de gra\u00e7a. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/fda6b1c1b126cb85c970e42335e1dc24.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Exceto que h\u00e1 erros, algumas particularidades. E sobre isso vamos falar agora. A maior parte ser\u00e1 sobre arqueologia industrial, sobre o que encontramos, com o que nos deparamos. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/9a3b383f83c5e227cd02952c5825b64f.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Se a consulta \u00e9 gerada dinamicamente. Isso acontece. Algu\u00e9m concatena strings, resultando em uma consulta SQL.<\/p>\n<p><\/p>\n<p>Por que isso \u00e9 ruim? \u00c9 ruim porque, a cada vez, acabamos com uma string diferente.<\/p>\n<p><\/p>\n<p>E esta linha variada precisa recalcular o hashCode. Esta \u00e9 realmente uma tarefa de CPU \u2013 encontrar um longo texto de consulta mesmo em um hash existente n\u00e3o \u00e9 t\u00e3o simples. Portanto, a conclus\u00e3o \u00e9 clara \u2013 n\u00e3o gere consultas. Armazene-as em uma \u00fanica vari\u00e1vel. E aproveite.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/bcd4f729204cecdb572a98f357bcc33f.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>O pr\u00f3ximo problema. Os tipos de dados s\u00e3o importantes. Existem ORMs que dizem que n\u00e3o importa qual NULL, pode ser qualquer um. Se for Int, ent\u00e3o dizemos setInt. E se for NULL, que seja sempre VARCHAR. E qual \u00e9 a diferen\u00e7a, afinal, qual NULL est\u00e1 l\u00e1? O banco de dados entender\u00e1 tudo isso sozinho. E essa l\u00f3gica n\u00e3o funciona. <\/p>\n<p><\/p>\n<p>Na pr\u00e1tica, o banco de dados n\u00e3o est\u00e1 nem um pouco indiferente. <strong>Se voc\u00ea disse pela primeira vez que era um n\u00famero, e na segunda vez disse que era VARCHAR, n\u00e3o \u00e9 poss\u00edvel reutilizar as declara\u00e7\u00f5es preparadas do servidor. E nesse caso, precisamos recriar nossa declara\u00e7\u00e3o.<\/strong><\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/552d9c2e25cf9abbdfef12c98d027740.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Se voc\u00ea executa a mesma consulta, certifique-se de que os tipos de dados nas colunas n\u00e3o se confundam. \u00c9 necess\u00e1rio prestar aten\u00e7\u00e3o ao NULL. Este \u00e9 um erro comum que tivemos depois que come\u00e7amos a usar PreparedStatements.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/5d73c8f5dfa281c3bda962fc3e5edd84.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Beleza, ativamos. Pegamos, talvez, um driver. E o desempenho caiu. Tudo ficou ruim. <\/p>\n<p><\/p>\n<p>Como isso pode acontecer? \u00c9 um bug ou uma funcionalidade? Infelizmente, n\u00e3o conseguimos entender se \u00e9 um bug ou uma funcionalidade. Mas existe um cen\u00e1rio bastante simples para reproduzir esse problema. Ele nos pegou de surpresa. E envolve a sele\u00e7\u00e3o de literalmente uma \u00fanica tabela. N\u00f3s, claro, tivemos mais consultas como essa. Geralmente, inclu\u00edam duas ou tr\u00eas tabelas, mas h\u00e1 esse cen\u00e1rio de reprodu\u00e7\u00e3o. Pegue qualquer vers\u00e3o do seu banco e reproduza.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/fa0fdd037c2eaa31667d5a527bb71f9c.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/gist.github.com\/vlsi\/df08cbef370b2e86a5c1\">https:\/\/gist.github.com\/vlsi\/df08cbef370b2e86a5c1<\/a><\/noindex><\/p>\n<p><\/p>\n<p>A quest\u00e3o \u00e9 que temos duas colunas, cada uma indexada. Em uma coluna h\u00e1 um milh\u00e3o de linhas com o valor NULL. E na outra coluna, apenas 20 linhas. Quando executamos sem vari\u00e1veis ligadas, tudo funciona bem. <\/p>\n<p><\/p>\n<p>Se come\u00e7armos a executar com vari\u00e1veis ligadas, ou seja, usamos o sinal \"?\" ou \"$1\" em nossa consulta, o que obtemos no final?<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/e91c397796c7ddcbade3bc090719e9d1.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/gist.github.com\/vlsi\/df08cbef370b2e86a5c1\">https:\/\/gist.github.com\/vlsi\/df08cbef370b2e86a5c1<\/a><\/noindex><\/p>\n<p><\/p>\n<p>A primeira execu\u00e7\u00e3o \u2013 como deveria ser. A segunda \u2013 um pouco mais r\u00e1pida. Algo foi armazenado em cache. A terceira, quarta e quinta. Depois, acontece uma queda \u2013 e de repente assim. E o pior \u00e9 que isso acontece na sexta execu\u00e7\u00e3o. Quem sabia que eram exatamente seis execu\u00e7\u00f5es para entender qual \u00e9 realmente o plano de execu\u00e7\u00e3o?<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/893c955bc1e2e15a6986690e516f72a9.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Quem \u00e9 o culpado? O que aconteceu? O banco de dados cont\u00e9m uma otimiza\u00e7\u00e3o. E ele est\u00e1 otimizado para um caso gen\u00e9rico. Assim, a partir de certo ponto, ele passa a utilizar um plano gen\u00e9rico que, infelizmente, pode acabar sendo diferente. Pode ser o mesmo ou pode ser outro. E h\u00e1 um valor limite que leva a esse comportamento. <\/p>\n<p><\/p>\n<p>O que se pode fazer sobre isso? Aqui, claro, \u00e9 mais dif\u00edcil especular. Existe uma solu\u00e7\u00e3o simples que usamos. \u00c9 +0, OFFSET 0. Com certeza voc\u00ea conhece tais solu\u00e7\u00f5es. Simplesmente pegamos e adicionamos \u00ab+0\u00bb na consulta e tudo fica bem. Mostrarei mais tarde. <\/p>\n<p><\/p>\n<p>E h\u00e1 outra op\u00e7\u00e3o \u2013 analisar os planos com mais aten\u00e7\u00e3o. O desenvolvedor n\u00e3o deve apenas escrever a consulta, mas tamb\u00e9m dizer \u00abexplain analyze\u00bb 6 vezes. Se for 5, n\u00e3o servir\u00e1. <\/p>\n<p><\/p>\n<p>E h\u00e1 uma terceira op\u00e7\u00e3o \u2013 escrever uma carta para pgsql-hackers. Eu escrevi, mas, at\u00e9 agora, n\u00e3o est\u00e1 claro se \u00e9 um bug ou uma feature.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/8dc8d3a78e0b794e1ceba20f6ca914ae.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/gist.github.com\/vlsi\/df08cbef370b2e86a5c1\">https:\/\/gist.github.com\/vlsi\/df08cbef370b2e86a5c1<\/a><\/noindex><\/p>\n<p><\/p>\n<p>Enquanto pensamos se \u00e9 um bug ou uma feature, vamos consertar isso. Pegamos nossa consulta e adicionamos \u00ab+0\u00bb. Tudo fica bem. S\u00e3o apenas dois caracteres e nem precisamos pensar muito sobre isso. Muito simples. N\u00f3s apenas proibimos o banco de dados de usar o \u00edndice nessa coluna. N\u00e3o temos um \u00edndice na coluna \u00ab+0\u00bb e, portanto, o banco de dados n\u00e3o usa o \u00edndice, tudo bem. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/5261d80d084786a15b9764f6367014cd.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Essa \u00e9 a regra dos 6 explains. Agora, nas vers\u00f5es atuais, \u00e9 necess\u00e1rio fazer 6 vezes se voc\u00ea tiver vari\u00e1veis relacionadas. Se voc\u00ea n\u00e3o tiver vari\u00e1veis relacionadas, fazemos assim. E no final, essa consulta espec\u00edfica falha. N\u00e3o \u00e9 complicado.<\/p>\n<p><\/p>\n<p>Parece que quanto mais olhamos, mais bugs encontramos. Realmente, h\u00e1 bugs por toda parte. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/674dc7e7b4624baa5c79eee9e1e89db0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos dar uma olhada mais. Por exemplo, temos dois esquemas. Esquema A com a tabela Y e esquema B com a tabela Y. A consulta \u2013 selecionar dados da tabela. O que teremos? Teremos um erro. Teremos tudo o que foi mencionado acima. A regra \u00e9 essa \u2013 bug por toda parte, teremos tudo o que foi mencionado acima.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/dd6f544bb9e930c39e36523c9afe6003.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Agora a pergunta \u00e9: \u00abPor qu\u00ea?\u00bb. Parece que h\u00e1 documenta\u00e7\u00e3o que, se temos um esquema, existe a vari\u00e1vel \u00absearch_path\u00bb que indica onde procurar a tabela. \u00c0 prima vista, a vari\u00e1vel existe.<\/p>\n<p><\/p>\n<p>Qual \u00e9 o problema? O problema \u00e9 que os server-prepared statements n\u00e3o suspeitam que algu\u00e9m pode alterar o search_path. Esse valor permanece como que constante para o banco de dados. E algumas partes podem n\u00e3o captar os novos valores. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/f738301606d84d72bfa7cc5b568c0df8.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Claro, isso depende da vers\u00e3o em que voc\u00ea est\u00e1 testando. Depende de qu\u00e3o diferentes s\u00e3o as suas tabelas. E a vers\u00e3o 9.1 apenas executar\u00e1 as consultas antigas. Vers\u00f5es mais novas podem detectar um problema e informar que voc\u00ea tem um erro.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/4761cd92835b94acbf9d6c9d21817316.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/www.postgresql.org\/message-id\/CAB=Je-GQOW7kU9Hn3AqP1vhaZg_wE9Lz6F4jSp-7cm9_M6DyVA@mail.gmail.com\">Defina search_path + declara\u00e7\u00f5es preparadas no servidor =<br \/>\no plano em cache n\u00e3o deve mudar o tipo de resultado<\/a><\/noindex><\/p>\n<p><\/p>\n<p>Como resolver isso? H\u00e1 uma receita simples \u2013 n\u00e3o fa\u00e7a assim. N\u00e3o mude o search_path enquanto a aplica\u00e7\u00e3o estiver em funcionamento. Se voc\u00ea mudar, \u00e9 melhor criar uma nova conex\u00e3o.<\/p>\n<p><\/p>\n<p>Podemos discutir, ou seja, abrir, discutir, adicionar. Talvez consigamos convencer os desenvolvedores de banco de dados que, quando algu\u00e9m muda um valor, o banco de dados deve informar ao cliente: \"Veja, o seu valor foi atualizado. Talvez voc\u00ea precise redefinir os statements, recri\u00e1-los?\". Atualmente, o banco de dados se comporta de forma discreta e n\u00e3o informa nada sobre a mudan\u00e7a interna dos statements. <\/p>\n<p><\/p>\n<p>E eu novamente enfatizo \u2013 isso n\u00e3o \u00e9 t\u00edpico para Java. N\u00f3s veremos a mesma coisa em PL\/pgSQL um a um. Mas l\u00e1 ir\u00e1 reproduzir.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/a4e7b201d0c0aa4a0092d2b6ff152df7.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos tentar escolher os dados novamente. Escolhemos, escolhemos. Temos uma tabela com um milh\u00e3o de linhas. Cada linha tem um quilobyte. Aproximadamente um gigabyte de dados. E temos uma mem\u00f3ria de trabalho na m\u00e1quina Java de 128 megabytes. <\/p>\n<p><\/p>\n<p>Como recomendado em todos os livros, usamos processamento em streaming. Ou seja, abrimos o resultSet e lemos os dados lentamente. Isso funcionar\u00e1? N\u00e3o ficar\u00e1 sem mem\u00f3ria? Ler\u00e1 aos poucos? Vamos confiar no banco, confiar no Postgres. N\u00e3o confiamos. Vamos enfrentar um OutOfMemory? Quem j\u00e1 enfrentou um OutOfMemory? E quem conseguiu consertar depois? Algu\u00e9m conseguiu consertar. <\/p>\n<p><\/p>\n<p>Se voc\u00ea tem um milh\u00e3o de linhas, n\u00e3o pode simplesmente selecionar. \u00c9 necess\u00e1rio usar OFFSET\/LIMIT. Quem apoia essa op\u00e7\u00e3o? E quem acha que devemos brincar com autoCommit? <\/p>\n<p><\/p>\n<p>Aqui, como de costume, a op\u00e7\u00e3o mais inesperada acaba sendo a correta. E se, por acaso, voc\u00ea desativar o autoCommit, isso ajudar\u00e1. Por que isso? A ci\u00eancia n\u00e3o sabe. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/2e5d9d2a8450a9069155de86a8ec387a.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Mas por padr\u00e3o, todos os clientes que se conectam ao banco de dados Postgres selecionam os dados por completo. O PgJDBC n\u00e3o \u00e9 exce\u00e7\u00e3o, seleciona todas as linhas.<\/p>\n<p><\/p>\n<p>H\u00e1 uma varia\u00e7\u00e3o sobre o tema FetchSize, ou seja, voc\u00ea pode, ao n\u00edvel de um statement individual, dizer que aqui, por favor, selecione os dados em grupos de 10, 50. Mas isso n\u00e3o funciona at\u00e9 voc\u00ea desativar o autoCommit. Desativou o autoCommit \u2013 come\u00e7a a funcionar. <\/p>\n<p><\/p>\n<p>Mas percorrer o c\u00f3digo e definir setFetchSize em todos os lugares \u00e9 inconveniente. Por isso, criamos uma configura\u00e7\u00e3o que define o valor padr\u00e3o para toda a conex\u00e3o.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/c9ac8e483dc6bfdbbc300972ee1ab1e0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Ent\u00e3o, n\u00f3s dissemos isso. Configuramos o par\u00e2metro. E o que temos? Se escolhemos por um pequeno n\u00famero, por exemplo, 10 linhas, nossas despesas com isso s\u00e3o bastante altas. Portanto, esse valor deve ser em torno de cem. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/073e7a4af63688396eefe332ba8261d6.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Idealmente, \u00e9 claro, tamb\u00e9m dever\u00edamos aprender a limitar em bytes, mas a receita \u00e9 a seguinte: definimos defaultRowFetchSize acima de cem e ficamos felizes. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/e3078faf2078fcaec10d6d99872a9990.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos passar para a inser\u00e7\u00e3o de dados. A inser\u00e7\u00e3o \u00e9 mais simples, existem diferentes op\u00e7\u00f5es. Por exemplo, INSERT, VALUES. Essa \u00e9 uma boa op\u00e7\u00e3o. Podemos falar em 'INSERT SELECT'. Na pr\u00e1tica, \u00e9 a mesma coisa. N\u00e3o h\u00e1 diferen\u00e7a em termos de desempenho. <\/p>\n<p><\/p>\n<p>Os livros dizem que devemos executar o Batch statement, os livros dizem que \u00e9 poss\u00edvel executar comandos mais complexos com v\u00e1rias chaves. E no Postgres h\u00e1 uma fun\u00e7\u00e3o maravilhosa - podemos fazer COPY, ou seja, fazer isso mais rapidamente. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/4d281c9515bdf8de7fbf1e9922d192d0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Se medirmos, podemos fazer algumas descobertas interessantes novamente. Como queremos que isso funcione? Queremos evitar parsing e n\u00e3o executar comandos desnecess\u00e1rios. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/7b2e4343c51dab3ddb6919bf0f117c4d.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Na pr\u00e1tica, o TCP n\u00e3o nos permite fazer isso. Se o cliente est\u00e1 ocupado enviando um pedido, o banco de dados, na tentativa de nos enviar as respostas, n\u00e3o l\u00ea os pedidos. No final, o cliente aguarda o banco de dados enquanto ele l\u00ea o pedido, e o banco de dados aguarda o cliente enquanto ele l\u00ea a resposta. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/17a062c30ddd83cb890775ec1722044f.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>E por isso o cliente \u00e9 obrigado a enviar periodicamente um pacote de sincroniza\u00e7\u00e3o. Intera\u00e7\u00f5es de rede desnecess\u00e1rias, perda de tempo adicional.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/ae7a4f32e2949c1d0550da051937de02.jpg\" style=\"display:block;margin: 0 auto;\" \/>E quanto mais os adicionamos, pior fica. O driver \u00e9 bastante pessimista e os adiciona com frequ\u00eancia, cerca de uma vez a cada 200 linhas, dependendo do tamanho das linhas, etc. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/82b1d597986633a8c4b7952517ca6477.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"https:\/\/github.com\/pgjdbc\/pgjdbc\/pull\/380\">https:\/\/github.com\/pgjdbc\/pgjdbc\/pull\/380<\/a><\/noindex><\/p>\n<p><\/p>\n<p>\u00c0s vezes, corrigir apenas uma linha pode acelerar tudo em 10 vezes. Isso acontece. Por qu\u00ea? Como normalmente, uma constante j\u00e1 havia sido usada em algum lugar. E o valor '128' significava - n\u00e3o usar batching.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/7b6bcd95591b36441037b4c4631d2ad0.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p><noindex><a rel=\"nofollow\" href=\"http:\/\/openjdk.java.net\/projects\/code-tools\/jmh\/\">Java microbenchmark harness<\/a><\/noindex><\/p>\n<p><\/p>\n<p>Ainda bem que isso n\u00e3o chegou \u00e0 vers\u00e3o oficial. Descobrimos antes de come\u00e7armos a lan\u00e7ar a vers\u00e3o. Todos os valores que menciono s\u00e3o baseados em vers\u00f5es modernas. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/28c82d9e11f6bdaf3cf62b49fc074d68.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Vamos fazer uma medi\u00e7\u00e3o. Medimos o InsertBatch simples. Medimos o InsertBatch m\u00faltiplo, ou seja, o mesmo, mas com muitas values. Um truque habilidoso. Nem todos sabem fazer isso, mas \u00e9 um truque simples, muito mais f\u00e1cil do que COPY.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/72f7cf6c3b9d9175d3e5a6d5410fe209.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Podemos fazer COPY.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/2b0d8bbcc50f1293c9a8a1eb8eb70d2e.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>E \u00e9 poss\u00edvel fazer isso em estruturas. Declare o tipo User default, passe um array e INSIRA diretamente na tabela. <\/p>\n<p><\/p>\n<p>Se voc\u00ea abrir o link: pgjdbc\/ubenchmsrk\/InsertBatch.java, ver\u00e1 que esse c\u00f3digo est\u00e1 no GitHub. Voc\u00ea pode verificar especificamente quais consultas est\u00e3o sendo geradas. N\u00e3o \u00e9 o principal.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/28dede211fe401286685ca62081453de.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>N\u00f3s executamos. E a primeira coisa que percebemos \u00e9 que n\u00e3o usar batch \u00e9 simplesmente inadmiss\u00edvel. Todas as op\u00e7\u00f5es de batching s\u00e3o equivalentes a zero, ou seja, o tempo de execu\u00e7\u00e3o \u00e9 praticamente zero em compara\u00e7\u00e3o com a execu\u00e7\u00e3o \u00fanica. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/4ea68f712bbdeadd35f5baa4ddaeff33.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Estamos inserindo dados. \u00c9 uma tabela bastante simples. Tr\u00eas colunas. E o que vemos aqui? Vemos que todas essas tr\u00eas op\u00e7\u00f5es s\u00e3o aproximadamente compar\u00e1veis. E COPY, claro, \u00e9 a melhor.<\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/2686f1d7eea347a839ac23aef2a27e53.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>Isso \u00e9 quando inserimos em partes. Quando dizemos que um valor \u00e9 VALUES, dois valores s\u00e3o VALUES, tr\u00eas valores s\u00e3o VALUES ou se os indicamos 10 separados por v\u00edrgula. Isso \u00e9 exatamente o que vemos na horizontal. 1, 2, 4, 128. \u00c9 vis\u00edvel que o Batch Insert, que est\u00e1 desenhado em azul, se torna muito mais leve por causa disso. Ou seja, quando voc\u00ea insere um a um ou mesmo quando insere quatro, melhora duas vezes, simplesmente por termos colocado um pouco mais em VALUES. Menos opera\u00e7\u00f5es EXECUTE.<\/p>\n<p><\/p>\n<p>Usar COPY para pequenos volumes \u00e9 extremamente pouco promissor. Eu nem desenhei nos dois primeiros. Eles v\u00e3o para os c\u00e9us, ou seja, esses n\u00fameros verdes s\u00e3o para COPY.<\/p>\n<p><\/p>\n<p>COPY deve ser usado quando voc\u00ea tem um volume de dados de pelo menos mais de cem linhas. Os custos de abertura dessa conex\u00e3o s\u00e3o altos. E, honestamente, n\u00e3o aprofundei nesse aspecto. Eu otimizei batch, COPY n\u00e3o. <\/p>\n<p><\/p>\n<p>O que fazemos a seguir? Medimos. Entendemos que precisamos usar ou estruturas ou um batch engenhoso que combine v\u00e1rios valores. <\/p>\n<p><\/p>\n<p><img decoding=\"async\" alt=\"PostgreSQL e JDBC, extraindo o m\u00e1ximo. Vladimir Sitnikov\" src=\"\/wp-content\/uploads\/2020\/05\/e02fa2574e2d1b678382db15064610d3.jpg\" style=\"display:block;margin: 0 auto;\" \/><\/p>\n<p><\/p>\n<p>O que precisa ser destacado da apresenta\u00e7\u00e3o de hoje?<\/p>\n<p><\/p>\n<ul>\n<li>PreparedStatement \u00e9 tudo para n\u00f3s. Isso traz muitos benef\u00edcios para o desempenho. Aumenta muito o 'barril de alcatr\u00e3o'. <\/li>\n<li>E precisamos fazer EXPLAIN ANALYZE 6 vezes.<\/li>\n<li>E \u00e9 necess\u00e1rio diluir OFFSET 0, e truques como +0 para ajustar o restante da porcentagem de nossas consultas problem\u00e1ticas.<\/li>\n<\/ul>\n<p>Fonte: <a content=\"nofollow\" rel=\"nofollow\" href=\"https:\/\/habr.com\/ru\/post\/499794\/\">habr.com<\/a> <\/p>","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"excerpt":{"rendered":"<p>\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043d\u0430\u0447\u0430\u043b\u0430 2016 \u0433\u043e\u0434\u0430 \u0412\u043b\u0430\u0434\u0438\u043c\u0438\u0440\u0430 \u0421\u0438\u0442\u043d\u0438\u043a\u043e\u0432\u0430 &quot;PostgreSQL \u0438 JDBC \u0432\u044b\u0436\u0438\u043c\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u043e\u043a\u0438&quot; \u0414\u043e\u0431\u0440\u044b\u0439 \u0434\u0435\u043d\u044c! \u041c\u0435\u043d\u044f \u0437\u043e\u0432\u0443\u0442 \u0412\u043b\u0430\u0434\u0438\u043c\u0438\u0440 \u0421\u0438\u0442\u043d\u0438\u043a\u043e\u0432. \u042f \u0440\u0430\u0431\u043e\u0442\u0430\u044e 10 \u043b\u0435\u0442 \u0432 \u043a\u043e\u043c\u043f\u0430\u043d\u0438\u0438 NetCracker. \u0418 \u0432 \u043e\u0441\u043d\u043e\u0432\u043d\u043e\u043c \u044f \u0437\u0430\u043d\u0438\u043c\u0430\u044e\u0441\u044c \u043f\u0440\u043e\u0438\u0437\u0432\u043e\u0434\u0438\u0442\u0435\u043b\u044c\u043d\u043e\u0441\u0442\u044c\u044e. \u0412\u0441\u0435, \u0447\u0442\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 Java, \u0432\u0441\u0435, \u0447\u0442\u043e \u0441\u0432\u044f\u0437\u0430\u043d\u043e \u0441 SQL \u2013 \u044d\u0442\u043e \u0442\u043e, \u0447\u0442\u043e \u044f \u043b\u044e\u0431\u043b\u044e. \u0418 \u0441\u0435\u0433\u043e\u0434\u043d\u044f \u044f \u0440\u0430\u0441\u0441\u043a\u0430\u0436\u0443 [&hellip;]<\/p>\n","protected":false,"gt_translate_keys":[{"key":"rendered","format":"html"}]},"author":1,"featured_media":79894,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[688],"tags":[],"class_list":["post-79893","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\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043d\u0430\u0447\u0430\u043b\u0430 2016 \u0433\u043e\u0434\u0430 \u0412\u043b\u0430\u0434\u0438\u043c\u0438\u0440\u0430 \u0421\u0438\u0442\u043d\u0438\u043a\u043e\u0432\u0430 &quot;PostgreSQL \u0438 JDBC \u0432\u044b\u0436\u0438\u043c\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u043e\u043a\u0438&quot;\" \/>\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\/postgresql-i-jdbc-vyzhimaem-vse-soki-vladimir-sitnikov\" \/>\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\udd47PostgreSQL \u0438 JDBC \u0432\u044b\u0436\u0438\u043c\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u043e\u043a\u0438. \u0412\u043b\u0430\u0434\u0438\u043c\u0438\u0440 \u0421\u0438\u0442\u043d\u0438\u043a\u043e\u0432 | ProHoster\" \/>\n\t\t<meta property=\"og:description\" content=\"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043d\u0430\u0447\u0430\u043b\u0430 2016 \u0433\u043e\u0434\u0430 \u0412\u043b\u0430\u0434\u0438\u043c\u0438\u0440\u0430 \u0421\u0438\u0442\u043d\u0438\u043a\u043e\u0432\u0430 &quot;PostgreSQL \u0438 JDBC \u0432\u044b\u0436\u0438\u043c\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u043e\u043a\u0438&quot;\" \/>\n\t\t<meta property=\"og:url\" content=\"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/postgresql-i-jdbc-vyzhimaem-vse-soki-vladimir-sitnikov\" \/>\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-05-01T11:43:12+00:00\" \/>\n\t\t<meta property=\"article:modified_time\" content=\"2020-05-01T11:43:12+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\udd47Extraindo o m\u00e1ximo de PostgreSQL e JDBC. Vladimir Sitnikov | ProHoster","description":"Gostaria de apresentar a transcri\u00e7\u00e3o da palestra de in\u00edcio de 2016 de Vladimir Sitnikov \"PostgreSQL e JDBC extraindo todos os sucos\"","canonical_url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/postgresql-i-jdbc-vyzhimaem-vse-soki-vladimir-sitnikov","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\udd47PostgreSQL \u0438 JDBC \u0432\u044b\u0436\u0438\u043c\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u043e\u043a\u0438. \u0412\u043b\u0430\u0434\u0438\u043c\u0438\u0440 \u0421\u0438\u0442\u043d\u0438\u043a\u043e\u0432 | ProHoster","og:description":"\u041f\u0440\u0435\u0434\u043b\u0430\u0433\u0430\u044e \u043e\u0437\u043d\u0430\u043a\u043e\u043c\u0438\u0442\u044c\u0441\u044f \u0441 \u0440\u0430\u0441\u0448\u0438\u0444\u0440\u043e\u0432\u043a\u043e\u0439 \u0434\u043e\u043a\u043b\u0430\u0434\u0430 \u043d\u0430\u0447\u0430\u043b\u0430 2016 \u0433\u043e\u0434\u0430 \u0412\u043b\u0430\u0434\u0438\u043c\u0438\u0440\u0430 \u0421\u0438\u0442\u043d\u0438\u043a\u043e\u0432\u0430 &quot;PostgreSQL \u0438 JDBC \u0432\u044b\u0436\u0438\u043c\u0430\u0435\u043c \u0432\u0441\u0435 \u0441\u043e\u043a\u0438&quot;","og:url":"https:\/\/prohoster.info\/pt\/blog\/administrirovanie\/postgresql-i-jdbc-vyzhimaem-vse-soki-vladimir-sitnikov","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-05-01T11:43:12+00:00","article:modified_time":"2020-05-01T11:43:12+00:00","article:publisher":"https:\/\/www.facebook.com\/prohoster","article:author":"https:\/\/www.facebook.com\/prohoster"},"aioseo_meta_data":{"post_id":"79893","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 16:29:39","updated":"2022-10-10 00:32:43","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\/79893","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=79893"}],"version-history":[{"count":0,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/posts\/79893\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media\/79894"}],"wp:attachment":[{"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/media?parent=79893"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/categories?post=79893"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/prohoster.info\/pt\/wp-json\/wp\/v2\/tags?post=79893"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}