Torna al catalogo
Season 13 17 Episodi 1h 4m 2026

High-Performance Python Async

Edizione 2026. Un'analisi approfondita su come accelerare asyncio in Python con uvloop e interfacciarsi direttamente con PostgreSQL utilizzando il protocollo binario di asyncpg.

Python Core Programmazione Asincrona
High-Performance Python Async
In Riproduzione
Click play to start
0:00
0:00
1
Il bisogno di velocità: l'architettura di uvloop
Scopri le differenze architetturali tra l'asyncio standard di Python e uvloop. Esploriamo come uvloop sfrutti Cython e libuv per ottenere prestazioni simili a quelle di Go.
3m 52s
2
Integrare uvloop
Impara a integrare uvloop nella tua applicazione Python. Questo episodio tratta l'approccio EventLoopPolicy per sostituire senza problemi l'event loop predefinito.
3m 59s
3
Introduzione ad asyncpg: il protocollo binario
Esplora il design fondamentale di asyncpg. Discutiamo del perché bypassare la DB-API standard a favore del protocollo binario di PostgreSQL porti a enormi guadagni prestazionali.
3m 37s
4
Connessione ed esecuzione di base
Inizia a usare asyncpg connettendoti a un database ed eseguendo query di base. Comprendi la sintassi nativa degli argomenti di Postgres.
3m 30s
5
Conversione nativa dei tipi
Scopri come asyncpg mappa automaticamente i tipi di dati di PostgreSQL in oggetti Python nativi, eliminando la necessità di complessi parsing tramite ORM.
4m 05s
6
Codec di tipo personalizzati
Impara a definire conversioni di dati personalizzate in asyncpg. Questo episodio spiega come utilizzare set_type_codec per decodificare automaticamente JSONB in dizionari Python.
3m 51s
7
Codec avanzati con PostGIS
Approfondisci i codec di tipo personalizzati mappando i tipi di geometria PostGIS di PostgreSQL in oggetti Shapely di Python utilizzando il formato binario.
3m 39s
8
Gestione delle transazioni
Padroneggia le transazioni del database in asyncpg. Trattiamo il comportamento di auto-commit e come eseguire in sicurezza query multiple utilizzando i context manager asincroni.
3m 38s
9
Connection Pooling
Scala la tua applicazione con il connection pooling integrato di asyncpg. Impara a gestire in modo efficiente le connessioni al database in servizi web ad alto traffico.
3m 19s
10
Caching dei Prepared Statement
Comprendi come asyncpg ottimizza il parsing delle query con i prepared statement automatici e perché pooler esterni come PgBouncer possono causare conflitti.
3m 56s
11
Array Postgres e clausole IN
Risolvi l'errore di sintassi più comune quando si migra ad asyncpg. Impara a filtrare correttamente le query rispetto a un elenco di valori utilizzando ANY().
4m 08s
12
Oggetti Record vs Named Tuple
Esplora il design unico degli oggetti Record di asyncpg. Comprendi perché la dot-notation è omessa per impostazione predefinita e come abilitarla con classi personalizzate.
3m 53s
13
Streaming dei risultati con i cursori
Previeni l'esaurimento della memoria quando interroghi dataset enormi. Impara a utilizzare i cursori di asyncpg per fare lo streaming dei risultati a blocchi.
4m 20s
14
Ingestion velocissima con COPY
Metti il turbo alle tue pipeline di data ingestion. Esploriamo il protocollo COPY di PostgreSQL per caricare dati in blocco in modo esponenzialmente più veloce rispetto alle istruzioni INSERT.
3m 40s
15
Listen e Notify asincroni
Sblocca architetture event-driven in tempo reale direttamente all'interno di PostgreSQL. Impara a utilizzare add_listener di asyncpg per la messaggistica pub/sub istantanea.
3m 25s
16
Telemetria e logging delle query
Ottieni una profonda osservabilità sulle prestazioni del tuo database. Scopri come utilizzare i log listener di asyncpg per tracciare le query lente e monitorare la telemetria di esecuzione.
3m 28s
17
Mettere in sicurezza le connessioni con SSL
Assicurati che le connessioni al tuo database siano sicure. Trattiamo la configurazione del contesto SSL e come imporre il TLS diretto quando ci si connette ai database cloud.
4m 18s

Episodi

1

Il bisogno di velocità: l'architettura di uvloop

3m 52s

Scopri le differenze architetturali tra l'asyncio standard di Python e uvloop. Esploriamo come uvloop sfrutti Cython e libuv per ottenere prestazioni simili a quelle di Go.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Python async ad alte prestazioni, episodio 1 di 17. E se potessi raddoppiare le prestazioni del tuo codice Python async senza riscrivere una singola funzione? Questa è esattamente la promessa della tecnologia che vedremo oggi: l'architettura di uvloop. Quando gli sviluppatori parlano del Python async standard, tendono a confondere due layer distinti. Il primo layer è l'application programming interface. Questa include le keyword async e await che scrivi, le future e i task. Il secondo layer è l'event loop stesso. L'event loop è lo scheduler interno che monitora i socket, gestisce le connessioni di rete e decide quale task eseguire subito dopo. Il Python standard fornisce sia l'interfaccia che un event loop di default scritto in puro Python. Ecco il punto chiave. L'interfaccia e l'event loop sono disaccoppiati. Python ti permette di sostituire lo scheduler sottostante senza cambiare la sintassi che scrivi. Pensala come un'auto. L'API async è il volante, i pedali e il cruscotto. Interagisci direttamente con questi elementi. L'event loop è il motore sotto il cofano. Sostituire l'event loop di default di Python con uvloop è come montare un motore V8 nella tua auto. Sterzi e freni esattamente allo stesso modo, ma l'auto va molto più veloce. Il core di uvloop si basa su due scelte architetturali per raggiungere questa velocità. Primo, è un drop-in replacement scritto interamente in Cython. Cython compila codice simile a Python in estensioni C altamente ottimizzate. Questo elimina l'overhead dell'interpreter Python standard durante l'esecuzione degli hot path dello scheduler. Gli event loop in puro Python passano un sacco di tempo a creare oggetti interni e a gestire lo stato dell'interpreter solo per gestire eventi di rete di routine. Cython elimina tutto questo. Ogni volta che il loop controlla un socket o risveglia un task, esegue codice C nativo invece di passare per il puro Python. Secondo, uvloop delega le interazioni effettive con il sistema operativo a una libreria C chiamata libuv. Se questo nome ti suona familiare, è perché libuv è il motore di I/O asincrono che fa girare Node.js. È battle-tested, altamente ottimizzato per workload network-heavy, e gestisce tutti i complessi dettagli cross-platform del networking asincrono. Wrappando libuv in una shell Cython molto compatta, uvloop porta esattamente lo stesso profilo di performance direttamente in Python. Il risultato architetturale è enorme. Bypassando lo scheduler in puro Python e affidandosi a un motore C compilato, uvloop rende le tue applicazioni asyncio almeno due volte più veloci. In molti scenari di benchmark con un'alta concurrency di connessioni, permette a Python di competere con le performance di linguaggi compilati come Go. Ottieni la developer velocity di Python con la pura velocità di esecuzione del networking nativo in C. La transizione richiede zero modifiche alla tua business logic, alle tue query a database o ai tuoi endpoint API. Il takeaway fondamentale qui è che i bottleneck di performance nel Python async standard raramente riguardano la sintassi del linguaggio, ma piuttosto il motore di esecuzione, e sostituire quel motore ti dà velocità native del C preservando le tue astrazioni Python esistenti. Se vuoi supportare lo show, puoi cercare DevStoriesEU su Patreon. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
2

Integrare uvloop

3m 59s

