<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0" xmlns:itunes="http://www.itunes.com/dtds/podcast-1.0.dtd" xmlns:googleplay="http://www.google.com/schemas/play-podcasts/1.0"><channel><title><![CDATA[Python Tech Lead]]></title><description><![CDATA[Aprender Python debería ser más fácil. Contenido creado por personas. Aléjate del clickbait y la basurilla AI con ejemplos reales. Años de experiencia creando y liderando equipos de Python.]]></description><link>https://newsletter.pythontechlead.com</link><image><url>https://substackcdn.com/image/fetch/$s_!GVS_!,w_256,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fc0c1690a-5bce-4b58-bfb1-49c2bf0bef98_608x608.png</url><title>Python Tech Lead</title><link>https://newsletter.pythontechlead.com</link></image><generator>Substack</generator><lastBuildDate>Fri, 01 May 2026 08:13:38 GMT</lastBuildDate><atom:link href="https://newsletter.pythontechlead.com/feed" rel="self" type="application/rss+xml"/><copyright><![CDATA[Python Tech Lead]]></copyright><language><![CDATA[es]]></language><webMaster><![CDATA[pythontechlead@substack.com]]></webMaster><itunes:owner><itunes:email><![CDATA[pythontechlead@substack.com]]></itunes:email><itunes:name><![CDATA[Python Tech Lead]]></itunes:name></itunes:owner><itunes:author><![CDATA[Python Tech Lead]]></itunes:author><googleplay:owner><![CDATA[pythontechlead@substack.com]]></googleplay:owner><googleplay:email><![CDATA[pythontechlead@substack.com]]></googleplay:email><googleplay:author><![CDATA[Python Tech Lead]]></googleplay:author><itunes:block><![CDATA[Yes]]></itunes:block><item><title><![CDATA[Crea imágenes de Docker con datos precargados]]></title><description><![CDATA[No pierdas a tu audiencia: genera tests fiables y reproducibles]]></description><link>https://newsletter.pythontechlead.com/p/crea-imagenes-de-docker-con-datos</link><guid isPermaLink="false">https://newsletter.pythontechlead.com/p/crea-imagenes-de-docker-con-datos</guid><dc:creator><![CDATA[Python Tech Lead]]></dc:creator><pubDate>Sun, 28 Sep 2025 16:15:22 GMT</pubDate><enclosure url="https://substack-post-media.s3.amazonaws.com/public/images/42e5e5b9-93d9-42d9-8ef2-d9bed041a29a_1280x720.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!-9S7!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!-9S7!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!-9S7!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!-9S7!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!-9S7!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!-9S7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png" width="1280" height="720" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:720,&quot;width&quot;:1280,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:264645,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:false,&quot;topImage&quot;:true,&quot;internalRedirect&quot;:&quot;https://newsletter.pythontechlead.com/i/174763606?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!-9S7!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 424w, https://substackcdn.com/image/fetch/$s_!-9S7!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 848w, https://substackcdn.com/image/fetch/$s_!-9S7!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 1272w, https://substackcdn.com/image/fetch/$s_!-9S7!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F697cf433-e5fd-4ece-bed5-d53d8dfb970f_1280x720.png 1456w" sizes="100vw" fetchpriority="high"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a></figure></div><p>Cuando preparamos una clase o una demo, lo &#250;ltimo que queremos es <strong>perder la atenci&#243;n de nuestra audiencia</strong> a los 3 minutos. Tenemos que encontrar la manera para tener entornos de pruebas - ya sea para ense&#241;ar o testear - que sean <strong>f&#225;ciles</strong> de usar y <strong>reproducibles</strong> en el tiempo.</p><p>Por suerte, hay bases de datos que nos permiten distribuir im&#225;genes de Docker que ya contengan los ingredientes que necesitas para tu pr&#243;xima charla (o <a href="https://newsletter.pythontechlead.com/p/perfilando-la-memoria-de-lectura">benchmark</a>)!</p><p>Veamos como podemos conseguirlo sin dolores de cabeza con <strong>Postgres</strong>.</p><h2>1. Preparar los datos</h2><p>Para empezar necesitamos un fichero SQL que se encargue de:</p><ol><li><p>Crear el usuario y contrase&#241;a que usaremos para conectarnos a la DB, ya que no necesariamente queremos usar los usuarios administrativos por defecto.</p></li><li><p>Definir el modelo de datos (esquemas, tablas,&#8230;)</p></li></ol><p>Un ejemplo sencillo en Postgres podr&#237;a ser:</p><pre><code>CREATE USER demo WITH PASSWORD &#8216;password&#8217;;
GRANT ALL PRIVILEGES ON DATABASE postgres TO demo;

