[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"article-deep-evm-28-pipeline-datos-alto-rendimiento-batch":3},{"article":4,"author":42},{"id":5,"category_id":6,"title":7,"slug":8,"excerpt":9,"content_md":10,"content_html":11,"locale":12,"author_id":13,"published":14,"published_at":15,"meta_title":16,"meta_description":17,"focus_keyword":18,"og_image":19,"canonical_url":19,"robots_meta":20,"created_at":15,"updated_at":15,"tags":21,"category_name":22,"related_articles":23},"d8000000-0000-0000-0000-000000000128","a0000000-0000-0000-0000-000000000085","Deep EVM #28: Pipeline de Datos de Alto Rendimiento — Inserciones Batch, COPY y Resolución de Conflictos","deep-evm-28-pipeline-datos-alto-rendimiento-batch","Construye pipelines de datos de alto rendimiento con PostgreSQL usando protocolo COPY, patrones de upsert masivo, ajuste de WAL, connection pooling con PgBouncer y monitoreo.","## El desafío de la ingestión de datos\n\nUn indexador de blockchain necesita procesar miles de transacciones por segundo. Las inserciones individuales con INSERT son demasiado lentas — necesitamos técnicas de batch para alcanzar el rendimiento necesario.\n\n## Protocolo COPY: la vía rápida\n\nCOPY es el método más rápido para insertar datos en PostgreSQL. Bypass el parsing SQL, el planificador de consultas y la mayoría del overhead:\n\n```rust\nuse tokio_postgres::types::ToSql;\nuse tokio_postgres::binary_copy::BinaryCopyInWriter;\n\nasync fn bulk_insert_transactions(\n    client: &Client,\n    transactions: &[Transaction],\n) -> Result\u003Cu64> {\n    let sink = client.copy_in(\n        \"COPY transactions (block_number, tx_hash, from_addr, to_addr, value) \\\n         FROM STDIN BINARY\"\n    ).await?;\n    \n    let writer = BinaryCopyInWriter::new(sink, &[\n        Type::INT8, Type::BYTEA, Type::BYTEA, Type::BYTEA, Type::NUMERIC,\n    ]);\n    \n    pin_mut!(writer);\n    \n    for tx in transactions {\n        writer.as_mut().write(&[\n            &tx.block_number,\n            &tx.hash.as_bytes(),\n            &tx.from.as_bytes(),\n            &tx.to.as_bytes(),\n            &tx.value,\n        ]).await?;\n    }\n    \n    writer.finish().await\n}\n```\n\nRendimiento comparativo:\n\n| Método | Filas\u002Fsegundo |\n|--------|---------------|\n| INSERT individual | 1,000 |\n| INSERT batch (1000 valores) | 15,000 |\n| COPY texto | 80,000 |\n| COPY binario | 120,000 |\n\n## Upsert masivo con ON CONFLICT\n\nPara datos que pueden duplicarse (re-indexación de bloques):\n\n```sql\nINSERT INTO transactions (block_number, tx_hash, from_addr, value)\nSELECT * FROM unnest($1::bigint[], $2::bytea[], $3::bytea[], $4::numeric[])\nON CONFLICT (tx_hash) DO UPDATE SET\n    value = EXCLUDED.value,\n    updated_at = NOW();\n```\n\nEn Rust con sqlx:\n\n```rust\nasync fn upsert_batch(\n    pool: &PgPool,\n    txs: &[Transaction],\n) -> Result\u003C()> {\n    let block_numbers: Vec\u003Ci64> = txs.iter().map(|t| t.block_number).collect();\n    let hashes: Vec\u003CVec\u003Cu8>> = txs.iter().map(|t| t.hash.to_vec()).collect();\n    \n    sqlx::query(\n        \"INSERT INTO transactions (block_number, tx_hash) \\\n         SELECT * FROM unnest($1::bigint[], $2::bytea[]) \\\n         ON CONFLICT (tx_hash) DO NOTHING\"\n    )\n    .bind(&block_numbers)\n    .bind(&hashes)\n    .execute(pool)\n    .await?;\n    \n    Ok(())\n}\n```\n\n## Ajuste de WAL (Write-Ahead Log)\n\nPara cargas de escritura intensiva, ajustar la configuración de WAL:\n\n```ini\n# postgresql.conf\nwal_buffers = 64MB\nwal_writer_delay = 200ms\ncheckpoint_completion_target = 0.9\nmax_wal_size = 4GB\nmin_wal_size = 1GB\nsynchronous_commit = off  # Solo si puedes tolerar pérdida de últimas transacciones\n```\n\n## Monitoreo con pg_stat_statements\n\nMonitorea la salud del pipeline:\n\n```sql\n-- Top consultas por tiempo total\nSELECT query, calls, total_exec_time, mean_exec_time, rows\nFROM pg_stat_statements\nORDER BY total_exec_time DESC\nLIMIT 10;\n\n-- Tasa de inserción\nSELECT relname, n_tup_ins, n_tup_upd, n_tup_del\nFROM pg_stat_user_tables\nWHERE relname = 'transactions';\n```\n\n## Connection pooling con PgBouncer\n\nPgBouncer reduce el overhead de crear conexiones:\n\n```ini\n[pgbouncer]\npool_mode = transaction\ndefault_pool_size = 25\nmax_client_conn = 1000\nreserve_pool_size = 5\nreserve_pool_timeout = 3\n```\n\n## Conclusión\n\nConstruir pipelines de datos de alto rendimiento con PostgreSQL requiere usar COPY para ingestión rápida, ON CONFLICT para idempotencia, ajustar WAL para escrituras intensivas, PgBouncer para pooling de conexiones, y pg_stat_statements para monitoreo. Con estas técnicas, PostgreSQL puede manejar cientos de miles de filas por segundo.","\u003Ch2 id=\"el-desaf-o-de-la-ingesti-n-de-datos\">El desafío de la ingestión de datos\u003C\u002Fh2>\n\u003Cp>Un indexador de blockchain necesita procesar miles de transacciones por segundo. Las inserciones individuales con INSERT son demasiado lentas — necesitamos técnicas de batch para alcanzar el rendimiento necesario.\u003C\u002Fp>\n\u003Ch2 id=\"protocolo-copy-la-v-a-r-pida\">Protocolo COPY: la vía rápida\u003C\u002Fh2>\n\u003Cp>COPY es el método más rápido para insertar datos en PostgreSQL. Bypass el parsing SQL, el planificador de consultas y la mayoría del overhead:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">use tokio_postgres::types::ToSql;\nuse tokio_postgres::binary_copy::BinaryCopyInWriter;\n\nasync fn bulk_insert_transactions(\n    client: &amp;Client,\n    transactions: &amp;[Transaction],\n) -&gt; Result&lt;u64&gt; {\n    let sink = client.copy_in(\n        \"COPY transactions (block_number, tx_hash, from_addr, to_addr, value) \\\n         FROM STDIN BINARY\"\n    ).await?;\n    \n    let writer = BinaryCopyInWriter::new(sink, &amp;[\n        Type::INT8, Type::BYTEA, Type::BYTEA, Type::BYTEA, Type::NUMERIC,\n    ]);\n    \n    pin_mut!(writer);\n    \n    for tx in transactions {\n        writer.as_mut().write(&amp;[\n            &amp;tx.block_number,\n            &amp;tx.hash.as_bytes(),\n            &amp;tx.from.as_bytes(),\n            &amp;tx.to.as_bytes(),\n            &amp;tx.value,\n        ]).await?;\n    }\n    \n    writer.finish().await\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>Rendimiento comparativo:\u003C\u002Fp>\n\u003Ctable>\u003Cthead>\u003Ctr>\u003Cth>Método\u003C\u002Fth>\u003Cth>Filas\u002Fsegundo\u003C\u002Fth>\u003C\u002Ftr>\u003C\u002Fthead>\u003Ctbody>\n\u003Ctr>\u003Ctd>INSERT individual\u003C\u002Ftd>\u003Ctd>1,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>INSERT batch (1000 valores)\u003C\u002Ftd>\u003Ctd>15,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>COPY texto\u003C\u002Ftd>\u003Ctd>80,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003Ctr>\u003Ctd>COPY binario\u003C\u002Ftd>\u003Ctd>120,000\u003C\u002Ftd>\u003C\u002Ftr>\n\u003C\u002Ftbody>\u003C\u002Ftable>\n\u003Ch2 id=\"upsert-masivo-con-on-conflict\">Upsert masivo con ON CONFLICT\u003C\u002Fh2>\n\u003Cp>Para datos que pueden duplicarse (re-indexación de bloques):\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">INSERT INTO transactions (block_number, tx_hash, from_addr, value)\nSELECT * FROM unnest($1::bigint[], $2::bytea[], $3::bytea[], $4::numeric[])\nON CONFLICT (tx_hash) DO UPDATE SET\n    value = EXCLUDED.value,\n    updated_at = NOW();\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Cp>En Rust con sqlx:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-rust\">async fn upsert_batch(\n    pool: &amp;PgPool,\n    txs: &amp;[Transaction],\n) -&gt; Result&lt;()&gt; {\n    let block_numbers: Vec&lt;i64&gt; = txs.iter().map(|t| t.block_number).collect();\n    let hashes: Vec&lt;Vec&lt;u8&gt;&gt; = txs.iter().map(|t| t.hash.to_vec()).collect();\n    \n    sqlx::query(\n        \"INSERT INTO transactions (block_number, tx_hash) \\\n         SELECT * FROM unnest($1::bigint[], $2::bytea[]) \\\n         ON CONFLICT (tx_hash) DO NOTHING\"\n    )\n    .bind(&amp;block_numbers)\n    .bind(&amp;hashes)\n    .execute(pool)\n    .await?;\n    \n    Ok(())\n}\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"ajuste-de-wal-write-ahead-log\">Ajuste de WAL (Write-Ahead Log)\u003C\u002Fh2>\n\u003Cp>Para cargas de escritura intensiva, ajustar la configuración de WAL:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-ini\"># postgresql.conf\nwal_buffers = 64MB\nwal_writer_delay = 200ms\ncheckpoint_completion_target = 0.9\nmax_wal_size = 4GB\nmin_wal_size = 1GB\nsynchronous_commit = off  # Solo si puedes tolerar pérdida de últimas transacciones\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"monitoreo-con-pg-stat-statements\">Monitoreo con pg_stat_statements\u003C\u002Fh2>\n\u003Cp>Monitorea la salud del pipeline:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-sql\">-- Top consultas por tiempo total\nSELECT query, calls, total_exec_time, mean_exec_time, rows\nFROM pg_stat_statements\nORDER BY total_exec_time DESC\nLIMIT 10;\n\n-- Tasa de inserción\nSELECT relname, n_tup_ins, n_tup_upd, n_tup_del\nFROM pg_stat_user_tables\nWHERE relname = 'transactions';\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"connection-pooling-con-pgbouncer\">Connection pooling con PgBouncer\u003C\u002Fh2>\n\u003Cp>PgBouncer reduce el overhead de crear conexiones:\u003C\u002Fp>\n\u003Cpre>\u003Ccode class=\"language-ini\">[pgbouncer]\npool_mode = transaction\ndefault_pool_size = 25\nmax_client_conn = 1000\nreserve_pool_size = 5\nreserve_pool_timeout = 3\n\u003C\u002Fcode>\u003C\u002Fpre>\n\u003Ch2 id=\"conclusi-n\">Conclusión\u003C\u002Fh2>\n\u003Cp>Construir pipelines de datos de alto rendimiento con PostgreSQL requiere usar COPY para ingestión rápida, ON CONFLICT para idempotencia, ajustar WAL para escrituras intensivas, PgBouncer para pooling de conexiones, y pg_stat_statements para monitoreo. Con estas técnicas, PostgreSQL puede manejar cientos de miles de filas por segundo.\u003C\u002Fp>\n","es","b0000000-0000-0000-0000-000000000001",true,"2026-03-28T10:44:31.664358Z","Pipeline de Datos de Alto Rendimiento — Inserciones Batch, COPY y Resolución de Conflictos","Pipeline de datos PostgreSQL de alto rendimiento: COPY, batch upserts, WAL tuning, PgBouncer y monitoreo con pg_stat_statements.","PostgreSQL pipeline datos alto rendimiento",null,"index, follow",[],"DevOps",[24,30,36],{"id":25,"title":26,"slug":27,"excerpt":28,"locale":12,"category_name":22,"published_at":29},"d8000000-0000-0000-0000-000000000127","Deep EVM #27: Rendimiento de PostgreSQL a Escala — Índices, VACUUM y Optimización de Consultas","deep-evm-27-rendimiento-postgresql-indices-vacuum","Optimiza PostgreSQL para alto rendimiento: tipos de índices y cuándo usarlos, VACUUM y autovacuum, EXPLAIN ANALYZE, y técnicas de optimización de consultas para tablas de millones de filas.","2026-03-28T10:44:31.659082Z",{"id":31,"title":32,"slug":33,"excerpt":34,"locale":12,"category_name":22,"published_at":35},"d8000000-0000-0000-0000-000000000126","Deep EVM #26: Sharding vs Particionamiento — Arquitectura para Tablas Masivas","deep-evm-26-sharding-vs-particionamiento-tablas-masivas","Comprende las diferencias entre sharding y particionamiento: cuándo usar cada uno, estrategias de clave de distribución, consistencia entre shards, y patrones para escalar PostgreSQL horizontalmente.","2026-03-28T10:44:31.653366Z",{"id":37,"title":38,"slug":39,"excerpt":40,"locale":12,"category_name":22,"published_at":41},"d8000000-0000-0000-0000-000000000125","Deep EVM #25: Particionamiento de Tablas en PostgreSQL — Cuando Tu Tabla Supera 10M+ Filas","deep-evm-25-particionamiento-tablas-postgresql","Guía práctica de particionamiento en PostgreSQL para tablas grandes. Cubre particionamiento por rango, lista y hash con ejemplos reales, estrategias de migración y planificación de consultas.","2026-03-28T10:44:31.648416Z",{"id":13,"name":43,"slug":44,"bio":45,"photo_url":19,"linkedin":19,"role":46,"created_at":47,"updated_at":47},"Open Soft Team","open-soft-team","The engineering team at Open Soft, building premium software solutions from Bali, Indonesia.","Engineering Team","2026-03-28T08:31:22.226811Z"]