Impara a integrare uvloop nella tua applicazione Python. Questo episodio tratta l'approccio EventLoopPolicy per sostituire senza problemi l'event loop predefinito.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Python async ad alte prestazioni, episodio 2 di 17. L'ottimizzazione delle performance più potente nella tua applicazione Python async non richiede modifiche all'architettura, nessun refactoring e nessuna configurazione complessa. Richiede esattamente due righe di codice. Oggi parliamo di come inserire uvloop. L'event loop della standard library asyncio è scritto in puro Python. Uvloop è un drop-in replacement basato su libuv, lo stesso engine che alimenta altri runtime ad alta concorrenza. Rende l'esecuzione del tuo codice Python async standard significativamente più veloce. Sostituire il meccanismo di scheduling principale della tua applicazione richiede di dire a Python di abbandonare il suo comportamento di default prima che inizi a fare qualsiasi lavoro effettivo. Nello script di entry point di un web server, come il file main di un'applicazione FastAPI o aiohttp, implementi questa sostituzione usando una event loop policy. Una event loop policy è un oggetto di configurazione globale all'interno del modulo asyncio standard. Stabilisce quale tipo di event loop viene istanziato ogni volta che l'applicazione ne richiede uno nuovo. Per scambiare il loop, importi il modulo asyncio e il modulo uvloop. Poi, chiami la funzione set event loop policy sul modulo asyncio. Le passi una nuova istanza della event loop policy fornita dal modulo uvloop. Ecco il punto chiave. Devi impostare questa policy molto presto. La chiamata deve trovarsi in cima in assoluto al tuo script di esecuzione main, subito dopo i tuoi import. La event loop policy influisce solo sulla creazione di nuovi loop. Se aspetti a impostare la policy fino a quando il tuo web framework è già partito, o dopo che un driver del database async si è inizializzato, è probabile che il loop standard in puro Python sia già in esecuzione. Cambiare la policy a quel punto non ha alcun effetto sul loop esistente. Il tuo codice ignorerà completamente uvloop, oppure si ritroverà con event loop misti che causano deadlock e connessioni interrotte. C'è un'alternativa all'approccio basato sulla policy. Invece di cambiare le regole globali per la creazione dei loop, puoi creare esplicitamente una singola istanza di uvloop. Lo fai chiamando la funzione new event loop direttamente dal modulo uvloop. Una volta che hai quell'oggetto loop in memoria, lo passi ad asyncio chiamando la funzione set event loop. Perché dovresti scegliere un approccio piuttosto che l'altro? Impostare la event loop policy è un override globale. Garantisce che qualsiasi libreria di terze parti, background task o componente del framework nel tuo processo che chiede ad asyncio un nuovo loop riceverà in modo sicuro un uvloop. È la scelta standard per un web server in cui vuoi performance uniformi sull'intero stack applicativo. L'approccio esplicito con new event loop è più mirato. Inietta un'istanza specifica anziché cambiare le regole della factory. Usi questo metodo esplicito quando gestisci ambienti complessi con thread multipli, o quando hai bisogno di un controllo rigoroso su quale loop esatto è in esecuzione in un contesto isolato senza mutare lo stato globale del processo. Per le web application standard, l'override della policy è tutto ciò di cui hai bisogno. Il meccanismo esatto che scegli per inserire uvloop conta meno del tempismo con cui lo applichi. La event loop policy detta le fondamenta della tua intera architettura async, quindi deve essere la primissima istruzione che la tua applicazione esegue prima ancora che venga stabilito qualsiasi contesto async. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
3

Introduzione ad asyncpg: il protocollo binario

3m 37s

Esplora il design fondamentale di asyncpg. Discutiamo del perché bypassare la DB-API standard a favore del protocollo binario di PostgreSQL porti a enormi guadagni prestazionali.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 3 di 17. Ottimizzi i tuoi indici, aggiorni la rete e fai tuning delle tue query, ma la tua applicazione continua a bruciare cicli di CPU per comunicare con il database. Spesso, il collo di bottiglia più grande nelle tue query al database non è la latenza di rete. È il tempo che la tua applicazione passa a fare il parsing del testo. Per eliminare questo overhead, introduciamo asyncpg e la sua implementazione del protocollo binario di PostgreSQL. È un errore comune pensare che asyncpg sia solo un wrapper asincrono per psycopg2. Non lo è. E non è nemmeno un adattamento della DB-API standard di Python. La specifica DB-API guida intrinsecamente i driver del database verso determinati pattern standardizzati per la gestione dei dati. asyncpg ignora completamente questa specifica. È una riscrittura da zero progettata esclusivamente per asyncio e PostgreSQL, che bypassa le interfacce standard del database per parlare con Postgres alle sue condizioni. Per capire perché questo design è importante, guarda come i driver tradizionali gestiscono il trasferimento dei dati. La maggior parte dei driver del database parla con PostgreSQL usando un formato basato su testo. Quando fai una query al database per un numero, un timestamp o un array complesso, il database prende la sua rappresentazione interna in memoria di quei dati e la converte in una string. Poi invia quella string sulla rete. Quando la tua applicazione Python la riceve, il driver deve fare il parsing di quella string di testo per riconvertirla in un integer Python, in un oggetto datetime o in una list. Pensa a questo approccio tradizionale come a un team che si affida a un traduttore per ogni conversazione interna. Il database legge le sue strutture dati native, le scrive come documenti di testo standardizzati e le invia sulla rete. La tua applicazione Python riceve questi documenti e traduce faticosamente il testo per riportarlo nei propri oggetti di memoria strutturati. Tutto questo encoding, stringifying e parsing brucia tempo di CPU e consuma memoria. asyncpg risolve questo problema parlando direttamente il protocollo binario di frontend e backend di PostgreSQL. Forza il database a usare esclusivamente input e output binari. Invece di affidarsi a un traduttore, il database e il driver parlano esattamente la stessa lingua nativa. Se fai una query per un integer a 64 bit, PostgreSQL invia i byte grezzi che rappresentano quell'integer. asyncpg legge quei byte direttamente in un oggetto integer di Python. Non c'è formattazione di string. Non c'è parsing del testo. Questa comprensione nativa si estende ai dati complessi. Quando richiedi un blocco JSON, uno universally unique identifier o un tipo di dato geometrico, il protocollo binario assicura che il payload rimanga compatto e rigorosamente strutturato. Il driver sa esattamente quanti byte leggere per ogni colonna senza dover mai cercare delimitatori di testo. Ecco il punto chiave. La velocità di asyncpg non deriva principalmente dalla natura non bloccante di asyncio in Python. Gli enormi guadagni di performance derivano dall'aver rimosso completamente il layer di traduzione del testo. Fai molto meno lavoro per ogni riga restituita. Imponendo rigorosamente il trasferimento binario dei dati, la tua applicazione smette di sprecare risorse per leggere testo e spende quel tempo di CPU per eseguire la tua vera business logic. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
4

Connessione ed esecuzione di base

3m 30s

