Voltar ao catálogo
Season 13 17 Episódios 1h 4m 2026

High-Performance Python Async

Edição de 2026. Uma análise aprofundada sobre como acelerar o asyncio de Python com o uvloop e interagir diretamente com o PostgreSQL utilizando o protocolo binário do asyncpg.

Python Core Programação Assíncrona
High-Performance Python Async
A Reproduzir
Click play to start
0:00
0:00
1
A Necessidade de Velocidade: Arquitetura do uvloop
Descubra as diferenças arquitetónicas entre o asyncio padrão do Python e o uvloop. Exploramos como o uvloop tira partido do Cython e do libuv para alcançar um desempenho semelhante ao do Go.
3m 58s
2
Integrar o uvloop
Aprenda a integrar o uvloop na sua aplicação Python. Este episódio aborda a abordagem EventLoopPolicy para substituir de forma transparente o event loop predefinido.
4m 10s
3
Apresentar o asyncpg: O Protocolo Binário
Explore o design fundamental do asyncpg. Discutimos por que razão contornar a DB-API padrão a favor do protocolo binário do PostgreSQL produz ganhos de desempenho massivos.
3m 40s
4
Ligação e Execução Básica
Comece a usar o asyncpg ligando-se a uma base de dados e executando consultas básicas. Compreenda a sintaxe nativa de argumentos do Postgres.
3m 29s
5
Conversão de Tipos Nativa
Descubra como o asyncpg mapeia automaticamente os tipos de dados do PostgreSQL para objetos nativos do Python, eliminando a necessidade de análises complexas de ORM.
3m 41s
6
Codecs de Tipos Personalizados
Aprenda a definir conversões de dados personalizadas no asyncpg. Este episódio explica como utilizar o set_type_codec para descodificar automaticamente JSONB em dicionários Python.
3m 57s
7
Codecs Avançados com PostGIS
Aprofunde-se nos codecs de tipos personalizados mapeando os tipos de geometria PostGIS do PostgreSQL para objetos Shapely do Python utilizando o formato binário.
3m 51s
8
Gerir Transações
Domine as transações de base de dados no asyncpg. Abordamos o comportamento de auto-commit e como executar múltiplas consultas de forma segura utilizando context managers assíncronos.
3m 46s
9
Pool de Ligações
Escale a sua aplicação com o connection pooling integrado do asyncpg. Aprenda a gerir eficientemente as ligações à base de dados em serviços web de alto tráfego.
3m 21s
10
Cache de Prepared Statements
Compreenda como o asyncpg otimiza a análise de consultas com prepared statements automáticos, e por que razão poolers externos como o PgBouncer podem causar conflitos.
3m 57s
11
Arrays do Postgres e Cláusulas IN
Resolva o erro de sintaxe mais comum ao migrar para o asyncpg. Aprenda a filtrar corretamente consultas em relação a uma lista de valores utilizando ANY().
4m 02s
12
Objetos Record vs Named Tuples
Explore o design único dos objetos Record do asyncpg. Compreenda por que razão a notação de ponto é omitida por predefinição e como ativá-la com classes personalizadas.
3m 44s
13
Streaming de Resultados com Cursors
Evite o esgotamento de memória ao consultar conjuntos de dados massivos. Aprenda a utilizar cursors do asyncpg para fazer streaming de resultados em blocos (chunk-by-chunk).
4m 23s
14
Ingestão Ultrarrápida com COPY
Potencie os seus pipelines de ingestão de dados. Exploramos o protocolo COPY do PostgreSQL para carregar dados em massa de forma exponencialmente mais rápida do que com instruções INSERT.
3m 53s
15
Listen e Notify Assíncronos
Desbloqueie arquiteturas orientadas a eventos em tempo real diretamente no PostgreSQL. Aprenda a utilizar o add_listener do asyncpg para mensagens pub/sub instantâneas.
3m 23s
16
Telemetria e Logging de Consultas
Obtenha uma observabilidade profunda do desempenho da sua base de dados. Descubra como utilizar os log listeners do asyncpg para rastrear consultas lentas e monitorizar a telemetria de execução.
3m 30s
17
Proteger Ligações com SSL
Garanta que as suas ligações à base de dados são seguras. Abordamos a configuração do contexto SSL e como impor o TLS direto ao ligar a bases de dados na cloud.
4m 02s

Episódios

1

A Necessidade de Velocidade: Arquitetura do uvloop

3m 58s

Descubra as diferenças arquitetónicas entre o asyncio padrão do Python e o uvloop. Exploramos como o uvloop tira partido do Cython e do libuv para alcançar um desempenho semelhante ao do Go.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Python async de alta performance, episódio 1 de 17. E se pudesses duplicar a performance do teu código Python async sem reescrever uma única função? Essa é exatamente a promessa da tecnologia que vamos analisar hoje: a arquitetura uvloop. Quando os developers falam sobre o Python async standard, tendem a confundir duas camadas distintas. A primeira camada é a application programming interface. Isto inclui as keywords async e await que escreves, os futures e as tasks. A segunda camada é o próprio event loop. O event loop é o scheduler interno que monitoriza sockets, gere ligações de rede e decide qual task corre a seguir. O Python standard fornece tanto a interface como um event loop default escrito em Python puro. Aqui está o ponto chave. A interface e o event loop estão desacoplados. O Python permite-te trocar o scheduler subjacente sem alterar a sintaxe que escreves. Pensa nisto como um carro. A API async é o volante, os pedais e o painel de instrumentos. Tu interages com eles diretamente. O event loop é o motor debaixo do capô. Trocar o event loop default do Python pelo uvloop é como meter um motor V8 no teu veículo. Continuas a conduzir e a travar exatamente da mesma maneira, mas o carro anda significativamente mais rápido. O núcleo do uvloop baseia-se em duas escolhas arquitetónicas para alcançar esta velocidade. Primeiro, é um drop-in replacement escrito inteiramente em Cython. O Cython compila código semelhante a Python para extensões C altamente otimizadas. Isto elimina o overhead do interpretador de Python standard ao executar os hot paths do scheduler. Event loops em Python puro passam muito tempo a criar objetos internos e a gerir o estado do interpretador apenas para lidar com eventos de rede rotineiros. O Cython elimina isso. Sempre que o loop verifica um socket ou acorda uma task, executa código C nativo em vez de passar por Python puro. Segundo, o uvloop delega as interações reais com o sistema operativo para uma library C chamada libuv. Se este nome te soa familiar, é porque o libuv é o motor de I/O async que alimenta o Node.js. É battle-tested, altamente otimizado para workloads com uso intensivo de rede, e lida com todos os detalhes complexos cross-platform de networking async. Ao encapsular o libuv numa shell Cython compacta, o uvloop traz exatamente esse mesmo perfil de performance diretamente para Python. O resultado arquitetónico é enorme. Ao fazer bypass ao scheduler de Python puro e depender de um motor C compilado, o uvloop torna as tuas aplicações asyncio pelo menos duas vezes mais rápidas. Em muitos cenários de benchmark que envolvem alta concurrency de ligações, permite que o Python rivalize com a performance de linguagens compiladas como Go. Ficas com a developer velocity do Python com a velocidade de execução bruta de networking C nativo. A transição requer zero alterações na tua business logic, nas tuas queries de base de dados, ou nos teus endpoints de API. O takeaway fundamental aqui é que os bottlenecks de performance no Python async standard raramente têm a ver com a sintaxe da linguagem, mas sim com o motor de execução, e substituir esse motor dá-te velocidades C nativas, preservando as tuas abstrações Python existentes. Se quiseres ajudar a apoiar o programa, podes procurar por DevStoriesEU no Patreon. É tudo para este episódio. Obrigado por ouvires, e continua a construir!
2

Integrar o uvloop

4m 10s

Aprenda a integrar o uvloop na sua aplicação Python. Este episódio aborda a abordagem EventLoopPolicy para substituir de forma transparente o event loop predefinido.