-- CREATE TABLE...</code></pre><p>En este caso, vamos a escribir un script algo m&#225;s interesante que nos ayudar&#225; a generar datos para una tabla <code>EMPLOYEES</code>. S&#237;, hay librer&#237;as como <a href="https://pypi.org/project/Faker/">Faker</a> en Python que nos ayudan una barbaridad y tienen funciones super sencillas para generar nombres, direcciones, textos,&#8230; A&#250;n as&#237;, tener herramientas a mano para poder jugar directamente en una DB puede resultar &#250;til. </p><p><strong>No siempre ser&#225; todo SQL o todo Python</strong>. La herramienta o el lenguaje es lo de menos, aprendamos a usar bien todo lo que tenemos a mano.</p><pre><code>CREATE TABLE EMPLOYEES (
  id SERIAL PRIMARY KEY,
  first_name VARCHAR(50),
  last_name VARCHAR(50),
  email VARCHAR(50),
  mobile_no BIGINT,
  date_of_birth DATE
);

ALTER TABLE public.employees OWNER TO demo;

CREATE FUNCTION get_random_string() RETURNS TEXT LANGUAGE SQL AS $$
SELECT STRING_AGG (SUBSTR(&#8217;abcdefghijklmnopqrstuvwxyz&#8217;, CEIL(RANDOM() * 26)::integer, 1), &#8216;&#8217;)
FROM GENERATE_SERIES(1, 10)
$$;

CREATE FUNCTION insert_record() RETURNS VOID LANGUAGE PLPGSQL AS $$
DECLARE first_name TEXT= INITCAP(get_random_string());
DECLARE last_name TEXT= INITCAP(get_random_string());
DECLARE email TEXT= LOWER(CONCAT(first_name, &#8216;.&#8217;, last_name, &#8216;@gmail.com&#8217;));
DECLARE mobile_no BIGINT=CAST(1000000000 + FLOOR(RANDOM() * 9000000000) AS BIGINT);
DECLARE date_of_birth DATE= CAST(NOW() - INTERVAL &#8216;100 year&#8217; * RANDOM() AS DATE);
BEGIN
INSERT INTO EMPLOYEES (
  first_name,
  last_name,
  email,
  mobile_no,
  date_of_birth
) VALUES (first_name, last_name, email, mobile_no, date_of_birth);
END;
$$;

SELECT insert_record() FROM GENERATE_SERIES(1, 1000000);</code></pre><p>Este script nos ayuda a generar <code>INSERT</code>s mediante funciones definidas directamente en Postgres, con strings, n&#250;meros y fechas aleatorias. Gracias al <code>GENERATE_SERIES</code> podemos f&#225;cilmente ejecutar la funci&#243;n <code>insert_record</code> 1M de veces, con lo que la tabla <code>EMPLOYEES</code> acabar&#225; con 1M de registros.</p><p>Adem&#225;s, si no conoc&#237;ais la tabla <a href="https://www.postgresql.org/docs/current/pgstatstatements.html">pg_stat_statements</a>, os recomiendo habilitarla para poder revisar estad&#237;sticas de ejecuci&#243;n de queries y planes de ejecuci&#243;n:</p><pre><code>CREATE EXTENSION pg_stat_statements;
GRANT pg_read_all_stats TO demo;</code></pre><p>Cerramos el primer cap&#237;tulo guardando nuestro fichero SQL con el nombre como queramos, por ejemplo <code>postgres-script.sql</code>.</p><h2>2. Crear el <code>Dockerfile</code></h2><p>Ahora toca asegurarnos que el script se va a cargar cuando se genere la imagen de nuestra DB:</p><pre><code>FROM postgres:16
WORKDIR /docker-entrypoint-initdb.d
COPY ./postgres-script.sql .
RUN chmod -R 775 /docker-entrypoint-initdb.d</code></pre><p>Postgres tiene un directorio <code>/docker-entrypoint-initdb.d</code> donde si ponemos ficheros de SQL, van a ejecutarse cuando la DB arranque. Copiando nuestro script ah&#237; nos aseguramos que cuando alguien use nuestra imagen, tenga el usuario y los datos que hemos definido.</p><h2>3. Construir y ejecutar</h2><p>Vamos a crear la imagen con:</p><pre><code>docker build . -t demo/postgres:local</code></pre><p>y la ejecutamos mapeando bien los puertos:</p><pre><code>docker run \
  -p 5432:5432 \
  -e POSTGRES_PASSWORD=password \
  -e POSTGRES_USER=postgres \
  --name demo_pg \
  --rm \
  demo/postgres:local postgres \
  -c shared_preload_libraries=pg_stat_statements \
  -c pg_stat_statements.track=all \
  -c max_connections=200</code></pre><p>Ya s&#243;lo queda conectarnos a <code>localhost:5432</code> (yo uso <a href="http://dbeaver.io">DBeaver</a>) y estamos listos!</p><p>Un abrazo.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.pythontechlead.com/subscribe?&quot;,&quot;text&quot;:&quot;Suscribirse&quot;,&quot;language&quot;:&quot;es&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">S&#250;mate para m&#225;s contenido hecho a mano, con cari&#241;o, del de antes, para que puedas navegar tu carrera con material de calidad y confianza.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Escribe tu correo electr&#243;nico..." tabindex="-1"><input type="submit" class="button primary" value="Suscribirse"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item><item><title><![CDATA[Perfilando la memoria de lectura con SQLAlchemy]]></title><description><![CDATA[Entendiendo el uso de cursores en el cliente y el servidor]]></description><link>https://newsletter.pythontechlead.com/p/perfilando-la-memoria-de-lectura</link><guid isPermaLink="false">https://newsletter.pythontechlead.com/p/perfilando-la-memoria-de-lectura</guid><dc:creator><![CDATA[Python Tech Lead]]></dc:creator><pubDate>Sun, 21 Sep 2025 14:26:23 GMT</pubDate><enclosure url="https://substackcdn.com/image/fetch/$s_!mqMA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p><a href="https://www.sqlalchemy.org/">SQLAlchemy</a> es la librer&#237;a m&#225;s usada para interactuar con bases de datos en Python. Pero <strong>trabajar con datos no es f&#225;cil</strong>! Estamos a&#241;adiendo una capa de dificultad externa al propio c&#243;digo y debemos conocer bien tanto los datos con los que estamos jugando como la plataforma en la que residen: la base de datos (DB).</p><p>Esta librer&#237;a nos proporciona abstracciones que son realmente c&#243;modas para que no tengamos que pensar demasiado qu&#233; pasa en el lado de la DB. Sacamos ciertos datos fuera, los mapeamos a objetos en Python y seguimos con nuestras vidas. Aun as&#237;, debemos ser capaces de elegir correctamente como interactuamos con el origen de los datos.</p><p>Uno de los puntos principales a tener en cuenta es administrar correctamente la <strong>memoria</strong> de nuestros procesos: Si trabajamos con muchos datos y los intentamos leer todos de golpe, seguramente acabemos con un <em>Out of Memory Error</em>, que se puede traducir en un error 137 (<code>SIGKILL</code>) si estamos trabajando en entornos containerizados.</p><p>En este post vamos a analizar tres estrategias diferentes, ver cu&#225;nta memoria consumen y como se comportan en el lado de la DB, leyendo siempre los mismos datos de la misma tabla origen.</p><blockquote><p><strong>Nota</strong>: Para el an&#225;lisis vamos a usar una &#250;nica tabla con 1M de registros en Postgres. Para el perfilado de la memoria, usaremos <a href="https://pypi.org/project/memory-profiler/">mprof</a>.</p></blockquote><h2>all()</h2><pre><code>from sqlalchemy import create_engine, text

connection_string = "postgresql://demo:password@localhost:5432/postgres"
engine = create_engine(connection_string)

with engine.connect() as conn:
    res = conn.execute(text("SELECT * FROM public.employees")).all()
    counter = 0
    for elem in res:
        counter += 1

print(counter)</code></pre><p>Vamos a empezar con la forma m&#225;s simple: <strong>Una &#250;nica query</strong> a la tabla <code>employees</code> que va a intentar sacar el mill&#243;n de registros de una sentada. El proceso corre deprisa, pero si miramos cu&#225;nta memoria nos estamos comiendo, llega por encima de los 600MB. </p><p>Esto no es necesariamente malo, s&#243;lo tenemos que entender los recursos que debemos proveer al servicio, o como se va a comportar si los datos evolucionan en el futuro.</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!mqMA!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!mqMA!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 424w, https://substackcdn.com/image/fetch/$s_!mqMA!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 848w, https://substackcdn.com/image/fetch/$s_!mqMA!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 1272w, https://substackcdn.com/image/fetch/$s_!mqMA!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!mqMA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png" width="716" height="407.1599353796446" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:704,&quot;width&quot;:1238,&quot;resizeWidth&quot;:716,&quot;bytes&quot;:509132,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.pythontechlead.com/i/174163267?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fa6c3cd31-14d8-4909-903b-0c2f98832c0f_1238x750.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!mqMA!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 424w, https://substackcdn.com/image/fetch/$s_!mqMA!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 848w, https://substackcdn.com/image/fetch/$s_!mqMA!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 1272w, https://substackcdn.com/image/fetch/$s_!mqMA!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcb03e60e-85c4-4cba-9c0f-8f3b68627d1e_1238x704.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Perfil de memoria de `all()`</figcaption></figure></div><p>Entender como el cliente de Python gestiona este escenario est&#225; bien, pero es a&#250;n m&#225;s interesante si entramos directamente en como la DB est&#225; respondiendo a nuestra llamada, con la siguiente query:</p><pre><code>SELECT
  u.usename,
  stats.queryid,
  stats.query,
  stats.total_exec_time
FROM pg_stat_statements stats
join pg_catalog.pg_user u
  on u.usesysid = stats.userid
where u.usename = 'demo';</code></pre><p>Aqu&#237; no deber&#237;a ser una sorpresa ver una &#250;nica entrada con la query <code>SELECT * FROM public.employees</code>. Estamos levantando un &#250;nico <code>Result</code> de la DB equivalente a toda la tabla, e iteramos sobre este resultado directamente en el cliente.</p><p>Veamos si lo podemos hacer algo mejor.</p><h2>yield_per()</h2><pre><code>with engine.connect() as conn:
    res = conn.execute(text(
      "SELECT * FROM public.employees"
    )).yield_per(10)
    counter = 0
    for rows in res:
        counter += 1
print(counter)</code></pre><p>Fijaos como ahora estamos llamando <code>yield_per</code> sobre el objeto que nos proporciona el <code>execute(...)</code>.</p><p>Esto crea un <strong>cursor en el cliente</strong> con el siguiente perfil de memoria:</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!8Jwh!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!8Jwh!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 424w, https://substackcdn.com/image/fetch/$s_!8Jwh!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 848w, https://substackcdn.com/image/fetch/$s_!8Jwh!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 1272w, https://substackcdn.com/image/fetch/$s_!8Jwh!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!8Jwh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png" width="1236" height="696" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/cfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:696,&quot;width&quot;:1236,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:459725,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.pythontechlead.com/i/174163267?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F27c09bcd-7b1c-4426-bfbc-bddca7db6c67_1236x743.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!8Jwh!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 424w, https://substackcdn.com/image/fetch/$s_!8Jwh!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 848w, https://substackcdn.com/image/fetch/$s_!8Jwh!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 1272w, https://substackcdn.com/image/fetch/$s_!8Jwh!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2Fcfd648b2-50d9-44ec-bed2-2cda8ee8e0c8_1236x696.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Perfil de memoria de `yield_per()`</figcaption></figure></div><p>Ahora estamos justo por debajo de 250MB, bastante mejor que con la llamada a <code>all()</code>. Si revisamos de nuevo las entradas en <code>pg_stats_statements</code>, todav&#237;a veremos una &#250;nica llamada a la query <code>SELECT * FROM public.employees</code>.</p><p>En este escenario seguimos haciendo una &#250;nica llamada a la DB, pero estamos iterando sobre los datos de forma algo m&#225;s eficiente del lado del cliente.</p><p>Todav&#237;a podemos hacerlo mejor.</p><h2>Cursores en el servidor</h2><p>Seg&#250;n la <a href="https://www.postgresql.org/docs/current/plpgsql-cursors.html">docu</a> de Postgres: <em>En lugar de ejecutar una consulta completa de una vez, es posible configurar un cursor que encapsule la consulta y luego leer el resultado unas pocas filas a la vez.</em></p><p>Esta ser&#237;a una estrategia estupenda a seguir aqu&#237;. En vez de cargar toda la tabla <code>employees</code>, podr&#237;amos <strong>obtener unas pocas filas en cada llamada</strong>. Esto implica m&#250;ltiples viajes a la DB, pero el uso de memoria ser&#225; mucho menor.</p><p>Por suerte, SQLAlchemy nos permite hacer <a href="https://docs.sqlalchemy.org/en/20/core/connections.html#using-server-side-cursors-a-k-a-stream-results">streams de resultados</a> para usar directamente los cursores de las bases de datos. Iremos algo m&#225;s lentos, pero la memoria del cliente no va a explotar.</p><pre><code>with engine.connect() as conn:
    result = conn.execution_options(stream_results=True, max_row_buffer=100).execute(
        text("select * from public.employees")
    )
    counter = 0
    for row in result:
        counter += 1
print(counter)</code></pre><p>Vamos a preparar el <code>execution_options</code> antes de llamar a <code>execute</code>, tuneando la comunicaci&#243;n db &lt;&gt; cliente.</p><p>Esto nos deja un uso de memoria algo mayor a 40MB!</p><div class="captioned-image-container"><figure><a class="image-link image2 is-viewable-img" target="_blank" href="https://substackcdn.com/image/fetch/$s_!T_-x!,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png" data-component-name="Image2ToDOM"><div class="image2-inset"><picture><source type="image/webp" srcset="https://substackcdn.com/image/fetch/$s_!T_-x!,w_424,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 424w, https://substackcdn.com/image/fetch/$s_!T_-x!,w_848,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 848w, https://substackcdn.com/image/fetch/$s_!T_-x!,w_1272,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 1272w, https://substackcdn.com/image/fetch/$s_!T_-x!,w_1456,c_limit,f_webp,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 1456w" sizes="100vw"><img src="https://substackcdn.com/image/fetch/$s_!T_-x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png" width="1237" height="702" data-attrs="{&quot;src&quot;:&quot;https://substack-post-media.s3.amazonaws.com/public/images/6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png&quot;,&quot;srcNoWatermark&quot;:null,&quot;fullscreen&quot;:null,&quot;imageSize&quot;:null,&quot;height&quot;:702,&quot;width&quot;:1237,&quot;resizeWidth&quot;:null,&quot;bytes&quot;:527598,&quot;alt&quot;:null,&quot;title&quot;:null,&quot;type&quot;:&quot;image/png&quot;,&quot;href&quot;:null,&quot;belowTheFold&quot;:true,&quot;topImage&quot;:false,&quot;internalRedirect&quot;:&quot;https://newsletter.pythontechlead.com/i/174163267?img=https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6defb627-60ce-435a-ac51-0b6f316b9d1a_1237x754.png&quot;,&quot;isProcessing&quot;:false,&quot;align&quot;:null,&quot;offset&quot;:false}" class="sizing-normal" alt="" srcset="https://substackcdn.com/image/fetch/$s_!T_-x!,w_424,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 424w, https://substackcdn.com/image/fetch/$s_!T_-x!,w_848,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 848w, https://substackcdn.com/image/fetch/$s_!T_-x!,w_1272,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 1272w, https://substackcdn.com/image/fetch/$s_!T_-x!,w_1456,c_limit,f_auto,q_auto:good,fl_progressive:steep/https%3A%2F%2Fsubstack-post-media.s3.amazonaws.com%2Fpublic%2Fimages%2F6e5f6c87-0ab8-4afc-9ca5-76aa9836f008_1237x702.png 1456w" sizes="100vw" loading="lazy"></picture><div class="image-link-expand"><div class="pencraft pc-display-flex pc-gap-8 pc-reset"><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container restack-image"><svg role="img" width="20" height="20" viewBox="0 0 20 20" fill="none" stroke-width="1.5" stroke="var(--color-fg-primary)" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><g><title></title><path d="M2.53001 7.81595C3.49179 4.73911 6.43281 2.5 9.91173 2.5C13.1684 2.5 15.9537 4.46214 17.0852 7.23684L17.6179 8.67647M17.6179 8.67647L18.5002 4.26471M17.6179 8.67647L13.6473 6.91176M17.4995 12.1841C16.5378 15.2609 13.5967 17.5 10.1178 17.5C6.86118 17.5 4.07589 15.5379 2.94432 12.7632L2.41165 11.3235M2.41165 11.3235L1.5293 15.7353M2.41165 11.3235L6.38224 13.0882"></path></g></svg></button><button tabindex="0" type="button" class="pencraft pc-reset pencraft icon-container view-image"><svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="lucide lucide-maximize2 lucide-maximize-2"><polyline points="15 3 21 3 21 9"></polyline><polyline points="9 21 3 21 3 15"></polyline><line x1="21" x2="14" y1="3" y2="10"></line><line x1="3" x2="10" y1="21" y2="14"></line></svg></button></div></div></div></a><figcaption class="image-caption">Perfil de memoria con un cursor en el servidor</figcaption></figure></div><p>Si ahora revisamos las entradas en <code>pg_stats_statements</code>, veremos que realmente estamos levantando la tabla <code>employees</code> a trozos (en <em>chunks</em>):</p><pre><code>DECLARE "c_10289e140_1" CURSOR WITHOUT HOLD FOR select * from public.employees
FETCH FORWARD 100 FROM "c_10289e140_1"
FETCH FORWARD 25 FROM "c_10289e140_1"
...</code></pre><h2>Conclusi&#243;n</h2><p>Qu&#233; m&#233;todo deber&#237;amos usar? Como siempre, depende: del tama&#241;o de los datos, la velocidad que necesita el proceso, los recursos con los que contamos, etc. Lo importante es elegir con conocimiento de causa, una vez entendemos las diferencias de como nuestro proceso se va a comportar.</p><p>En el siguiente post, vamos a adentrarnos en c&#243;mo preparar el set de datos con el que hemos jugado de una manera superc&#243;moda.</p><p>Un abrazo.</p><div class="subscription-widget-wrap-editor" data-attrs="{&quot;url&quot;:&quot;https://newsletter.pythontechlead.com/subscribe?&quot;,&quot;text&quot;:&quot;Suscribirse&quot;,&quot;language&quot;:&quot;es&quot;}" data-component-name="SubscribeWidgetToDOM"><div class="subscription-widget show-subscribe"><div class="preamble"><p class="cta-caption">S&#250;mate para m&#225;s contenido hecho a mano, con cari&#241;o, del de antes, para que puedas navegar tu carrera con material de calidad y confianza.</p></div><form class="subscription-widget-subscribe"><input type="email" class="email-input" name="email" placeholder="Escribe tu correo electr&#243;nico..." tabindex="-1"><input type="submit" class="button primary" value="Suscribirse"><div class="fake-input-wrapper"><div class="fake-input"></div><div class="fake-button"></div></div></form></div></div>]]></content:encoded></item></channel></rss>