Inizia a usare asyncpg connettendoti a un database ed eseguendo query di base. Comprendi la sintassi nativa degli argomenti di Postgres.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 4 di 17. Se sei abituato ai driver database standard di Python, il modo in cui passi le variabili alle query ti coglierà di sorpresa, e il tuo codice si romperà all'istante. I driver Python standard si basano su marker di string formatting, ma questa libreria parla direttamente col database engine. Oggi parliamo di connessione ed esecuzione di base. Per iniziare a parlare col tuo database, usi la funzione connect di asyncpg. Fai l'await di questa funzione e le passi un Data Source Name, che è un URI di connessione Postgres standard. Sembra esattamente un indirizzo web. Inizia con postgresql due punti slash slash, seguito dal tuo username, due punti, la tua password, una chiocciola, l'indirizzo host e infine uno slash col nome del database. Fare l'await di questa funzione stabilisce il link di rete e ti restituisce un connection object attivo. Ora vuoi inserire uno username e una data di nascita in una tabella. È qui che la sintassi della query cambia. Non usare percento s o punti interrogativi per i tuoi query parameter. Dato che asyncpg bypassa deliberatamente la database API standard di Python, ti costringe a usare i placeholder nativi di Postgres. Scrivi dollaro uno, dollaro due e così via. La tua query string sembrerà un normale insert statement, ma i valori saranno dollaro uno e dollaro due. Per lanciare questa query senza chiedere dati indietro, fai l'await del metodo execute sul tuo connection object. Passi prima la query string, seguita dalle variabili vere e proprie per il nome e la data di nascita. Il metodo execute esegue lo statement e scarta qualsiasi dato tabellare. Restituisce semplicemente una status string da Postgres, qualcosa come insert zero uno. Non restituisce le righe effettive del database. Se hai bisogno che il database generi un ID univoco per questo nuovo utente, e ti serve quell'ID indietro in Python, execute è lo strumento sbagliato. Cambi la tua SQL query per aggiungere una clausola returning id alla fine. Dato che ora ti aspetti dei dati indietro, usi il metodo fetchval. Il metodo fetchval esegue la query e restituisce esattamente un valore specifico. Guarda la prima riga restituita, prende la prima colonna e ti dà solo quel dato. Questo è perfetto per recuperare uno user ID appena generato. Se ti serve più del solo ID, magari vuoi i default del database per diverse colonne, usi invece fetchrow. Fare l'await di fetchrow restituisce un singolo record object che contiene tutte le colonne di quella prima riga. Puoi accedere ai dati dentro questo record esattamente come in un dictionary Python, usando i nomi delle colonne come chiavi. Quando hai finito di inserire i tuoi dati, devi fare l'await del metodo close sul connection object per pulire il network socket e rilasciare le risorse del database. Ecco il punto chiave. Costringerti a usare i placeholder nativi col simbolo del dollaro non è solo una stranezza stilistica. Permette ad asyncpg di bypassare completamente la string interpolation lato client, mappando i tipi Python direttamente ai formati binari di Postgres per la massima velocità e una protezione completa contro le SQL injection. Grazie per l'ascolto, happy coding a tutti!
5

Conversione nativa dei tipi

4m 05s

Scopri come asyncpg mappa automaticamente i tipi di dati di PostgreSQL in oggetti Python nativi, eliminando la necessità di complessi parsing tramite ORM.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 5 di 17. Scrivi una raw SQL query, la esegui e ti prepari a fare il parsing manuale delle string di date, a splittare gli array separati da virgola e a fare il cast dei valori delle valute. Ma non devi farlo per forza. Non hai bisogno di un ORM per ottenere oggetti Python completamente tipizzati dal tuo database. Asyncpg gestisce tutto questo automaticamente tramite la conversione nativa dei tipi. Quando asyncpg comunica con PostgreSQL, utilizza il protocollo binario del database. Sa esattamente quale data type rappresenta ogni colonna. Invece di passarti raw text string che richiedono un parsing secondario in Python, asyncpg traduce i tipi di PostgreSQL direttamente in oggetti della standard library di Python. Questa traduzione avviene automaticamente in entrambe le direzioni. Quando passi un oggetto Python come query parameter, asyncpg lo codifica nel corretto formato binario di PostgreSQL. Quando il database risponde, asyncpg decodifica i dati binari nel corrispondente tipo Python. Considera uno scenario in cui fai il fetch di un profilo utente dal tuo database. La tua query SQL richiede uno username, un array di tag utente, l'orario di creazione dell'account e l'ultimo indirizzo IP conosciuto. Con molti database driver, otterresti indietro delle string di cui devi fare il parsing manualmente. Con asyncpg, il record risultante è già tipizzato. Lo username è una string standard di Python. La colonna dei tag, che in PostgreSQL è un array, arriva come una list nativa di string di Python. L'orario di creazione è un oggetto datetime standard. L'indirizzo IP, salvato come tipo inet nel database, viene mappato direttamente agli oggetti ipaddress built-in di Python. Scrivi zero logica di parsing per ottenere questo risultato. C'è un mapping rigoroso per i numeri che prende in contropiede alcuni sviluppatori. Se la tua colonna PostgreSQL è definita come numeric, non viene convertita in un float di Python. Asyncpg mappa il tipo numeric di PostgreSQL direttamente alla classe decimal punto Decimal di Python. Questo preserva la precisione esatta. Se stai facendo query su record finanziari o misurazioni precise, non perderai dati a causa di errori di arrotondamento del floating-point. I tipi floating-point standard in Postgres, come real o double precision, vengono invece mappati ai float di Python. Ecco il punto chiave per altri tipi specifici. Se fai una select su una colonna UUID nativa, ricevi un oggetto uuid punto UUID di Python, non una rappresentazione generica a string. Le date diventano oggetti datetime punto date. Gli interval di Postgres si mappano perfettamente ai timedelta di Python. I dati binari salvati in una colonna bytea vengono convertiti direttamente in bytes di Python. Le colonne JSON e JSONB si comportano in modo leggermente diverso. Asyncpg converte i dati JSON e JSONB in string standard di Python di default. Non ne fa il parsing automatico in dictionary di Python. Ricevi la raw string, che puoi poi passare al modulo json standard di Python se hai bisogno di manipolare i dati annidati. Affidarsi a questa traduzione binaria dei tipi mantiene pulita la logica della tua applicazione e sposta il peso della type safety sul database driver, dove è giusto che stia. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
6

Codec di tipo personalizzati

3m 51s

Impara a definire conversioni di dati personalizzate in asyncpg. Questo episodio spiega come utilizzare set_type_codec per decodificare automaticamente JSONB in dizionari Python.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 6 di 17. Fai una query a un database per un record utente e ottieni in risposta una raw string. Ogni singola volta, scrivi lo stesso codice boilerplate per fare il parse di quella string in un dictionary prima di poter effettivamente usare i dati. Puoi insegnare al driver del tuo database a parlare nativamente il formato dati della tua applicazione, salvandoti dal parsing manuale a ogni query. Questo viene fatto usando i Custom Type Codec. Quando fai una query su PostgreSQL usando il driver, i tipi di base come interi e testo vengono tradotti automaticamente. Ma quando usi tipi di database complessi come JSON, il driver deve sapere come vuoi che quei dati siano rappresentati in Python. Un Custom Type Codec agisce come un layer di traduzione. Si posiziona tra la connection al database e la logica della tua applicazione. Per configurarlo, usi un metodo chiamato set type codec. Chiami questo metodo direttamente su un oggetto connection attivo. Richiede quattro informazioni principali. Primo, fornisci il nome del tipo di database, come la string jsonb. Secondo, specifichi lo schema in cui risiede questo tipo. Per i tipi built-in di PostgreSQL, questo è lo schema pg catalog. Poi, fornisci la logica di traduzione passando una funzione di encoder e una funzione di decoder. L'encoder definisce come Python invia i dati al database. Prende il tuo oggetto Python e restituisce un formato che PostgreSQL capisce, tipicamente una string. Se stai lavorando con JSON, puoi semplicemente passare la funzione json dumps della standard library. Il decoder definisce come i dati tornano dal database. Riceve la raw string da PostgreSQL e restituisce l'oggetto Python che desideri. Per JSON, ti basta passare la funzione json loads. Considera un sistema che memorizza le preferenze utente non strutturate. Nel tuo database, la colonna delle preferenze è definita come jsonb. Nella tua applicazione Python, gestisci le preferenze come un dictionary standard. Una volta configurato il tuo codec, esegui una semplice query select per un utente. La colonna delle preferenze arriva nella tua applicazione già strutturata come un dictionary Python. Quando lanci una query insert, passi un dictionary direttamente come argomento della query. Il driver attiva automaticamente il tuo encoder, converte il dictionary in JSON e lo invia al database. Non chiami mai un encoder o un decoder manualmente nel codice di esecuzione della tua query. Ecco il punto chiave. I Custom Type Codec non si applicano globalmente all'intero driver del database. Il metodo set type codec modifica solo la specifica connection su cui viene chiamato. Se configuri un codec su una connection, una seconda connection non ne saprà nulla e restituirà nuovamente delle raw string. Questo comportamento causa spesso problemi quando i developer introducono un connection pool. Non puoi configurare un pool con una singola chiamata al metodo del codec. Invece, devi registrare il tuo custom codec ogni volta che viene stabilita una nuova connection. Ottieni questo risultato definendo una funzione di inizializzazione. All'interno di quella funzione, accetti il nuovo oggetto connection e chiami set type codec su di esso. Poi passi questa funzione di inizializzazione alla logica di creazione del tuo pool. Il pool esegue la tua funzione automaticamente ogni volta che apre una nuova connection, garantendo che i tuoi codec siano sempre presenti e attivi. Spingere la serializzazione dei dati giù nel layer del driver tramite i custom type codec rimuove la logica di parsing ripetitiva e assicura che i formati dei tuoi dati rimangano perfettamente sincronizzati in tutta la tua applicazione. Grazie per l'ascolto, happy coding a tutti!
7