Download
Olá, daqui é o Alex do DEV STORIES DOT EU. Python Async de Alta Performance, episódio 2 de 17. A otimização de performance mais poderosa para a tua aplicação Python async não requer alterações de arquitetura, refactoring, nem configurações complexas. Tem exatamente duas linhas de código. Hoje, vamos falar sobre fazer drop-in do uvloop. O event loop da standard library do asyncio é escrito em Python puro. O uvloop é um drop-in replacement construído em cima do libuv, o mesmo motor que alimenta outros runtimes de alta concorrência. Faz com que o teu código Python async standard execute significativamente mais rápido. Substituir o mecanismo de scheduling core da tua aplicação requer dizer ao Python para abandonar o seu comportamento default antes de começar a fazer qualquer trabalho real. Num script de entry point de um web server, como o ficheiro main de uma aplicação FastAPI ou aiohttp, tu implementas esta substituição usando uma event loop policy. Uma event loop policy é um objeto de configuração global dentro do módulo asyncio standard. Ela dita que tipo de event loop é instanciado sempre que a aplicação pede um novo. Para trocares o loop, importas o módulo asyncio e o módulo uvloop. A seguir, chamas a função set event loop policy no módulo asyncio. Passas-lhe uma nova instância da event loop policy fornecida pelo módulo uvloop. Aqui está o ponto crucial. Tens de definir esta policy cedo. A chamada precisa de estar no topo absoluto do teu script de execução main, logo a seguir aos teus imports. A event loop policy afeta apenas a criação de novos loops. Se esperares para definir a policy até depois do teu web framework já ter arrancado, ou depois de um driver de base de dados async ter inicializado, o loop standard de Python puro provavelmente já está a correr. Mudar a policy nessa altura não faz nada ao loop existente. O teu código ou vai ignorar o uvloop por completo, ou vai acabar com event loops misturados que causam deadlocks e broken connections. Existe uma alternativa à abordagem da policy. Em vez de mudares as regras globais para a criação de loops, podes criar explicitamente uma única instância do uvloop. Fazes isto chamando a função new event loop diretamente do módulo uvloop. Assim que tiveres esse objeto de loop em memória, passas isso para o asyncio chamando a função set event loop. Porque escolherias uma abordagem em vez da outra? Definir a event loop policy é um override global. Isso garante que qualquer library de terceiros, background task, ou componente de framework no teu processo que peça um novo loop ao asyncio vai receber um uvloop em segurança. É a escolha standard para um web server onde queres uma performance uniforme em toda a stack da aplicação. A abordagem explícita do new event loop é mais restrita. Injeta uma instância específica em vez de mudar as regras da factory. Usas este método explícito quando estás a gerir ambientes complexos com múltiplas threads, ou quando precisas de controlo rigoroso sobre qual loop está a correr num contexto isolado sem mutar o state global do processo. Para aplicações web standard, o override da policy é tudo o que precisas. O mecanismo exato que escolhes para fazer drop-in do uvloop importa menos do que o timing em que o aplicas. A event loop policy dita a fundação de toda a tua arquitetura async, por isso tem de ser a primeira instrução que a tua aplicação executa antes de qualquer contexto async ser estabelecido. É tudo por este episódio. Obrigado por ouvires, e continua a construir!
3

Apresentar o asyncpg: O Protocolo Binário

3m 40s

Explore o design fundamental do asyncpg. Discutimos por que razão contornar a DB-API padrão a favor do protocolo binário do PostgreSQL produz ganhos de desempenho massivos.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. High-Performance Python Async, episódio 3 de 17. Otimizas os teus indexes, fazes upgrade à tua rede e afinas as tuas queries, mas a tua aplicação continua a queimar ciclos de CPU na comunicação com a base de dados. O maior bottleneck nas tuas queries à base de dados muitas vezes não é a latência da rede. É o tempo que a tua aplicação passa a fazer parsing de texto. Para eliminar este overhead, apresentamos o asyncpg e a sua implementação do protocolo binário do PostgreSQL. É um equívoco comum pensar que o asyncpg é apenas um wrapper async do psycopg2. Não é. Também não é uma adaptação da DB-API standard de Python. A especificação da DB-API orienta inerentemente os drivers de bases de dados para certos padrões standard de manipulação de dados. O asyncpg ignora esta especificação por completo. É um rewrite feito de raiz, desenhado exclusivamente para asyncio e PostgreSQL, ignorando as interfaces standard de bases de dados para falar com o Postgres nos seus próprios termos. Para perceberes porque é que este design importa, olha para a forma como os drivers tradicionais lidam com a transferência de dados. A maioria dos drivers de bases de dados comunica com o PostgreSQL usando um formato baseado em texto. Quando fazes uma query à base de dados a pedir um número, um timestamp ou um array complexo, a base de dados pega na sua representação interna de memória desses dados e converte-a numa string. A seguir, envia essa string pela rede. Quando a tua aplicação Python a recebe, o driver tem de fazer o parsing dessa string de texto de volta para um integer de Python, um objeto datetime ou uma list. Pensa nesta abordagem tradicional como uma equipa que depende de um tradutor para cada conversa interna. A base de dados lê as suas estruturas de dados nativas, escreve-as como documentos de texto standard e envia-as pela rede. A tua aplicação Python recebe estes documentos e traduz meticulosamente o texto de volta para os seus próprios objetos de memória estruturados. Todo este encoding, stringifying e parsing queima tempo de CPU e consome memória. O asyncpg resolve este problema falando diretamente o protocolo binário de frontend e backend do PostgreSQL. Ele força a base de dados a usar input e output binários exclusivamente. Em vez de dependerem de um tradutor, a base de dados e o driver falam exatamente a mesma linguagem nativa. Se fizeres uma query a pedir um integer de sessenta e quatro bits, o PostgreSQL envia os raw bytes que representam esse integer. O asyncpg lê esses bytes diretamente para um objeto integer de Python. Não há formatação de strings. Não há parsing de texto. Esta compreensão nativa estende-se a dados complexos. Quando pedes um bloco JSON, um universally unique identifier, ou um tipo de dados geométrico, o protocolo binário garante que o payload se mantém compacto e estritamente estruturado. O driver sabe exatamente quantos bytes ler para cada coluna, sem nunca ter de fazer scan por delimitadores de texto. Aqui está o insight principal. A velocidade do asyncpg não vem principalmente da natureza non-blocking do asyncio de Python. Os ganhos massivos de performance vêm da remoção completa da camada de tradução de texto. Estás a fazer significativamente menos trabalho por cada row devolvida. Ao forçar estritamente a transferência de dados binários, a tua aplicação para de desperdiçar recursos a ler texto e gasta esse tempo de CPU a executar a tua verdadeira business logic. É tudo por este episódio. Obrigado por ouvires, e continua a construir!
4

Ligação e Execução Básica

3m 29s