Codec avanzati con PostGIS

3m 39s

Approfondisci i codec di tipo personalizzati mappando i tipi di geometria PostGIS di PostgreSQL in oggetti Shapely di Python utilizzando il formato binario.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 7 di 17. Gestire le coordinate geografiche di solito significa fare un parsing di string un po' caotico. Scrivi una query, ottieni in risposta un'enorme string di testo piena di numeri, e poi bruci cicli di CPU per scomporla solo per trovare una latitudine e una longitudine. I codec avanzati con PostGIS all'interno di asyncpg risolvono completamente questo problema. Quando recuperi custom type da PostgreSQL, hai a che fare con i codec. Un codec dice ad asyncpg come tradurre un data type di PostgreSQL in un object Python. Spesso c'è confusione tra il text format e il binary format. Il text format è il default per molti tool di database. Con PostGIS, una text query restituisce il Well-Known Text. Questo si presenta come la parola POINT seguita dalle coordinate tra parentesi. È human-readable, ma leggerlo nel codice richiede di allocare string, cercare le parentesi e fare il cast dei caratteri in numeri floating-point. Fare il parsing del testo è lento, e scala male quando elabori migliaia di righe. Quello che vuoi è il binary format. PostGIS usa uno standard chiamato Well-Known Binary. Quando configuri il tuo codec in asyncpg, imposti esplicitamente l'argument format su binary. Il database salta la text generation e ti restituisce raw bytes. Ora, ti serve un modo per tradurre quei bytes in qualcosa che la tua applicazione Python possa effettivamente usare. È qui che entra in gioco una library Python come Shapely. Shapely gestisce geometry complesse, e sa già esattamente come leggere il Well-Known Binary. Dici ad asyncpg di usare un custom type codec chiamando il method set type codec direttamente sulla tua connection al database. Specifichi il type name di PostgreSQL, che è geometry. Poi fornisci una encoder function e una decoder function. Il decoder prende la raw byte string da PostgreSQL e la passa direttamente al binary reader di Shapely. Pensa a fare una query per la posizione dell'Empire State Building. Senza un custom binary codec, il tuo database restituisce una string, la tua applicazione ne fa il parsing, costruisce un dictionary, e alla fine crea un geometry object. Con il binary codec configurato, esegui uno standard select statement. Asyncpg intercetta i binary data, esegue la tua decoder function, e ti consegna all'istante un object Shapely Point completamente formato. Puoi accedere immediatamente alle coordinate x e y sull'object restituito. Il processo funziona al contrario per i dati che tornano nel database. La tua encoder function prepara i dati Python per essere inviati a PostgreSQL. Gli object Shapely implementano uno standard chiamato geo interface. Questa è una comune struttura dictionary di Python usata per la geometry. Il tuo encoder prende qualsiasi object Python che supporta questa interface, usa Shapely per fare il serialize in Well-Known Binary, e invia quei raw bytes di nuovo al database. Non tocchi mai una text representation. Se trovi utili questi deep dive, puoi supportare lo show cercando DevStoriesEU su Patreon. Ecco il punto chiave. Usando rigorosamente il binary format per i custom type codec, elimini il bottleneck della serialization, permettendo al tuo database e alla tua applicazione Python di comunicare alla velocità della memoria. Grazie per l'ascolto, happy coding a tutti!
8

Gestione delle transazioni

3m 38s

Padroneggia le transazioni del database in asyncpg. Trattiamo il comportamento di auto-commit e come eseguire in sicurezza query multiple utilizzando i context manager asincroni.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 8 di 17. Esegui una update query, poi il tuo script crasha prima che venga eseguita la query successiva. Controlli il database aspettandoti che non sia cambiato nulla, ma il primo update è proprio lì, salvato in modo permanente. In asyncpg, se non richiedi esplicitamente una transazione, ogni singola query viene committata nel millisecondo in cui termina. La gestione delle transazioni è il modo in cui risolvi questo comportamento. Di default, asyncpg opera in modalità auto-commit. Questo significa che se scrivi del codice che esegue tre query una dopo l'altra, non stai eseguendo un unico blocco logico. Stai eseguendo tre operazioni completamente isolate. Se la seconda query fallisce, la prima query è già stata finalizzata nel database. La mancanza di un transaction block esplicito è una causa frequente di un application state corrotto. Prendi uno scenario in cui stai trasferendo denaro tra due conti bancari. Devi sottrarre dei fondi dal conto A, e poi aggiungere quegli stessi fondi al conto B. Entrambi gli update devono avere successo insieme, o entrambi devono fallire insieme. Se la sottrazione ha successo ma l'aggiunta fallisce, i soldi semplicemente svaniscono. Per legare insieme queste operazioni, usi il metodo transaction sul tuo oggetto connection. Questo metodo restituisce un context manager asincrono. Nel tuo codice, scrivi async with connection dot transaction, seguito dai due punti, e poi indenti le tue query correlate. Quando Python entra in questo blocco, asyncpg dice a PostgreSQL di avviare una nuova transazione. All'interno del blocco, esegui la query di sottrazione, seguita dalla query di aggiunta. Se entrambe le query girano senza problemi e Python raggiunge la fine del blocco, asyncpg lancia automaticamente un comando di commit. Le modifiche a entrambi i conti diventano visibili al resto del database nello stesso identico momento. Ecco il punto chiave. Se si verifica un qualsiasi problema all'interno di quel blocco, il database rimane al sicuro. Il problema potrebbe essere una violazione di un constraint del database, un timeout di rete, o persino un puro errore Python come una variabile mancante o una divisione per zero. Se viene sollevata un'eccezione, il context manager la intercetta. Invia automaticamente un comando di rollback a PostgreSQL, cancellando la sottrazione dal conto A, e poi lascia che l'eccezione Python continui a risalire il tuo call stack. Puoi anche annidare questi blocchi in modo sicuro. Se apri un nuovo transaction context manager mentre sei già all'interno di un transaction block attivo, asyncpg non si confonde. Invece, crea automaticamente un savepoint nel database. Un savepoint funziona come un segnalibro all'interno di una transazione in corso. Se il blocco interno incontra un errore, fa il rollback dello stato del database solo fino a quel segnalibro. Il blocco esterno rimane completamente intatto e può ancora committare il proprio lavoro, o scegliere di fallire in base alla tua logica. Non devi scrivere comandi savepoint manuali, ti basta annidare i tuoi blocchi async with. In definitiva, il transaction context manager lega in modo permanente lo stato del tuo database allo stato di esecuzione di Python, garantendo che un'eccezione Python non gestita sia una garanzia assoluta contro update parziali del database. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
9

Connection Pooling

3m 19s