Comece a usar o asyncpg ligando-se a uma base de dados e executando consultas básicas. Compreenda a sintaxe nativa de argumentos do Postgres.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. High-Performance Python Async, episódio 4 de 17. Se estás habituado aos drivers standard de bases de dados de Python, a forma como passas variáveis para as queries vai apanhar-te desprevenido, e o teu código vai quebrar imediatamente. Os drivers standard de Python dependem de marcadores de formatação de strings, mas esta library fala diretamente com o motor da base de dados. Hoje cobrimos a ligação e a execução básica. Para começares a falar com a tua base de dados, usas a função connect do asyncpg. Fazes await desta função e passas-lhe um Data Source Name, que é um connection URI standard do Postgres. Isto parece exatamente um endereço web. Começa com postgresql dois pontos barra barra, seguido pelo teu username, dois pontos, a tua password, um arroba, o endereço do host, e finalmente uma barra com o nome da base de dados. Fazer await desta função estabelece a ligação de rede e dá-te um connection object ativo. Agora queres inserir um username e uma data de nascimento numa tabela. É aqui que a sintaxe da query muda. Não uses percentagem s ou pontos de interrogação para os teus query parameters. Como o asyncpg faz bypass deliberadamente à database API standard de Python, obriga-te a usar placeholders nativos do Postgres. Escreves sinal de dólar um, sinal de dólar dois, e por aí fora. A tua query string vai parecer um insert statement standard, mas os valores serão dólar um e dólar dois. Para correres esta query sem pedires dados de volta, fazes await do método execute no teu connection object. Passas a query string primeiro, seguida pelas variáveis reais para o nome e a data de nascimento. O método execute corre o statement e descarta quaisquer dados tabulares. Devolve simplesmente uma status string do Postgres, algo como insert zero one. Não devolve as rows reais da base de dados. Se precisares que a base de dados gere um ID único para este novo user, e precisares desse ID de volta em Python, o execute é a ferramenta errada. Mudas a tua SQL query para adicionar uma cláusula returning id no final. Como agora esperas dados de volta, usas o método fetchval. O método fetchval corre a query e devolve exatamente um valor específico. Ele olha para a primeira row devolvida, agarra na primeira coluna, e dá-te apenas esse pedaço de dados. Isto é perfeito para agarrar um user ID recém-gerado. Se precisares de mais do que apenas o ID, talvez queiras os defaults da base de dados para várias colunas, usas o fetchrow em vez disso. Fazer await do fetchrow devolve um único record object contendo todas as colunas dessa primeira row. Podes aceder aos dados dentro deste record exatamente como num dictionary de Python, usando os nomes das colunas como keys. Quando terminares de inserir os teus dados, tens de fazer await do método close no connection object para limpar o network socket e libertar os recursos da base de dados. Aqui está o ponto chave. Obrigar-te a usar placeholders nativos de sinal de dólar não é apenas uma peculiaridade estilística. Isto permite ao asyncpg fazer bypass total à string interpolation do lado do cliente, mapeando os tipos de Python diretamente para os formatos binários do Postgres para máxima velocidade e proteção completa contra SQL injection. Obrigado por ouvirem, happy coding a todos!
5

Conversão de Tipos Nativa

3m 41s

Descubra como o asyncpg mapeia automaticamente os tipos de dados do PostgreSQL para objetos nativos do Python, eliminando a necessidade de análises complexas de ORM.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Python Async de Alta Performance, episódio 5 de 17. Escreves uma raw SQL query, executas e preparas-te para fazer o parse manual de strings de datas, fazer o split de arrays separados por vírgulas e fazer o cast de valores monetários. Mas não tens de o fazer. Não precisas de um ORM para obter objetos Python totalmente tipados da tua base de dados. O asyncpg trata disso automaticamente através da conversão nativa de tipos. Quando o asyncpg comunica com o PostgreSQL, usa o protocolo binário da base de dados. Sabe exatamente que tipo de dados cada coluna representa. Em vez de te entregar raw text strings que exigem um parse secundário em Python, o asyncpg traduz os tipos do PostgreSQL diretamente para objetos da standard library do Python. Esta tradução acontece automaticamente em ambas as direções. Quando passas um objeto Python como query parameter, o asyncpg faz o encode para o formato binário correto do PostgreSQL. Quando a base de dados responde, o asyncpg faz o decode dos dados binários de volta para o tipo Python correspondente. Considera um cenário em que vais buscar um perfil de utilizador à tua base de dados. A tua SQL query pede um username, um array de tags do utilizador, a data de criação da conta e o último endereço IP conhecido. Em muitos database drivers, receberias strings das quais terias de fazer o parse manualmente. Com o asyncpg, o record de resultado já vem tipado. O username é uma string standard do Python. A coluna de tags, que é um array no PostgreSQL, chega como uma list nativa de strings do Python. A data de criação é um objeto datetime standard. O endereço IP, guardado como um tipo inet na base de dados, mapeia diretamente para objetos ipaddress built-in do Python. Escreves zero lógica de parsing para conseguir isto. Existe um mapeamento rigoroso para números que apanha alguns developers desprevenidos. Se a tua coluna no PostgreSQL estiver definida como numeric, não é convertida para um float do Python. O asyncpg mapeia o tipo numeric do PostgreSQL diretamente para a classe decimal dot Decimal do Python. Isto preserva a precisão exata. Se estiveres a consultar registos financeiros ou medições precisas, não vais perder dados devido a erros de arredondamento de floating-point. Tipos floating-point standard no Postgres, como real ou double precision, mapeiam para floats do Python. Aqui está o insight principal para outros tipos específicos. Se selecionares uma coluna UUID nativa, recebes um objeto uuid dot UUID do Python, e não uma representação genérica em string. Datas tornam-se objetos datetime dot date. Intervalos do Postgres mapeiam perfeitamente para timedeltas do Python. Dados binários guardados numa coluna bytea são convertidos diretamente para bytes do Python. Colunas JSON e JSONB comportam-se de forma ligeiramente diferente. O asyncpg converte dados JSON e JSONB para strings standard do Python por default. Não faz o parse automático para dicionários do Python. Recebes a raw string, que podes depois passar para o módulo json standard do Python se precisares de manipular os dados aninhados. Confiar nesta tradução binária de tipos mantém a lógica da tua aplicação limpa e transfere o peso do type safety para o database driver, que é o seu lugar. É tudo por este episódio. Obrigado por ouvires, e continua a programar!
6

Codecs de Tipos Personalizados

3m 57s

Aprenda a definir conversões de dados personalizadas no asyncpg. Este episódio explica como utilizar o set_type_codec para descodificar automaticamente JSONB em dicionários Python.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. High-Performance Python Async, episódio 6 de 17. Fazes uma query a uma base de dados para obter um registo de utilizador e recebes uma raw string de volta. Todas as vezes, escreves o mesmo código boilerplate para fazer o parse dessa string para um dictionary antes de poderes realmente usar os dados. Podes ensinar o driver da tua base de dados a falar o formato de dados da tua aplicação nativamente, poupando-te ao parsing manual em cada query. Isto é feito usando Custom Type Codecs. Quando fazes uma query ao PostgreSQL usando o driver, tipos básicos como integers e text são traduzidos automaticamente. Mas quando usas tipos de base de dados mais ricos como JSON, o driver precisa de saber como queres que esses dados sejam representados em Python. Um custom type codec atua como uma camada de tradução. Fica entre a connection da base de dados e a lógica da tua aplicação. Para configurar isto, usas um método chamado set type codec. Chamas este método diretamente num objeto de connection ativo. Ele requer quatro informações principais. Primeiro, forneces o nome do tipo da base de dados, como a string jsonb. Segundo, especificas o schema onde este tipo vive. Para tipos built-in do PostgreSQL, este é o schema pg catalog. A seguir, forneces a lógica de tradução passando uma função encoder e uma função decoder. O encoder define como o Python envia dados para a base de dados. Ele recebe o teu objeto Python e retorna um formato que o PostgreSQL entende, tipicamente uma string. Se estiveres a trabalhar com JSON, podes simplesmente passar a função json dumps da standard library. O decoder define como os dados voltam da base de dados. Ele recebe a raw string do PostgreSQL e retorna o teu objeto Python desejado. Para JSON, basta passares a função json loads. Considera um sistema que armazena preferências de utilizador não estruturadas. Na tua base de dados, a coluna de preferências é definida como jsonb. Na tua aplicação Python, manipulas as preferências como um dictionary standard. Assim que o teu codec estiver configurado, executas uma query select básica para um utilizador. A coluna de preferências chega à tua aplicação já estruturada como um dictionary Python. Quando corres uma query insert, passas um dictionary diretamente como argumento da query. O driver aciona automaticamente o teu encoder, converte o dictionary para JSON e envia-o para a base de dados. Nunca chamas um encoder ou decoder manualmente no teu código de execução da query. Aqui está o ponto chave. Os custom type codecs não se aplicam globalmente a todo o driver da base de dados. O método set type codec modifica apenas a connection específica na qual é chamado. Se configurares um codec numa connection, uma segunda connection não saberá nada sobre ele e retornará raw strings novamente. Este comportamento frequentemente causa problemas quando os developers introduzem uma connection pool. Não podes configurar uma pool com uma única chamada ao método do codec. Em vez disso, tens de registar o teu custom codec sempre que uma nova connection é estabelecida. Consegues isto definindo uma função de inicialização. Dentro dessa função, aceitas o novo objeto de connection e chamas o set type codec nele. Depois, passas esta função de inicialização para a tua lógica de criação da pool. A pool corre a tua função automaticamente sempre que abre uma nova connection, garantindo que os teus codecs estão sempre presentes e ativos. Mover a data serialization para a camada do driver através de custom type codecs remove a lógica de parsing repetitiva e garante que os teus formatos de dados permanecem perfeitamente sincronizados em toda a tua aplicação. Obrigado por ouvirem, happy coding a todos!
7

Codecs Avançados com PostGIS

3m 51s

Aprofunde-se nos codecs de tipos personalizados mapeando os tipos de geometria PostGIS do PostgreSQL para objetos Shapely do Python utilizando o formato binário.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. High-Performance Python Async, episódio 7 de 17. Lidar com coordenadas geográficas normalmente significa fazer parsing confuso de strings. Escreves uma query, recebes uma string de texto gigante cheia de números, e depois queimas ciclos de CPU a desconstruí-la só para encontrar uma latitude e uma longitude. Codecs avançados com PostGIS dentro do asyncpg resolvem completamente esta fricção. Quando extrais custom types do PostgreSQL, estás a lidar com codecs. Um codec diz ao asyncpg como traduzir um data type do PostgreSQL para um objeto Python. Muitas vezes há confusão aqui entre o formato de texto e o formato binário. O formato de texto é o default para muitas ferramentas de base de dados. Com o PostGIS, uma query de texto devolve Well-Known Text. Isto parece-se com a palavra POINT seguida pelas coordenadas dentro de parênteses. É human-readable, mas lê-lo em código requer alocar strings, procurar por parênteses e fazer o casting de caracteres para números floating-point. Fazer o parsing de texto é lento, e escala mal quando estás a processar milhares de rows. Tu queres o formato binário. O PostGIS usa um standard chamado Well-Known Binary. Quando configuras o teu codec no asyncpg, defines explicitamente o argumento format para binary. A base de dados salta a geração de texto e entrega raw bytes. Agora, precisas de uma forma de traduzir esses bytes para algo que a tua aplicação Python possa realmente usar. É aqui que entra uma library Python como o Shapely. O Shapely lida com geometria complexa, e já sabe exatamente como ler Well-Known Binary. Dizes ao asyncpg para usar um custom type codec chamando o método set type codec diretamente na tua ligação à base de dados. Especificas o nome do type no PostgreSQL, que é geometry. Depois, forneces uma função encoder e uma função decoder. O decoder pega na raw byte string do PostgreSQL e passa-a diretamente para o binary reader do Shapely. Pensa em fazer uma query da localização do Empire State Building. Sem um custom binary codec, a tua base de dados devolve uma string, a tua aplicação faz o parsing, constrói um dicionário, e eventualmente cria um objeto de geometria. Com o binary codec implementado, executas um select statement standard. O asyncpg interceta os dados binários, corre a tua função decoder, e entrega-te um objeto Shapely Point totalmente formado instantaneamente. Podes aceder imediatamente às coordenadas x e y no objeto devolvido. O processo funciona ao contrário para dados que voltam para a base de dados. A tua função encoder prepara os dados Python para serem enviados para o PostgreSQL. Os objetos Shapely implementam um standard chamado geo interface. Esta é uma estrutura de dicionário Python comum usada para geometria. O teu encoder pega em qualquer objeto Python que suporte esta interface, usa o Shapely para o serializar como Well-Known Binary, e envia esses raw bytes de volta para a base de dados. Tu nunca tocas numa representação de texto. Se estás a achar estes deep dives úteis, podes apoiar o podcast procurando por DevStoriesEU no Patreon. Aqui está o insight principal. Ao usar estritamente o formato binário para custom type codecs, eliminas o bottleneck de serialização, permitindo que a tua base de dados e a tua aplicação Python comuniquem à velocidade da memória. Obrigado por ouvirem, happy coding para todos!
8

Gerir Transações

3m 46s

Domine as transações de base de dados no asyncpg. Abordamos o comportamento de auto-commit e como executar múltiplas consultas de forma segura utilizando context managers assíncronos.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Python Async de Alta Performance, episódio 8 de 17. Executas uma query de update e, a seguir, o teu script crasha antes da próxima query correr. Vais verificar a base de dados à espera que nada tenha mudado, mas o primeiro update está lá, guardado permanentemente. No asyncpg, se não pedires explicitamente uma transaction, cada query faz commit no milissegundo em que termina. Gerir transactions é a forma de corrigires este comportamento. Por defeito, o asyncpg opera em modo auto-commit. Isto significa que, se escreveres código que executa três queries umas a seguir às outras, não estás a correr um único bloco de lógica. Estás a correr três operações completamente isoladas. Se a segunda query falhar, a primeira já está finalizada na base de dados. A falta de um bloco de transaction explícito é uma causa frequente de corrupção do state da aplicação. Imagina um cenário em que estás a transferir dinheiro entre duas contas bancárias. Tens de deduzir fundos da conta A e, a seguir, adicionar exatamente esses fundos à conta B. Ambos os updates têm de ter sucesso juntos, ou ambos têm de falhar juntos. Se a dedução tiver sucesso, mas a adição falhar, o dinheiro simplesmente desaparece. Para ligar estas operações, usas o método transaction no teu connection object. Este método devolve um context manager assíncrono. No teu código, escreves async with connection dot transaction, seguido de dois pontos, e depois indentas as tuas queries relacionadas. Quando o Python entra neste bloco, o asyncpg diz ao PostgreSQL para iniciar uma nova transaction. Dentro do bloco, executas a query de dedução, seguida pela query de adição. Se ambas as queries correrem sem problemas e o Python chegar ao fim do bloco, o asyncpg emite automaticamente um comando commit. As alterações em ambas as contas ficam visíveis para o resto da base de dados exatamente no mesmo instante. Aqui está o ponto chave. Se ocorrer algum problema dentro desse bloco, a base de dados permanece segura. O problema pode ser uma violação de constraint da base de dados, um timeout de rede, ou até mesmo um erro puro de Python, como uma variável em falta ou uma divisão por zero. Se uma exception for lançada, o context manager interceta-a. Ele envia automaticamente um comando de rollback para o PostgreSQL, apagando a dedução da conta A, e depois deixa a exception de Python continuar a propagar-se pela tua call stack. Também podes aninhar estes blocos com segurança. Se abrires um novo context manager de transaction enquanto já estiveres dentro de um bloco de transaction ativo, o asyncpg não se confunde. Em vez disso, cria automaticamente um savepoint na base de dados. Um savepoint funciona como um marcador dentro de uma transaction em curso. Se o bloco interno der um erro, faz rollback do state da base de dados apenas até esse marcador. O bloco externo permanece completamente intacto e ainda pode fazer commit do seu próprio trabalho, ou optar por falhar com base na tua lógica. Não precisas de escrever comandos de savepoint manualmente, basta aninhares os teus blocos async with. Em última análise, o context manager de transaction liga permanentemente o state da tua base de dados ao teu state de execução de Python, garantindo que uma exception de Python não tratada seja uma garantia absoluta contra updates parciais na base de dados. É tudo por este episódio. Obrigado por ouvires, e continua a desenvolver!
9

Pool de Ligações

3m 21s