Scala la tua applicazione con il connection pooling integrato di asyncpg. Impara a gestire in modo efficiente le connessioni al database in servizi web ad alto traffico.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Python Async ad alte prestazioni, episodio 9 di 17. Aprire una nuova connessione al database per ogni web request in entrata è un modo molto efficace per mandare completamente in crash il tuo server. Il solo overhead di rete bloccherà la tua applicazione, ed esaurirai rapidamente il limite di connessioni sul tuo database. La soluzione a questo è il Connection Pooling. Quando usi una libreria come asyncpg per comunicare con PostgreSQL, stabilire una connessione base è un'operazione costosa. Richiede un TCP handshake, una negoziazione sicura e l'autenticazione al database. Se gestisci un web service ad alto traffico, semplicemente non puoi permetterti di pagare questa tassa di latenza su ogni singola HTTP request. Invece, devi mantenere una collezione stabile di connessioni pronte all'uso. In asyncpg, ottieni questo usando la funzione create pool. Tipicamente, chiami questa funzione una volta sola durante la fase di startup della tua applicazione. Fornisci le credenziali del tuo database, host e port, e asyncpg avvia un set di connessioni idle in background. Da quel momento in poi, i tuoi route handler e i tuoi background task non creano mai una nuova connessione da zero. Prendono solo in prestito quelle esistenti. C'è una trappola comune qui che fa inciampare molti sviluppatori. Non confondere il prendere in prestito una connessione con l'aprire una transazione sul database. Sono operazioni completamente separate. Quando prendi in prestito una connessione dal pool, stai solo riservando il socket di rete per il tuo uso esclusivo e temporaneo. Se la tua operazione richiede atomicità tra più query, devi comunque avviare esplicitamente una transazione su quella specifica connessione presa in prestito. Pensa a un web service FastAPI o aiohttp ad alto traffico. Diciamo che hai un endpoint che accetta un intero, fa una query al database per calcolare la potenza di due per quel numero, e restituisce il risultato. Quando una request arriva al tuo endpoint, chiami il metodo acquire sul tuo oggetto pool. Lo fai usando un context manager asincrono. Questo preleva temporaneamente una connessione dal pool. Usi quindi quella specifica istanza di connessione per eseguire la tua query sul database per calcolare la potenza di due. Una volta che il blocco di codice finisce, il context manager rilascia automaticamente la connessione. Ne pulisce lo stato e la restituisce al pool, rendendola immediatamente disponibile per la successiva HTTP request in entrata. Se un improvviso picco di traffico colpisce il tuo web server e tutte le connessioni nel pool sono attualmente occupate, la request successiva non va in crash. Aspetta. La chiamata acquire metterà semplicemente in pausa l'esecuzione finché un'altra request non finisce e restituisce la sua connessione. Ecco il punto chiave. Il connection pool non fa solo risparmiare tempo sui network handshake. Agisce come un rigoroso e affidabile limitatore di concorrenza. Protegge il tuo database PostgreSQL dall'essere sopraffatto da un'inaspettata ondata di traffico. Se configuri il tuo pool per mantenere esattamente venti connessioni, il database non vedrà mai più di venti query attive in concorrenza da quell'istanza dell'applicazione, non importa quante migliaia di request simultanee colpiscano il tuo web server. Grazie per l'ascolto. Alla prossima!
10

Caching dei Prepared Statement

3m 56s

Comprendi come asyncpg ottimizza il parsing delle query con i prepared statement automatici e perché pooler esterni come PgBouncer possono causare conflitti.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 10 di 17. La tua applicazione funziona perfettamente sulla tua macchina locale. Ma nel momento in cui fai il deploy in produzione dietro un connection router del database, inizi a vedere dei crash casuali. I log segnalano statement che esistono già, o statement che non si trovano. Non è cambiato nulla nel tuo codice. Questo succede a causa di un'ottimizzazione built-in chiamata Prepared Statement Caching. Per capire l'errore, dobbiamo prima guardare cosa fa asyncpg dietro le quinte. Ogni volta che mandi una query tramite asyncpg, la libreria la traduce automaticamente in un prepared statement sul server PostgreSQL. Normalmente, quando un database riceve una query, deve fare il parsing del testo, analizzare la sintassi e costruire un execution plan. Questo richiede tempo. Preparando lo statement, PostgreSQL fa il grosso del lavoro esattamente una volta e gli assegna un nome interno. Per tutte le esecuzioni future di quell'esatta query, asyncpg invia solo i nuovi parametri e il nome dello statement. Questo salta completamente la fase di parsing e fornisce un enorme boost di performance. asyncpg mantiene una cache di questi prepared statement in memoria, strettamente legata alla connessione attiva al database. Ecco il punto cruciale. Il problema nasce da un conflitto tra il modo in cui asyncpg gestisce le sue connessioni interne e come operano i pooler esterni come PgBouncer. asyncpg dà per scontato di avere una connessione fisica dedicata e persistente al server Postgres. Quando crea un prepared statement, si fida del fatto che lo statement rimarrà disponibile su quell'esatta connessione fino alla sua chiusura. Ora introduciamo PgBouncer nell'architettura, in particolare in esecuzione in transaction mode. PgBouncer si posiziona tra la tua applicazione e il database. Mantiene un piccolo pool di connessioni Postgres reali e le condivide tra migliaia di richieste client in entrata. In transaction mode, PgBouncer dà alla tua applicazione una connessione fisica al database solo per la durata di una singola transazione. Nel momento in cui quella transazione fa il commit, PgBouncer si riprende la connessione fisica e la passa a un client completamente diverso. Questo rompe la cache dei prepared statement. La tua applicazione manda una query. asyncpg la prepara e la mette in cache sulla connessione fisica A. La transazione finisce. Pochi secondi dopo, la tua applicazione manda l'esatta stessa query. asyncpg si ricorda di aver già preparato questa query, quindi dice a Postgres di eseguire lo statement salvato. Ma questa volta, PgBouncer ha instradato la tua applicazione sulla connessione fisica B. La connessione B non ha alcuna traccia di quel prepared statement. Il database lancia un errore dicendo che lo statement non esiste. È vero anche il contrario. Un client diverso potrebbe essere instradato sulla connessione A, provare a preparare uno statement con lo stesso nome interno, e scatenare un errore dicendo che lo statement esiste già. Il fix è semplice ma richiede un trade-off. Devi dire ad asyncpg di disabilitare questa ottimizzazione. Quando inizializzi la tua connessione asyncpg o il connection pool, passi un argomento specifico che imposta la dimensione della statement cache a zero. Questo disattiva completamente il caching automatico dei prepared statement. Le tue query ora verranno parsate da PostgreSQL ogni singola volta che vengono eseguite. Sacrifichi una piccola parte di performance di parsing, ma la tua applicazione diventerà all'istante stabile su connessioni distribuite. Se le tue connessioni al database vengono instradate dinamicamente per transazione, la tua applicazione non può più dare per scontato che il server del database ricordi assolutamente nulla tra una query e l'altra. Questo è tutto per questo episodio. Grazie per aver ascoltato, e continua a sviluppare!
11

Array Postgres e clausole IN

4m 08s