Escale a sua aplicação com o connection pooling integrado do asyncpg. Aprenda a gerir eficientemente as ligações à base de dados em serviços web de alto tráfego.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Python Async de Alto Desempenho, episódio 9 de 17. Abrir uma nova connection à base de dados para cada web request que chega é uma maneira altamente eficaz de crashares completamente o teu server. Só o network overhead vai paralisar a tua aplicação, e vais esgotar rapidamente o limite de connections da tua base de dados. A solução para isto é Connection Pooling. Quando usas uma library como o asyncpg para falar com o PostgreSQL, estabelecer uma bare connection é uma operação dispendiosa. Requer um TCP handshake, negociação segura e autenticação na base de dados. Se corres um web service de alto tráfego, simplesmente não te podes dar ao luxo de pagar esta taxa de latência em cada HTTP request. Em vez disso, precisas de manter uma coleção estável de connections prontas a usar. No asyncpg, consegues isto usando a função create pool. Normalmente, chamas esta função uma vez durante a fase de startup da tua aplicação. Forneces as credenciais da base de dados, o host e o port, e o asyncpg arranca com um conjunto de idle connections em background. A partir desse ponto, os teus route handlers e background tasks nunca criam uma nova connection do zero. Apenas pedem emprestadas as que já existem. Há aqui uma armadilha comum que engana muitos developers. Não confundas pedir uma connection emprestada com abrir uma transaction na base de dados. São operações completamente distintas. Quando pedes uma connection emprestada do pool, estás apenas a reservar o network socket para teu uso exclusivo e temporário. Se a tua operação exigir atomicidade entre múltiplas queries, ainda tens de iniciar explicitamente uma transaction nessa connection emprestada específica. Pensa num web service FastAPI ou aiohttp de alto tráfego. Imagina que tens um endpoint que aceita um número inteiro, faz uma query à base de dados para calcular a potência de dois desse número, e devolve o resultado. Quando um request chega ao teu endpoint, chamas o método acquire no teu objeto pool. Fazes isto usando um context manager assíncrono. Isto retira temporariamente uma connection do pool. A seguir, usas essa instância de connection específica para executar a tua query à base de dados para calcular a potência de dois. Assim que o bloco de código termina, o context manager liberta a connection automaticamente. Limpa o seu estado e devolve-a ao pool, tornando-a imediatamente disponível para o próximo HTTP request que chegar. Se um pico repentino de tráfego atingir o teu web server e todas as connections no pool estiverem em uso, o próximo request não crasha. Fica à espera. A chamada acquire vai simplesmente pausar a execução até que outro request termine e devolva a sua connection. Aqui está o ponto chave. O connection pool não poupa apenas tempo em network handshakes. Atua como um concurrency throttle rigoroso e fiável. Protege a tua base de dados PostgreSQL de ser sobrecarregada por uma enxurrada inesperada de tráfego. Se configurares o teu pool para manter exatamente vinte connections, a base de dados nunca vai ver mais de vinte queries ativas em simultâneo dessa instância da aplicação, não importa quantos milhares de requests simultâneos atinjam o teu web server. Obrigado por ouvires. Até à próxima!
10

Cache de Prepared Statements

3m 57s

Compreenda como o asyncpg otimiza a análise de consultas com prepared statements automáticos, e por que razão poolers externos como o PgBouncer podem causar conflitos.

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Python Async de Alta Performance, episódio 10 de 17. A tua aplicação corre perfeitamente na tua máquina local. Mas no momento em que fazes o deploy para produção por trás de um router de ligações à base de dados, começas a ver crashes aleatórios. Os logs queixam-se de statements que já existem, ou statements que não conseguem ser encontrados. Nada no teu código mudou. Isto acontece por causa de uma otimização built-in chamada Prepared Statement Caching. Para perceber o erro, primeiro precisamos de olhar para o que o asyncpg faz nos bastidores. Sempre que envias uma query através do asyncpg, a library traduz a query automaticamente para um prepared statement no servidor PostgreSQL. Normalmente, quando uma base de dados recebe uma query, tem de fazer o parse do texto, analisar a sintaxe e construir um plano de execução. Isto demora tempo. Ao preparar o statement, o PostgreSQL faz este trabalho pesado exatamente uma vez e atribui-lhe um nome interno. Para todas as execuções futuras dessa mesma query, o asyncpg envia apenas os novos parâmetros e o nome do statement. Isto salta a fase de parsing por completo e dá um boost enorme de performance. O asyncpg mantém uma cache destes prepared statements em memória, estritamente associada à ligação ativa da base de dados. Aqui está o ponto chave. O problema surge de um conflito entre a forma como o asyncpg gere as suas ligações internas, e como poolers externos, como o PgBouncer, operam. O asyncpg assume que tem uma ligação física dedicada e persistente ao servidor Postgres. Quando cria um prepared statement, confia que o statement vai continuar disponível nessa mesma ligação até que a ligação feche. Agora introduz o PgBouncer na arquitetura, especificamente a correr em transaction mode. O PgBouncer fica entre a tua aplicação e a base de dados. Ele mantém uma pequena pool de ligações Postgres reais e partilha-as por milhares de requests de clientes que chegam. Em transaction mode, o PgBouncer dá à tua aplicação uma ligação física à base de dados apenas durante a duração de uma única transação. No momento em que essa transação faz commit, o PgBouncer retira a ligação física e entrega-a a um cliente completamente diferente. Isto quebra a cache de prepared statements. A tua aplicação envia uma query. O asyncpg prepara-a e guarda-a em cache na ligação física A. A transação termina. Alguns segundos depois, a tua aplicação envia exatamente a mesma query. O asyncpg lembra-se de que já preparou esta query, por isso diz ao Postgres para executar o statement guardado. Mas desta vez, o PgBouncer encaminhou a tua aplicação para a ligação física B. A ligação B não tem qualquer registo desse prepared statement. A base de dados lança um erro a dizer que o statement não existe. O inverso também é verdade. Um cliente diferente pode ser encaminhado para a ligação A, tentar preparar um statement com o mesmo nome interno, e disparar um erro a dizer que o statement já existe. A solução é simples, mas requer um trade-off. Tens de dizer ao asyncpg para desativar esta otimização. Quando inicializas a tua ligação ou pool de ligações do asyncpg, passas um argumento específico a definir o tamanho da cache de statements para zero. Isto desativa completamente o caching automático de prepared statements. As tuas queries vão agora ser parsed pelo PostgreSQL sempre que correrem. Sacrificas um pouco de performance de parsing, mas a tua aplicação vai ficar instantaneamente estável em ligações distribuídas. Se as tuas ligações à base de dados estiverem a ser encaminhadas dinamicamente por transação, a tua aplicação já não pode assumir que o servidor de base de dados se lembra de seja o que for entre queries. É tudo por este episódio. Obrigado por ouvires, e continua a desenvolver!
11

Arrays do Postgres e Cláusulas IN

4m 02s

Resolva o erro de sintaxe mais comum ao migrar para o asyncpg. Aprenda a filtrar corretamente consultas em relação a uma lista de valores utilizando ANY().