Risolvi l'errore di sintassi più comune quando si migra ad asyncpg. Impara a filtrare correttamente le query rispetto a un elenco di valori utilizzando ANY().

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 11 di 17. Scrivi una query SQL standard per filtrare i record. Passi una lista di valori nei parametri della tua query, proprio come hai fatto in decine di altre librerie di database. Postgres lancia immediatamente un errore di sintassi. Il problema non sono i tuoi dati. Il problema è come gestisci gli array di Postgres e le clausole IN. Questo è l'errore di sintassi numero uno per gli sviluppatori che migrano ad asyncpg da altre interfacce di database. In molte librerie più vecchie, il driver intercetta la tua query string. Se passi una lista Python a una clausola IN, la libreria riscrive la stringa SQL al volo. Espande la tua lista in una stringa di parametri individuali separati da virgola prima di inviarla al database. Asyncpg non lo fa. Si basa interamente sulle prepared statement native lato server di Postgres. Ecco il punto chiave. Nello standard SQL di Postgres, l'operatore IN richiede una lista di valori scalari separati da virgola e racchiusi tra parentesi. Non accetta un singolo oggetto array. Quando passi una lista Python ad asyncpg come parametro, asyncpg mappa direttamente quella lista a un array nativo di Postgres. La tua query finisce per essere valutata come un'espressione IN un oggetto array, che è una sintassi non valida. Postgres si aspetta un'espressione IN valore uno, valore due. Per risolvere questo problema, devi smettere di usare l'operatore IN per le liste parametrizzate. Usa invece la funzione any di Postgres. La logica passa dal chiedere se un valore è IN una lista, al chiedere se un valore è uguale a un qualsiasi elemento all'interno di un array. L'operatore any è progettato specificamente per funzionare con i tipi array di Postgres. Valuta il valore a sinistra, controlla l'array a destra e restituisce true se trova un match. Devi anche dire a Postgres che tipo di array sta ricevendo facendo il casting del parametro. Se ti aspetti un array di stringhe di testo, fai il casting esplicito del tuo parametro a un text array. Questo type casting esplicito garantisce che Postgres sappia esattamente come pianificare ed eseguire la query senza indovinare il data type sottostante del binary stream in ingresso. Considera uno scenario in cui stai filtrando una lista di prodotti. Vuoi confrontare la categoria del prodotto con una lista dinamica di categorie selezionate dall'utente. Scrivi una query per selezionare i prodotti in cui la categoria è uguale a any parametro uno, e fai il casting del parametro uno a un text array. Nel tuo codice Python, chiami il metodo fetch del tuo database. Passi la query string come primo argomento e la tua lista Python di stringhe, come elettronica e libri, come secondo argomento. Asyncpg impacchetta la tua lista Python in un text array binario di Postgres e lo invia sulla rete come singolo parametro. Postgres riceve la query, vede il text array e confronta le categorie in modo efficiente usando la funzione any. Questo approccio è nettamente migliore della string manipulation. Poiché la struttura della query non cambia mai, indipendentemente da quante categorie ci sono nella lista, Postgres fa il parsing e pianifica lo statement esattamente una volta. Il database mette in cache il query plan, risparmiando tempo di esecuzione nelle chiamate successive. Elimini anche il rischio di SQL injection, poiché i dati vengono trasmessi in formato binario, completamente separati dal query text. Se passi una lista Python a un parametro del database, trattala come un array nativo, fai il casting al tipo corretto e valutala con la funzione any. Questo è tutto per questo episodio. Grazie per l'ascolto e continua a sviluppare!
12

Oggetti Record vs Named Tuple

3m 53s

Esplora il design unico degli oggetti Record di asyncpg. Comprendi perché la dot-notation è omessa per impostazione predefinita e come abilitarla con classi personalizzate.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 12 di 17. Fai una query sul tuo database, ottieni una riga in risposta e, istintivamente, digiti il nome della variabile punto id. Immediatamente, Python lancia un attribute error. I tuoi dati sono lì, ma non puoi accedervi nel modo che ti aspetti. Questo succede a causa del design intenzionale dei Record Object rispetto alle named tuple. Quando fai il fetch dei dati usando asyncpg, questo non restituisce dizionari Python standard, e non restituisce named tuple. Invece, restituisce un custom object altamente ottimizzato chiamato Record. Se sei abituato ad altri database driver o object-relational mapper, potresti aspettarti che la dot-notation funzioni out of the box. Con un Record di asyncpg, va in errore intenzionalmente. Potresti chiederti perché il driver non usi semplicemente una named tuple Python standard, dato che le named tuple supportano nativamente la dot-notation. Il motivo è pura performance. Una named tuple richiede a Python di generare una struttura di classe completamente nuova per ogni combinazione unica di colonne restituite da una query. Se la tua applicazione esegue centinaia di query con strutture diverse, generare quelle classi dinamiche crea un enorme overhead di esecuzione. La libreria è costruita per la velocità assoluta, quindi aggira completamente questo collo di bottiglia restituendo invece il proprio tipo Record compilato. Questo custom Record object si comporta come un ibrido veloce. Supporta l'indicizzazione tramite interi esattamente come una tuple standard, il che significa che puoi accedere alla prima colonna usando l'indice zero. Ma fornisce anche un mapping simile a un dizionario. Accedi alle tue colonne usando la bracket notation, passando il nome della colonna come string. Ecco il punto chiave. I creatori hanno disabilitato attivamente la dot-notation su questi oggetti per proteggere la tua applicazione da conflitti di namespace. Pensa ai metodi di mapping standard in Python, come keys, items, values o get. Se la tua tabella del database dovesse avere una colonna chiamata keys, e il driver supportasse la dot-notation, digitare record punto keys creerebbe un conflitto strutturale. Python non saprebbe se vuoi il valore del database o il metodo built-in. Imponendo la bracket notation basata su string, il driver garantisce che i nomi delle tue colonne non entreranno mai in conflitto con gli attributi Python standard. Tuttavia, se controlli interamente il tuo database schema, sai per certo di non usare parole riservate di Python come nomi di colonna, e hai bisogno della dot-notation per la tua codebase, hai una via d'uscita. Puoi fare l'override del comportamento di default. Quando stabilisci la tua database connection, puoi fornire un parametro specifico chiamato record class. Per implementarlo, scrivi una custom class che eredita direttamente dal Record base di asyncpg. All'interno di questa nuova classe, implementi il metodo Python built-in chiamato double underscore getattr. Istruisci questo metodo per prendere il nome dell'attributo richiesto e semplicemente cercarlo usando la bracket notation sicura come fallback. Una volta passata questa custom class al setup della tua connessione, ogni singola riga restituita dalle tue query sarà un'istanza del tuo custom object. Python ti permetterà quindi di usare la dot-notation, instradando senza problemi la richiesta dell'attributo attraverso il tuo custom method per fare il fetch dei dati della colonna sottostante. In definitiva, la rigida bracket notation in un Record di default non è una svista, ma un confine di sicurezza strutturale che garantisce che il tuo accesso ai dati rimanga prevedibile, indipendentemente da come cambia il tuo database schema. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
13

Streaming dei risultati con i cursori

4m 20s

Previeni l'esaurimento della memoria quando interroghi dataset enormi. Impara a utilizzare i cursori di asyncpg per fare lo streaming dei risultati a blocchi.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 13 di 17. Devi esportare una tabella utenti da un milione di righe in un file, quindi esegui un normale fetch dal database. All'improvviso, la tua applicazione Python consuma tutta la RAM disponibile e il server va in crash. Non puoi caricare enormi dataset in memoria tutti in una volta, ed è proprio per questo che usiamo lo streaming dei risultati con i cursor. Normalmente, quando esegui una query in asyncpg, l'intero result set viene scaricato dalla rete da PostgreSQL e tenuto nella memoria di Python. Il database costruisce la response completa, la invia, e asyncpg crea gli oggetti Python per ogni singola riga prima che il tuo codice possa elaborare la prima. Questo va benissimo per cinquanta righe. È un problema immediato per cinque milioni di righe. Per evitare di mandare in crash il server, devi sorseggiare i dati anziché inghiottirli tutti in un colpo. Un cursor del database ti fornisce un pointer ai risultati della query sul server PostgreSQL. Invece di scaricare tutto, il cursor permette alla tua applicazione Python di fare il fetch dei dati in modo incrementale. In asyncpg, fai questo chiamando il metodo cursor sulla tua connection. Fornisci la tua query SQL e tutti gli argomenti necessari. Questo metodo restituisce un async iterator. Scrivi un ciclo async for per iterare su questo cursor. Dietro le quinte, asyncpg fa automaticamente il fetch delle righe da PostgreSQL in piccoli batch gestibili. Il tuo codice elabora un po' di righe, le scrive nel tuo file di export e va avanti. Python pulisce le vecchie righe dalla memoria, il che mantiene il memory footprint totale della tua applicazione completamente piatto, non importa quanto sia grande la tabella. C'è una regola ferrea qui che fa inciampare molti sviluppatori. Se provi a iterare un cursor direttamente su una connection standard, asyncpg lancia immediatamente un InterfaceError. Il messaggio di errore dirà che i cursor non possono essere usati al di fuori di una transaction. Ecco il punto chiave. I cursor di PostgreSQL sono strutturalmente legati alle transaction del database. Quando una transaction fa commit o rollback, PostgreSQL distrugge tutti i cursor attivi associati ad essa. Di default, asyncpg opera in modalità auto-commit. Questo significa che ogni singola query che esegui è wrappata nella sua piccola transaction invisibile che si chiude nel momento in cui la query finisce. Se asyncpg ti permettesse di aprire un cursor in modalità auto-commit, quella transaction implicita finirebbe all'istante, e PostgreSQL distruggerebbe il tuo cursor prima che tu possa fare il fetch di una singola riga. Per far funzionare i cursor, devi gestire esplicitamente il confine della transaction. Lo fai aprendo un context manager asincrono usando il metodo transaction sulla tua connection. Una volta che sei al sicuro dentro quel blocco transaction, chiami il metodo cursor e fai partire il tuo ciclo async for. Dato che la transaction rimane aperta per l'intera durata del blocco del context manager, il tuo cursor rimane vivo sul server PostgreSQL, permettendoti di fare lo streaming di tutto il milione di righe in modo sicuro. C'è una rara eccezione a questa regola. PostgreSQL supporta una feature in cui puoi dichiarare un cursor SQL raw con la frase WITH HOLD. Questo dice al database engine di materializzare il risultato e mantenere il cursor vivo anche dopo che la transaction è completata. Fare questo consuma le risorse del database, e bypassa l'efficienza dello streaming standard. Per quasi tutti i task di streaming in asyncpg, il blocco transaction esplicito è l'approccio richiesto. Se trovi utili questi episodi e vuoi supportare lo show, cerca DevStoriesEU su Patreon. Ricorda che un cursor trasforma la tua interazione con il database da un'allocazione di memoria massiccia e rischiosa in una pipeline controllata e persistente, in grado di elaborare in sicurezza qualsiasi volume di dati. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!
14

Ingestion velocissima con COPY

3m 40s

Metti il turbo alle tue pipeline di data ingestion. Esploriamo il protocollo COPY di PostgreSQL per caricare dati in blocco in modo esponenzialmente più veloce rispetto alle istruzioni INSERT.

Download
Ciao, sono Alex di DEV STORIES DOT EU. Python Async ad alte prestazioni, episodio 14 di 17. Devi spostare un milione di righe dentro o fuori dal tuo database. Se il tuo primo istinto è creare un batch con un'enorme lista di statement di insert o fare il fetch di tutte le righe in una gigantesca lista Python, stai scegliendo la strada più lenta disponibile. Esiste un meccanismo creato appositamente per aggirare questo overhead. Oggi parliamo di ingestion estremamente veloce con COPY. COPY è un protocollo specifico di Postgres per il trasferimento dati in bulk. Quando esegui statement di insert o select standard, Postgres deve parsare la query, pianificarla ed eseguirla. Farlo ripetutamente aggiunge un overhead enorme. Il protocollo COPY salta completamente la pipeline di query standard. Apre uno stream diretto verso lo storage layer, spostando i dati in un formato altamente ottimizzato. Per questo motivo, è ordini di grandezza più veloce rispetto alle bulk insert. In asyncpg, fai il push dei dati nel database usando un metodo chiamato copy to table. Passi il nome della tabella di destinazione e una data source. Questa sorgente può essere un file path locale, un file-like object, o un iteratore asincrono che fa yield di record. Se lo punti a un file CSV locale, Postgres gestisce il parsing nativamente. Non devi aprire il file in Python, parsare le righe e mapparle a delle variabili. Il driver del database fa lo stream dei byte raw del file direttamente al server. Puoi anche passare una semplice lista Python di tuple se i tuoi dati sono già in memoria, e asyncpg li manderà in stream usando il protocollo COPY dietro le quinte. Estrarre i dati è altrettanto veloce. Se ti serve un export completo, usi copy from table. Questo prende l'intero contenuto di una tabella e lo spara fuori su un file o uno stream. Tuttavia, fare il dump di un'intera tabella è raramente ciò di cui hai effettivamente bisogno. Di solito, vuoi dati filtrati o joinati. È qui che entra in gioco copy from query. Un malinteso comune è che questo metodo faccia solo il dump dei risultati della query in un file statico. Questo non è affatto vero. Anche se può scrivere direttamente su un file path, puoi anche fornire una funzione di callback. Asyncpg eseguirà la query e manderà in stream i risultati in chunk alla tua callback, permettendoti di elaborare un dataset enorme al volo senza mai tenere l'intero result set nella memoria di sistema. Considera uno scenario in cui devi generare un report CSV di tutti gli utenti attivi. Un approccio standard è eseguire una query di select, fare il fetch di centomila righe in Python, formattarle usando il modulo CSV e scriverle su disco. Questo consuma molta memoria e CPU. Ecco il punto chiave. Puoi saltare completamente l'elaborazione in Python. Chiami copy from query, gli passi il tuo specifico statement di select, imposti il parametro format su CSV e fornisci un file path di output. Postgres esegue la query, formatta i risultati in CSV nativamente sul server del database, e asyncpg manda in stream il testo finito direttamente al tuo disco rigido. La tua applicazione Python funge da semplice pipe, facendo quasi zero manipolazione dei dati. Dovresti continuare a usare gli statement di insert e select standard per la logica applicativa di tutti i giorni, ma nel momento in cui il volume dei dati raw diventa il tuo bottleneck, passa al protocollo COPY per bypassare completamente il parser SQL. Questo è tutto per questo episodio. Grazie per aver ascoltato, e continua a sviluppare!
15

Listen e Notify asincroni

3m 25s

Sblocca architetture event-driven in tempo reale direttamente all'interno di PostgreSQL. Impara a utilizzare add_listener di asyncpg per la messaggistica pub/sub istantanea.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 15 di 17. Di solito ricorri a un message broker separato come Redis nel momento in cui hai bisogno di eventi in real-time. Ma se la tua applicazione usa già un database, potresti aggiungere complessità all'infrastruttura senza alcun motivo. PostgreSQL ha un message bus in real-time integrato. Oggi parliamo di Listen e Notify asincroni. Postgres supporta nativamente il pattern publish-subscribe usando due comandi, listen e notify. La libreria asyncpg espone questa funzionalità in Python tramite un metodo chiamato add listener. Invece di scrivere un loop che fa polling su una tabella del database ogni pochi secondi per cercare nuovi dati, registri una funzione di callback asincrona in Python su uno specifico canale. Quando si verifica un evento dentro Postgres, viene trasmesso un messaggio a quel canale, e la tua callback Python viene eseguita immediatamente. Ecco il punto chiave. Un listener non è collegato globalmente alla tua applicazione, e non è associato a un connection pool. È vincolato a una singola e specifica connessione al database. Questo è un point of failure molto comune. Se prendi una connessione da un pool, registri il tuo listener e poi rilasci la connessione al pool, il listener cade. Per usare questa feature in modo affidabile, devi acquisire una connessione da asyncpg, chiamare il metodo add listener su di essa e tenerla aperta a tempo indeterminato. Diventa una pipeline di ascolto dedicata. Vediamo uno scenario pratico. Hai un background worker che deve svegliarsi ed elaborare dei record ogni volta che viene inserita una nuova riga in una tabella jobs. Invece di fare polling, imposti un trigger sul database. Ogni volta che avviene una insert, il trigger esegue un comando notify su un canale chiamato new jobs. Invia anche un breve payload di testo, come l'identificatore univoco della nuova riga. Nel tuo codice Python, scrivi una funzione di callback asincrona. Questa funzione si aspetta di ricevere quattro argomenti da asyncpg: l'oggetto connection, il process identifier di Postgres, il nome del canale e il payload di testo stesso. Dopodiché, acquisisci la tua connessione dedicata. Chiami add listener su quella connessione, passando la string del canale new jobs insieme alla tua funzione di callback. Infine, mantieni lo script in esecuzione, in genere facendo l'await di un evento asincrono che non viene mai settato. Il tuo processo Python ora rimane completamente in idle. Usa quasi zero risorse CPU e smette di bombardare il database con query vuote. Nel momento in cui una transazione fa il commit di una nuova riga job, Postgres invia la notifica attraverso il socket di rete aperto. Asyncpg legge quel socket e pianifica immediatamente la tua callback sull'event loop di Python, passandole il nuovo identificatore del job. Il vero potere di questo pattern è la consistenza transazionale. Se una transazione del database fa un rollback, tutti i comandi notify eseguiti durante quella transazione vengono scartati automaticamente da Postgres. Questo garantisce che i tuoi worker Python si sveglieranno e reagiranno solo ai dati che sono stati salvati con successo sul disco. Questo è tutto per questo episodio. Grazie per l'ascolto e continua a sviluppare!
16