Download
Olá, daqui é o Alex do DEV STORIES DOT EU. Python Async de Alta Performance, episódio 11 de 17. Escreves uma query SQL standard para filtrar registos. Passas uma lista de valores para os parâmetros da tua query, tal como já fizeste em dezenas de outras libraries de bases de dados. O Postgres dá imediatamente um syntax error. O problema não são os teus dados. O problema é como lidas com arrays e cláusulas IN do Postgres. Este é o syntax error número um para developers que migram para asyncpg a partir de outras interfaces de bases de dados. Em muitas libraries mais antigas, o driver interceta a tua query string. Se passares uma lista Python para uma cláusula IN, a library reescreve a string SQL dinamicamente. Expande a tua lista numa string de parâmetros individuais separados por vírgulas antes de a enviar para a base de dados. O asyncpg não faz isto. Depende inteiramente de prepared statements nativos do Postgres do lado do servidor. Aqui está o ponto chave. Em SQL standard do Postgres, o operador IN exige uma lista de valores escalares separados por vírgulas e entre parênteses. Não aceita um único objeto array. Quando passas uma lista Python para o asyncpg como parâmetro, o asyncpg mapeia essa lista diretamente para um array nativo do Postgres. A tua query acaba por ser avaliada como uma expressão IN num objeto array, o que é uma sintaxe inválida. O Postgres espera uma expressão IN valor um, valor dois. Para corrigir isto, tens de parar de usar o operador IN para listas parametrizadas. Em vez disso, usa a função any do Postgres. A lógica muda de perguntar se um valor está IN numa lista, para perguntar se um valor é igual a qualquer item dentro de um array. O operador any foi desenhado especificamente para funcionar com tipos array do Postgres. Avalia o valor à esquerda, verifica o array à direita, e devolve true se encontrar um match. Também precisas de dizer ao Postgres que tipo de array está a receber, fazendo cast do parâmetro. Se esperas um array de text strings, fazes um cast explícito do teu parâmetro para um text array. Este type casting explícito garante que o Postgres sabe exatamente como planear e executar a query sem ter de adivinhar o data type subjacente da binary stream que está a chegar. Considera um cenário onde estás a filtrar uma lista de produtos. Queres fazer match da categoria do produto com uma lista dinâmica de categorias selecionadas pelo utilizador. Escreves uma query para selecionar produtos onde a categoria é igual a any parâmetro um, e fazes cast do parâmetro um para um text array. No teu código Python, chamas o método fetch da tua base de dados. Passas a query string como primeiro argumento, e a tua lista Python de strings — como eletrónica e livros — como segundo argumento. O asyncpg empacota a tua lista Python num text array binário do Postgres e envia-a pela rede como um único parâmetro. O Postgres recebe a query, vê o text array, e faz match das categorias de forma eficiente usando a função any. Esta abordagem é estritamente melhor do que a manipulação de strings. Como a estrutura da query nunca muda, independentemente de quantas categorias estejam na lista, o Postgres faz o parse e planeia o statement exatamente uma vez. A base de dados faz cache do query plan, poupando tempo de execução em chamadas subsequentes. Também eliminas o risco de SQL injection, já que os dados são transmitidos em formato binário, completamente separados do texto da query. Se passares uma lista Python para um parâmetro da base de dados, trata-a como um array nativo, faz cast para o tipo correto, e avalia-a com a função any. É tudo por este episódio. Obrigado por ouvires, e continua a desenvolver!
12

Objetos Record vs Named Tuples

3m 44s

Explore o design único dos objetos Record do asyncpg. Compreenda por que razão a notação de ponto é omitida por predefinição e como ativá-la com classes personalizadas.

Download
Olá, daqui é o Alex do DEV STORIES DOT EU. Python Async de Alta Performance, episódio 12 de 17. Fazes uma query à tua base de dados, recebes uma row de volta e, instintivamente, escreves o nome da variável ponto id. Imediatamente, o Python lança um AttributeError. Os teus dados estão lá, mas não lhes consegues aceder da forma que esperas. Isto acontece devido ao design deliberado por trás dos Record objects em vez das named tuples. Quando fazes fetch de dados usando o asyncpg, ele não devolve dicionários Python standard, nem devolve named tuples. Em vez disso, devolve um custom object altamente otimizado chamado Record. Se estás habituado a outros database drivers ou object-relational mappers, podes esperar que a dot-notation funcione out of the box. Com um Record do asyncpg, isso quebra intencionalmente. Podes perguntar-te por que razão o driver não usa simplesmente uma named tuple standard do Python, já que as named tuples suportam nativamente dot-notation. A razão é pura performance. Uma named tuple exige que o Python gere uma estrutura de class totalmente nova para cada combinação única de colunas devolvida por uma query. Se a tua aplicação executa centenas de queries com formatos diferentes, gerar essas dynamic classes cria um overhead de execução enorme. A library foi construída para velocidade absoluta, por isso contorna completamente esse bottleneck, devolvendo o seu próprio tipo Record compilado. Este custom Record object funciona como um híbrido rápido. Suporta integer indexing exatamente como uma tuple standard, o que significa que podes aceder à primeira coluna usando o index zero. Mas também fornece um mapping semelhante a um dicionário. Acedes às tuas colunas usando bracket notation, passando o nome da coluna como uma string. Aqui está o ponto chave. Os criadores desativaram ativamente a dot-notation nestes objetos para proteger a tua aplicação de namespace clashes. Pensa nos métodos de mapping standard em Python, como keys, items, values ou get. Se a tua tabela da base de dados tiver uma coluna chamada keys, e o driver suportasse dot-notation, escrever record ponto keys criaria um conflito estrutural. O Python não saberia se querias o valor da base de dados ou o built-in method. Ao forçar a bracket notation baseada em strings, o driver garante que os nomes das tuas colunas nunca vão colidir com os atributos standard do Python. No entanto, se controlas o teu database schema por completo, sabes de certeza que não usas palavras reservadas do Python como nomes de colunas, e precisas de dot-notation para a tua codebase, tens uma saída. Podes fazer override do comportamento default. Quando estabeleces a tua database connection, podes fornecer um parâmetro específico chamado record class. Para implementar isto, escreves uma custom class que herda diretamente do Record base do asyncpg. Dentro desta nova class, implementas o built-in method do Python chamado double underscore getattr. Instruis este método a receber o nome do atributo pedido e simplesmente procurá-lo usando o fallback seguro da bracket notation. Assim que passares esta custom class para o teu connection setup, cada row devolvida pelas tuas queries será uma instance do teu custom object. O Python vai então permitir-te usar dot-notation, fazendo o routing do pedido do atributo de forma transparente através do teu custom method para fazer fetch dos dados da coluna subjacente. Em última análise, a bracket notation estrita num Record default não é um descuido, mas sim uma fronteira de segurança estrutural que garante que o teu data access permanece previsível, independentemente de como o teu database schema mude. É tudo por agora. Obrigado por ouvires, e continua a desenvolver!
13

Streaming de Resultados com Cursors

4m 23s

Evite o esgotamento de memória ao consultar conjuntos de dados massivos. Aprenda a utilizar cursors do asyncpg para fazer streaming de resultados em blocos (chunk-by-chunk).

Download
Olá, daqui fala o Alex da DEV STORIES DOT EU. Python Async de Alta Performance, episódio 13 de 17. Precisas de exportar uma tabela de utilizadores com um milhão de rows para um ficheiro, por isso fazes um fetch normal à base de dados. De repente, a tua aplicação Python consome toda a RAM disponível e o servidor crasha. Não podes puxar datasets massivos para a memória de uma só vez, e é exatamente por isso que usamos streaming de resultados com cursors. Normalmente, quando executas uma query no asyncpg, todo o result set é puxado através da rede a partir do PostgreSQL e mantido na memória do Python. A base de dados constrói a resposta completa, envia-a, e o asyncpg constrói objetos Python para cada row antes que o teu código consiga processar a primeira. Isto não tem qualquer problema para cinquenta rows. Mas torna-se um problema imediato para cinco milhões de rows. Para evitar que o servidor crashe, precisas de consumir os dados aos poucos, em vez de os engolir de uma só vez. Um cursor da base de dados dá-te um pointer para os resultados da query no servidor PostgreSQL. Em vez de puxar tudo, o cursor permite que a tua aplicação Python faça fetch dos dados de forma incremental. No asyncpg, fazes isto chamando o método cursor na tua connection. Passas a tua query SQL e quaisquer argumentos necessários. Este método devolve um iterator assíncrono. Escreves um loop async for para iterar sobre este cursor. Nos bastidores, o asyncpg faz automaticamente o fetch de rows em pequenos batches fáceis de gerir a partir do PostgreSQL. O teu código processa algumas rows, escreve-as no teu ficheiro de exportação e avança. O Python limpa as rows antigas da memória, o que mantém o memory footprint total da tua aplicação completamente estável, não importa o quão grande a tabela seja. Existe aqui uma regra estrita que engana muitos developers. Se tentares iterar um cursor diretamente numa connection standard, o asyncpg lança imediatamente um InterfaceError. A mensagem de erro vai indicar que os cursors não podem ser usados fora de uma transaction. Aqui está a informação crucial. Os cursors do PostgreSQL estão estruturalmente ligados a transactions da base de dados. Quando uma transaction faz commit ou rollback, o PostgreSQL destrói quaisquer cursors ativos associados a ela. Por default, o asyncpg opera em modo auto-commit. Isto significa que cada query individual que corres é encapsulada na sua própria transaction pequena e invisível, que fecha no momento em que a query termina. Se o asyncpg te permitisse abrir um cursor em modo auto-commit, essa transaction implícita terminaria instantaneamente, e o PostgreSQL mataria o teu cursor antes que pudesses fazer fetch de uma única row. Para que os cursors funcionem, tens de gerir explicitamente o limite da transaction. Fazes isto abrindo um context manager assíncrono usando o método transaction na tua connection. Assim que estiveres em segurança dentro desse bloco da transaction, chamas o método cursor e inicias o teu loop async for. Como a transaction permanece aberta durante toda a duração do bloco do context manager, o teu cursor mantém-se vivo no servidor PostgreSQL, permitindo-te fazer stream de todo o milhão de rows com segurança. Há uma rara exceção a esta regra. O PostgreSQL suporta uma feature onde podes declarar um cursor SQL raw com a expressão WITH HOLD. Isto diz ao motor da base de dados para materializar o resultado e manter o cursor vivo mesmo após a transaction estar concluída. Fazer isto consome recursos da base de dados e faz bypass à eficiência do streaming standard. Para quase todas as tarefas de streaming no asyncpg, o bloco de transaction explícito é a abordagem necessária. Se achas estes episódios úteis e queres apoiar o programa, procura por DevStoriesEU no Patreon. Lembra-te que um cursor transforma a tua interação com a base de dados de uma memory allocation massiva e arriscada, num pipeline controlado e persistente que pode processar com segurança qualquer volume de dados. É tudo por este episódio. Obrigado por ouvires, e continua a programar!
14

Ingestão Ultrarrápida com COPY

3m 53s

Potencie os seus pipelines de ingestão de dados. Exploramos o protocolo COPY do PostgreSQL para carregar dados em massa de forma exponencialmente mais rápida do que com instruções INSERT.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Python Async de Alta Performance, episódio 14 de 17. Precisas de mover um milhão de rows para dentro ou para fora da tua base de dados. Se o teu primeiro instinto for fazer batch de uma lista enorme de statements insert ou fazer fetch de todas as rows para uma lista gigante em Python, estás a escolher o caminho mais lento disponível. Existe um mecanismo criado especificamente para contornar este overhead. Hoje, vamos falar sobre ingestão ultrarrápida com COPY. O COPY é um protocolo específico do Postgres para transferência de dados em bulk. Quando corres statements insert ou select standard, o Postgres tem de fazer o parse da query, planeá-la e executá-la. Fazer isto repetidamente adiciona um overhead enorme. O protocolo COPY ignora completamente o pipeline de queries standard. Abre uma stream direta para a storage layer, movendo os dados num formato altamente otimizado. Por causa disto, é ordens de magnitude mais rápido do que bulk inserts. No asyncpg, envias dados para a base de dados usando um método chamado copy to table. Forneces o nome da tabela de destino e uma data source. Essa source pode ser um file path local, um file-like object, ou um iterador assíncrono que faz yield de records. Se o apontares para um ficheiro CSV local, o Postgres lida com o parsing nativamente. Não precisas de abrir o ficheiro em Python, fazer o parse das rows e mapeá-las para variáveis. O driver da base de dados faz stream dos bytes raw do ficheiro diretamente para o servidor. Também podes passar uma simples lista de tuples em Python se os teus dados já estiverem em memória, e o asyncpg vai fazer stream disso usando o protocolo COPY under the hood. Mover dados para fora é igualmente rápido. Se precisares de um export completo, usas o copy from table. Isto pega em todo o conteúdo de uma tabela e envia-o para um ficheiro ou stream. No entanto, fazer dump de uma tabela inteira raramente é o que realmente precisas. Normalmente, queres dados filtrados ou com joins. É aqui que o copy from query entra em ação. Um equívoco comum é que este método apenas faz dump dos resultados da query para um ficheiro estático. Isso simplesmente não é verdade. Embora possa escrever diretamente para um file path, também podes fornecer uma função de callback. O asyncpg vai executar a query e fazer stream dos resultados em chunks para a tua callback, permitindo-te processar um dataset massivo on the fly sem nunca manteres o result set completo na memória do sistema. Considera um cenário em que precisas de gerar um report CSV de todos os utilizadores ativos. Uma abordagem standard é executar uma query select, fazer fetch de cem mil rows para o Python, formatá-las usando o módulo CSV e gravá-las no disco. Isso consome muita memória e CPU. Aqui está o ponto chave. Podes saltar o processamento em Python completamente. Chamas o copy from query, passas-lhe o teu statement select específico, defines o parâmetro format para CSV e forneces um file path de output. O Postgres executa a query, formata os resultados em CSV nativamente no servidor da base de dados, e o asyncpg faz stream do texto finalizado diretamente para o teu disco rígido. A tua aplicação Python atua como um simples pipe, fazendo quase zero manipulação de dados. Deves continuar a usar statements insert e select standard para a lógica de aplicação do dia a dia, mas no momento em que o volume de dados raw se tornar o teu bottleneck, muda para o protocolo COPY para fazeres bypass ao parser SQL completamente. É tudo por este episódio. Obrigado por ouvires, e continua a desenvolver!
15

Listen e Notify Assíncronos

3m 23s

Desbloqueie arquiteturas orientadas a eventos em tempo real diretamente no PostgreSQL. Aprenda a utilizar o add_listener do asyncpg para mensagens pub/sub instantâneas.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Python Async de Alto Desempenho, episódio 15 de 17. Tu recorres a um message broker separado, como o Redis, no momento em que precisas de eventos em tempo real. Mas se a tua aplicação já usa uma base de dados, podes estar a adicionar complexidade de infraestrutura sem motivo absolutamente nenhum. O PostgreSQL tem um message bus em tempo real integrado. Hoje vamos falar sobre Listen e Notify assíncronos. O Postgres suporta nativamente um pattern publish-subscribe usando dois comandos, listen e notify. A library asyncpg expõe esta capacidade em Python através de um método chamado add listener. Em vez de escreveres um loop que faz polling a uma tabela da base de dados a cada poucos segundos para verificar se há novos dados, tu registas uma função de callback assíncrona em Python num channel com um nome específico. Quando um evento ocorre no Postgres, ele faz broadcast de uma mensagem para esse channel, e o teu callback em Python é executado imediatamente. Aqui está o ponto chave. Um listener não está ligado à tua aplicação globalmente, e não está ligado a um connection pool. Ele está vinculado a uma connection de base de dados específica e individual. Este é um ponto de falha comum. Se tu puxares uma connection de um pool, registares o teu listener, e depois libertares a connection de volta para o pool, o listener cai. Para usares esta funcionalidade de forma fiável, tens de adquirir uma connection do asyncpg, chamar o método add listener nela, e manter essa connection aberta indefinidamente. Torna-se num pipeline de escuta dedicado. Vejamos um cenário prático. Tu tens um background worker que precisa de acordar e processar registos sempre que uma nova row é inserida numa tabela de jobs. Em vez de fazeres polling, configuras um trigger na base de dados. Sempre que ocorre um insert, o trigger executa um comando notify num channel chamado new jobs. Também envia um pequeno payload de texto, como o identificador único da nova row. No teu código Python, escreves uma função de callback assíncrona. Esta função espera receber quatro argumentos do asyncpg: o objeto da connection, o identificador do processo do Postgres, o nome do channel, e o próprio payload de texto. A seguir, adquires a tua connection dedicada. Chamas o método add listener nessa connection, passando a string do channel new jobs juntamente com a tua função de callback. Finalmente, manténs o script a correr, tipicamente fazendo await de um evento assíncrono que nunca faz set. O teu processo Python agora fica completamente idle. Usa quase zero recursos de CPU e para de martelar a base de dados com queries vazias. No momento em que uma transação faz commit de uma nova row de job, o Postgres faz push da notificação pelo network socket aberto. O asyncpg lê esse socket e agenda imediatamente o teu callback no event loop do Python, passando-lhe o identificador do novo job. O verdadeiro poder deste pattern é a consistência transacional. Se uma transação da base de dados fizer rollback, quaisquer comandos notify executados durante essa transação são automaticamente descartados pelo Postgres. Isto garante que os teus workers em Python só vão acordar e reagir a dados que foram guardados com sucesso no disco. É tudo por este episódio. Obrigado por ouvires, e continua a programar!
16