Telemetria e logging delle query

3m 28s

Ottieni una profonda osservabilità sulle prestazioni del tuo database. Scopri come utilizzare i log listener di asyncpg per tracciare le query lente e monitorare la telemetria di esecuzione.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 16 di 17. Per scoprire perché il tuo database è lento, non devi tirare a indovinare. Potresti affidarti ai log lato server, ma a quelli sfugge la latenza di rete e l'overhead lato Python. Misurare manualmente le performance intorno a ogni chiamata al database appesantisce rapidamente la tua business logic. La soluzione è la telemetria e il query logging. Spesso le persone confondono i log listener e i query logger. I log listener catturano i notice del server Postgres. I query logger catturano la telemetria di esecuzione lato client. Gestiscono due flussi di informazioni completamente distinti. I query logger gestiscono la telemetria. Colleghi una funzione di callback alla tua connessione usando il metodo add query logger. Una volta collegata, ogni volta che una query finisce di essere eseguita, asyncpg passa automaticamente un oggetto LoggedQuery a questa callback. Questo avviene a livello globale per quella connessione, in modo completamente disaccoppiato dalla funzione specifica che fa la richiesta al database. L'oggetto LoggedQuery contiene tre dati fondamentali. Contiene il testo esatto della query SQL, gli argomenti passati a quella query e il tempo di esecuzione. Gli argomenti vengono catturati esattamente come li ha forniti la tua applicazione. Questo ti evita di dover scrivere manualmente della logica di string formatting, solo per capire quali parametri hanno causato una risposta lenta. Considera un ambiente di produzione in cui devi intercettare qualsiasi query che ci metta più di 500 millisecondi. Definisci una funzione Python standard che accetta due parametri: la connessione al database e l'oggetto LoggedQuery. All'interno di questa funzione, controlli l'attributo del tempo di esecuzione. Se il tempo supera zero virgola cinque secondi, scrivi il testo della query, gli argomenti e la durata esatta nel sistema di monitoraggio della tua applicazione. Poi passi questa funzione di callback al metodo add query logger. Ora, la tua applicazione traccia automaticamente e silenziosamente le query lente in background. Se mai dovessi aver bisogno di fermare questo tracciamento, ti basta passare la stessa funzione di callback al metodo remove query logger. Ora, il secondo elemento di tutto questo è il log listener. Mentre il query logger gestisce le tempistiche lato client, il log listener gestisce la messaggistica lato server. A volte, Postgres invia messaggi che non sono errori e non restituiscono righe di dati. Si tratta di notice asincroni, warning o messaggi di log custom generati direttamente dal database engine. Per catturare questi messaggi, colleghi una callback usando il metodo add log listener. Quando Postgres emette un notice o un warning, asyncpg attiva questa callback. Passa la connessione e uno specifico oggetto messaggio alla tua funzione. Questo dà alla tua applicazione una visibilità immediata sui warning a livello di database, in modo completamente indipendente dai risultati standard delle tue query. Proprio come per il query logger, puoi scollegare questa callback in seguito usando remove log listener. Ecco il punto chiave. Il query logging lato client ti dà la vera durata di esecuzione percepita dalla tua applicazione Python, evitando completamente di dover tirare a indovinare tra il ritardo di rete e l'elaborazione del database. Grazie per l'ascolto. Statemi bene.
17

Mettere in sicurezza le connessioni con SSL

4m 18s

Assicurati che le connessioni al tuo database siano sicure. Trattiamo la configurazione del contesto SSL e come imporre il TLS diretto quando ci si connette ai database cloud.

Download
Ciao, sono Alex di DEV STORIES DOT EU. High-Performance Python Async, episodio 17 di 17. Connettersi a un database cloud gestito senza SSL è come urlare le tue credenziali del database in una stanza affollata. Hai bisogno della crittografia, ma configurarla correttamente spesso porta a errori di connessione confusi o a default non sicuri. Oggi proteggeremo le connessioni con SSL in asyncpg. Spesso ci si confonde su come configurare SSL nella propria connection logic. Se usi le connection URI, asyncpg fa il parsing nativo dei query parameter sslmode standard di PostgreSQL, come impostare sslmode su require. Questo funziona per i setup di base. Ma quando hai bisogno di un controllo preciso, come connetterti in modo sicuro a un database cloud gestito usando un bundle di Certificate Authority custom, le stringhe URI standard non bastano. Per questo, usi il parametro ssl programmatico. Il parametro ssl nelle funzioni di connessione di asyncpg stabilisce come viene negoziato il TLS. Accetta due tipi di valori. Il primo tipo è un preset di tipo string. Puoi passare la string prefer, che tenta una connessione SSL ma fa un fallback a una non crittografata se il server non la supporta. Puoi passare require, che forza la crittografia ma salta la verifica dell'identità del server. Oppure puoi passare verify-full, che forza la crittografia e valida rigorosamente il certificato del server rispetto alle root attendibili. Ecco il punto chiave. Quando il tuo scenario richiede una Certificate Authority custom, non affidarti ai preset di tipo string. Invece, crei un oggetto SSLContext standard di Python. Configuri questo oggetto con i tuoi file di certificato custom, forzi una verifica rigorosa e poi passi quell'oggetto Python direttamente nel parametro ssl di asyncpg. Questo ti dà il controllo esatto sull'handshake crittografico, bypassando qualsiasi certificato di sistema di default. Questo copre le regole di crittografia, ma come inizia effettivamente la connessione? Questo ci porta al parametro direct TLS. Di default, PostgreSQL usa un protocollo chiamato STARTTLS. Il client stabilisce una connessione in plain-text, chiede al server se supporta la crittografia e, se il server dice di sì, fanno un upgrade della connessione a TLS. Tuttavia, i moderni setup proxy, come alcuni connection pooler o cloud load balancer, spesso si aspettano una connessione direct TLS fin dal primissimo byte. Non vogliono la negoziazione in plain-text. Se la tua infrastruttura è costruita in questo modo, passi true al parametro di connessione direct TLS. Quando fai questo, asyncpg salta la negoziazione STARTTLS e avvia immediatamente un handshake TLS raw. Naturalmente, questo funziona solo se fornisci anche una configurazione ssl valida. Se abiliti il direct TLS ma lasci vuoto il parametro ssl, la connessione fallirà. Quando proteggi le tue connessioni al database, ricorda che, sebbene i preset di tipo string siano comodi, passare un oggetto SSLContext esplicito è l'unico modo per garantire in modo assoluto che la tua applicazione si fidi della corretta identità del server. Dato che questo è l'ultimo episodio della serie, ti incoraggio a esplorare la documentazione ufficiale di asyncpg e a provare questi parametri di connessione hands-on. Puoi visitare devstories dot eu per suggerire argomenti per le nostre serie future. Questo è tutto per questo episodio. Grazie per l'ascolto, e continua a sviluppare!