Telemetria e Logging de Consultas

3m 30s

Obtenha uma observabilidade profunda do desempenho da sua base de dados. Descubra como utilizar os log listeners do asyncpg para rastrear consultas lentas e monitorizar a telemetria de execução.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Python Async de Alto Desempenho, episódio 16 de 17. Para descobrires porque é que a tua base de dados está lenta, não tens de adivinhar. Podes recorrer aos logs server-side, mas eles não apanham a latência da rede e o overhead do lado do Python. Medir a performance manualmente à volta de cada chamada à base de dados complica rapidamente a tua business logic. A solução é Telemetry e Query Logging. As pessoas costumam confundir log listeners e query loggers. Os log listeners apanham os notices do servidor Postgres. Os query loggers apanham a telemetria de execução client-side. Eles lidam com duas streams de informação completamente distintas. Os query loggers tratam da telemetria. Associas uma função de callback à tua ligação usando o método add query logger. Uma vez associada, sempre que uma query termina a execução, o asyncpg passa automaticamente um objeto LoggedQuery a esse callback. Isto acontece globalmente para essa ligação, completamente desacoplado da função específica que faz a request à base de dados. O objeto LoggedQuery contém três dados críticos. Ele guarda o texto exato da query SQL, os argumentos passados a essa query e o tempo de execução. Os argumentos são capturados exatamente como a tua aplicação os forneceu. Isto poupa-te o trabalho de escreveres lógica manual de formatação de strings só para descobrires que parâmetros causaram uma resposta lenta. Considera um ambiente de produção onde precisas de apanhar qualquer query que demore mais de 500 milissegundos. Defines uma função Python standard que aceita dois parâmetros: a ligação à base de dados e o objeto LoggedQuery. Dentro desta função, verificas o atributo do tempo de execução. Se o tempo exceder zero ponto cinco segundos, escreves o texto da query, os argumentos e a duração exata no teu sistema de monitorização da aplicação. Depois, passas esta função de callback ao método add query logger. Agora, a tua aplicação faz o tracking automático de queries lentas, silenciosamente em background. Se alguma vez precisares de parar este tracking, simplesmente passas a mesma função de callback ao método remove query logger. Agora, a segunda parte disto é o log listener. Enquanto o query logger trata do timing client-side, o log listener trata do messaging server-side. Às vezes, o Postgres envia mensagens que não são erros e não devolvem rows de dados. Estes são notices assíncronos, warnings ou mensagens de log customizadas, geradas diretamente pelo motor da base de dados. Para capturar estas mensagens, associas um callback usando o método add log listener. Quando o Postgres emite um notice ou um warning, o asyncpg dispara este callback. Ele passa a ligação e um objeto de mensagem específico à tua função. Isto dá à tua aplicação visibilidade imediata sobre os warnings ao nível da base de dados, de forma completamente independente dos resultados das tuas queries standard. Tal como com o query logger, podes desassociar este callback mais tarde usando o remove log listener. Aqui está o ponto chave. O query logging client-side dá-te a verdadeira duração de execução sentida pela tua aplicação Python, contornando completamente as suposições entre o delay da rede e o processamento da base de dados. Obrigado por ouvirem. Fiquem bem, todos.
17

Proteger Ligações com SSL

4m 02s

Garanta que as suas ligações à base de dados são seguras. Abordamos a configuração do contexto SSL e como impor o TLS direto ao ligar a bases de dados na cloud.

Download
Olá, daqui é o Alex da DEV STORIES DOT EU. Python Async de Alta Performance, episódio 17 de 17. Ligar a uma base de dados gerida na cloud sem SSL é como gritares as tuas credenciais da base de dados numa sala cheia de gente. Precisas de encriptação, mas configurá-la corretamente leva muitas vezes a erros de ligação confusos ou a defaults inseguros. Hoje, vamos proteger as ligações com SSL no asyncpg. As pessoas muitas vezes atrapalham-se a configurar o SSL na sua lógica de ligação. Se usas URIs de ligação, o asyncpg faz o parse nativamente dos query parameters standard do PostgreSQL para o sslmode, como definir o sslmode para require. Isso funciona para setups básicos. Mas quando precisas de controlo preciso — como ligar com segurança a uma base de dados gerida na cloud usando um bundle de Certificate Authority customizado — as strings de URI standard não são suficientes. Para isso, usas o parâmetro ssl programático. O parâmetro ssl nas funções de ligação do asyncpg dita como o TLS é negociado. Ele aceita dois tipos de valores. O primeiro tipo é um preset de string. Podes passar a string prefer, que tenta uma ligação SSL, mas faz fallback para não encriptada se o server não a suportar. Podes passar require, que força a encriptação, mas ignora a verificação da identidade do server. Ou podes passar verify-full, que força a encriptação e valida rigorosamente o certificado do server contra trusted roots. Aqui está o ponto chave. Quando o teu cenário exigir uma Certificate Authority customizada, não confies em presets de string. Em vez disso, crias um objeto SSLContext standard do Python. Configuras este objeto com os teus ficheiros de certificado customizados, forças uma verificação rigorosa e, depois, passas esse objeto Python diretamente para o parâmetro ssl do asyncpg. Isto dá-te controlo exato sobre o handshake criptográfico, fazendo bypass a quaisquer certificados default do sistema. Isto cobre as regras de encriptação, mas como é que a ligação realmente começa? Isso leva-nos ao parâmetro direct TLS. Por default, o PostgreSQL usa um protocolo chamado STARTTLS. O client faz uma ligação em plain-text, pergunta ao server se suporta encriptação e, se o server disser que sim, eles fazem o upgrade da ligação para TLS. No entanto, setups de proxy modernos — como certos connection poolers ou cloud load balancers — muitas vezes esperam uma ligação TLS direta desde o primeiro byte. Eles não querem a negociação em plain-text. Se a tua infraestrutura for construída desta forma, passas true para o parâmetro de ligação direct TLS. Quando fazes isto, o asyncpg faz skip à negociação STARTTLS e inicia imediatamente um raw TLS handshake. Naturalmente, isto só funciona se também forneceres uma configuração ssl válida. Se ativares o direct TLS, mas deixares o parâmetro ssl vazio, a ligação vai falhar. Ao protegeres as tuas ligações à base de dados, lembra-te que, embora os presets de string sejam convenientes, passar um objeto SSLContext explícito é a única forma de garantir absolutamente que a tua aplicação confia na identidade correta do server. Como este é o episódio final da série, encorajo-te a explorar a documentação oficial do asyncpg e a experimentares estes parâmetros de ligação hands-on. Podes visitar devstories dot eu para sugerires tópicos para as nossas futuras séries. É tudo por este episódio. Obrigado por ouvires, e continua a programar!