[{"title":"Contacto","href":"/es/contact/","content":" Tu email: Motivo de contacto: Select a reason... Service Request Offering Services General contact Mensaje: Enviar\n"},{"title":"Actualización Mayor de Postgres y Modelo Django con Replicación Lógica","href":"/es/posts/2025-11/pgmajorupgradedjango/","content":"La historia Un cliente necesitaba actualizar su Aurora RDS Postgres de v13 a v16 debido al EOL de v13. Este es un requisito común y, hasta ahora, un proceso directo. La complejidad surgió cuando necesitaron actualizar su modelo de aplicación, lo cual estaba tomando mucho tiempo debido a que la mayoría de las tablas requerían una reescritura completa.\nLa migración de Django estaba tomando varias horas en ejecutarse, lo que hacía inviable una actualización blue/green y la posterior ejecución de la migración debido al largo tiempo de inactividad que causaría.\nAsí que decidimos probar un enfoque más sofisticado:\nCrear un esquema vacío con la migración ya aplicada en un v16 Agregar las columnas deprecadas en el clúster de destino (para evitar que la Replicación Lógica se queje) Configurar la Replicación Lógica Corregir la deriva del esquema eliminando las columnas agregadas después del cambio. De esta manera, el cambio sería casi sin interrupciones.\nℹ️ \u0026nbsp; El procedimiento de rollback consistió en crear un pub/sub desde el destino al origen. Ten en cuenta que la suscripción puede carecer de algunos de los parámetros, ya que es una versión anterior. v13 no soporta: streaming, run_as_owner, y disable_on_error. La intención de este artículo es demostrar que la Replicación Lógica no es tan estricta como puede parecer, considerando que:\nLos tipos de datos en el suscriptor no son idénticos, pero comparten las mismas primitivas o pueden ser convertidos implícitamente. La mayoría de las tablas estaban moviéndose de int a bigint y algunas de text a varchar. Las migraciones de Django son estrictas, por lo que podemos evitar esta restricción restaurando la tabla django_migrations para evitar elegir la ruta de migración. Esto se hace porque el clúster origen no tenía las últimas migraciones realizadas en la versión v16. Si faltan columnas en el destino, esto no sería un problema si usas v15, ya que puedes expandir columnas para cada una de las tablas publicadas. Sin embargo, en este caso, v13 carece de esta característica, por lo que necesitamos usar ADD COLUMN y DROP COLUMN. Se agregaron y renombraron nuevas restricciones en la nueva versión del modelo. Estas no impactan en la Replicación Lógica. El proceso 1) Esquema y datos de la tabla django_migrations Volcamos el nuevo esquema a un archivo. Este esquema contenía la migración de Django aplicada. También volcamos el contenido de la tabla django_migrations, ya que algunas migraciones se ejecutaron en la nueva instancia de prueba v16.\npg_dump -c -s --no-owner \\ --no-acl $PGURL_DESTINATION \u0026gt; sql/schema.sql pg_dump $PGURL_DESTINATION \\ --table=django_migrations \\ --data-only \\ --no-owner \\ --no-privileges \\ -f ./sql/django_migrations_data.sql 2) Restaurar el esquema en el nuevo clúster Creamos el clúster usando un grupo de parámetros personalizado deshabilitando el autovacuum. Una vez que el nuevo clúster está activo, restauramos los archivos que contenían el esquema, la nueva columna y los datos de django_migrations:\npsql -f ./sql/schema.sql $PGURL_DESTINATION psql -f ./sql/000_add_column.sql $PGURL_DESTINATION psql -f ./sql/django_migrations_data.sql $PGURL_DESTINATION 3) Publicación personalizada en el clúster origen Como usamos PostGIS (spatial_ref_sys se actualiza con metadatos y no es compatible con la Replicación Lógica), necesitamos expandir las tablas en la definición de la publicación, por lo que FOR ALL TABLES no era una opción. En la extensión RDS, los metadatos de PostGIS están bajo la propiedad rdsadmin, por lo que solo transmitimos tablas propiedad del usuario actual.\nWITH reptables AS ( SELECT schemaname ||\u0026#39;.\u0026#39;|| tablename AS tablefqdn FROM pg_tables WHERE tableowner = \u0026#39;\u0026lt;owner_user\u0026gt;\u0026#39; -- Exclude django_migrations table AND tablename NOT LIKE \u0026#39;django_migrations\u0026#39; ORDER BY tablefqdn ), agg AS ( SELECT string_agg(tablefqdn, \u0026#39;, \u0026#39;) as tables FROM reptables ) SELECT \u0026#39;CREATE PUBLICATION migration_v16_expanded_pub FOR TABLE \u0026#39; || (SELECT tables FROM agg ) || \u0026#39;;\u0026#39; as cmd; El comando anterior genera el comando necesario para crear la publicación expandida.\n4) Crear y habilitar suscripción en el clúster destino El siguiente comando crea la suscripción en el clúster destino.\nCREATE SUBSCRIPTION subs_migration_v16 CONNECTION \u0026#39;${PGURL_ORIGIN}?sslmode=require\u0026#39; PUBLICATION migration_v16_expanded_pub WITH (copy_data = true, create_slot = true, slot_name = subs_slot_migration_v16, enabled = false, disable_on_error = true, run_as_owner = true, streaming = \u0026#39;on\u0026#39; ); Puedes notar que aún no habilitamos la suscripción. Hacemos esto para poder habilitarla después:\nALTER SUBSCRIPTION subs_migration_v16 ENABLE; Una vez que la suscripción fue habilitada, el streaming comenzó.\n5) Monitorear la sincronización inicial y el lag de replicación El clúster consistía en medio terabyte de datos, por lo que tomó algunas horas hacer la sincronización inicial.\nPodemos verificar el estado de la sincronización inicial ejecutando la consulta:\nSELECT srsubstate as code, CASE srsubstate WHEN \u0026#39;i\u0026#39; THEN \u0026#39;initializing\u0026#39; WHEN \u0026#39;d\u0026#39; THEN \u0026#39;data copying\u0026#39; WHEN \u0026#39;f\u0026#39; THEN \u0026#39;finished data copy\u0026#39; WHEN \u0026#39;s\u0026#39; THEN \u0026#39;synchronized\u0026#39; WHEN \u0026#39;r\u0026#39; THEN \u0026#39;ready\u0026#39; ELSE \u0026#39;unknown\u0026#39; END AS state , count(*) as count_states , CASE srsubstate WHEN \u0026#39;d\u0026#39; THEN string_agg(srrelid::regclass::text,\u0026#39;,\u0026#39;) END as copying_tables FROM pg_subscription_rel WHERE srsubid = (SELECT oid FROM pg_subscription WHERE subname = \u0026#39;subs_migration_v16\u0026#39;) GROUP BY srsubstate; Sabremos cuándo la sincronización inicial está completa una vez que todas las tablas estén en estado ready (r).\nLas siguientes consultas permiten rastrear el lag sobre el streaming del slot:\nOrigin Lag Destination Lag SELECT slot_name, active, active_pid, pg_size_pretty(pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn)) AS diff_size, pg_wal_lsn_diff(pg_current_wal_lsn(), confirmed_flush_lsn) AS diff_bytes, confirmed_flush_lsn as LSN_origin, aurora_volume_logical_start_lsn() FROM pg_replication_slots WHERE slot_type = \u0026#39;logical\u0026#39;; SELECT subname, received_lsn as LSN_destination, last_msg_receipt_time, latest_end_lsn, latest_end_time, pg_wal_lsn_diff(received_lsn, latest_end_lsn) AS bytes_pending_apply, pg_size_pretty(pg_wal_lsn_diff(received_lsn, latest_end_lsn)) AS pending_apply, clock_timestamp() - latest_end_time AS lag, clock_timestamp() as current_time, aurora_volume_logical_start_lsn() FROM pg_stat_subscription; Una vez que el clúster destino se haya puesto al día, estamos listos para proceder con el cambio.\n6) Preparación para el cambio ℹ️ \u0026nbsp; En Postgres v18, las estadísticas se restauran, por lo que no es necesario ejecutar analyze en el destino. Ejecutamos vacuum analyze antes de proceder con el cambio. En la mayoría de los casos, un analyze simple sería suficiente. Sin embargo, en nuestro caso, algunas tablas tienen muchas escrituras, por lo que ejecutamos:\nVACUUM ANALYZE; Después de que el vacuum termine, reiniciamos el clúster destino para volver a habilitar autovacuum.\n7) Cambio y sincronización de secuencias Desafortunadamente, en este caso el cliente carece de una flota de pgbouncer, por lo que las escrituras necesitaban detenerse en la aplicación. Sin embargo, como también estábamos haciendo una actualización de la aplicación, no había otra opción que desplegar la nueva versión de la aplicación de todos modos.\nDurante la fase en la que las escrituras se detuvieron, necesitamos sincronizar las secuencias.\nℹ️ \u0026nbsp; v18 no requiere este paso, ya que las secuencias se transmiten en la Replicación Lógica. Las siguientes consultas hacen lo mismo. Las ejecutamos en el clúster origen y su salida se ejecutó en el destino:\nWith ALTER With setval SELECT \u0026#39;ALTER SEQUENCE IF EXISTS \u0026#39; || sequencename || \u0026#39; RESTART \u0026#39; || pg_sequence_last_value(sequencename::regclass) + 1 || \u0026#39;;\u0026#39; FROM pg_sequences where last_value IS NOT NULL; SELECT \u0026#39;SELECT setval(\u0026#39; || quote_literal(quote_ident(n.nspname) || \u0026#39;.\u0026#39; || quote_ident(c.relname)) || \u0026#39;, \u0026#39; || s.last_value + 1 || \u0026#39;);\u0026#39; FROM pg_class c JOIN pg_namespace n ON n.oid = c.relnamespace JOIN pg_sequences s ON s.schemaname = n.nspname AND s.sequencename = c.relname WHERE c.relkind = \u0026#39;S\u0026#39; AND s.last_value IS NOT NULL AND c.relname NOT LIKE \u0026#39;awsdms_ddl_%\u0026#39;; Después de este paso:\nDesplegamos la aplicación apuntando al clúster destino. Creamos la publicación en el destino. Creamos la suscripción de rollback en el origen. Como se mencionó, v13 carece de algunos de los parámetros que usamos en la suscripción principal: CREATE SUBSCRIPTION rollback_subs_migration_v16 CONNECTION \u0026#39;${PGURL_DESTINATION}?sslmode=require\u0026#39; PUBLICATION rollback_migration_v16_expanded_pub WITH (copy_data = true, create_slot = true, slot_name = subs_slot_migration_v16, enabled = false ); 8) Limpieza de suscripción LR, slot y publicación En el destino, procedimos con la eliminación de la suscripción:\nDROP SUBSCRIPTION subs_migration_v16; En el origen, eliminamos la publicación:\nDROP PUBLICATION migration_v16_expanded_pub; Todavía teníamos la suscripción de rollback. Una vez que pasó el período de tolerancia, procedimos con el pub/sub de rollback y eliminamos la columna adicional agregada:\nBEGIN; CREATE TABLE IF NOT EXISTS public.__backup__\u0026lt;table\u0026gt; AS SELECT id, \u0026lt;column\u0026gt; FROM public.\u0026lt;table\u0026gt;; ALTER TABLE public.\u0026lt;table\u0026gt; DROP COLUMN \u0026lt;column\u0026gt;; COMMIT; Lo anterior solo hace una copia de seguridad del contenido de esta columna. Este paso es opcional, pero es un paso adicional de seguridad.\n"},{"title":"Postgres, Databases and integration laboratories","href":"/es/toolbox/2025/labs/","content":"Description ℹ️ \u0026nbsp; Laboratories Repository This repository contains several laboratories around Postgres implementations for lecture and training purposes.\nSome of them are focused on High Availability, others are focused on Change Data Capture (CDC), but not limited to.\nPatroni HA with callbacks Doing failovers with callbacks against PgBouncer, for managing connection. Callbacks are used for Pausing, updating databases, and resuming connections. Logical Replication through different Postgres Versions This laboratory shows how to use Logical Replication with slight changes in the DDL. The approach can be used for Major Upgrades, eg. The pgbench implementation allows to test the failover and monitor connection states. Sharding with PGCat Implementation of a Hash-Sharding based using PgCat. Potentially reusable with PgDog. Patroni on Swarm Implementing a Patroni cluster over Docker Swarm in AWS EC2 machines. Several solutions are present in this laboratory: Timescale HA, and Patroni/Citus, among others. Currently WIP. Other laboratories contain Transactional Outbox strategies, Debezium and Airflow integrations, which would be used in future posts.\n"},{"title":"Backups Online sin archivos en PGBackRest","href":"/es/posts/2025-09/noarchive/","content":"Aunque no está documentado en la documentación oficial, es posible evitar almacenar archivos en configuraciones de PGBackRest. En ciertos escenarios, puedes querer ignorar el almacenamiento de archivos para reducir costos, especialmente si estás usando almacenamiento en la nube para tus backups.\nEsas situaciones podrían ser:\nEntornos de pruebas, ya sea que ejecutes pruebas intensivas pero no necesitas o te importa PITR (Point in Time Recovery). Bases de datos Postgres altamente actualizadas, donde puedes recuperar cambios por otros medios, como scraping o restaurando desde fuentes externas. ⚠️ \u0026nbsp; No almacenar archivos puede llevarte a pérdida de datos si no tienes una estrategia adecuada para manejar los deltas. Todavía necesitas almacenar archivos durante la ejecución del backup si quieres backups online. De lo contrario, no serán recuperables. Si no quieres almacenar archivos en absoluto, necesitas ejecutar el backup offline. Es decir, deteniendo tu instancia y ejecutando pgbackrest backup. Configuration Configuración en tu postgresql.conf:\narchive_mode=on archive_command=\u0026#39;test -f /tmp/pgbackrest/\u0026lt;STANZA\u0026gt;-backup.lock || exit 0 \u0026amp;\u0026amp; pgbackrest --stanza=\u0026lt;STANZA\u0026gt; archive-push %p\u0026#39; En tu comando pgbackrest, necesitas agregar la opción --archive-check=n:\nOPTION=\u0026#34;full\u0026#34; # or \u0026#34;incr\u0026#34; pgbackrest --stanza=\u0026lt;STANZA\u0026gt; --type=\u0026#34;${OPTION}\u0026#34; backup \\ --archive-check=n \\ --archive-timeout=1d --log-level-console=info \\ --buffer-size \u0026#39;16MiB\u0026#39; \\ --no-resume # optional How this works? La opción --archive-check=n evita la verificación previa del estado del archivo. Esto hace que el comando no falle si los archivos no se están almacenando.\nEl archive_command mostrado arriba, solo almacenará archivos si el backup está ejecutándose, verificando si existe el archivo de bloqueo de pgbackrest.\nUps, I wan\u0026rsquo;t archives now Si quieres volver a habilitar los archivos, necesitas ejecutar un backup completo después de cambiar tu archive_command, de lo contrario, esos archivos almacenados serían inútiles para PITR.\nReferences Aquí, puedes explorar los hilos originales sobre este tema:\nHilo Original Comentario de sean0101n "},{"title":"Postgres Releases Timeline and Major Features chart","href":"/es/toolbox/2025/pg_releases/","content":" ℹ️ \u0026nbsp; More information, see the versioning page. Why upgrade? More detailed feature diff at Depesz. timeline title PostgreSQL Releases (1990–1999) 1995 : Postgres95 : SQL support introduced : Client/server architecture : Initial performance tuning 1996 : PostgreSQL 6.0 : Renamed PostgreSQL : MVCC introduced : Major codebase rework 1997 : PostgreSQL 6.1 1998 : PostgreSQL 6.3, 6.4 1999 : PostgreSQL 6.5 : WAL logging : Foreign keys : Improved SQL compliance timeline title PostgreSQL Releases (2000–2003) 2000 : PostgreSQL 7.0 : TOAST storage : Improved join performance : Foreign key refinements 2001 : PostgreSQL 7.1 : WAL refined : Outer joins : Enhanced PL/pgSQL 2002 : PostgreSQL 7.2, 7.3 2003 : PostgreSQL 7.4 : Auto-vacuum introduced : Statement-level triggers : Index optimizations timeline title PostgreSQL Releases (2005–2009) 2005 : PostgreSQL 8.0, 8.1 : Native Windows support : Tablespaces : Savepoints 2006 : PostgreSQL 8.2 : GIN indexes : Planner improvements : Warm standby via WAL 2008 : PostgreSQL 8.3 : HOT (Heap Only Tuples) : XML datatype : UUID datatype 2009 : PostgreSQL 8.4 : Window functions : Recursive queries : Parallel restore timeline title PostgreSQL Releases (2010–2016) 2010 : PostgreSQL 9.0 : Streaming replication : Hot standby : Column-level triggers 2011 : PostgreSQL 9.1 : Synchronous replication : Unlogged tables : Foreign tables 2012 : PostgreSQL 9.2 : JSON datatype : Index-only scans : Range types 2013 : PostgreSQL 9.3 : JSON improvements : Materialized views : Writable foreign tables 2014 : PostgreSQL 9.4 : JSONB datatype : Logical decoding : ALTER SYSTEM command 2016 : PostgreSQL 9.5 : UPSERT support : Row-level security : BRIN indexes 2016 : PostgreSQL 9.6 : Parallel queries : Phrase full-text search : Replication improvements timeline title PostgreSQL Releases (2017–2019) 2017 : PostgreSQL 10 : Declarative partitioning : Logical replication : Improved parallelism 2018 : PostgreSQL 11 : JIT compilation : Stored procedures : Partitioning enhancements 2019 : PostgreSQL 12 : Partitioning refinements : Generated columns : REINDEX CONCURRENTLY timeline title PostgreSQL Releases (2020–Present) 2020 : PostgreSQL 13 : B-tree improvements : Parallel vacuum : Incremental sorting 2021 : PostgreSQL 14 : JSONB subscripting : Logical replication streaming : Performance enhancements 2022 : PostgreSQL 15 : MERGE statement : Enhanced logical replication : Index/sort improvements 2023 : PostgreSQL 16 : Logical replication from standby : CPU scalability : Enhanced monitoring 2024 : PostgreSQL 17 : Vacuum memory reduced 20x : JSON_TABLE support : Incremental backups : Synchronous Logical Slots "},{"title":"Evaluando el Sharding por hash de PGCat","href":"/es/posts/2025-01/pgcat/","content":" ℹ️ \u0026nbsp; La característica de sharding de PGCat está actualmente en fase experimental. El código del laboratorio se puede encontrar en lab_pgcat. Mecanismos de sharding de PGCat A partir de hoy, PGCat soporta 2 mecanismos de sharding a través de sintaxis extendida:\nEstableciendo el shard explícitamente: SET SHARD TO '\u0026lt;index\u0026gt;';, lo que te permite hacer sharding determinístico, ya sea que elijas tu shard de acuerdo a una regla, como una búsqueda, región, grupo de clientes, etc. Esto es genial si tienes uno de esos bien delimitados o una distribución uniforme. Pero, sigue siendo un buen enfoque y algo escalable. No nos enfocaremos en esta estrategia en este post, ya que su implementación depende de requisitos personalizados. Estableciendo sharding_function para usar una de las funciones disponibles: pg_bigint_hash y sha1. La sintaxis extendida SET SHARDING KEY TO '\u0026lt;value\u0026gt;'; calculará el índice. No está muy claro en la documentación cómo se usa la función sha1, así que este post se enfocará en el caso de pg_bigint_hash. Shard por hash es una estrategia audaz, particularmente si esperas tener una carga de trabajo grande, y necesitas tener suficiente cómputo en todos los shards. Esta sintaxis extendida se puede hacer a través de comentarios, consulta la documentación de Sharding de pgcat. En este laboratorio, nos enfocaremos en la función pg_bigint_hash. No está claro en la documentación de PGCat cómo debería implementarse sha1, pero extenderé el laboratorio para cubrirlo \u0026ndash; eso es, si supero mis problemas de habilidad :P . En este punto, puedes estar consciente de las complejidades de implementar sharding, y qué limitaciones esperamos del enfoque de hashing. Ten en cuenta que esta característica de PGCat está vinculada a la partición de Postgres, basada en la misma HASH_PARTITION_SEED. Consulta también pgcat hash seed.\nEl resharding es costoso, siempre. Y en este caso, estar vinculado a un esquema de partición de Postgres hace las cosas más difíciles. p. ej. necesitarás reconstruir tablas para la nueva distribución hash si necesitas escalar el clúster.\nLa estrategia usual cuando haces shard por hash, es comenzar con un módulo grande (es decir, cantidad total de particiones). Debido a esto, puede que no quieras comenzar con un shard por nodo, ya que puedes terminar con una arquitectura costosa desde el principio.\nLa estrategia es simple en este laboratorio: asignar más de un shard por nodo. Un gráfico de ejemplo de esto sería el siguiente:\nflowchart TD POOL(fa:fa-database PGCat/shardpool) CLIENT(fa:fa-database Client) -. Port 15432 .-\u003e POOL POOL --\u003e|Remainder 0| TABLEP0(fa:fa-table Parent Table) POOL --\u003e|Remainder 1| TABLEP1(fa:fa-table Parent Table) POOL --\u003e|Remainder 2| TABLEP2(fa:fa-table Parent Table) POOL --\u003e|Remainder 3| TABLEP3(fa:fa-table Parent Table) POOL --\u003e|Remainder 4| TABLEP4(fa:fa-table Parent Table) POOL --\u003e|Remainder 5| TABLEP5(fa:fa-table Parent Table) subgraph Node1 subgraph Shard0 fa:fa-database TABLEP0 -.-\u003e PART0(fa:fa-table Modulus 0 Partition) end subgraph Shard3 fa:fa-database TABLEP3 -.-\u003e PART3(fa:fa-table Modulus 3 Partition) end end subgraph Node2 subgraph Shard1 fa:fa-database TABLEP1 -.-\u003e PART1(fa:fa-table Modulus 1 Partition) end subgraph Shard4 fa:fa-database TABLEP4 -.-\u003e PART4(fa:fa-table Modulus 4 Partition) end end subgraph Node3 subgraph Shard2 fa:fa-database TABLEP2 -.-\u003e PART2(fa:fa-table Modulus 2 Partition) end subgraph Shard5 fa:fa-database TABLEP5 -.-\u003e PART5(fa:fa-table Modulus 5 Partition) end end Dentro de este enfoque, puedes comenzar asignando más de un shard por nodo, y migrar esos shards a nuevos nodos sin la necesidad de cambiar el módulo, que define la cantidad total de particiones permitidas.\nCada shard contiene la misma tabla padre, con solo una partición por shard que corresponde con su resto hash. Este es el único giro, y evita inserciones accidentales en la partición incorrecta. También, te permite operar esas particiones independientemente.\nℹ️ \u0026nbsp; Nota que podrías fusionar particiones en el mismo shard y dejar que PGCat apunte al mismo nodo/shard, sin embargo el nombrado del shard debería usar una convención diferente. p. ej. shard_A, shard_B, etc. Esto, combinado con Replicación Lógica y la capacidad de PGCat para recargar configuración sobre la marcha, permitirá migrar cada shard independientemente.\nLaboratory Extendí las pruebas originales para ejecutarse en una arquitectura personalizada y usar el scripting de pgbench para hacer benchmarks adicionales.\nEl laboratorio consiste en contenedores generados vía playbook de Ansible. La razón de no usar Docker Compose aquí, es porque no tiene suficiente flexibilidad para escalar contenedores que no son stateless. Los playbooks de Ansible nos permiten hacer configuración más dinámica sobre la misma familia de contenedores. Puedes encontrar que no hay mucha diferencia entre ellos, ya que los atributos mantienen coherencia entre tecnologías.\nAquí están las partes clave del laboratorio:\n0: Ansible Inventory 1: main.yaml 2: Model 3: PGCat configuration El inventario contiene 2 variables importantes: shard_factor y shard_modulus, donde shard_modulus debería ser divisible por el shard_factor.\nall: vars: postgres_user: \u0026#34;node_user\u0026#34; postgres_password: \u0026#34;password\u0026#34; postgres_db: \u0026#34;postgres\u0026#34; shard_prefix: \u0026#34;shard_\u0026#34; shard_factor: 3 # How many shards per node by default shard_modulus: 9 # How many total shards pgcat_expose_port_ix: 15432 El playbook de ansible usa el módulo community.docker.docker_container para desplegar los contenedores.\n# Deploy PostgreSQL Node - name: Run PostgreSQL Node Container community.docker.docker_container: name: \u0026#34;{{ item.stdout }}\u0026#34; image: postgres:17 restart_policy: unless-stopped env: POSTGRES_USER: \u0026#34;{{ postgres_user }}\u0026#34; POSTGRES_PASSWORD: \u0026#34;{{ postgres_password }}\u0026#34; POSTGRES_DB: \u0026#34;{{ postgres_db }}\u0026#34; ports: - \u0026#34;16000-16100:5432\u0026#34; networks: - name: back-net labels: com.docker.scaled.kind: node volumes: - \u0026#34;.conf/init_{{ item.stdout }}.sql:/docker-entrypoint-initdb.d/init.sql\u0026#34; command: \u0026gt; postgres -c wal_level=logical -c max_replication_slots=4 -c max_wal_senders=4 -c listen_addresses=\u0026#39;*\u0026#39; -c hot_standby_feedback=on -c sync_replication_slots=true loop: \u0026#34;{{ nodes.results }}\u0026#34; # Deploy PgCat (Connection Pooler) - name: Run PgCat Container community.docker.docker_container: name: pgcat image: ghcr.io/postgresml/pgcat:latest restart_policy: unless-stopped ports: - \u0026#34;15432:5432\u0026#34; volumes: - \u0026#34;.conf/pgcat.toml:/etc/pgcat/pgcat.toml\u0026#34; labels: com.docker.scaled.kind: pgcat networks: - name: back-net Cada nodo Postgres contiene una tabla con una sola partición para el resto correspondiente del shard, obteniendo su módulo extrayendo el índice del nombre de la base de datos.\n{% for shard in range(nodeix | int, shard_modulus, shard_factor) %} CREATE DATABASE {{ shard_prefix }}{{ shard }}; {% endfor %} {% for shard in range(nodeix | int, shard_modulus, shard_factor) %} \\c {{ shard_prefix }}{{ shard }} CREATE EXTENSION IF NOT EXISTS pgcrypto; CREATE TABLE IF NOT EXISTS users ( username BIGINT PRIMARY KEY, email TEXT NOT NULL ) PARTITION BY HASH (username); CREATE TABLE users_{{ shard }}_v1 PARTITION OF users FOR VALUES WITH (MODULUS {{ shard_modulus }}, REMAINDER {{ shard }}); {% endfor %} El pgcat.toml se genera a través de la siguiente plantilla jinja:\n[pools.shardpool.users.0] username = \u0026#34;{{ postgres_user }}\u0026#34; password = \u0026#34;{{ postgres_password }}\u0026#34; pool_size = 10 pool_mode = \u0026#34;transaction\u0026#34; {% for i in nodes.results %} {% for shard in range(i.item, shard_modulus, shard_factor) %} [pools.shardpool.shards.{{ shard }}] servers = [[\u0026#34;{{ i.stdout }}\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;{{ shard_prefix }}{{ shard }}\u0026#34; {% endfor %} {% endfor %} La plantilla renderizada construirá la lista de shards de la siguiente manera:\n[pools.shardpool.users.0] username = \u0026#34;node_user\u0026#34; password = \u0026#34;password\u0026#34; pool_size = 10 pool_mode = \u0026#34;transaction\u0026#34; [pools.shardpool.shards.0] servers = [[\u0026#34;node_0\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_0\u0026#34; [pools.shardpool.shards.3] servers = [[\u0026#34;node_0\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_3\u0026#34; [pools.shardpool.shards.6] servers = [[\u0026#34;node_0\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_6\u0026#34; [pools.shardpool.shards.1] servers = [[\u0026#34;node_1\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_1\u0026#34; [pools.shardpool.shards.4] servers = [[\u0026#34;node_1\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_4\u0026#34; [pools.shardpool.shards.7] servers = [[\u0026#34;node_1\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_7\u0026#34; [pools.shardpool.shards.2] servers = [[\u0026#34;node_2\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_2\u0026#34; [pools.shardpool.shards.5] servers = [[\u0026#34;node_2\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_5\u0026#34; [pools.shardpool.shards.8] servers = [[\u0026#34;node_2\u0026#34;, 5432, \u0026#34;primary\u0026#34;]] database = \u0026#34;shard_8\u0026#34; Para iniciar el clúster, ejecuta los siguientes comandos:\npipenv shell pipenv install # If you are on MacOS: export DOCKER_HOST=$(docker context inspect $(docker context show) | jq -r \u0026#39;.[].Endpoints.docker.Host\u0026#39;) ansible-playbook main.yaml Para limpiar:\nansible-playbook main.yaml --tags clean Ahora, para ejecutar benchmarks básicos, reutilizaremos las pruebas originales y las portaremos al formato pgbench (ver pgbench-shard.sql):\n\\set key random(1, 10000 * :scale) -- Write: Insert a new user SET SHARDING KEY TO \u0026#39;:key\u0026#39;; BEGIN; INSERT INTO users (username, email) VALUES ( :key, :key || \u0026#39;@example.com\u0026#39; ) ON CONFLICT (username) DO NOTHING; SELECT * FROM users WHERE email = :key || \u0026#39;@example.com\u0026#39;; END; Cambiar el ID de las filas requiere algo de trabajo adicional, pero PGCat permite esto haciendo (eso migrará la fila a través de particiones remotas \u0026ndash; ¡genial!):\n\\set newkey random(1, 10000 * :scale) SET SHARDING KEY TO \u0026#39;:key\u0026#39;; BEGIN; DELETE FROM users WHERE username = :key; END; SET SHARDING KEY TO \u0026#39;:newkey\u0026#39;; BEGIN; INSERT INTO users VALUES (:newkey, \u0026#39;:key\u0026#39; || \u0026#39;@changed.com\u0026#39;) ON CONFLICT (username) DO NOTHING; END; ℹ️ \u0026nbsp; El playbook ejecuta un benchmark rápido usando una imagen docker personalizada. Se pretende extender esto para ejecutarse durante un período de tiempo para recopilar métricas. La forma en que nos conectamos al pool con sharding es usando su nombre como la base de datos (shardpool en este caso). Por ejemplo, podemos ejecutar la prueba localmente emitiendo algo como esto (estoy usando aquí Postgres.app para Mac, usa tus binarios locales):\nPGBIN=/Applications/Postgres.app/Contents/Versions/17/bin/ PGPASSWORD=password ${PGBIN}/pgbench -h localhost -p 15432 -U shard_user -d shardpool -f pgbench-shard.sql -c 10 -T 10 ¿Qué sigue? Realizar un benchmark de estrés podría necesitar algunos recursos, y probablemente no es el punto de esta investigación. El punto aquí es combinar tanto la migración lógica como la capacidad de pgcat para gestionar todo el proceso de cambio de endpoints. También, el laboratorio fue una especie de intento experimental en módulos de Ansible para gestionar contenedores docker, debido a que había experimentado limitaciones con compose/stack templating servicios stateful y configuración renderizada. Así que probé Ansible y todas esas limitaciones desaparecieron, no sin algo de trabajo y escritura adicional ya que necesitas reproducir la creación de todos los objetos a través de diferentes tareas (por supuesto). Pero, lo encontré mucho más fácil de gestionar, extender y corregir. Soy capaz de tener, digamos, diferentes stacks usando la misma infraestructura central. ¡Gracias por leer!\n"},{"title":"[BabelfishPG] Habilitando el registro de tiempo de ejecución de statements y planes","href":"/es/posts/2024-10/babelfishpg_slowqueries/","content":"Introducción ℹ️ \u0026nbsp; El laboratorio para este post se puede encontrar en babelfishpg-lab. ⚠️ \u0026nbsp; En este post cubrimos la versión 4.2.0. Hay una ligera diferencia en la configuración y formato de logging desde 4.1.1. Aunque BabelfishPG es una variante de Postgres, la configuración se hace a través de sus extensiones (babelfish_tds y babelfish_tsql). En este post, nos enfocamos en cómo registrar tiempos de consulta y habilitar mostrar planes de consulta.\nPara Postgres vanilla, puedes registrar consultas lentas configurando log_min_duration_statements, pero esto no registrará statements TSQL. Las consultas se ejecutan a través de un fork de backend diferente, por lo que los statements dirigidos ejecutados a través del protocolo TDS, no se registrarán.\nSin embargo, BabelfishPG sí proporciona una forma de registrar statements lentos y, lo más importante, Stored Procedures. Es bastante detallado, pero extremadamente verboso.\nConfiguraciones disponibles para rastrear eventos Las siguientes variables de la extensión babelfish_tsql, te permitirían habilitar el rastreo de statements:\nℹ️ \u0026nbsp; Todas estas configuraciones se pueden recargar sin reiniciar el servidor. babelfishpg_tsql.trace_exec_time babelfishpg_tsql.trace_tree babelfishpg_tsql.trace_exec_nodes babelfishpg_tsql.trace_exec_counts La extensión babelfish_tds controla la verbosidad del log a través de la configuración babelfishpg_tds.tds_debug_log_level, que proporciona 4 niveles de debug, de 0 a 3 siendo 1 el predeterminado, que es suficientemente verboso para propósitos de análisis de consultas.\nFormato del log En este punto, puedes pensar si PGBadger funciona para analizar logs, y lo hace para la mayoría de las entradas. Sin embargo verás que agrega ciertos prefijos no-vanilla a los statements en el reporte. Entonces, funciona, pero no filtra el tiempo de consulta.\nLa salida de la entrada del log para statements es:\n2024-10-26 19:47:22.677 UTC [117] CONTEXT: PL/tsql function generate_date() line 9 at GOTO SQL statement \u0026#34;INSERT [Users] (username, balance) SELECT generate_username(), CAST(RAND()*100000 AS MONEY);\u0026#34; PL/tsql function generate_random_users(integer) line 8 at SQL statement SQL statement \u0026#34;EXEC generate_random_users 1000\u0026#34; PL/tsql function inline_code_block line 2 at EXEC TDS Protocol: Message Type: SQL BATCH, Phase: TDS_REQUEST_PHASE_PROCESS. Writing Done Token 2024-10-26 19:47:22.680 UTC [117] LOG: Execution Trace: Execution Summary: master_dbo.generate_random_users total execution code size 6, total execution time 5579ms [ 0] COND GOTO 4 (master_dbo.generate_random_users:0) (C:1001, T: 0ms) [ 1] SQL statement (master_dbo.generate_random_users:8) (C:1000, T: 5029ms) [ 2] assignment (master_dbo.generate_random_users:10) (C:1000, T: 0ms) [ 3] GOTO 0 (master_dbo.generate_random_users:0) (C:1000, T: 0ms) [ 4] RETURN (master_dbo.generate_random_users:0) (C: 1, T: 0ms) [ 5] GOTO 6 (master_dbo.generate_random_users:0) (C: 1, T: 0ms) Query Plans BabelfishPG proporciona una forma de obtener el Plan de Consulta desde la conexión TDS. Configuraciones para configurar el showplan:\nbabelfishpg_tsql.showplan_all babelfishpg_tsql.showplan_text babelfishpg_tsql.showplan_xml Ejemplo de ejecución y salida:\n1\u0026gt; set babelfish_showplan_all on 2\u0026gt; SELECT TOP 5 3\u0026gt; U.username, 4\u0026gt; SUM(S.points) AS total_points 5\u0026gt; FROM 6\u0026gt; Users U 7\u0026gt; JOIN ScoreBoard S ON U.username = S.username 8\u0026gt; GROUP BY 9\u0026gt; U.username 10\u0026gt; ORDER BY 11\u0026gt; total_points DESC; 12\u0026gt; GO QUERY PLAN Query Text: SELECT TOP 5 U.username, SUM(S.points) AS total_points FROM Users U JOIN ScoreBoard S ON U.username = S.username GROUP BY U.username ORDER BY total_points DESC Limit (cost=94.33..94.34 rows=5 width=36) -\u0026gt; Sort (cost=94.33..96.88 rows=1020 width=36) Sort Key: (sum(s.points)) DESC NULLS LAST -\u0026gt; HashAggregate (cost=64.64..77.39 rows=1020 width=36) Group Key: u.username -\u0026gt; Hash Join (cost=32.95..58.49 rows=1230 width=34) Hash Cond: ((s.username)::\u0026#34;varchar\u0026#34; = (u.username)::\u0026#34;varchar\u0026#34;) -\u0026gt; Seq Scan on scoreboard s (cost=0.00..22.30 rows=1230 width=34) -\u0026gt; Hash (cost=20.20..20.20 rows=1020 width=32) -\u0026gt; Seq Scan on users u (cost=0.00..20.20 rows=1020 width=32) (20 rows affected) "},{"title":"Timeouts en cascada a través del Pool (PGBouncer)","href":"/es/posts/2024-10/pgbouncer/","content":"Combinando query_timeout y statement_timeout En la documentación de pgbouncer se establece que el query_timeout debería configurarse ligeramente más alto que el statement_timeout de Postgres. Aunque esto aplica en la mayoría de los casos, depende de los requisitos del negocio.\nGeneralmente, el statement_timeout debería configurarse al percentil 99 de la duración de tus statements. Sin embargo, hay casos donde ciertos statements requieren timeouts más largos, debido a particularidades como un conjunto grande de clientes, o campos más grandes, como en casos de compresión TOAST.\nLa siguiente secuencia describe lo que sucedería si el query_timeout se configura a un valor ligeramente más alto que el statement_timeout:\nsequenceDiagram autonumber participant Application participant PgBouncer participant Postgres Application-\u003e\u003e+PgBouncer: Connect PgBouncer-\u003e\u003e+Postgres: Connect Postgres-\u003e\u003ePgBouncer: Connection established PgBouncer-\u003e\u003e-Application: Connection established Application-\u003e\u003eApplication: Custom User statement_timeout Application-\u003e\u003e+PgBouncer: Command PgBouncer-\u003e\u003ePostgres: Forward command Postgres-\u003e\u003ePostgres: Apply statement_timeout(5 or default) Postgres--\u003e\u003e-PgBouncer: statement_timeout PgBouncer--X Application: Timeout PgBouncer-\u003e\u003ePgBouncer: Apply query_timeout PgBouncer--\u003e\u003e-Application: query_timeout Postgres--X Application: Non-applicable custom statement_timeout Puedes estar preguntándote sobre esos casos que requieren una configuración de timeout diferente. Un enfoque probablemente recomendado sería configurar el query_timeout a un valor que signifique una especie de límite duro en términos de tiempo de ejecución. Entonces, lo ideal sería tener este timeout por encima del statement_timeout tan grande como para cubrir consultas de ejecución de casos extremos.\nsequenceDiagram autonumber participant Application participant PgBouncer participant Postgres Application-\u003e\u003e+PgBouncer: Connect PgBouncer-\u003e\u003e+Postgres: Connect Postgres-\u003e\u003ePgBouncer: Connection established PgBouncer-\u003e\u003e-Application: Connection established Application-\u003e\u003eApplication: Custom User statement_timeout Application-\u003e\u003e+PgBouncer: Command PgBouncer-\u003e\u003ePostgres: Forward command alt is default Postgres-\u003e\u003ePostgres: Apply default statement_timeout else is custom Postgres-\u003e\u003ePostgres: Apply custom statement_timeout end Postgres--\u003e\u003e-PgBouncer: statement_timeout PgBouncer--X Application: Timeout PgBouncer-\u003e\u003e PgBouncer: Apply query_timeout PgBouncer--X- Application: query_timeout Es decir, en el caso de un statement_timeout por defecto de 30 segundos y un statement_timeout personalizado de 60 segundos para la consulta más larga, el query_timeout podría configurarse a un poco más de 60 segundos.\nConclusion Usa query_timeout como un límite duro para la duración de la consulta, y statement_timeout como un límite \u0026ldquo;suave\u0026rdquo;.\n"},{"title":"[BabelfishPG] Usando tds_fdw para acceder a BabelfishPG","href":"/es/posts/2024-09/tdsfdw/","content":"¿Soporta TDS, verdad? Algunas cosas pasan una vez en la vida, y la historia alrededor de esto es bastante particular. Un cliente requirió algo que al principio sonaba contraintuitivo: migrar una base de datos Postgres existente a BabelfishPG.\nLa cosa era que la aplicación era un núcleo crítico del negocio, con una gran cantidad de código que requeriría años para migrar completamente para soportar otra estrategia de almacenamiento. Pero la razón real era que su cliente no quería apegarse a ningún modelo de licenciamiento privado, y requería usar soluciones Open Source.\nBabelfish era su primera opción, WiltonDB también fue considerado. Pero, considerando Babelfish una variante de Postgres \u0026ndash; ya que implementa protocol hooks a nivel de motor \u0026ndash;, sería imprudente apegarse a un fork de variante considerando que las releases son más frecuentes que el upstream de Postgres.\nEntonces, como profesional, pregunté continuamente las preguntas correspondientes:\nTodo el concepto de Babelfish es permitir que una gran porción de las aplicaciones MSSQL se ejecuten bajo una licencia Open Source. Puedes estar preguntándote sobre el impacto en el rendimiento de hacer doble-parsing, pero si esto es un asunto para ti, podrías reconsiderar mantener la compatibilidad TSQL.\nPero si no tienes demasiadas opciones y decides usar BabelfishPG, aquí hay algunas consideraciones además del mantenimiento estándar de Postgres:\nLas actualizaciones requieren algunos pasos adicionales, siempre que se actualice la versión del motor y la extensión. Las soluciones de pool son bastante limitadas en el campo Open Source, para aprovechar conexiones a través del backend TDS. Cubrí esto en TDSPool con BabelfishPG. Se espera alguna degradación de rendimiento ya que aplica un doble-parsing de los statements. El volcado de datos tiene algunas adiciones además del vanilla Postgres. No está muy bien documentado, pero parece estar relacionado con una reescritura pesada de la utilidad pg_dump en el núcleo. El soporte TSQL es limitado, y necesitas evaluar tu migración a través de la herramienta Babelfish Compass, para adaptar las piezas necesarias. Aparte de eso, BabelfishPG es un Postgres. Entonces, almacenamiento, replicación y configuración permanecen iguales.\nMigración de Postgres a BabelfishPG (o MSSQL Server a Postgres) Como dijimos, Babelfish almacena datos en tipos de datos de Postgres. Cómo Babelfish mapea esos tipos de datos es un tema por sí mismo.\nEl caso más común sería insertar los datos directamente en las tablas de Postgres:\nUn FDW desde un Postgres remoto a la base de datos Postgres que tiene la base de datos Babelfish inicializada. Transformar los datos vía consultas en el remoto, e insertar en tablas Postgres que fueron definidas previamente en Babelfish. Como dije, este caso es atípico, ya que estamos moviendo Postgres a MSSQL Server. Aunque, podría ser posible que tengas las mismas razones sobre licenciamiento y quieras migrar MSSQL Server a Babelfish.\nAfortunadamente, hay una extensión que proporciona un Foreign Data Wrapper que soporta TDS: tds_fdw.\nSi el caso es que quieres migrar desde un MSSQL Server a BabelfishPG, el proceso sería;\nUna vez que inicializaste Babelfish con la estructura, crea la extensión tdf_fdw, define las tablas foráneas. A través de consultas, extrae datos del remoto e inserta en las tablas Postgres. Usando tds_fdw contra BabelfishPG Los siguientes pasos funcionan en bases de datos no inicializadas, es decir, fuera de la base de datos BabelfishPG. Para soportar tds_fdw en Babelfish +4, necesitas compilar la extensión babelfishpg_tsql como se establece en las instrucciones de instalación del paquete:\nPG_CPPFLAGS=\u0026#39;-I/usr/include -DENABLE_TDS_LIB\u0026#39; SHLIB_LINK=\u0026#39;-lsybdb -L/usr/lib64\u0026#39; make PG_CPPFLAGS=\u0026#39;-I/usr/include -DENABLE_TDS_LIB\u0026#39; SHLIB_LINK=\u0026#39;-lsybdb -L/usr/lib64\u0026#39; make install ⚠️ \u0026nbsp; Más información sobre construir e instalar la extensión se puede encontrar en este enlace. La característica de servidores vinculados está soportada usando la biblioteca FreeTDS que está licenciada bajo la licencia GNU LGPL. Ver COPYING_LIB.txt para detalles. Aunque sería más rápido insertar datos directamente en Postgres, también es posible hacerlo a través del protocolo TDS.\nPuedes usar este FDW bidireccionalmente (como la mayoría de los FDW que hay):\nTraer datos de TDS a Postgres. Enviar datos de Postgres a TDS. Implementar vistas Postgres usando query en el FDW contra un MSSQL Server o BabelfishPG. En este caso, me apegaré al escenario de mover datos de Postgres a BabelfishPG, aunque migramos datos usando FDW vanilla.\nactor Client == Request Initialization == box \u0026#34;Postgres Engine\u0026#34; #LightBlue Client --\u0026gt; Postgres \u0026#43;\u0026#43; : Request activate Client Postgres -\u0026gt; Parse --\u0026#43;\u0026#43; #DarkSalmon: Local Query Parsing deactivate Parse Parse -\u0026gt; FDW --\u0026#43;\u0026#43; #LightSalmon: FDW Access end box FDW --\u0026gt; MSSQL \u0026#43;\u0026#43; : TDS Protocol MSSQL -\u0026gt; MSSQL: Execute query by tds_fdw create control CursorIteration MSSQL --\u0026gt; CursorIteration deactivate MSSQL CursorIteration --\u0026gt; FDW: Fetch Cursor FDW --\u0026gt; Parse deactivate FDW Parse -\u0026gt; Postgres: Transform Rows Postgres -\u0026gt; Postgres: Store Data Postgres --\u0026gt; Client: ResultSet deactivate Postgres deactivate Client == Request Done == Afortunadamente, tds_fdw depende mucho de FreeTDS, así que necesitarás instalar las dependencias apropiadas:\nsudo apt install freetds-common freetds-dev freetds-bin La configuración de conexión para tds_fdw reside en el freetds.conf:\n[global] tds version = 7.4 dump file = /var/log/tdspool.log [babelfish] host = localhost port = 1433 database = master ; puedes apegarse a una sola base de datos si no cambias entre esquemas ⚠️ \u0026nbsp; BabelfishPG no soporta tds version = auto. 7.4 es la versión predeterminada. Ver cómo controlar la versión del protocolo TDS a través de babelfishpg_tds.tds_default_protocol_version. Instalar la extensión está adecuadamente documentado en Instalación en Ubuntu.\nexport TDS_FDW_VERSION=\u0026#34;2.0.3\u0026#34; wget https://github.com/tds-fdw/tds_fdw/archive/v${TDS_FDW_VERSION}.tar.gz tar -xvzf v${TDS_FDW_VERSION}.tar.gz cd tds_fdw-${TDS_FDW_VERSION}/ make PG_CONFIG=/opt/babelfish/4.1.0/bin/pg_config USE_PGXS=1 sudo make PG_CONFIG=/opt/babelfish/4.1.0/bin/pg_config USE_PGXS=1 install Una vez que te conectas a la base de datos:\nCREATE EXTENSION tds_fdw; Ahora, el siguiente paso requerirá crear el SERVIDOR. Usaremos la etiqueta babelfish en el nombre del servidor, como se establece en el freetds.conf arriba. Más documentación en Foreign Server.\nDROP SERVER babelfish CASCADE; CREATE SERVER babelfish FOREIGN DATA WRAPPER tds_fdw OPTIONS (servername \u0026#39;babelfish\u0026#39;, port \u0026#39;1433\u0026#39;, database \u0026#39;externalTDS\u0026#39;, tds_version \u0026#39;7.4\u0026#39;); User Mapping:\nCREATE USER MAPPING FOR postgres SERVER babelfish OPTIONS (username \u0026#39;babelfish_admin\u0026#39;, password \u0026#39;xxx\u0026#39;); Ahora, creamos la Foreign Table en la base de datos Postgres para apuntar al Servidor correspondiente:\nCREATE FOREIGN TABLE tbl_employee_remote ( EMPLOYEE_ID uuid, TYP_EMPLOYEE_ID uuid, EMPLOYEE_NUMBER varchar(60), EMPLOYEE_DOCUMENT varchar(40), DEP_ID uuid, EMPLOYEE_STATUS boolean, BAN_ACC_NUMBER varchar(250), EMPLOYEE_CREATEDATE timestamp, EMPLOYEE_START_DATE timestamp, EMPLOYEE_FINAL_DATE timestamp, EMPLOYEE_ADDRESS varchar(400), ) SERVER babelfish OPTIONS ( table_name \u0026#39;tbl_employee\u0026#39; ); Documentación en Foreign Tables. Cubriremos conversiones de tipos de datos en otro post.\nAhora, los datos pueden transformarse e insertarse con solo una consulta estándar:\nINSERT INTO tbl_employee_remote SELECT ... FROM tbl_employee ... Incompatibilidades ⚠️ \u0026nbsp; La siguiente declaración no funciona en BabelfishPG debido a vistas del sistema faltantes al extraer el esquema. IMPORT FOREIGN SCHEMA externalTDS FROM SERVER babelfish INTO extenalTDSSchema OPTIONS (import_default \u0026#39;true\u0026#39;); ¡Gracias por leer!\n"},{"title":"[BabelfishPG] Pooling de conexiones TDS en BabelfishPG con FreeTDS","href":"/es/posts/2024-08/tdsbabelfish/","content":" El siguiente post cubrirá pruebas de rendimiento usando tdspool.\nArquitectura de conexión de BabelfishPG Heredada de la arquitectura de conexión de Postgres, cada conexión a través del puerto TDS instanciará un backend de Postgres. Como en Postgres, BabelfishPG necesita un middleware para canalizar conexiones a través del puerto TDS para evitar quedarse sin conexiones y capacidad de procesamiento en el servidor de base de datos.\nPara Postgres, tenemos muchas opciones, como PGBouncer, Odyssey, pgcat, nómbralo. Para T-SQL (léase como lenguaje compatible con MSSQL), no hay muchas soluciones de código abierto.\nUna de las opciones que exploramos aquí, es del proyecto FreeTDS: tdspool, parte del paquete freetds-bin.\nDos limitaciones muy importantes antes de considerar esto en un entorno productivo al usar tdspool:\nEl pool de conexiones FreeTDS actualmente no soporta la versión TDS 5.0 (Sybase) y conexiones encriptadas. ¡Esta restricción aplica tanto a las conexiones cliente-a-pool como pool-a-servidor! No permite ajustar un límite en conexiones frontend. Si eres nuevo alrededor del proyecto BabelfishPG y llegaste aquí por cualquier razón, ten en cuenta que hay dos tipos de arquitecturas de base de datos establecidas en babelfish_tsql.migration_mode: single-db y multi-db.\nGeneralmente, la mayoría de los casos puedes querer elegir entre ellas. Aquí está mi opinión personal:\nSi tus bases de datos son pequeñas y necesitas acceder a todas ellas, tal vez multi-db es una buena opción. Sin embargo, si esto aplica a un entorno de desarrollo, pero en producción esperas que cada una de esas bases de datos esté en recursos separados ten en cuenta que el mapeo de usuarios en la instancia Postgres será diferente. Quieres tener bases de datos grandes cada una en recursos dedicados, single-db. Si este es el caso, y quieres tener un entorno de desarrollo, puedes querer apegarse a este modo en lugar de usar multi-db para consolidar. Pooling con TDSPool (FreeTDS) Para este ejemplo, vamos a configurar la siguiente arquitectura de pool:\nflowchart TD A[App] --\u003e|Port 5000| B(fa:fa-filter appdbpool) A --\u003e|Port 5001| F(fa:fa-filter appreportpool) B -.-\u003e|Port 1433 5-30 server-side conns| D(fa:fa-database BabelfishPG) F -.-\u003e|Port 1433 5-30 server-side conns| D tdspool depende de 2 archivos de configuración, .freetds.conf y .pool.conf. Por defecto, espera que esos archivos estén en el directorio home del usuario.\n.freetds.conf .pool.conf [global] tds version = auto dump file = /var/log/tdspool.log [babelfish] host = localhost port = 1433 database = master Babelfish usa 7.4 si se desea especificar la versión.\n[global] min pool conn = 5 max pool conn = 30 max member age = 120 [appdbpool] server user = babelfish_admin server password = themainuserpassword server = babelfish user = appuser database = appdb password = apppassoword max pool conn = 30 port = 5000 [appreportpool] server user = babelfish_admin server password = themainuserpassword server = babelfish user = appreport database = appdbreport password = apppassoword max pool conn = 30 port = 5001 Además de la configuración de autorización y credenciales, las configuraciones más importantes son:\nmin pool size: es el número mínimo de conexiones al servidor para mantener abiertas, así menos latencia para esas consultas ejecutadas después de un período de inactividad. max pool size: este valor está vinculado a la capacidad de CPU y la configuración max_connections a nivel de Postgres. max member age: usado para recolectar conexiones basura. Cuando inicias tdspool, necesitas especificar sobre qué pool servirá. El contexto de la base de datos cambiará si la autorización tiene éxito, ya que el servidor está conectado a master en este caso de ejemplo. En producción, puedes querer aislar el acceso teniendo diferentes configuraciones de servidor con sus propios usuarios y bases de datos.\nIniciando los servicios:\ntdspool -c .pool.conf appdbpool tdspool -c .pool.conf appreportpool La configuración anterior configurará dos pools para servir las bases de datos appdb y appreport, con diferentes usuarios. Esto es, por ejemplo un caso donde hay diferentes cargas de trabajo entre ambas partes de la aplicación (aplicación principal y consultas de reportes asíncronos).\nPara conectarse usando tsql, que es nuestro cliente disponible en el conjunto de herramientas FreeTDS, necesitamos especificar el servidor con la opción -S:\ntsql -S babelfish -p 5000 -P ${APPDB_PASS} -D appdb -U appuser ¡Gracias por leer, mantente atento para el siguiente post!\n"},{"title":"[Característica Earthly +v0.8.10]: Deshabilitando labels internos de Earthly","href":"/es/posts/2024-07/nolabelsearthlyfeature/","content":" NOTA: Esto aún no ha sido actualizado en la documentación de Earthly, pero estoy bastante seguro de que lo será pronto (y actualizaré este post en consecuencia)\n¿Qué hacen los labels a las builds reproducibles? Por defecto, Earthly agrega labels dev.earthly.* en la imagen construida. Puedes encontrarlos haciendo un docker inspect \u0026lt;imageID\u0026gt; | jq -r '.[].Config.Labels'.\nAquí hay un ejemplo de la configuración de la imagen incluyendo los labels dev.earthly.*:\n\u0026#34;Config\u0026#34;: { ... \u0026#34;Labels\u0026#34;: { \u0026#34;dev.earthly.built-by\u0026#34;: \u0026#34;homebrew\u0026#34;, \u0026#34;dev.earthly.git-sha\u0026#34;: \u0026#34;6b41f8409d7ffef0d25072c2c04250074b6e3c72\u0026#34;, \u0026#34;dev.earthly.version\u0026#34;: \u0026#34;v0.8.14\u0026#34;, ... } } Probablemente puedas inferir cuál es el problema potencial con estos valores.\nSi cualquiera de tu entorno de ejecución, git-sha, o versión de Earthly cambia, el checksum de la imagen final cambia. Incluso si el contenido de la imagen es idénticamente el mismo.\nHay implementaciones de registros de imágenes que pueden manejar checksums duplicados con un mecanismo diferente, como evitar empujar imágenes duplicadas.\nAunque estos labels son importantes en términos de información, ciertos casos de uso requieren incluir esta información (o no) en su propio dominio de labels. Los labels impactan en la reproducibilidad de la build, porque cambia los hashes producidos de las imágenes, llevando a potencialmente imágenes duplicadas.\nAl eliminar los labels, podemos tener control total a través de nuestros labels de dominio personalizado, y evitar que artefactos duplicados se empujen upstream.\nPor eso el flag \u0026ndash;allow-without-earthly-labels fue propuesto por el Equipo de Earthly e implementado por mí.\nPuedes leer más información en el issue.\n¿Cómo funciona esta característica? Desde Earthly v0.8.10, se ha agregado la característica flag --allow-without-earthly-labels.\nLa forma de usar es la siguiente:\nVERSION --allow-without-earthly-labels 0.8 # Habilita con el VERSION flag `--allow-without-earthly-labels`. ... SAVE IMAGE --push --without-earthly-labels .... # Agrega el flag --without-earthly-labels al comando SAVE IMAGE. ¡Gracias por leer!\n"},{"title":"BabelfishPG loose contributions","href":"/es/toolbox/2024/babelfish-on-docker/","content":"BabelfishPG Docker files and images ℹ️ \u0026nbsp; Repository: babelfish-on-docker. Original work at Github. Babelfish for Postgres is a slightly-modified Postgres distribution that provides extensions for supporting TDS protocol and T-SQL language.\nI\u0026rsquo;m updating the generated images with the latest releases, plus adding a basic dockerization for the tdspool utility, for setting up a pool for the TDS connections.\nBabelfishPG Example Database ℹ️ \u0026nbsp; Original work. BabelfishPG provides a limited set of T-SQL, here is an example of a T-SQL compatible script:\nif exists (select 1 from sysobjects where id = object_id(\u0026#39;Users\u0026#39;) and type = \u0026#39;U\u0026#39;) DROP TABLE ScoreBoard DROP TABLE Users DROP FUNCTION generate_username DROP FUNCTION generate_date DROP PROCEDURE generate_random_users DROP PROCEDURE generate_random_score GO CREATE FUNCTION generate_username () RETURNS varchar(40) AS BEGIN DECLARE @genuser varchar(40) DECLARE @length int SET @length = CAST(RAND() * 38 as INT) SELECT @genuser = \u0026#39;\u0026#39; WHILE @length \u0026gt; 0 BEGIN SELECT @genuser = @genuser + CHAR(48 + ABS(CHECKSUM(NEWID())) % 80) SET @length = @length - 1 END IF EXISTS ( SELECT username FROM Users WHERE username = @genuser) SELECT @genuser = generate_username() RETURN @genuser END GO CREATE PROCEDURE generate_random_users @scale int AS BEGIN WHILE (@scale \u0026lt;\u0026gt; 0) BEGIN INSERT [Users] (username, balance) SELECT generate_username(), CAST(RAND()*100000 AS MONEY) SET @scale = @scale - 1 END END GO CREATE FUNCTION generate_date () RETURNS datetime AS BEGIN DECLARE @gentime DATETIME SET @gentime = GETDATE() + (365 * 2 * RAND() - 365) RETURN @gentime END GO CREATE PROCEDURE generate_random_score @scale int AS BEGIN DECLARE @userpick nvarchar(40) DECLARE @randtiny tinyint DECLARE @query varchar(4000) -- FIXME! Not supported in Babelfish -- INSERT [ScoreBoard] (username, points) -- SELECT TOP 1 username, CAST(ROUND(RAND()*100) AS tinyint) -- FROM Users ORDER BY NEWID() -- Msg 33557097 (severity 16, state 1) from BABELFISH Line 1: -- \u0026#34;syntax error at or near \u0026#34;TOP\u0026#34;\u0026#34; -- To over come limitation above, we can use a CTE for inserting rows from a TOP claused query set @query = \u0026#39;WITH randus AS ( SELECT TOP \u0026#39; + CAST(@scale as varchar) + \u0026#39;username, CAST(ROUND(RAND()*100) AS tinyint) FROM Users ORDER BY NEWID() ) INSERT INTO [ScoreBoard] (username, points) SELECT * FROM randus\u0026#39; execute(@query) END GO CREATE TABLE Users ( Id int IDENTITY, username nvarchar(40) not null, created datetime not null default generate_date(), balance MONEY, constraint PK_USERS primary key (Id), constraint UQ_USERS unique (username) ) GO CREATE TABLE ScoreBoard ( Id int IDENTITY PRIMARY KEY, username nvarchar(40), points tinyint, FOREIGN KEY (username) REFERENCES Users (username) ON DELETE CASCADE ON UPDATE CASCADE ) GO EXEC generate_random_users 1000 GO EXEC generate_random_score 50 GO SELECT count(*) FROM Users GO SELECT count(*) FROM ScoreBoard GO SELECT CAST(serverproperty(\u0026#39;babelfishversion\u0026#39;) AS VARCHAR) GO "},{"title":"Implementando una función multi-proceso funcional en Bash","href":"/es/posts/2024-06/waiter/","content":"Si estás en este post, es porque necesitas implementar una forma consistente de agregar paralelización a tus scripts. Esta implementación es limitada, ya que no contempla agrupar trabajos para establecer diferentes grupos de procesos con diferentes configuraciones o prioridades.\nAunque, probablemente la mayoría de los casos solo necesitas ejecutar código que corre dentro de un bloque o simplemente hace algo en paralelo. El comando parallel es una buena opción, pero requiere perder algo de legibilidad del código, particularmente en bloques de código que podrían inyectar una semántica compleja.\nPuedes comenzar con esta definición:\n... maxJobs=4 ... waiter(){ while test $(jobs -p | wc -l) -ge $maxJobs ; do sleep 1 ; wait -n; done } waitall(){ wait \u0026lt; \u0026lt;(jobs -p) } La función waiter hace la verificación de que el número actual de trabajos actualmente generados por el proceso principal no exceda el valor maxJobs usando la llamada estándar wait.\nEl comando wait -n espera que el siguiente trabajo termine. Esto es para todos los trabajos que están ejecutándose en ese punto en el tiempo. Implementar un enfoque más elaborado para esperar por grupos, sería almacenar el id del trabajo y usar wait -f ID y coordinar en consecuencia.\nEl jobs -p lista los IDs de los trabajos, combinado con solo un wc -l simple que cuenta la lista. Si llegas a almacenar esos IDs, es posible coordinar y configurar grupos de trabajos. También, jobs -n te permite listar solo esos trabajos que han cambiado su estado, como una cola de mensajes. Consulta la página del manual para más detalles.\nEl waitall esperará todos los pids devueltos por jobs -p. Ver este hilo de SO.\nEl uso es simple, y depende del concepto de bloques de código en bash. Puedes usar solo comandos simples también, pero organizar a través de bloques bash puede ser alentado.\n... waiter ( # operations ... ) \u0026amp; ... # una vez que el loop termine, espera todos los trabajos waitall Aquí hay un ejemplo implementado en una función. En este caso implemento una iteración para generar trabajos, solo por el bien del ejemplo:\nfunction AFuncThatSpawnsSubProcesses() { for file in $(ls output/*.json) do # Worker initialization org=$(echo $file | grep -Po \u0026#39;(?\u0026lt;=_)[^_]+(?=_)\u0026#39;) repo=$(echo $file | grep -Po \u0026#39;(?\u0026lt;=_)[^_]+(?=\\.)\u0026#39;) # Operation block waiter # Check and wait to a job release ( someOutput=$(ExecuteSomethingInFunc $org $repo) ExecuteSomethingElseInFunc $someOutput ) \u0026amp; done waitall # wait the rest of jobs to finish } Manejo de Interrupciones Cuando generas procesos hijos, necesitas manejar interrupciones para controlar los trabajos ejecutándose en el proceso. Si quieres manejar esto bien, o tienes intención de detener solo un grupo de trabajos, necesitas almacenar los PIDS \u0026ndash; digamos \u0026ndash; en un array para matarlos en el manejador de interrupción.\nEsto te permite implementar lógica más compleja, como tener un conjunto de workers que quieres matar de manera diferente \u0026ndash; almacenando el estado, p. ej. \u0026ndash; y otro grupo para ser matado inmediatamente.\nLas siguientes definiciones son un enfoque perezoso, que solo mata todos los hijos y finalmente, mata el padre.\ntrap \u0026#34;ctrlc\u0026#34; SIGINT ctrlc(){ kill $(jobs -p) # kills childs pkill -P $$ # kills parent } Reutilizando funciones También puedes usar un script bash estilo biblioteca como este Gist.\n. ./workers.sh "},{"title":"Go Testcontainers for Postgres flavors","href":"/es/toolbox/2024/testcontainers/","content":"Description ℹ️ \u0026nbsp; Source Code of the laboratory. All examples are functional, follow the instructions in the README file for setting up. The csv file is generated by the Docker Compose setup, you can regenerate the example by running docker compose up -d if you need a different data scale. Testcontainers allows you to test your code with ephemeral containers right inside your tests. It provides different modules for simplifying the process, however, sometimes you many need to customize the container beyond the default parameters or it contents.\nThis laboratory is an exploratory approach for setting tests that require Postgres services of different kinds.\n"},{"title":"Personalización de Testcontainers para pruebas de Postgres","href":"/es/posts/2024-05/testcontainers-postgres/","content":"Testcontainers te permite probar tu código con contenedores efímeros directamente dentro de tus pruebas. Proporciona diferentes módulos para simplificar el proceso, sin embargo, a veces puedes necesitar personalizar el contenedor más allá de los parámetros predeterminados o su contenido.\nCódigo Fuente del laboratorio. Todos los ejemplos son funcionales, sigue las instrucciones en el archivo README para configurar. El archivo csv se genera por la configuración de Docker Compose, puedes regenerar el ejemplo ejecutando docker compose up -d.\nTestcontainers ofrece una API genérica de contenedores, y para servicios específicos, proporciona módulos que son ayudantes con las configuraciones más comunes. Los módulos disponibles se pueden encontrar aquí.\nEn este post veremos algunas de las consideraciones en las últimas actualizaciones de API en Testcontainers v0.31.0.\nSi no tienes una buena idea de qué puede hacer Testcontainers, intentaré elaborar así:\nEjecutar contenedores efímeros de cualquier tipo para hacer (principalmente) pruebas de integración o funcionales.\nEs muy conveniente si tienes una gran cantidad de Servicios Como Una Dependencia, si tal término existe. Pero ciertamente, juega un papel en el desarrollo dirigido por pruebas, simplificando la prueba para arquitecturas de servicios complejas. La documentación de los módulos usados en el laboratorio se puede encontrar en los siguientes enlaces:\nGeneric Containers Postgres Module Docker Compose El laboratorio se puede ejecutar de la siguiente manera\ngo run main.go # generic run, no test go test -v generic_test.go --args -imageName=postgres:16-bookworm go test -v ts_test.go o ejecutando ./e2e.sh.\nℹ️ \u0026nbsp; El laboratorio cargará y ejecutará la migración de datos, ver el contenido de los scripts de inicialización dentro de la carpeta test. Usando Generic Container request Construir un contenedor genérico requiere inicializar creando una solicitud (ContainerRequest), y pasando la solicitud al constructor GenericContainer. También establecemos un flag en el script de prueba para que sea posible cambiar la imagen en cada prueba. Si usas una imagen no compatible con el módulo Postgres, esta podría ser tu opción.\nEjemplo completo en generic_test.go.\nimageName flag Request Build Container Run // go test -v main_test.go -args -imageName=... var imageName = flag.String(\u0026#34;imageName\u0026#34;, \u0026#34;postgres:16-bookworm\u0026#34;, \u0026#34;URL of the image\u0026#34;) ctx := context.Background() req := tc.ContainerRequest{ Image: *imageName, ExposedPorts: []string{\u0026#34;5432/tcp\u0026#34;}, Env: map[string]string{ \u0026#34;POSTGRES_PASSWORD\u0026#34;: \u0026#34;postgres\u0026#34;, \u0026#34;POSTGRES_HOST_AUTH_METHOD\u0026#34;: \u0026#34;trust\u0026#34;}, WaitingFor: wait.ForLog(\u0026#34;Ready to accept connections\u0026#34;), Files: []tc.ContainerFile{ { HostFilePath: \u0026#34;test/generic\u0026#34;, ContainerFilePath: \u0026#34;/docker-entrypoint-initdb.d\u0026#34;, FileMode: 0o666, }, { HostFilePath: \u0026#34;test/containerdata/devices.csv\u0026#34;, ContainerFilePath: \u0026#34;/tmp/devices.csv\u0026#34;, FileMode: 0o666, }, }, } postgresC, _ := tc.GenericContainer(ctx, tc.GenericContainerRequest{ ContainerRequest: req, Started: true, }) defer func() { if err := postgresC.Terminate(ctx); err != nil { t.Fatalf(\u0026#34;failed to terminate container: %s\u0026#34;, err) } }() Usando Postgres Module con una imagen no-vanilla ¿Qué pasa con las imágenes que son servicios Postgres pero no son una imagen vanilla? Este módulo soporta diferentes imágenes que son compatibles con la imagen oficial de Postgres. Un ejemplo de tal podría ser la imagen Timescale, que está basada en la imagen oficial de Postgres, pero agrega la extensión y su inicialización.\nEl módulo Postgres permite iniciar el contenedor en un solo paso, y proporciona un conjunto de funciones ayudantes para extraer la información del contenedor\nEjemplo completo en ts_test.go.\nInitialization ConnectionString helper Execute command inside the container ctx := context.Background() dbName := \u0026#34;iot\u0026#34; dbUser := \u0026#34;postgres\u0026#34; dbPassword := \u0026#34;password\u0026#34; usageData := filepath.Join(\u0026#34;test/containerdata\u0026#34;, \u0026#34;devices.csv\u0026#34;) r, err := os.Open(usageData) if err != nil { t.Fatal(err) } postgresContainer, err := postgres.RunContainer(ctx, tc.WithImage(\u0026#34;timescale/timescaledb:latest-pg16\u0026#34;), // We execute the generator with docker-compose, so we have a deterministic test // postgres.WithInitScripts(filepath.Join(\u0026#34;test/containerdata\u0026#34;, \u0026#34;003_generator.sql\u0026#34;)), postgres.WithInitScripts(filepath.Join(\u0026#34;test/timescale\u0026#34;, \u0026#34;004_init.sql\u0026#34;)), postgres.WithInitScripts(filepath.Join(\u0026#34;test/timescale\u0026#34;, \u0026#34;005_load.sql\u0026#34;)), postgres.WithDatabase(dbName), postgres.WithUsername(dbUser), postgres.WithPassword(dbPassword), tc.CustomizeRequest(tc.GenericContainerRequest{ ContainerRequest: tc.ContainerRequest{ Files: []tc.ContainerFile{ { Reader: r, HostFilePath: usageData, ContainerFilePath: \u0026#34;/tmp/devices.csv\u0026#34;, FileMode: 0o666, }, }, }}), tc.WithEnv(map[string]string{ \u0026#34;TS_TUNE_MEMORY\u0026#34;: \u0026#34;1GB\u0026#34;, \u0026#34;TS_TUNE_WAL\u0026#34;: \u0026#34;1GB\u0026#34;, \u0026#34;TS_TUNE_NUM_CPUS\u0026#34;: \u0026#34;2\u0026#34;}), tc.WithWaitStrategy( wait.ForLog(\u0026#34;database system is ready to accept connections\u0026#34;). WithOccurrence(2). WithStartupTimeout(10*time.Second)), // we add a large startup due that we are loading data ) // Database pointer creation connStr, err := postgresContainer.ConnectionString(ctx, \u0026#34;sslmode=disable\u0026#34;) if err != nil { log.Fatalf(\u0026#34;failed to get connection string: %s\u0026#34;, err) } db, err := sql.Open(\u0026#34;postgres\u0026#34;, connStr) if err != nil { log.Fatalf(\u0026#34;failed to open database: %s\u0026#34;, err) } defer db.Close() if _, out, err := postgresContainer.Exec(ctx, []string{\u0026#34;psql\u0026#34;, \u0026#34;-U\u0026#34;, dbUser, \u0026#34;-w\u0026#34;, dbName, \u0026#34;-c\u0026#34;, `SELECT count(*) from devices;`}); err != nil { log.Println(err) t.Fatal(\u0026#34;couldn\u0026#39;t count devices\u0026#34;) } else { // read io.Reader out io.Copy(os.Stdout, out) } Usando docker compose Otra forma sería reutilizar una definición de Docker Compose.\nEjemplo completo en compose_test.go.\nDocker Compose Executing the container networks: # A network for the data traffic data: services: timescale: image: timescale/timescaledb:latest-pg16 ## Once in prod # restart: always container_name: \u0026#34;pgtc-ts\u0026#34; environment: - POSTGRES_USER=postgres - POSTGRES_PASSWORD=postgres - POSTGRES_DB=iot - TS_TUNE_MEMORY=1GB - TS_TUNE_WAL=1GB - TS_TUNE_NUM_CPUS=2 healthcheck: test: [\u0026#34;CMD-SHELL\u0026#34;, \u0026#34;pg_isready -U postgres\u0026#34;] interval: 5s timeout: 5s retries: 5 networks: - data ports: - 15432:5432 volumes: - ./_pgdata:/var/lib/postgresql/data - ./test/timescale:/docker-entrypoint-initdb.d - ./test/containerdata/devices.csv:/tmp/devices.csv import ( \u0026#34;context\u0026#34; \u0026#34;testing\u0026#34; \u0026#34;github.com/stretchr/testify/require\u0026#34; tc \u0026#34;github.com/testcontainers/testcontainers-go/modules/compose\u0026#34; ) func TestSomething(t *testing.T) { compose, err := tc.NewDockerCompose(\u0026#34;docker-compose.yaml\u0026#34;) require.NoError(t, err, \u0026#34;NewDockerComposeAPI()\u0026#34;) t.Cleanup(func() { require.NoError(t, compose.Down(context.Background(), tc.RemoveOrphans(true), tc.RemoveImagesLocal), \u0026#34;compose.Down()\u0026#34;) }) ctx, cancel := context.WithCancel(context.Background()) t.Cleanup(cancel) require.NoError(t, compose.Up(ctx, tc.Wait(true)), \u0026#34;compose.Up()\u0026#34;) // Do tests... } Puedes estar preguntándote sobre el prefijo numérico en este ejemplo. Ten en cuenta que algunas imágenes de Postgres pueden haber incluido otros archivos en la carpeta docker-entrypoint-initdb.d. p. ej., la imagen Timescale viene con 2 archivos, así que no queremos sobrescribirlos. El orden de ejecución de los archivos es relevante, así que ten cuidado de esto si tus operaciones requieren varios pasos.\n"},{"title":"Construyendo binarios dinámicamente enlazados en imágenes distroless","href":"/es/posts/2024-05/distroless-sqlite/","content":"Caso He estado desarrollando una aplicación en Go que usa SQLite como caché local del backend. La historia terminaría ahí si no fuera que decidí ir por una imagen distroless para propósitos de optimización, y también, ¿por qué no?\nLa cosa aquí es que para habilitar ciertas características del driver database/sql, el artefacto debería estar dinámicamente enlazado a través de CGO_ENABLED=1 y con -tags \u0026quot;fts5\u0026quot; (para el soporte de la característica FTS5, ver este comentario del issue).\nDe lo contrario, si no usas ninguna etiqueta de característica en el driver \u0026ndash; en este caso CGO_ENABLED=0 \u0026ndash; el artefacto será estático, y puedes solo usar la imagen static-\u0026lt;distro\u0026gt;.\nDesde que las imágenes base están disponibles en los paquetes, ahora puedes componer tu binario dinámicamente enlazado en una imagen distroless. La imagen base contiene glibc, y libssl.\n¿Por qué Distroless? Además de las ventajas de imágenes minimalistas, como el tamaño, hay algunos beneficios desde la perspectiva de seguridad. Reduciendo las dependencias de la imagen, hay una reducción en vulnerabilidades de seguridad heredadas.\nHay software que no es tan directo de implementar como distroless, pero eventualmente, es posible hacerlo.\nTambién, algunas implementaciones podrían necesitar herramientas o depender de ellas, lo que puede ser una advertencia al implementar. Esas herramientas pueden implementarse como sidecars, junto a un pod \u0026ndash; si usas k8s, p. ej. \u0026ndash;, pero al costo de un despliegue más complejo.\nConstrucción de Capas Primero, ten en cuenta que necesitas construir tu componente en la misma distribución que la que quieres usar como distroless. Si echas un vistazo a las imágenes disponibles para Golang, verás que cada versión tiene una distribución upstream diferente.\nEl siguiente ejemplo, compila el artefacto en golang:1.22-bookworm(Debian 12) y copia el artefacto generado a la imagen distroless gcr.io/distroless/base-debian12.\n## Build layer FROM golang:1.22-bookworm as build WORKDIR /go/scr/app_demo COPY . . RUN go mod download RUN go vet -v ./... RUN go test -v ./... RUN CGO_ENABLED=1 go build -tags \u0026#34;fts5\u0026#34; -o /app/app_demo cmd/backend/main.go RUN chmod a+x /app/app_demo ## Final image FROM gcr.io/distroless/base-debian12 COPY --from=build /app /app WORKDIR /app CMD [\u0026#34;/app/app_demo\u0026#34;] Tamaños La imagen final en mi caso con el base es de aproximadamente 65.4MBs. Una imagen más pequeña con glibc, cgr.dev/chainguard/glibc-dynamic terminó con ~32MBs, que es adecuada si no esperas usar libssl.\nEjecutando imagen Construir el binario podría tomar algo de tiempo, especialmente si ejecutas la generación de código, instalar templ , o hacer el vet completo sobre el código. Para desacoplar estos, agregaremos los archivos de configuración y directorios para assets personalizados vía volumes. Esto nos permitirá modificar la configuración localmente en el host, en lugar de copiar archivos a la imagen.\nNo podrás escribir un archivo directamente en la imagen, esa es la razón por la que mapeamos un volumen a una carpeta local que contendrá el archivo de base de datos para SQLite.\nnetworks: app_demo: services: backend: build: context: . dockerfile: ./docker/backend/Dockerfile image: app_demobackend container_name: \u0026#34;app_demo_backend\u0026#34; ports: - 8082:8082 volumes: - type: bind source: ./docker/backend/app_demo.yaml target: /app/app_demo.yaml - ./docker/backend/cache:/app/cache env_file: - ./docker/backend/.env depends_on: postgres: condition: service_healthy networks: - app_demo command: \u0026gt; /app/app_demo -index=true -config=/app/app_demo.yaml ... other services ... El volumen /app/cache es una carpeta vacía en el host que se montará para mantener el archivo de base de datos localmente.\n"},{"title":"Usando parallel con múltiples argumentos","href":"/es/posts/2024-04/multiargparallel/","content":"Una herramienta útil para paralelización Si estás leyendo este post, es porque has oído sobre parallel. Es una herramienta desarrollada por GNU para paralelizar comandos a través de conjuntos de argumentos. Seré honesto, suelo olvidar su existencia hasta que necesito hacer cosas rápidas y sucias cuando trato con tareas que requieren paralelización, y siempre que el tiempo es una restricción.\nTiene muchas opciones y argumentos, aunque una vez que te acostumbras a su uso, es muy útil en muchos casos.\nMi caso Uno de los proyectos en los que he estado trabajando recientemente involucra construir imágenes OCI basadas en componentes distroless. El número de imágenes generadas es bastante alto, con alrededor de 4,000 imágenes empujadas hasta la fecha. Estas deberían considerarse capas en lugar de imágenes independientes, ya que forman una cadena de dependencia para crear una imagen funcional final.\nEl desafío era verificar la información de la imagen vía la API de GitHub. Para abordar esto, construí un script que recorría todos los contenedores existentes (el término que GitHub usa para imágenes) a través de varias páginas.\nMi enfoque inicial era directo: extraer el nombre e ID de cada imagen, analizar los datos, realizar sustitución de variables, y luego hacer scraping y almacenar la información de la imagen. Sin embargo, todo el proceso estaba tomando 12 minutos para completarse, lo cual era claramente ineficiente e inaceptable.\nEn el pasado, había usado procesamiento paralelo, pero esta situación era diferente ya que los argumentos no eran combinaciones sino filas individuales de información para cada versión de imagen.\nUsar procesamiento paralelo en este caso redujo el tiempo de ejecución completo a alrededor de 6 minutos, incluso con un límite de solo cuatro trabajos concurrentes para evitar superar las restricciones de cuota de la API.\nAbordando el problema: pasando parámetros en JSON a parallel El siguiente fragmento es parte de una función llamada index_container_pages que produce los parámetros en un formato JSON, con la información necesaria para hacer scraping del ID de imagen desde la API.\njq -r \u0026#39;.[] | {id: .id , name: .name , url: .url} | @json\u0026#39; \\ $(get_container_pages) \u0026gt; ${OUTDIR}/paramix.json El get_container_pages es una función que solo devuelve todas las páginas previamente descargadas del registro. Considera que estamos hablando de alrededor de 4,000 imágenes (contenedores, como se conocen en Github), así que esto está devolviendo alrededor de +30 páginas de archivos JSON. Dentro del comando anterior, pude combinarlos todos en el archivo JSON que sirve como parámetros para el comando parallel.\nEste archivo generado es la lista de argumentos que vamos a usar para parametrizar parallel.\nEl código dentro del script, terminó viéndose así:\nHEADER=\u0026#34;curl -s -L -H \\\u0026#34;Accept: application/vnd.github+json\\\u0026#34; \\ -H \\\u0026#34;Authorization: Bearer ${GH_TOKEN}\\\u0026#34; \\ -H \\\u0026#34;X-GitHub-Api-Version: 2022-11-28\\\u0026#34;\u0026#34; parallel --progress --colsep \u0026#39;\\t\u0026#39; -j4 \\ \u0026#39;[ ! -d \u0026#39;\u0026#39;\u0026#39;${OUTDIR}\u0026#39;\u0026#39;\u0026#39;/{2} ] \u0026amp;\u0026amp; { mkdir -p \u0026#39;\u0026#39;\u0026#39;${OUTDIR}\u0026#39;\u0026#39;\u0026#39;/{2} ; } ; \\ \u0026#39;\u0026#39;\u0026#39;${HEADER}\u0026#39;\u0026#39;\u0026#39; \\ {1} \u0026gt; \u0026#39;\u0026#39;\u0026#39;${OUTDIR}\u0026#39;\u0026#39;\u0026#39;/{2}/{3}.json ; sleep 0.1\u0026#39; \\ :::: \u0026lt;(jq -r \u0026#39;. | \u0026#34;\\(.url)\\t\\(.name)\\t\\(.id)\u0026#34;\u0026#39; ${OUTDIR}/paramix.json) parallel --progress --colsep \u0026#39;\\t\u0026#39; -j4 \\ \u0026#39;\u0026#39;\u0026#39;\u0026#39;${HEADER}\u0026#39;\u0026#39;\u0026#39; \\ {1}/versions \u0026gt; \u0026#39;\u0026#39;\u0026#39;${OUTDIR}\u0026#39;\u0026#39;\u0026#39;/{2}/versions.json ; sleep 0.1\u0026#39; \\ :::: \u0026lt;(jq -r \u0026#39;. | \u0026#34;\\(.url)\\t\\(.name)\u0026#34;\u0026#39; ${OUTDIR}/paramix.json) HEADER es solo una Macro del comando curl. Esas variables usando comillas simples triples son esas \u0026ldquo;constantes\u0026rdquo;, así que las inyectamos directamente en el comando parallel. Sé que se ve raro usar comillas simples triples, pero IMHO es la mejor forma de escapar de comillas en el shell. Puedes inyectar lo que tengas declarado sin caer en back-slashes molestos y es más consistente. El :::: es el equivalente de parallel de while, y leemos los parámetros del paramix.json en formato TSV. Puedes usar CSV también. En cualquier caso, esto se controla por --colsep '\\t'. El {1}, {2} y {3} son las variables que alimentamos desde el comando jq. Duh. El sleep no es estrictamente necesario, pero controla mejor el caso en el que la iteración podría considerarse sospechosa. El registro de GH se quejaba cuando intenté emitir el comando sin sleep. El name, devuelve el nombre de ruta de la imagen, así que usamos ese nombre exacto para crear la ruta local. Puedes incluso hacer mejor control de la paralelización con el flag -j obteniendo el factor de procesamiento que quieres asignar. P. ej.:\n-j $(expr $(nproc) / 2 + 1) -j $(nproc) Ten en cuenta que hay ciertas limitaciones respecto a la cantidad de solicitudes permitidas a través de cualquier API. Aunque, el ejemplo anterior puede usarse para procesar cosas localmente.\nOtras combinaciones Los argumentos de parallel pueden básicamente controlarse por :::, :::+ y :::: (que es el que usamos arriba para tomar argumentos de un archivo). El ::: solo combina todos los argumentos, mientras que :::+ fuerza una sola ejecución para cada argumento.\nparallel echo ::: 1 2 3 ::: A B C ::: T 1 A T 1 B T 1 C T 2 A T 2 B T 2 C T 3 A T 3 B T 3 C T Ahora, supongamos que solo quiero las combinaciones para A B C solamente, así que puedo usar :::+ para hacer una sola iteración:\nparallel echo ::: 1 2 3 :::+ A B C ::: T 1 A T 2 B T 3 C T parallel echo ::: 1 2 3 ::: A B C :::+ T 1 A T 2 A T 3 A T Los argumentos más a la derecha tienen precedencia sobre los argumentos más a la izquierda, así que ten esto en cuenta al construir listas de argumentos.\n¡Gracias por leer!\n"},{"title":"Layout de Teclado Ergodox para Colemak, QWERTY y Dvorak","href":"/es/posts/2023-02/oryxlayout/","content":"\nℹ️ \u0026nbsp; Este es mi Layout MacOS actual. Hace unos meses, compré el Ergodox OrthoLinear Split Keyboard. Después de un período de probarlo, encontré que el layout QWERTY era considerablemente incómodo para escribir en la disposición del teclado. No era el único, por supuesto, así que empecé el viaje para buscar un layout adecuado. Durante un período de tiempo, cambié de un lado a otro entre tres layouts (QWERTY, Dvorak, y Colemak), aunque elegí Colemak al final.\nRealmente me gusta Dvorak, pero encontré que Colemak era mejor para mí ya que también uso DAWs con muchos atajos y otras utilidades de MacOs. Pero también, porque lo encontré natural. El balance de frecuencia de teclas sobre el layout compensa ambas manos. Mientras que en Dvorak, tu mano izquierda trabajará ligeramente más que tu derecha.\nPrimero que nada, ¿vale la pena el Teclado Ergodox? Sí. Aunque, vi algunas otras opciones interesantes como UHK60, que no es Orto-lineal, pero es un teclado dividido. Pero, el Teclado Orto-lineal era algo que quería probar. Fue un movimiento audaz, porque en ese momento el precio era fácilmente superior a 350USD (sin teclas de tapa, y los switches cherry inflaron el precio).\nEl diseño es severo, la calidad general es buena, aunque desde afuera no parece un teclado sofisticado. Probablemente a primera vista no se vea como una pieza de hardware cara. Brilla en otros aspectos, no solo porque es un teclado programable, sino porque puede ser completamente personalizado.\nMi veredicto, vale la pena. Lo estoy usando diariamente, sin arrepentimientos hasta ahora. Probablemente compraré otro con las mismas características (Split-Ortholinear), para tener un respaldo.\n¿Qué ha sido usar un layout completamente nuevo? Aunque no tengo estadísticas decentes sobre cuál era mi velocidad de escritura anterior antes del cambio, la percepción general es que la velocidad no fue mucha mejora. Quiero decir, estoy seguro de que hay, pero no cuantificada.\nSin embargo, hay una comodidad perceptible al escribir, menos estrés en los dedos al final del día. Por otro lado, cambiar de vez en cuando a QWERTY no es sin algo de tiempo de puesta al día, pero no olvidado completamente. El problema de esto reside en el hecho de que empecé a practicar la técnica de 10 dedos (Ya estaba haciendo touch typing) después de cambiar configuraciones (de un teclado ISO/QWERTY a un teclado Split-Ortholinear con un layout Colemak). Eso significó que era capaz de escribir a ciegas, pero no estaba usando mis dedos apropiadamente.\nAdemás de esos inconvenientes, en general es que todavía estoy cómodo con el layout Colemak. Respecto a la disposición de teclas split-ortholinear, mi opinión es que vale la pena.\nLa cosa es, no usaría QWERTY en mi teclado dividido, ya que lo encontré bastante difícil acostumbrarse a él. Parece que si eliges un teclado orto-lineal, podrías querer cambiar layouts. Hay teclados divididos estándar en el mercado, si no quieres pasar por el entrenamiento doloroso que requiere un orto-lineal.\nMás allá de esas mejoras físicas, los teclados avanzados hoy en día soportan programación, permitiéndote desarrollar atajos personalizados. Eso es lo que creo que hace brillar a estos teclados: macros, colocación de teclas especiales, capas, etc.\n¿Por qué los dedos se vuelven locos entre disposiciones de teclado? Algunas observaciones que aún no pude descifrar:\nEntre ISO y el Ergodox, el dedo que presiona espacio cambia. En mi ISO, el pulgar derecho hace eso. En el Ergodox, el dedo izquierdo lo hace. ¿Por qué? No estoy seguro. Intenté forzarme a cambiar al pulgar derecho en el Ergodox, pero me encontré presionando con el pulgar izquierdo la mayoría de las veces. Las teclas en el Ergodox tienen más espacio entre ellas, así que las teclas del borde permanecen un poco más inalcanzables que en el ISO. Aunque no es un gran problema, es perceptible. Probablemente uno de mis errores fue elegir switches cherry para el Ergodox. Son silenciosos y rápidos, pero lo suficientemente sensibles para ser más inexactos. Aunque, tus compañeros de oficina podrían apreciarlo. La búsqueda del layout más compatible Colemak aprovecha la compatibilidad QWERTY con algunos cambios que hacen que las teclas de acceso rápido estén en la fila media, sin embargo, no todos los teclados programables son iguales, y los layouts pueden ser ligeramente diferentes entre ellos. Es decir, al final, puedes terminar usando subvariaciones de un layout dependiendo de tu colección de teclados ruidosos y con clic.\nDéjame decirte que no es un gran problema, ya que mantendrás algo de memoria muscular al cambiar entre teclados, aunque será bastante confuso al principio al diseñar tu propio layout.\nEntonces, al final, recomendaré personalizar tanto como puedas, hasta que empieces a sentirte molesto por errores.\n¿Es esto una religión o es solo para nerds? Definitivamente para nerds, y marketing.\n¿Por qué no Dvorak? Tomé un tiempo decente investigando antes de saltar a Colemak. Mantener algo de compatibilidad con QWERTY vale la pena, realmente. Dvorak es más preciso en mi caso, y no fue tan difícil cambiar al principio, pero se sintió raro. No me malinterpretes, estoy bastante seguro de que no tendría arrepentimientos si la elección fuera diferente, pero la única cosa que me importaba era tener un layout más adecuado para teclados orto-lineales, no velocidad por sí misma.\nTen en cuenta que Dvorak es la única alternativa estándar a QWERTY, eso es algo a considerar \u0026ndash; por supuesto, Colemak ha estado empujando mucho para que esté disponible en la mayoría de las distribuciones de OS.\n"},{"title":"A custom Python tool for building ndjson-content packages","href":"/es/toolbox/2023/artifact_builder/","content":"Description The artifact_builder is a tool that converts SQL files into a single-packaged ndjson file. It works as a submodule in the targeted repositories \u0026ndash; eg. pgqueries \u0026ndash; , and generates a package of the SQL files to be indexed and stored in a database.\nThis project has a very specific domain, and it is not for general-purpose use. The ndjson format was intended for provinding sort of an universal supported type for importing the results into any kind of data store or search-endpoint.\nThis is a very generic example on how it can be parsed and stored:\npackage storage // Initial work on the indexer import ( \u0026#34;bytes\u0026#34; \u0026#34;encoding/json\u0026#34; \u0026#34;fmt\u0026#34; \u0026#34;internal/types\u0026#34; \u0026#34;internal/utils\u0026#34; _ \u0026#34;github.com/mattn/go-sqlite3\u0026#34; ) type Repos struct { Engine string URL string } // Index downloads JSON files, processes the data and inserts it into // a SQLite database. It returns an error if any step fails. func (s *Storage) index(cfg *types.ConfigFile) error { urls := make([]types.Repos, 0, len(cfg.Repos)) for k, v := range cfg.Repos { urls = append(urls, types.Repos{Engine: k, URL: v.URL, Version: v.Version}) } // Fetch and process JSON data for each URL for _, url := range urls { // Fetch JSON data jsonData, err := utils.FetchJSON(url.URL) if err != nil { return fmt.Errorf(\u0026#34;error fetching JSON from %s: %v\u0026#34;, url, err) } // Remove BOM (Byte Order Mark) from the beginning of the JSON file, if it exists jsonData = bytes.TrimPrefix(jsonData, []byte(\u0026#34;\\xef\\xbb\\xbf\u0026#34;)) m := make(map[string]jsonArtifactRow) if err := json.Unmarshal(jsonData, \u0026amp;m); err != nil { return fmt.Errorf(\u0026#34;error parsing JSON: %v\u0026#34;, err) } // Insert queries into SQLite database for queryTitle, row := range m { row := \u0026amp;QueriesRow{ Id: utils.GetMD5Hash(url.Engine + row.Title + row.FPath + row.Category), // Hash the query Engine: url.Engine, Name: queryTitle, Title: row.Title, Doc: row.Doc, DocPath: row.DocPath, FPath: row.FPath, Category: row.Category, Query: row.Query, } s.insertRowOnConflict(row) // Here I do a ON CONFLICT QUERY, not the best approach, but practical // this use-case. } } return nil } "},{"title":"Postgres iff function support","href":"/es/toolbox/2023/iif/","content":"Description Source Code: iif Postgres Extension\nThe iif function is a SQL Server function that returns one of two values based in a conditional boolean expression.\nThe core of the extension was developed time ago, and was converted into a extension as a training material.\n"},{"title":"Dirty REST for Postgres Role Information","href":"/es/toolbox/2023/postgres_role_rest/","content":"Description Source Code: bgw_role_rest\nThis experimental extension was developed as a training material for building Postgres extensions that spawn background workers.\nThe reason of the REST response, is for integrating with Load Balancers that support REST endpoints for getting the node status. Within this, it was possible to point the LB to the correct node once promoted, by pointing to the exposed endpoint by the Postgres node.\nThis mimics the Patroni API endpoint for the leader status in a rustic manner. There was no intention to make this extensions for a production stage, the solely purpose was to have an example beyond a \u0026quot;Hello, world!\u0026quot;.\n"},{"title":"Postgres' Pool exhaustion laboratory","href":"/es/toolbox/2023/poolexhaustation/","content":"Description ℹ️ \u0026nbsp; To see more details about the code used and the results, see the code here. This laboratory is a hypothetical scenario, whether application does empty transactions in order to emulate sort of a DoS attack against the pools. The intention here, was to study the behavior of 2 very popuplar Postgres Pools in these cases.\nThe main difference between both pools relies on how they use CPU resources. PgBouncer is a single-threaded services, whether Odyssey uses multiple and configurable workers. It is expected certain overhead on multi-workers in these cases, and that was the moto of this laboratory.\nThe intention here is not to choose one or another, but to study in which cases one could use single-threaded or multi-worker pools.\nOne of the tests, focused on \u0026ldquo;single per-worker\u0026rdquo; benchmark performance. Of course, in a production environment, it would be a terrible design from HA perspective to have a single worker. The idea was to isolate the worker performance, as in Kubernetes, services can be allocated easily with a CPU Quota across a multl-node cluster orchestration1. So, creating a pool layer with load balancing is trivial.\nIn bare-metal setups, that don\u0026rsquo;t use such orchestration, you need to rely on a scalable pool service layer both horizontally (more computes) and vertically (more Cores). In these cases, is more efficient to rely on services that are stable and can scale vertically automatically.\n⚠️ \u0026nbsp; The data on this laboratory is outdated, as new versions have been released from both tools, so conclusions may be inaccurate for newer versions. Although, I do not expect much difference as the architecture of those components haven\u0026rsquo;t changed radically. The solely purpose of this page, is to document the architecture of the benchmark. Context This laboratory a continuation of the PGIbz 2019\u0026rsquo;s talk about Pooling Performance, and was inspired in the work we have done at Gitlab with the OnGres teams.\nWe want to repeat the laboratory and publish the results at OnGres in the future. This is just a portfolio of the benchmark architecture.\nArchitecture of the benchmark The architecture was spawn in 4 different machines: Client (to isolate noise in services), the Odyssey compute, the PGBouncer compute and the Postgres compute.\ngraph TB odysseyp[\"Odyssey\"] -. Pool Size=32 .-\u003e Postgres pgbouncer[\"PgBouncer\"] -. Pool Size=32 .-\u003e Postgres pgbench[\"PgBench\"] -. unlimited clients .-\u003e odysseyp pgbench[\"PgBench\"] -. max_client_conn=100k .-\u003e pgbouncer subgraph CLI[\"Client Compute\"] pgbench[\"PgBench\"] pgbench_N[\"PgBench...N\"] end subgraph PGB[\"Pgbouncer Compute\"] pgbouncer[\"PgBouncer\"] end subgraph ODY[\"Odyssey Compute\"] odysseyp[\"Odyssey Parent\"] -.- odysseysys odysseyp[\"Odyssey Parent\"] -. workers=N .- odysseyworkerN odysseysys[\"Odyssey System\"] odysseyworkerN[\"Odyssey Workers\"] end subgraph PG[\"Postgres Compute\"] Postgres Postgres --- disk(\"pd-ssd\") end Technology Stack The technology stack is compoused as:\nAWS EC2 Instances. Terraform as the Deployment tool. Ansible as the Provisioning and Configuration tool. Cloud Init. ☠︎ Bash. Don\u0026rsquo;t blame, this was a PoC, nothing fancy. Very Early Conclusions ℹ️ \u0026nbsp; See Docs with the collected stats. PgBouncer is more perfomant at single-thread, no surprise. However, the stability of Odyssey may be relevant to consider. PgBouncer stalls during a certain period, mostly because its mechanism to put connections in wait state meanwhile the core capacity is fully allocated. Odyssey can be less performant, but it didn\u0026rsquo;t stall badly. That makes it more stable for vertical scalability on the nodes, but less relevant on orchestrated environments, where you expect CPU efficiency.\nIt may probably be more recommedable to go with Odyssey (or other multi-threaded solution) when spawning pools on dedicted hardware or resources, as it is straightforward to do so. PgBouncer supports multi-process in the same machine through so_reuseport, but it ties each process to a core, which makes the configuration and core assignment a little bit more hackish.\nHowever, in automated architectures such as Kubernetes (whether you an assign services programmatically), PgBouncer might be more convenient, as it is more performant whenever it keeps connections out of waiting state.\nMissing points How to deterministically calculate the maximum client-connection capacity of PgBouncer? That is, considering the rate frequency of the core, register capacity, and hardware multi-threading ( which should impact negatively on single-process services). The lab lacks of a monitoring, which could shred more information about the behavior observed. That will be in another post with updated versions of all the components (and probably ditch Terraform). PgCat seems to be a very appealing solution for pooling to be compared with PgBouncer. Orchestration\u0026rsquo;s CPU cycles are like Taxes. As the Louis XIV\u0026rsquo;s Finance Minister, Jean-Baptiste Colbert, declared: \u0026ldquo;the art of taxation consists in so plucking the goose as to obtain the largest possible amount of feathers with the smallest possible amount of hissing.\u0026rdquo;.\n\u0026#160;\u0026#x21a9;\u0026#xfe0e; "},{"title":"[Código Inútil] Intercambiando valores de variables sin bibliotecas adicionales","href":"/es/posts/2023-02/swap/","content":"Background Hubo un concurso en un hilo de internet sobre cómo codificar un intercambio de variables tan bajo nivel y minimalista como sea posible. Entonces, terminé escribiendo una función de bajo nivel en C llamando instrucciones para una plataforma amd64 moderna.\n#include \u0026lt;stdio.h\u0026gt; #include \u0026lt;stdlib.h\u0026gt; void swapshit(int *arg1, int *arg2) { __asm__ __volatile__ (\u0026#34;movl %2, %%eax;\u0026#34; \u0026#34;movl %3, %%ebx;\u0026#34; \u0026#34;movl %%eax, %0;\u0026#34; \u0026#34;movl %%ebx, %1;\u0026#34; : \u0026#34;=g\u0026#34; (*arg2) , \u0026#34;=g\u0026#34; (*arg1) : \u0026#34;a\u0026#34; (*arg1), \u0026#34;b\u0026#34; (*arg2) ); } int main(int argc, char *argv[]) { int arg1 = atoi(argv[1]); int arg2 = atoi(argv[2]); swapshit(\u0026amp;arg1,\u0026amp;arg2); printf(\u0026#34;arg1 es %d , arg2 es %d\\n\u0026#34;,arg1,arg2); return 0; } La explicación de lo anterior es bastante simple. Usamos registros para asignar los valores y los llamamos en un orden diferente usando la instrucción movl. El main es solo un wrapper que pasa argumentos.\n"},{"title":"Emanuel Calvo","href":"/es/resume/resume/","content":" ℹ️ \u0026nbsp; Descargar currículum 👉🏻 📎 Posiciones Actuales Experiencia Pasada Portafolio Educación, Publicaciones, y Certificaciones Bio(graffiti) Soy un profesional del área de Ingeniería de Infraestructura de Bases de Datos y Software, con pasión por las tecnologías Open Source, y un fuerte enfoque en escalabilidad, confiabilidad y rendimiento.\nActualmente, trabajo en Workato como Staff Infrastructure Engineer en el equipo Data Stores, donde soy responsable de diseñar e implementar Infraestructuras de Bases de Datos e Ingeniería de Sistemas.\nMis tareas involucran Desarrollo, Despliegue, Provisionamiento, CD/CI y Operaciones de 2do Día para escalabilidad y rendimiento. También tengo un fuerte enfoque en Ingeniería de Software, principalmente para sistemas de bases de datos.\nOpen Source es una parte clave de mi trabajo, y soy un contribuidor activo y pasado de varios proyectos, como Babelfish for Postgres, Earthly, entre otros. Consulta toolbox para algunas de mis contribuciones.\nTambién soy ponente en conferencias y meetups, donde comparto mi conocimiento y experiencias en el campo de la Ingeniería de Bases de Datos y Sistemas. Puedes encontrar algunas de mis charlas en mi repositorio de slides.\nNacido en 🇦🇷, viviendo en 🇪🇸.\nTecnologías Experiencia considerable en escalabilidad, confiabilidad, arquitectura y mantenimiento de Sistemas de Bases de Datos automatizados a gran escala, incluyendo pero no limitado a Postgres, MySQL, ClickHouse, y Elastic. La mayoría de los sistemas son/fueron críticos para el negocio. Experiencia en los siguientes lenguajes: Go, Python, C, Bash. Amplia experiencia en SQL moderno e indexación personalizada en Postgres. Conocimiento en Framework de desarrollo de Extensiones de Postgres e internos. CI/CD para desplegar infraestructura de producción basada en Cloud y bare-metal usando Terraform/Packer, Ansible, Earthly, GH Actions, Gitlab CI, Kubernetes, etc. Experiencia en servicios containerizados como en Kubernetes, Docker. También amplia experiencia en tecnologías de Virtualización. Conocimiento de bajo nivel de tecnologías de contenedores y construcción programática de imágenes con el lenguaje Earthly, entre otras herramientas. Experiencia consistente en casi todos los principales proveedores Cloud. Azure certificado, GCloud, y AWS. GNU/Linux Habilidades Blandas Comunicador hábil, gestionando cuentas y proyectos de clientes a nivel mundial. Resolución de problemas complejos, y con experiencia avanzada en el manejo de incidentes complejos de Producción. Desarrollo de herramientas de ingeniería para asegurar operación sin interrupciones y máxima eficiencia. Current Positions Workato Inc. - Staff Infrastructure Engineer | Data Stores Team 100% Remoto desde Madrid, España\nSeptiembre 2024 - Presente\nIngeniería de Infraestructura. Tecnologías: AWS, Kubernetes, Terraform, Ansible, PostgreSQL, ArgoCD, Aurora, entre muchas otras. Lenguajes: Go, Python, Bash. Ver perfil GH. Consultor Independiente Remoto. Madrid, España\nServicios incluyen, pero no limitados a:\nIngeniería de Infraestructura. Ingeniería de Bases de Datos. Desarrollo de Sistemas. Clientes Leonardo247 (US) Wipshub (Mexico) Bitscrunch (Germany) Past Experience Ongres Inc. - Professional Services Tech Lead | Database Platform Engineer Remoto. Nueva York/Madrid\nDesde Diciembre 2017 como OnGres, previamente trabajando como Consultor en 8kdata - Septiembre 2024.\nComo Technical Lead de Professional Services e Ingeniería de Plataforma de Bases de Datos y Sistemas en OnGres, actualmente diseño arquitecturas de sistemas innovadoras para implementar en los clústeres de producción de nuestros clientes. Además, desarrollo herramientas de sistema para asegurar operación sin interrupciones y máxima eficiencia.\nProyectos destacados:\nBBM (Blackberry - Emtek - Google Cloud): Migración en vivo de Oracle a clúster automatizado completo de Patroni, desde un entorno basado en premisas a Google Cloud. PgBouncer-Consul-Postgres Conrad (Google Cloud Partner): Implementación completa de Patroni HA usando etcd como clúster de consenso, Balanceo de Carga a través de Google iLB con configuración API con Patroni, y PgBouncer. Migración de Gitlab como parte del equipo Database Reliability y diseño de Arquitectura de Bases de Datos. Postmates: Implementación Terraform multi-proyecto de Patroni con un DCS centralizado. Benchmark automatizado Postgres vs MongoDB de 10k U$S, IaaC (ecosistema Hashicorp, basado en proveedor AWS). Google Cloud como Partner. Soporte ARM e implementación de automatización. Trabajé en el equipo de Babelfish for Postgres de AWS, como Developer Advocate para la versión Open Source. Proporcioné parches, construcción, pruebas de Babelfish for Postgres. Impartí entrenamiento al equipo sobre el Ecosistema de Hooks de Postgres. También proporcioné entrenamientos y consultoría en una variedad de clientes como Gitlab, American Express, Nutanix, BBM (Blackberry), Conrad Electronics, ARM, Microsoft, y Postmates. Diseño de Arquitectura de stcpay y Líder Técnico del Proyecto de Bases de Datos. Tecnologías:\nInfra y CD/CI: HCL (Terraform, Packer), Ansible, Kubernetes, Docker. Bases de Datos: Postgres (y variantes), MySQL, Oracle (para migrar fuera guiño, guiño) Lenguajes: Golang, Python, C, bash. De vez en cuando programo en NodeJS. Principales Proveedores Cloud: Google Cloud, AWS, etc. ViaDB / Plataform3 SL - Fundador Buenos Aires, Argentina / Madrid, España\nDiciembre, 2021 - Febrero 2025\nFui el fundador de ViaDB, especializado en consultoría y desarrollo de herramientas para Bases de Datos Open Source, usando una variedad de lenguajes de programación incluyendo Go, Python, y Bash. La empresa cerró sus operaciones en 2023 debido a reubicación.\nViaDB era una empresa independiente de Soporte de Bases de Datos Open Source y desarrollo de herramientas de Sistema. Cerrada debido a reubicación. Ver más en ViaDB. Clientes ViaDB/Plataform FreebieFlow (US) GrupoTX (Panama) Maper (Argentina) Openpass (Argentina) Mural (Argentina) Frávega (Argentina) OnGres (Spain) iMedicare (USA) Eloquentix (US) 8kdata (Madrid) Avalon Informática (Madrid) Init (País Vasco) Scienza (Argentina) Syscope (Argentina) Mexane (Argentina) TreeLogic (Asturias) BTS (Usa - Zaragoza) LCRCOM.net (Madrid) Autofarma (Argentina) VsTour (Argentina) CycleLogic (Argentina) Ericsson/EDB (USA) Pic-pic (USA) PFA (Argentina) Jampp (Arg/USA) Contenta Mobile (Argentina) Slots Machines (San Luis, Argentina) Pampa Business Solutions (Argentina) Sindicato de Pasteleros y afines (Argentina) Percona - Senior Technical Services Engineer Buenos Aires, Argentina\nAgosto 2016 - Septiembre 2017\nDescripción completa del rol aquí. En Percona, trabajé como DBA Remoto en el equipo Atlas. La tecnología principal era MySQL y MariaDB, para infraestructuras a gran escala. Comprensión profunda del protocolo de replicación, disponibilidad y sharding. Algunos clientes destacados: Hyperwallet, Fitbit, Lookout, Shipwire 8kData - Asesor Externo de Datos Madrid, España\nEnero 2016 - Septiembre 2017\nAsesor técnico externo y consultor para Postgres y otras tecnologías relacionadas. Pythian - Technical Team Lead | Miembro ACTG (Advanced Consulting Team Group) Ottawa, Canadá\nEnero 2015 - Junio 2016\nSoporte al Perfil Externo de Pythian. Miembro del ACTG como consultor principal de Postgres. Otras Tecnologías: MySQL/MariaDB, xtraDB, Galera, Docker, Ansible, MongoDB, PostgreSQL, Vertica, Oracle. Consultor Líder de Bases de Datos en T73. Algunos clientes destacados: Microsoft, Beats (Apple), Adobe Echosign, Fitbit, Sendgrid, Mozilla Foundation, Zendesk, SendGrid.\n2ndQuadrant - Consultor PostgreSQL Buenos Aires, Argentina\nFebrero 2014 - Enero 2015\nConsultor PostgreSQL, soporte y entrenamiento. Pruebas de nuevas características, depuración y revisión de código. BlackBird IT (ex-PalominoDB) - Database Operations - Professional Services Las Vegas, NV\nSeptiembre 2011 - Febrero 2014\nDBA/Teach Lead Principal de las bases de datos PostgresXC. Administración MySQL-y-forks/PostgreSQL-y-Forks sobre varios clientes. Cantidad considerable de experiencia ejecutando Revisiones DDL/Query/Capacidad/Servidor. Escritor técnico (el sitio está bajo \u0026ldquo;pythian.com\u0026rdquo;): http://www.palominodb.com/category/postgres Backups, Replicación, HA, Monitoreo, Tendencias, Revisiones de Salud, Revisiones DDL, toolkits de percona y varias herramientas de Postgres. Algunas otras bases de datos con las que actualmente trabajo: MongoDB, Cassandra, Redis. Experiencia AWS. EC2, RDS, S3, LB, EBS, Redshift, DynamoDB, AWS API. Experiencia Open Stack Cloud. Amplia experiencia en revisiones de servidor, trazado de carga de trabajo y reescritura y ajuste de SQL. Scripting/desarrollo: Python, Perl , PL/pgsql y bash. En orden inverso. Algunos de los clientes destacados: Adobe Echosign, Fitbit, Zendesk, Chegg, SendGrid\nAedgency - Administrador de Bases de Datos MySQL/Oracle Barcelona, España\nNoviembre 2010 - Marzo 2012\nAdministración de bases de datos MySQL/Oracle. Ajuste de consultas y rendimiento. Integración ETL entre bases de datos. Desarrollo PL/SQL. Scripting Bash. Configuración básica de Sphinx, corrección y monitoreo. SIU - DBA y Consultoría de Producción de Bases de Datos Buenos Aires, Argentina\nJulio 2009 - Julio 2010\nProporcioné entrenamiento y soporte a universidades nacionales (como UNLP, UNC, UNNE, UNNOBA, UNCU, UNPA, UNR, etc), soporte a desarrolladores en modelado de BD y administración de bases de datos Postgresql-Informix-MySQL. Tareas de Arquitecto de Datos y revisiones de servidor. Planificación de migraciones y estrategias futuras para abordar nuevas versiones. Coordiné equipos para soporte de bases de datos para mejorar el cumplimiento entre aplicaciones de SIU. Desarrollo del plan de seguimiento de producción y pre-prod para todas las aplicaciones de SIU. Métricas de servidor y otras cosas para servidores de alto rendimiento. Globant - Administrador de Sistemas Unix/Linux - EA Buenos Aires, Argentina\nFebrero 2009 - Julio 2009\nSysAdmin Unix en EA (Electronic Arts) Hydra - proyecto GOP. Mantuve plataforma consistente en base de datos Oracle 10, Apaches y Tomcats (VAP, VCM) Clustered, con varias etapas de despliegue. Las tareas principales estaban relacionadas con despliegues, configuración de entorno, mantenimiento y monitoreo de Infraestructura Cliente VMWare. Realicé tareas DBA MySQL en Servidores Prod, Pre-Producción y Desarrollo para un proyecto de Foros. Corrigiendo algunos problemas de conjunto de caracteres y DDL, backups, restauraciones, verificación, rendimiento y otros. Conocimiento básico en despliegue / proceso con Maven - Ant - Hudson. Asignado 50% a otro proyecto para proporcionar Soporte de Ingeniería en EA sobre scripting Python. Correo Argentino Los siguientes roles fueron en Correo Argentino, bajo diferentes unidades administrativas.\nAdministrador Senior de Sistemas Monte Grande, Argentina\nAgosto 2008 - Febrero 2009\nGestioné plataforma de producción en Correo Argentino (más de 400 terminales con bases de datos Postgres locales y entorno Linux). Desarrollé una herramienta de despliegue personalizada, acelerando la actualización de estaciones de trabajo de días a menos de 2 horas. Algunas máquinas estaban enlazadas con un Active Directory a través de WinBind. Creé una herramienta de despliegue mejorada para scripts, recuperación de bases de datos, control y monitoreo, etc. También gestioné servidores sobre Centos 5 para la aplicación de base de conocimientos del área de soporte (7 DB MySQL 5.1) Siemens - Desarrollador PHP Monte Grande, Argentina\nEnero - Agosto 2008\nDesarrollador de Plataforma Admitel. Tecnologías: PHP + (Javascript, AJAX, x/HTML, XML) - Oracle 8i. Scripting Perl Huenei - Soporte de Aplicaciones Monte Grande , Argentina\nOctubre 2007 - Enero 2008\nAdministración de terminales en servidores remotos W/NT y 2K. Configuración, control, monitoreo. Atender tickets Remedy para corregir problemas y otros. La aplicación estaba en bases de datos Paradox y Oracle, en las que mis tareas consistían en reparar tablas, corregir algunos datos incorrectos y otros. Reporte y detección de bugs. Depuración y soporte MOSAIC. Cliente Correo Argentino / Siemens. Education ESBA - Analista Desarrollador Escuela Penitenciaria de la Nación (Juan José O\u0026rsquo;Connor) - 2000 - Título en Ciencias Criminales External publications 2 de julio, 2013: AWS - Postgres en AWS PostgreSQL ejecutándose sobre instancias EC2 con almacenamiento EBS/PIOPS. Arquitectura mínima, consejos y recomendaciones. Descargar AWS en Postgres desde local Certifications Certificación PostgreSQL 9.4 Associate, EDB33986. Certificación PostgreSQL 11 Professional Azure Data Engineer y Microsoft Certified Trainer. Certificado Profesional de Automatización Workato I \u0026amp; II. Otras certificaciones Otras certificaciones relacionadas con Especialización en DataScience:\nToolbox R programming Getting and Cleaning Data Exploratory Analysis Reproducible Research Statistical inference Regression Models Big Data, University of San Diego Trabajos Pre-IT Antes de hacer la transición a una carrera en TI, entre 1998 y 2006, serví en el Servicio Penitenciario Federal (SPF) como oficial, gestionando grupos operativos en instituciones y complejos que van desde seguridad media a alta.\nPosiciones en SPF Complejo de Seguridad Máxima (CPF 1) Complejo Penitenciario Federal 1 (CPF1)\n2005-2006\nMódulo 6 - Sección de Sanciones\nTrabajé como Jefe de Turno Interno. Realicé procedimientos de sanción e investigaciones de delitos (Sumarios Preventivos). Unidad 31 (Instalación de Mujeres) Complejo Penitenciario de Mujeres\n2004 - 2005\nTrabajé como Jefe de Turno Externo, capacidad de armas y gestión de almacenamiento. Realicé procedimientos de sanción e investigaciones de delitos dentro de las instalaciones. Cooperé en el diseño de algunos de los procedimientos de seguridad y emergencia. Miembro del Consejo de Seguridad. Complejo de Jóvenes Adultos (Unidad 24,26 e Instalación de Rehabilitación de Drogadicción) Complejo Penitenciario de Jóvenes Adultos de Marcos Paz (CPFA)\n2003 - 2004\nJefe de Turno Interno. Escuela de Suboficiales Escuela de Suboficiales Cnel. Rómulo Páez\n2002 - 2003\nInstructor Complejo de Jóvenes Adultos (Unidad 24,26 e Instalación de Rehabilitación de Drogadicción) Complejo Penitenciario de Jóvenes Adultos de Marcos Paz (CPFA)\nEnero 2000 - 2002\nJefe de Turno Externo, capacidad de armas y Gerente de Almacenamiento. Cooperé en el diseño de algunos de los procedimientos de seguridad y emergencia. "},{"title":"Emanuel Calvo 🧙‍♂️","href":"/es/about/about/","content":" Créditos de la foto de perfil 🙌🏻 a Alex Efimov.\n¡Salve, noble viajero! Me conocen como 3manuek, aunque en los reinos de la gente de TI, me llaman Tresma. Provengo de las lejanas tierras de Argentina 🇦🇷 y ahora habito dentro de las fronteras de España 🇪🇸.\nHe caminado en los senderos de la carrera de TI desde el año de nuestro Señor MMVI (Currículum), y antes de eso, trabajé en el campo de la seguridad.\nEn este sitio, encontrarás fragmentos de mi trabajo, escritos y otros asuntos diversos.\nℹ️ \u0026nbsp; Comprometido con mis principios y valores que impulsan mi trabajo, también colaboré con organizaciones sin fines de lucro argentinas como Fundación Apolo, Fundación Oportunidades entre otras para desarrollar y apoyar sus ecosistemas de TI. ⚠️ \u0026nbsp; Practico Radical Candor, y también soy un gran fan de Colemak. "},{"title":"Experiencia cambiando layout y disposición de teclas","href":"/es/posts/2022-12/colemakjourney/","content":"Este post no tiene intención de discutir qué layout podría ser mejor en términos de eficiencia o esfuerzo. Hay una cantidad considerable de grandes artículos relacionados con todo el análisis a través de diferentes layouts, e incluso herramientas para ingerir texto y estimar el esfuerzo de escribir el texto para varios layouts.\nEncontrarás este artículo más útil si ya decidiste cambiar a Colemak, y hacerlo tu layout principal. Personalmente, he encontrado muy cómodo escribir en Dvorak, aunque hay algunas ventajas en Colemak respecto a compatibilidad de atajos entre otros beneficios que me hicieron hacer la elección. Puedes leer una comparación muy interesante en este post de Xahlee.\nTeclados orto-lineales: el detonante Aunque tenía en mente entrenar con otros layouts desde hace algún tiempo, el verdadero detonante para la decisión fue la adquisición del Ergodox no-labeled-keys Split Keyboard.\nCuando empecé a practicar, noté que estaba haciendo un esfuerzo extra considerable para alcanzar algunas teclas usando el layout QWERTY. La disposición y alineación de las teclas hacen el área de teclas más grande. Eso me hizo empezar a investigar sobre el Esfuerzo de Escritura, y encontré que no hay mucha diferencia entre Dvorak y Colemak. Además, hay layouts generados por IA, que son muy interesantes, pero no tienen un amplio soporte (ver Carplax Optimizer).\nTambién, era obvio que no estaba usando mis dedos correctamente. Estaba haciendo Touch Typing en mi Daskeyboard sin etiquetas (disposición de teclas ISO) incorrectamente, no usando apropiadamente todos mis dedos.\nEntonces, no era solo una excusa, me encontré en la decisión de cambiar a Colemak en ambos teclados. Entonces, empecé a entrenar el nuevo layout en estos aspectos:\nTouch typing apropiado de 10 dedos + en Colemak. 10 dedos es la técnica donde colocas tus dedos y asignas teclas para cada uno. Touch Typing es la técnica de escribir sin mirar el teclado. Reconfigurar mi memoria muscular del layout qwerty, para corregir los malos hábitos. Touch Typing y el método de 10 dedos Generalmente, ambas técnicas se mencionan como una, pero no son lo mismo. Aunque, usar solo una no tendría sentido. El nuevo teclado orto-lineal requería ensayar y pulir algunas habilidades.\nComo el teclado tiene teclas sin etiquetas, Touch Typing es necesario. Touch Typing es la técnica de no hacer contacto visual con el teclado \u0026ndash; es más complejo que eso, pero necesitarás investigar y practicar esto de todos modos.\nLa disposición orto-lineal es más óptima para el acceso del dedo meñique, y corrige la posición de la mano considerablemente. Entonces entrenar el dedo meñique sería crucial para acelerar la escritura.\nEn mi caso, no fue algo enorme. Ya estaba haciendo Touch Typing. Pero en tu caso, si nunca has oído sobre esto, recomiendo:\nPrimero, practica Touch Typing: es decir, no necesitas mirar el teclado al escribir. Entrenas tu memoria muscular para saber dónde están las teclas. Practica usando el método de 10 dedos. Este es el gran ganador. Pensé que estaba haciendo eso, hasta que me vi escribiendo. Cuando empiezas a practicar un nuevo layout esto es directo, así que recomiendo que mantengas esto en tu prioridad. Los mejores sitios para comenzar con Touch Typing apropiado son:\ntypingstudy.com keybr.com Precisión y Velocidad: 2 métodos diferentes para practicar Para entrenamiento de precisión, la mejor forma es entrenar consistencia. Es decir, mantener tanto como sea posible la velocidad de escritura a través de un período de tiempo. Textos más largos o por encima del minuto de duración son los mejores para hacer esto.\nLa mejor forma de entrenar velocidad es hacer competencias online. Aquí hay una lista de mis favoritas para practicar velocidad:\nMi favorita: keybr. 10 fast fingers tiene un servidor bastante poblado. Monkeytype no es una competencia en vivo, pero lo uso para entrenar velocidad. Para practicar velocidad, necesitarás usar herramientas que te permitan reescribir la palabra si hay un error. Es decir, evitar el uso de la tecla backspace.\nEn mi caso, prefiero trabajar en precisión. Todo el punto de cambiar layouts era reducir el esfuerzo de escritura. Mientras más rápida la velocidad, menor la precisión la mayoría de las veces. Aunque, entreno velocidad frecuentemente, y me gustan las competencias online ya que suelen estar personalizadas para ignorar o reducir el uso del backspace.\nN-grams Una cosa que parece fácil pero podría ser más difícil de lo que esperas, es entrenar con conjuntos de 2 y 3 gramos. Cualquier herramienta que te permita establecer tu texto puede servir al propósito.\n3-gram 2-gram Combinaciones de 3-gram más frecuentes en el idioma inglés:\nthe ing and ion tio ent for ati ter ate ers res her est com pro ere all int men you ons our con are tha ver ess thi rea sta tin hat ist ect ort ear ine age his ted ont nce sto ith nte sin tor ore lin ive ite wit not nth tra ome ica per art ste cti oft oth ice out ill ide eth ies one ser eco era str eve din rat ona ces edi iti eri ran san ity oun nal use ure rin ame act igh ese ave sti nts hes sit der fth les man ant ind new pri ree ble ast nta tur por ght ain anc cha eas par ove nde rom rec ert lan cal sof can orm tes ost cat son tic end hea ina red wor ber lic han mat tat hin nes pre sho ren ein eme ric ust fro rth inc eat ase nti ard rch ndi lea tan ssi nin min ail omp ins cou ell erv tal enc ass tth lle ele mor ean sth tem sea rma als und pla ple eal rie ema ali nda ack hen ial ord ana arc ori che tho eof lis dis een car ngt ire ead eto ene att nto omm pos abl lat nds dat lit gra mes hec kin esa vie ote dth ern sio ono esi inf mer nfo hav mar chi ten uni ime nat des hou rit tim den sco ans hel Combinaciones de 2-gram más frecuentes:\nin th er re he an on es or te at ti st en nt ar to nd al it se ed is ea ng ou le co me ne ri ro de ra io ic li of as et ve ta si ha ma ec om ce el ll ca ur la ch hi di ho fo ns ot ac na rs so pr rt sa ss us no il ts em ct ge lo ee be tr ni pe ie pa nc po ol da ad vi un am ut wi mo sh yo ai ew ow os fi ep tt mi op ia we ag su id do oo Entonces, la única cosa que hacer aquí es copiar pegar estos en tu herramienta de escritura de preferencia.\nPara más información sobre n-grams, ver Natural Language Corpus Data: Beautiful Data y [N-grams].\nTodas las letras del alfabeto inglés Otro buen ejercicio es escribir variantes de la siguiente oración:\nThe quick brown fox; jumps, over the lazy \u0026ndash; dog.\nLa puntuación extraña es a propósito: puedes querer agregar símbolos en tus ejercicios frecuentemente. Pero en este punto, supongo que entiendes el punto. Esto es un poco más difícil de lo que piensas, pero no más difícil que escribir el alfabeto. Pero creo que este es un muy buen ejercicio, ya que no te pierdes ninguna letra.\nEn español puedes tener cosas como:\nEl veloz murciélago hindú comía feliz cardillo y kiwi.\nLa cigüeña tocaba el saxofón detrás del palenque de paja.\nEntrenando con código Escribir código es mucho más difícil al principio, así que sugeriré practicar hasta que obtengas una velocidad decente antes de convertirte en tu layout productivo. Símbolos y puntuación no son fáciles debido a que hacen que tu mano cambie la posición de tus manos. Preferirás entrenar velocidad de código más tarde.\nOpiniones Sesgadas Cambiar layouts no es cosa de semanas, sino de meses hasta que obtengas una velocidad decente para igualar tu velocidad actual. Al principio, se sentirá extremadamente agotador pensar y escribir de manera constante.\nA largo plazo, creo que vale la pena. Lo primero que notarás es que las manos hacen menos movimiento sobre el teclado, así que cuando haces escritura rápida la posición de las manos no sería la misma que en QWERTY. Sentirás el imperativo de colocar tus dedos en una disposición diferente.\nPara esos nerds de guitarra, esto puede recordarte la técnica de la mano izquierda de Joe Satriani, en la que mantienes las yemas de tus dedos cerca de las cuerdas, para que puedas hacer arpegios más suaves.\n"},{"title":"Complex datatype for Postgres","href":"/es/toolbox/2022/complex-ext/","content":"Complex datatype PL/pgsql extension in Postgres Source at Github\nThis extension was developed as an example of some of our trainings at OnGres, with the purpose of a showcase the creation of a complex datatype extension in plain PL/pgsql.\n"},{"title":"Open Labs","href":"/es/toolbox/2021/openlabs/","content":"\nDatabase Laboratories During the last years, I\u0026rsquo;ve been working on several open labs for Database trainings and webinars for OnGres and CanalDBA community. Some of these laboratories may look out-of-date, but they should commit their purpose for references and examples on how to setup them with few lines of code and with the most popular technologies.\nIf you are willing to start with Ansible, Terraform, Docker and others for Databases, you may found this resources interesting for you.\nBenchmark-styled projects Even tho I would rather use a different approach for benchmarks today (probably CDK or Pulumi), benchplatform have been developed to test Postgres and Mongo under sysbench data modeling. It uses Terraform and Packer for provisioning.\nVery old but relevant in terms of results (encryption measurements and performance) is pgCryptoBench, which compares encryption efficiency through PgCrypto extension.\nGeneric POCs Another environment created for a benchmark purpose is POC-Odyssey, which has been developed in Terraform. If you are interested in Postgres Pooling, this might be useful for understanding its setup and general practices.\nIf you are rather more interested in Modern HA concepts, HA_POC is a project that I developed for Nerdear.la 2018 and it implements HA under different engines and databases. Some components are not finished or buggy, but it can also be useful for those starting with Docker with Databases.\nAs part of CanalDBA, we also started this dockerlab, which does very basic stuff although it is the most up-to-date.\nAlso, here is a Yugabyte laboratory, for exploring proxying in a sharded environment, using Envoy Proxy.\nKubernetes and Databases Even tho the most recommended way to start with Kube is through Operators (I recommend to checkout Stackgres.io), pocoyoonk8s is a practical example on how to spin resources on it. It uses different techniques: with templates and straight task deploy.\n"},{"title":"Benchmark platform for Postgres/MongoDB","href":"/es/toolbox/2020/benchplatform/","content":"Description Source Code\nThis project was a programatic benchmark comparision between Postgres and MongoDB on a single instance. Filesystems used: XFS and ZFS. It also uses AWS SQS for notifying the status of each run.\nIt was coded using Terraform and AWS API. The analysis was conducted through Python Notebooks.\nRelevant and published links:\nWhitepaper Benchmark Post Results and Notebooks Personal findings Postgres is faster than MongoDB on a single node when the active dataset doesn\u0026rsquo;t fit in memory. For datasets that fit in memory, MongoDB performs better, which is no surprise. Unless, you implement a Pooling layer. The MongoDB setup was slightly easier than Postgres. Although, less tunable options are available in MongoDB. Scaling Postgres requires a Pool layer, otherwise it will hit a performance bottleneck, unless you keep a low number of active connections. This can be persued through client pool, although you may need to reconfigure your applications to reduce the amount of connections if more backends added. Postgres consumes less CPU in all cases than MongoDB. Some examples of the collected results The following shows the results of Mongo, Postgres, and PG+PGBouncer on XFS with 95% of reads and 5% writes: The following shows the results of Mongo, Postgres, and PG+PGBouncer on ZFS with 50/50 distribution: "},{"title":"High Availability Docker Compose Laboratories for Open Source Databases","href":"/es/toolbox/2020/hapocs/","content":" Source Code\nAs part of several lectured trainings and conference talks, I created this Docker Compose based laboratories to explore the HA implementations of the most popular Open Source Database engines, such as Postgres, MySQL, among others.\nThe picked technologies to do so, aren\u0026rsquo;t conventional solutions, as the objective was to show Modern HA, using consensus for configuration consistency.\nMore up-to-date version of this, can be found between at OnGres Labs, as part of the material for its trainings.\nTraining Sessions Patroni ETCD "},{"title":"HN tag search","href":"/es/toolbox/2019/hntagsearch/","content":"Hacker News tag search Source code HN tag search\nThis script was coded as a Open Source tool for CanalDBA community, for searching relevant tags related to databases in HN. It was coded with Matteo Fleitas, another member of the community.\nNothing special, just a BeatifulSoup library and Python example.\n"},{"title":"Deploying Postgres in Kubernetes with Ansible","href":"/es/toolbox/2019/ansiblekubernetes/","content":"Laboratory Even tho Kubernetes allows to deploy production-grade databases, it is possible to deploy a straightforward Postgres instance without operators or major architecture designs. Of course, this won\u0026rsquo;t be suitable for a production setup, but is good enough for understanding the concepts behind k8s API calls.\nSee the source: Postgres deployment on k8s using Ansible\nInstall minikube or similar Minikube\ncurl -Lo minikube \\ https://storage.googleapis.com/minikube/releases/v1.2.0/minikube-darwin-amd64\\ \u0026amp;\u0026amp; chmod +x minikube \u0026amp;\u0026amp; sudo cp minikube /usr/local/bin/ \u0026amp;\u0026amp; rm minikube Getting the token Access Kubernetes API\nAnsible k8s Ansible k8s module\nK8S_AUTH_API_KEY in environment.\nGetting facts with k8s_fact k8s_fact\n"},{"title":"Google Cloud TCP Internal Load Balancing con HTTP Health Checks en Terraform para servicios stateful","href":"/es/posts/2019-06/ilb-internal-gcp/","content":" ¡Feliz cumpleaños a mi querida esposa, Laura!\nConsideraciones generales de integración de iLB y Terraform de GCP Implementar un Internal Network Load Balancer en GCP a través de HCL (Terraform) requiere colocar un conjunto de recursos como piezas de lego, para hacerlo funcionar dentro de tu arquitectura.\nEstamos excluyendo la opción external en este post ya que no se usa frecuentemente para servicios stateful o arquitecturas backend como bases de datos, que es la preocupación aquí. También, su implementación de Terraform varía fuertemente entre sí, p.ej. ciertos recursos como el Target Pool no se usan en el modo de esquema internal, haciendo que la configuración de autoscaling se vincule de manera diferente con su contraparte.\nSe recomienda una lectura completa de la documentación de Google Cloud load balancing\nConfigurar un Load Balancer dependerá de qué recursos se hayan elegido para hacer girar los computes. Es decir, google_compute_region_instance_group_manager, google_compute_instance_group_manager o computes únicos. En este post particular me apegaré a google_compute_region_instance_group_manager por el bien de la abstracción.\nEl iLB como se muestra en el post actual, apunta a el nodo (a través del Backend Service) que devuelve OK a su Health Check correspondiente (la mecánica interna de esto se comenta en la sección Health Check a continuación ). Diferentemente de una flota stateless, para servicios stateful, solo un nodo puede mantener el leader lock para recibir transacciones de escritura. DCS proporciona una forma de tener una configuración consistente y k/v sobre un cluster de nodos, centralizándola y proporcionando un consenso para ellos, evitando escenarios de split-brain o configuración diferente en los nodos. Es decir, asegura que un único líder esté actuando en un cluster en todo momento. Puede sonar muy simplista en la tarea, pero en entornos Cloud esto resulta ser crucial no solo en materia de consistencia, sino también en el nivel de aprovisionamiento y configuración automática que hace una arquitectura resiliente y confiable.\nDCS proporciona una forma de desplegar configuración consistente a todos los componentes relacionados en la arquitectura, no solo los Health Checks. p.ej. Los agentes de Consul pueden observar y aplicar configuración a ciertos servicios que necesitan refrescar endpoints o propagar una nueva configuración a todos los nodos.\nOrganización de recursos de componentes iLB La vista de vuelo de la arquitectura expuesta del iLB se verá así en un diagrama:\nVista de vuelo de los recursos Terraform del iLB. Nota: Ten en cuenta que el protocolo iLB sigue siendo TCP, aunque su health check (HC) está basado en HTTP. El otro HC, TCP, se usa para propósitos de autoscaling, y vuelve a hacer girar compute si el servicio API está caído.\nInstance Managed Groups No hay configuración iLB siendo parametrizada en este recurso, pero vale la pena mencionar que el bloque Autohealing apuntará a su Health Check específico para verificar si un servicio está disponible o no. Usualmente, para servicios stateless, podemos usar el mismo Health Check para el autohealing (o, al menos es funcional hacerlo); aunque, servicios stateful como bases de datos, pueden devolver código de respuesta no disponible (503) desde la API pero eso no significa que el servicio esté caído, ya que podría tener más estados complejos dependiendo de la ruta/método de solicitud (el servicio está arriba, pero no puede recibir escrituras).\nEs importante definir un retraso inicial para verificar el servicio, especialmente en componentes stateful que podrían pasar cierto tiempo antes de que estén disponibles debido a transferencias de datos o aprovisionamiento. Durante el desarrollo, puedes querer eliminar este bloque, hasta que tus servicios estén disponibles, en caso contrario los computes serán destruidos en un bucle sin fin.\nauto_healing_policies { health_check = \u0026#34;${google_compute_health_check.tcp_hc.self_link}\u0026#34; initial_delay_sec = \u0026#34;${var.initial_delay_sec}\u0026#34; } Como estamos configurando un iLB, google_compute_region_instance_group_manager ha sido elegido ya que es compatible con su configuración. Este recurso gestiona computes a través de una región, a través de varias zonas de disponibilidad.\nHealth Checks Considera una API a través del puerto 8008, ya sea que devuelva código de respuesta 503/200 sobre métodos master/replica. La salida siguiente muestra las respuestas para ambos métodos sobre el mismo nodo master:\ncurl -sSL -D - http://127.0.0.1:8008/master HTTP/1.0 200 OK ... curl -sSL -D - http://127.0.0.1:8008/replica HTTP/1.0 503 Service Unavailable ... Estos métodos pueden usarse para la configuración del Backend Service para refrescar el nodo iLB que actúa como master o aquellos para réplicas (para iLB RO).\nEs importante aclarar que crear Health Check no afecta los otros recursos a menos que estén vinculados. Preferirás definir este recurso incluso si tus servicios no están funcionando, ya que puedes conectarlo una vez que estén disponibles (como se mostrará en la sección Backend Service).\nHay un recurso legacy llamado google_compute_http_health_check, que contiene la siguiente nota:\nNota: google_compute_http_health_check es un health check legacy. El más nuevo google_compute_health_check debería preferirse para todos los usos excepto Network Load Balancers que aún requieren la versión legacy.\nAunque, para iLB, es posible usar el recurso más nuevo (google_compute_health_check) y usar su bloque correspondiente http_health_check/tcp_health_check en consecuencia:\nresource \u0026#34;google_compute_health_check\u0026#34; \u0026#34;http_hc\u0026#34; { name = \u0026#34;${var.name}-health-check\u0026#34; check_interval_sec = 4 timeout_sec = 4 healthy_threshold = 2 unhealthy_threshold = 4 description = \u0026#34;este HC devuelve OK dependiendo del método\u0026#34; http_health_check { request_path = \u0026#34;/${var.reqpath}\u0026#34; port = \u0026#34;${var.hcport}\u0026#34; } } resource \u0026#34;google_compute_health_check\u0026#34; \u0026#34;tcp_hc\u0026#34; { name = \u0026#34;${var.name}-health-check\u0026#34; check_interval_sec = 4 timeout_sec = 4 healthy_threshold = 2 unhealthy_threshold = 4 description = \u0026#34;este HC es para autohealing y devuelve OK si el servicio está arriba\u0026#34; tcp_health_check { port = \u0026#34;${var.hcport}\u0026#34; } } El health check de autohealing puede ser TCP o HTTP, solo asegúrate de que el método API HTTP devuelva no disponible solo si el servicio está completamente caído.\nOtra recomendación es mantener tus umbrales e intervalos de verificación relativamente bajos, ya que puede amplificar el tiempo de inactividad por cada segundo agregado.\nMás lectura disponible en Health Check/ Legacy Health Checks.\nRecurso Backend Service (BackEnd del iLB) Particularmente en este caso, el recurso correspondiente para google_compute_region_instance_group_manager es google_compute_region_backend_service. Este recurso necesita:\nQué instancias están en el tier backend (${google_compute_region_instance_group_manager.instance_group_manager.instance_group}), qué health check está en uso para determinar el nodo disponible (${google_compute_health_check.http_hc.self_link}). Un ejemplo de esto sería el siguiente:\nresource \u0026#34;google_compute_region_backend_service\u0026#34; \u0026#34;instance_group_backendservice\u0026#34; { name = \u0026#34;${var.name}-rig-bs\u0026#34; description = \u0026#34;Region Instance Group Backend Service\u0026#34; protocol = \u0026#34;TCP\u0026#34; timeout_sec = 10 session_affinity = \u0026#34;NONE\u0026#34; backend { group = \u0026#34;${google_compute_region_instance_group_manager.instance_group_manager.instance_group}\u0026#34; } health_checks = [\u0026#34;${google_compute_health_check.http_hc.self_link}\u0026#34;] } El bloque backend proporciona las instancias creadas por google_compute_region_instance_group_manager y health_checks apunta al HC predefinido arriba. Un backend service puede apuntar a varios Health Checks como se construye en google-lb-internal:\nresource \u0026#34;google_compute_region_backend_service\u0026#34; \u0026#34;default\u0026#34; { ... health_checks = [\u0026#34;${element(compact(concat(google_compute_health_check.tcp.*.self_link,google_compute_health_check.http.*.self_link)), 0)}\u0026#34;] } resource \u0026#34;google_compute_health_check\u0026#34; \u0026#34;tcp\u0026#34; { count = \u0026#34;${var.http_health_check ? 0 : 1}\u0026#34; project = \u0026#34;${var.project}\u0026#34; name = \u0026#34;${var.name}-hc\u0026#34; tcp_health_check { port = \u0026#34;${var.health_port}\u0026#34; } } resource \u0026#34;google_compute_health_check\u0026#34; \u0026#34;http\u0026#34; { count = \u0026#34;${var.http_health_check ? 1 : 0}\u0026#34; project = \u0026#34;${var.project}\u0026#34; name = \u0026#34;${var.name}-hc\u0026#34; http_health_check { port = \u0026#34;${var.health_port}\u0026#34; } } Este no es el caso que queremos configurar aquí, pero es una advertencia interesante si quieres agregar más verificaciones sobre más de un puerto.\nTen en cuenta que estamos configurando un Load Balancer interno, que es compatible con google_compute_region_backend_service , divergiendo del Load Balancer externo que requiere google_compute_backend_service. La diferencia es el nivel de abstracción al aprovisionar nodos, que en el externo, necesitas terminar definiendo recursos más explícitamente que usando interno.\nNota: Los backend services de región solo pueden usarse cuando se usa load balancing interno. Para load balancing externo, usa google_compute_backend_service en su lugar. Terraform Doc\nForwarding Rule (FrontEnd del iLB) La Forwarding Rule es un recurso que definirá las opciones del LB, en las que las más notables son:\nload_balancing_scheme y backend_service. El cambio de load_balancing_scheme requerirá cambios considerables en la arquitectura, así que necesitas definir esto de antemano al hacer el diseño de tu infra. Respecto al backend_service, esto necesita apuntar al backend_service correspondiente que haces girar para el Instance Managed Group.\nresource \u0026#34;google_compute_forwarding_rule\u0026#34; \u0026#34;main_fr\u0026#34; { project = \u0026#34;${var.project}\u0026#34; name = \u0026#34;fw-rule-${var.name}\u0026#34; region = \u0026#34;${var.region}\u0026#34; network = \u0026#34;${var.network}\u0026#34; backend_service = \u0026#34;${google_compute_region_backend_service.instance_group_backendservice.self_link}\u0026#34; load_balancing_scheme = \u0026#34;INTERNAL\u0026#34; ports = [\u0026#34;${var.forwarding_port_ranges}\u0026#34;] ip_address = [\u0026#34;${google_compute_address.ilb_ip.address}\u0026#34;] } Es una práctica común predefinir una dirección compute para el iLB en lugar de dejar que GCP elija una por nosotros y, yendo más allá, puedes prevenir que este recurso sea destruido accidentalmente, ya que esta IP es un punto de entrada para tu aplicación y podría estar codificada en una pieza diferente de arquitectura:\nresource \u0026#34;google_compute_address\u0026#34; \u0026#34;ilb_ip\u0026#34; { name = \u0026#34;${var.name}-theIP\u0026#34; project = \u0026#34;${var.project}\u0026#34; region = \u0026#34;${var.region}\u0026#34; address_type = \u0026#34;INTERNAL\u0026#34; lifecycle { prevent_destroy = true } } ¡No olvides las reglas de Firewall! Todos los recursos anteriores son centrales para la configuración del iLB, aunque hay un recurso que aunque no es estrictamente parte del componente, debe especificarse para permitir que los servicios y computes hablen entre sí. En este caso, los source_ranges deben coincidir con la configuración de subred correspondiente de todos los recursos anteriores. Si estás en fase de desarrollo , puedes usar 0.0.0.0/0 para abrir ampliamente la regla, aunque puedes querer especificar un rango de IP estrecho en producción.\nresource \u0026#34;google_compute_firewall\u0026#34; \u0026#34;default-lb-fw\u0026#34; { project = \u0026#34;${var.project}\u0026#34; name = \u0026#34;${format(\u0026#34;%v-%v-fw-ilb\u0026#34;,var.name,count.index)}\u0026#34; network = \u0026#34;${var.network}\u0026#34; allow { protocol = \u0026#34;tcp\u0026#34; ports = [\u0026#34;${var.forwarding_port_ranges}\u0026#34;] } source_ranges = [\u0026#34;0.0.0.0/0\u0026#34;] target_tags = [\u0026#34;${var.tags}\u0026#34;] } Más información sobre Google Cloud Load Balancer Encontré un artículo interesante sobre Maglev, el Load Balancer de Infra de Google, que es una serie de posts explicando los internos, échale un vistazo.\n¡Espero que te haya gustado este post, no dudes en señalar preguntas y comentarios!\n"},{"title":"Start pt-stalk with delay","href":"/es/toolbox/2018/ptplugin/","content":" Source\nThis plugin was developed for addressing one of Percona\u0026rsquo;s customer issues, in which a custom process was runtime error\u0026rsquo;ing after a frequent amount of time.\npt-stalk name is self-explanatory, so this tool started the debug collection after a period of time to avoid unnecesary logs \u0026ndash; the stalking output can be extremelly large and impactful for the database throughput.\n"},{"title":"Galera Cluster with Vagrant","href":"/es/toolbox/2017/galera/","content":" Vagrant Galera\nImplementation of a 3-node Vagrant cluster with Galera.\n"},{"title":"Sampling de Clickhouse en el motor MergeTree.","href":"/es/posts/2019-06/clickhouse-sampling-data/","content":"\n¿Por qué el sampling es importante y de qué necesitas estar consciente? Cuando lidias con cantidades muy grandes de datos, probablemente quieras ejecutar tus consultas solo para un conjunto de datos más pequeño en tus tablas actuales. Especialmente si tu conjunto de datos no cabe en RAM.\nMergeTree es el primer y más avanzado motor en Clickhouse que querrás probar. Soporta indexación por Clave Primaria y es obligatorio tener una columna de tipo Date (usada para particionamiento automático).\nEs el único motor que soporta sampling, y solo si la expresión de sampling fue definida en la creación de la tabla. Entonces, la regla general es que si el conjunto de datos no cabe en RAM preferirás crear la tabla con soporte de sampling. De lo contrario, no hay ganancia de rendimiento usando sampling en tablas relativamente pequeñas que caben en RAM.\nLa expresión de sampling usa una función hash sobre una columna elegida para generar pseudo aleatoriamente datos en cada una de las columnas seleccionadas definidas en la clave primaria. Entonces puedes habilitar esta característica accediendo a los datos usando la cláusula SAMPLE en la consulta.\nLos valores de las funciones de agregación no se corrigen automáticamente, así que para obtener un resultado aproximado, el valor \u0026lsquo;count()\u0026rsquo; se multiplica manualmente por el factor de la muestra. Por ejemplo, una muestra de 0.1 (10%) necesitará multiplicarse por 10, 0.2 necesitará multiplicarse por 5.\nSupongamos que tenemos 96MM filas en una tabla distribuida, dividida en 2 shards:\nSELECT count(*) FROM database_report.stats_table_distributed ┌──count()─┐ │ 96414151 │ └──────────┘ 1 rows in set. Elapsed: 0.026 sec. Processed 96.41 million rows, 192.83 MB (3.68 billion rows/s., 7.36 GB/s.) Si usas SAMPLE \u0026gt; 100, probablemente obtendrás algunos resultados sucios, especialmente si ejecutas sobre un paraguas distribuido. En el ejemplo siguiente es posible ver que el SAMPLE está sobre cada tabla local tabla local y agregado después localmente (hay 2 shards):\nTabla Local Tabla Distribuida SELECT count(*) FROM database_report.stats_table_local SAMPLE 1000 ┌─count()─┐ │ 1015 │ └─────────┘ 1 rows in set. Elapsed: 1.296 sec. Processed 48.21 million rows, 2.07 GB (37.18 million rows/s., 1.60 GB/s.) SELECT count(*) FROM database_report.stats_table_distributed SAMPLE 1000 ┌─count()─┐ │ 2032 │ └─────────┘ 1 rows in set. Elapsed: 1.256 sec. Processed 96.41 million rows, 4.15 GB (76.75 million rows/s., 3.30 GB/s.) En su lugar, usando el formato de coeficiente relativo, las agregaciones son más precisas/consistentes en términos de filas totales recolectadas, aunque necesitarás corregir la estimación dependiendo del coeficiente:\nSELECT count(*) AS count_over_sample, -- Sin corregir, tenemos x10 menos filas count(*) * 10 AS count_estimated -- Por 10 ya que estamos muestreando 10% de la tabla FROM database_report.stats_table_distributed SAMPLE 1 / 10 ┌─count_over_sample─┬─count_estimated─┐ │ 9641965 │ 96419650 │ └───────────────────┴─────────────────┘ 1 rows in set. Elapsed: 1.442 sec. Processed 96.41 million rows, 4.15 GB (66.84 million rows/s., 2.87 GB/s.) El camino de la ejecución en sampling se puede ver en la siguiente animación:\nFunciones hash para sampling Int y Strings Tienes varias funciones hash (intHash32 para enteros y cityHash64 para strings) aunque puedes quedarte con aquellas no criptográficas para no afectar el rendimiento.\nEjemplo sin soporte de sampling: MergeTree(EventDate, (CounterID, EventDate), 8192)\nEjemplo con soporte de sampling: MergeTree(EventDate, intHash32(UserID), (CounterID, EventDate, intHash32(UserID)), 8192)\nLos ejemplos en este artículo usan cityHash64, ya que el id es un String. También la distribución es aleatoria, para garantizar la paralelización de las consultas:\nCREATE TABLE database_report.stats_table_local ( ...) ENGINE = MergeTree(normdate, cityHash64(id), (created_at, id, cityHash64(id)), 8192); CREATE TABLE database_report.stats_table_distributed AS database_report.stats_table_local ENGINE = Distributed(database_report, database_report, stats_table_local, rand()); Manejo apropiado de precisión Aquí hay otro ejemplo al recolectar agregaciones sobre sampling. La declaración siguiente es una consulta sin sampling:\nSELECT DISTINCT address, count(*) FROM database_report.stats_table_distributed GROUP BY address HAVING count(*) \u0026gt; 500000 ORDER BY count(*) DESC ┌─address─────────┬─count()─┐ │ 10.0.1.222 │ 7431672 │ │ 1.3.2.1 │ 4727411 │ │ 104.123.123.198 │ 2377910 │ │ 10.0.20.110 │ 2366481 │ │ 10.0.5.6 │ 1852113 │ │ 12.1.2.4 │ 1413009 │ │ 54.84.210.50 │ 1141153 │ │ 63.138.62.1 │ 950598 │ │ 10.1.0.11 │ 738150 │ │ 10.0.1.15 │ 709582 │ │ 90.110.131.100 │ 601535 │ │ 65.30.67.32 │ 584043 │ └─────────────────┴─────────┘ 12 rows in set. Elapsed: 1.668 sec. Processed 96.41 million rows, 2.04 GB (57.79 million rows/s., 1.23 GB/s.) Pero, si muestreamos sin corregir las agregaciones:\nSELECT DISTINCT address, count(*) FROM database_report.stats_table_distributed SAMPLE 1 / 10 GROUP BY address HAVING count(*) \u0026gt; 500000 ORDER BY count(*) DESC ┌─address────────┬─count()─┐ │ 10.0.0.222 │ 744235 │ └────────────────┴─────────┘ 1 rows in set. Elapsed: 2.127 sec. Processed 96.41 million rows, 6.00 GB (45.32 million rows/s., 2.82 GB/s.) Puedes agregar algo de corrección alrededor y aumentar la muestra para obtener resultados más precisos:\nSELECT DISTINCT address, count(*) * 10 FROM database_report.stats_table_distributed SAMPLE 1 / 10 GROUP BY address HAVING (count(*) * 10) \u0026gt; 500000 ORDER BY count(*) DESC ┌─address─────────┬─multiply(count(), 10)─┐ │ 10.0.1.222 │ 7442350 │ │ 1.3.2.1 │ 4725650 │ │ 104.123.123.198 │ 2381920 │ │ 10.0.20.110 │ 2363170 │ │ 10.0.5.6 │ 1856500 │ │ 12.1.2.4 │ 1413860 │ │ 54.84.210.50 │ 1141190 │ │ 63.138.62.1 │ 954630 │ │ 10.1.0.11 │ 739530 │ │ 10.0.1.15 │ 712970 │ │ 90.110.131.100 │ 604510 │ │ 65.30.67.32 │ 583320 │ └─────────────────┴───────────────────────┘ 12 rows in set. Elapsed: 2.134 sec. Processed 96.41 million rows, 6.00 GB (45.17 million rows/s., 2.81 GB/s.) SELECT DISTINCT address, count(*) * 5 FROM database_report.stats_table_distributed SAMPLE 2 / 10 GROUP BY address HAVING (count(*) * 5) \u0026gt; 500000 ORDER BY count(*) DESC ┌─address─────────┬─multiply(count(), 5)─┐ │ 10.0.1.222 │ 7430545 │ │ 1.3.2.1 │ 4730535 │ │ 104.123.123.198 │ 2378665 │ │ 10.0.20.110 │ 2364765 │ │ 10.0.5.6 │ 1854600 │ │ 12.1.2.4 │ 1412980 │ │ 54.84.210.50 │ 1142130 │ │ 63.138.62.1 │ 952105 │ │ 10.1.0.11 │ 740335 │ │ 10.0.1.15 │ 709805 │ │ 90.110.131.100 │ 603960 │ │ 65.30.67.32 │ 582545 │ └─────────────────┴──────────────────────┘ 12 rows in set. Elapsed: 2.344 sec. Processed 96.41 million rows, 6.00 GB (41.13 million rows/s., 2.56 GB/s.) Advertencia de rendimiento Si el conjunto de datos es más pequeño que la cantidad de RAM, el sampling no ayudará en términos de rendimiento. Lo siguiente es un ejemplo de un conjunto de resultados más grande usando sin-sampling y sampling.\nSELECT some_type, count(*) FROM database_report.stats_table_distributed GROUP BY some_type HAVING count(*) \u0026gt; 1000000 ORDER BY count(*) DESC [...] 15 rows in set. Elapsed: 1.534 sec. Processed 96.41 million rows, 1.95 GB (62.84 million rows/s., 1.27 GB/s.) SELECT some_type, count(*) * 10 FROM database_report.stats_table_distributed SAMPLE 1 / 10 GROUP BY some_type HAVING (count(*) * 10) \u0026gt; 1000000 ORDER BY count(*) DESC [...] 15 rows in set. Elapsed: 2.123 sec. Processed 96.41 million rows, 5.90 GB (45.41 million rows/s., 2.78 GB/s.) "},{"title":"listen-notify","href":"/es/toolbox/2017/listen-notify/","content":"listen-notify Source at Github\nThis laboratory was conducted in order to stress out the Listen/Notify mechanism in PostgreSQL from a Go based client.\n"},{"title":"Importar datos de Redshift a Clickhouse en un solo comando.","href":"/es/posts/2017-03/import-data-to-ch-from-rs/","content":"\nAlcance Si has oído sobre Clickhouse y te estás preguntando cómo probar con tus datos residiendo en Redshift, aquí hay un comando que te mostrará algunos consejos para acelerarte.\nActualización (4 de julio): Hay una serie de posts sobre comparaciones Clickhouse vs Redshift, el primer post es este.\nLa forma estándar de mover tus datos fuera de Redshift es usando el comando UNLOAD, que empuja la salida a archivos S3. No sorprendentemente, Redshift no soporta COPY (\u0026lt;query\u0026gt;) TO STDOUT, lo que podría hacer la vida más fácil (ya que está basado en Postgres versión 8.0.2, bastante antiguo). Información sobre esto, aquí.\nClickhouse soporta varios motores pero hasta ahora, seguramente comenzarás con MergeTree. Los tipos soportados son más finitos, aunque deberían ser suficientes para análisis simples. Se recomienda agregar soporte de sampling en la creación de la tabla , en los parámetros del motor a través de la función hash correspondiente con el tipo de columna que devuelve enteros sin signo después de la definición de la clave. En este caso he elegido cityHash64 ya que no es criptográfico, tiene una precisión decente y mejor rendimiento.\nLa tabla en CH es la siguiente:\nCREATE TABLE thenewdb.thetable ( normdate Date, id String, datefield DateTime, (... many others ...) data String ) ENGINE = MergeTree(normdate,cityHash64(id), (datefield, id,cityHash64(id)),8192); NOTA: Los parámetros del motor son: una columna de fecha, la expresión de sampling opcional (cityHash64) la clave primaria (datefield,id) y la granularidad del índice.\nLa tabla en Redshift es:\nColumn | Type | Modifiers ----------------+-----------------------------+----------- id | character varying(32) | not null datefield | timestamp without time zone | not null (... other columns...) data | character varying(8192) | Indexes: \u0026#34;thetable_pkey1\u0026#34; PRIMARY KEY, btree (id) ClickHouse requiere una columna Date, que termina siendo una columna adicional en tu estructura de tabla. Para más información, revisa la doc de MergeTree.\nLa magia Abre una sesión screen/ tmux.\nEjecuta el comando:\ntime psql -h rs1.garbagestring.redshift.amazonaws.com \\ -p 5439 -U redshift thedatabase \\ -Antq --variable=\u0026#34;FETCH_COUNT=1000000\u0026#34; -F $\u0026#39;\\t\u0026#39; \u0026lt;\u0026lt;EOF | \\ clickhouse-client --database thenewdb --query=\u0026#34;INSERT INTO thenewdb.thetable FORMAT TabSeparated\u0026#34; select trunc(datefield), id, datefield::timestamp(0) , store_id , (... many columns more ... ) regexp_replace(data,\u0026#39;\\\\t|\\\\n\u0026#39;,\u0026#39;\u0026#39;) from theoriginaltable EOF Cálculo de RAM El motor MergeTree es de hecho una implementación interesante. No es un LSM ya que no procesa en memtables ni tampoco en log. Procesa los datos en lotes y escribe directamente al sistema de archivos, consumiendo una cantidad significativa de RAM a costa de ahorrar operaciones de disco (y ocasionalmente ciclos de CPU) por workers en segundo plano que hacen los merges.\nUn error común cuando te quedas sin memoria debido a estos procesos de merge consumiendo RAM es:\nCode: 240. DB::Exception: Allocator: Cannot mremap., errno: 12, strerror: Cannot allocate memory La razón por la que esto sucede es debido a la RAM consumida en merges en segundo plano. Hay cinco elementos a tener en cuenta para calcular la memoria necesaria:\nbackground_pool_size es 6, determinando el número máximo de merges en segundo plano. Número máximo de piezas de merge durante el merge (predeterminado 100) tamaño de bloque para el merger (8192 filas) tamaño promedio de fila sin comprimir overhead máximo de asignación de memoria para buffers (2) Puedes asumir un tamaño de fila de 1024 bytes y multiplicar todo lo anterior junto. p.ej. SELECT formatReadableSize( 2* 6 * 100 * 8192 * 1024);\nEl problema actual es que el algoritmo de merge procesa por fila en lugar de cada columna por separado, y se espera tener una ganancia de rendimiento. Puedes probar el algoritmo vertical estableciendo enable_vertical_merge_algorithm en el archivo de configuración.\nEntonces, adivinando que obtienes un tamaño de fila de 13557 bytes (14k) medido usando la consulta 1), puedes obtener una aproximación de RAM necesaria para el bloque de operaciones 2).\ntime psql -h rs-clusterandhash.us-east-1.redshift.amazonaws.com\\ -p 5439 -U redshift reportdb -Antq --variable=\u0026#34;FETCH_COUNT=1000000\u0026#34; -F $\u0026#39;\\t\u0026#39; \u0026lt;\u0026lt;EOF | wc -c select * from big_table LIMIT 1 EOF 13835 SELECT formatReadableSize((((2 * 6) * 100) * 8192) * 13557) ┌─formatReadableSize(multiply(multiply(multiply(multiply(2, 6), 100), 8192), 13557))─┐ │ 124.12 GiB │ └────────────────────────────────────────────────────────────────────────────────────┘ Más información sobre esto en este hilo de google groups.\nDesafortunadamente, el cliente aún no puede manejar esto apropiadamente. Incluso limitando el uso de memoria con --max_memory_usage 5GB (p.ej), obtendrás un error diferente como este:\nCode: 241. DB::Exception: Received from localhost:9000, 127.0.0.1. DB::Exception: Memory limit (for query) exceeded: would use 1.00 MiB (attempt to allocate chunk of 1048576 bytes), maximum: 5.00 B. Si la RAM necesaria está muy cerca de tu recurso actual, una posible solución sería usar el motor ReplacingMergeTree, pero la deduplicación no está garantizada y de hecho jugarás en límites muy pequeños (deberías estar muy cerca del cálculo anterior). También, hay varios ajustes a nivel de motor para afinar el motor mergetree a través de configuración en MergeTreeSettings.h\np.ej., lo siguiente reducirá el consumo de RAM considerablemente, a costa de reducir durabilidad y cambiar el algoritmo de merge:\n\u0026lt;merge_tree\u0026gt; \u0026lt;max_suspicious_broken_parts\u0026gt;20\u0026lt;/max_suspicious_broken_parts\u0026gt; \u0026lt;enable_vertical_merge_algorithm\u0026gt;1\u0026lt;/enable_vertical_merge_algorithm\u0026gt; \u0026lt;max_delay_to_insert\u0026gt;5\u0026lt;/max_delay_to_insert\u0026gt; \u0026lt;parts_to_delay_insert\u0026gt;100\u0026lt;/parts_to_delay_insert\u0026gt; \u0026lt;/merge_tree\u0026gt; La explicación ¿Por qué TabSeparated? Clickhouse ofrece varios formatos de entrada/salida, muchos. Aunque, el tab en este caso parecía suficiente para importar textos simples (hasta que un JSON mágico con tabs y saltos de línea rompió la importación).\n¿Por qué castear sin microsegundos ::timestamp(0)? CH no soporta microsegundos.\n¿Por qué hacer replace regexp_replace(data,'\\\\t|\\\\n','')? Estamos importando usando TSV, que por estándar no soporta saltos de línea y obviamente, tabs. Desafortunadamente, no es posible en este momento usar codificación/decodificación usando base64 para insertar sin reemplazar (haciendo stream de los datos codificados y decodificando sobre la marcha por Clickhouse).\n¿Por qué --variable=\u0026quot;FETCH_COUNT=1000000\u0026quot;? Esta es la salsa. psql intentará colocar todo el conjunto de resultados en memoria, haciendo que la caja explote en unos pocos minutos después de comenzar a ejecutarse. Con esto, crea un cursor del lado del servidor, permitiéndonos importar conjuntos de resultados más grandes que la máquina cliente.\n¿Por qué -F $'\\t'? Dependiendo de tu shell, puedes considerar esto. Necesitas usar un tab literal, lo que significa que necesita ser el carácter mismo. En UNIX Ctrl-V tab debería hacerlo.\nPuedes hacer una pequeña prueba sobre esto con echo. La opción -e permite la interpretación de escapes de backslash. También printf es una opción limpia para imprimir caracteres especiales.\nubuntu@host:~$ echo $\u0026#39;\\n\u0026#39; ubuntu@host:~$ echo \u0026#39;\\n\u0026#39; \\n ubuntu@host:~$ echo -e \u0026#39;\\n\u0026#39; Números rebeldes El proceso en sí es considerablemente rápido: movió una tabla de 15GB a un MergeTree de Clickhouse de alrededor de 11GB en 20 minutos.\nDetalles de instancia para RS: dc1.large 15GB RAM, vCPU 2, 2 nodos + 1 coordinador Instancia CH: EC2 r4.2xlarge única, volumen 3000 iops EBS\n¡Espero que encuentres este consejo útil!\n"},{"title":"Overhead estimado de postgres_fdw","href":"/es/posts/2017-03/fdw-overhead/","content":"\nConcepto En el concepto actual, vamos a combinar herencia de tablas foráneas con la extensión postgres_fdw, ambas siendo características ya disponibles desde la versión 9.5.\nEl particionamiento entre nodos permite una mejor localidad de datos y un modelo más escalable que mantener particiones locales. Dicho esto, los datos se dividirán en varios nodos y se organizarán usando una clave particular, que determinará en qué shard se asignarán los datos. Para el POC actual, vamos a especificar el shardKey , que es un tipo simple char(2).\nCómo se hacía esto antes Hasta hoy, la única forma de realizar hallazgos sobre este método, era desde la capa de aplicación, emitiendo consultas directamente a los nodos manteniendo cierta forma determinística como {1} o usando una tabla de catálogo como {2} (NOTA: los ejemplos siguientes usan código pseudo).\nDeterminísticamente Técnica de tabla de catálogo query = \u0026#34;SELECT name,lastname FROM \u0026#34; + relation + partition \u0026#34; WHERE \u0026#34; id =\u0026#34; + person_id shard = query(\u0026#34;SELECT shard FROM catalog WHERE key = \u0026#34; + person_id) query = \u0026#34;SELECT name,lastname FROM \u0026#34; + relation + shard + \u0026#34; WHERE \u0026#34; id =\u0026#34; + person_id Cómo vamos a implementar esto ahora Como las tablas foráneas (FT) no contienen datos por sí mismas, es posible mantener copias alrededor de todas las bases de datos involucradas y también en instancias separadas si esto es necesario.\nTodas las operaciones contra la tabla se harán a través de la tabla padre de las tablas FT del árbol y Postgres mismo determinará la FT de destino usando la característica de exclusión de restricciones, que se detallará más adelante.\nPara HA, estás limitado en los nodos de datos para implementar cualquier otra solución de replicación disponible en la versión core. Para ser justos, 9.6 soporta streaming replication y logical decoding, que es usado por la herramienta pglogical para proporcionar replicación lógica avanzada por tabla.\n[Fig. 1] Sharding manual con implementación actual de FDW. Tablas foráneas Las tablas foráneas no contienen datos por sí mismas y solo hacen referencia a una tabla externa en una base de datos Postgres diferente. Hay muchas extensiones diferentes permitiendo tablas externas en diferentes soluciones de almacenamiento de datos, pero en este artículo particular vamos a enfocarnos en postgres_fdw ya que queremos explorar más sobre condition pushdowns, que hace que las consultas contra estas tablas sean más performantes en consultas más complejas.\nUn benchmark más extenso se puede encontrar en mi siguiente artículo.\nEl framework subyacente para los Foreign Data Wrappers, soporta tanto operaciones de lectura como de escritura. postgres_fdw no es la excepción y también soporta condition pushdown para evitar escaneos grandes en las tablas fuente.\nEn cada base de datos que contiene la FT, necesitas invocar la creación de la extensión:\nCREATE EXTENSION postgres_fdw; Las FT tienen dos elementos principales, necesarios para apuntar correctamente tanto en la fuente como en los privilegios de usuario. Si eres lo suficientemente paranoico, preferirás usar usuarios sin privilegios con grants limitados sobre las tablas que usas.\nServer User Mapping {1}\nCREATE SERVER shard1_main FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;5434\u0026#39;,dbname \u0026#39;shard1\u0026#39;); CREATE SERVER shard2_main FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;5435\u0026#39;,dbname \u0026#39;shard2\u0026#39;); -- Slaves CREATE SERVER shard1_main_replica FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;7777\u0026#39;,dbname \u0026#39;shard1\u0026#39;); CREATE SERVER shard2_main_replica FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;8888\u0026#39;,dbname \u0026#39;shard2\u0026#39;); {2}\n-- User mapping CREATE USER MAPPING FOR postgres SERVER shard1_main OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard2_main OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard1_main_replica OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard2_main_replica OPTIONS(user \u0026#39;postgres\u0026#39;); La definición de FT es bastante directa si no queremos hacer ningún filtrado adicional de columnas:\nCREATE TABLE main (shardKey char(2), key bigint, avalue text); CREATE FOREIGN TABLE main_shard01 (CHECK (shardKey = \u0026#39;01\u0026#39;)) INHERITS (main) SERVER shard1_main; CREATE FOREIGN TABLE main_shard02 (CHECK (shardKey = \u0026#39;02\u0026#39;)) INHERITS (main) SERVER shard2_main; FDWs escribibles Incluso si no recomiendo el siguiente enfoque, puede ser muy fácil centralizar las escrituras a los shards a través de la FT. Aunque, requiere codificar un trigger para manejar esto. Actualmente, el nivel mínimo de transacción para tablas foráneas es REPEATABLE READ, pero probablemente cambiará en versiones futuras.\nUn enfoque muy simplista para un trigger INSERT sería como el siguiente:\nCREATE OR REPLACE FUNCTION f_main_part() RETURNS TRIGGER AS $FMAINPART$ DECLARE partition_name text; BEGIN partition_name := \u0026#39;main_shard\u0026#39; || NEW.shardKey; EXECUTE \u0026#39;INSERT INTO \u0026#39; || quote_ident(partition_name) || \u0026#39; SELECT ($1).*\u0026#39; USING NEW ; RETURN NULL; END; $FMAINPART$ LANGUAGE plpgsql; CREATE TRIGGER t_main BEFORE INSERT ON main FOR EACH ROW EXECUTE PROCEDURE f_main_part(); Datos en shards Como los shards contienen datos, la declaración termina siendo una tabla común con el sufijo necesario para localización:\nCREATE TABLE main_shard01( shardKey char(2), key bigint, avalue text, CHECK(shardKey=\u0026#39;01\u0026#39;)); CREATE INDEX ON main_shard01(key); Una prueba simple podría hacerse emitiendo:\nproxy=# INSERT INTO main SELECT \u0026#39;0\u0026#39; || round(random()*1+1),i.i,random()::text FROM generate_series(1,20000) i(i) ; INSERT 0 0 Probablemente estés intuyendo que la declaración anterior inserta datos en ambos nodos, y el trigger derivará la fila en consecuencia al shard correspondiente.\nNOTA: el número de shard se genera por random()*1+1 que redondea la salida entre 1 y 2.\nAgárralos de las columnas ocultas Consultar datos puede ser bastante transparente, como se muestra a continuación. El tableoid en este caso particular puede ser engañoso, ya que los oid reportados son los de los nodos, no la máquina local. Se usa solo para mostrar que son efectivamente diferentes tablas:\nproxy=# select tableoid,count(*) from main group by tableoid; tableoid | count ----------+------- 33226 | 104 33222 | 96 (2 rows) Por ejemplo, recuperar una sola fila es fácil como:\nproxy=# SELECT avalue FROM main WHERE key = 1500 and shardKey = \u0026#39;01\u0026#39;; avalue ------------------- 0.971926014870405 (1 row) Detrás de escena, la consulta empujada a los servidores remotos contiene el filtro correspondiente ((key = 1500)) y localmente, la exclusión de restricciones permite evitar escaneos adicionales en la otra FT hija.\nproxy=# explain (VERBOSE true)SELECT avalue FROM main WHERE key = 1500 and shardKey = \u0026#39;01\u0026#39;; QUERY PLAN -------------------------------------------------------------------------------- Append (cost=0.00..131.95 rows=2 width=32) -\u0026gt; Seq Scan on public.main (cost=0.00..0.00 rows=1 width=32) Output: main.avalue Filter: ((main.key = 1500) AND (main.shardkey = \u0026#39;01\u0026#39;::bpchar)) -\u0026gt; Foreign Scan on public.main_shard01 (cost=100.00..131.95 rows=1 width=32) Output: main_shard01.avalue Remote SQL: SELECT avalue FROM public.main_shard01 WHERE ((key = 1500)) AND ((shardkey = \u0026#39;01\u0026#39;::bpchar)) (7 rows) Incluso si no queremos proporcionar el shardKey, el filtro key se empujará a través de todos los nodos shard. Si tus claves no son únicas entre shards, obtendrás un conjunto de resultados de múltiples filas.\nproxy=# explain (VERBOSE true)SELECT avalue FROM main WHERE key = 1500; QUERY PLAN -------------------------------------------------------------------------------- Append (cost=0.00..256.83 rows=15 width=32) -\u0026gt; Seq Scan on public.main (cost=0.00..0.00 rows=1 width=32) Output: main.avalue Filter: (main.key = 1500) -\u0026gt; Foreign Scan on public.main_shard01 (cost=100.00..128.41 rows=7 width=32) Output: main_shard01.avalue Remote SQL: SELECT avalue FROM public.main_shard01 WHERE ((key = 1500)) -\u0026gt; Foreign Scan on public.main_shard02 (cost=100.00..128.41 rows=7 width=32) Output: main_shard02.avalue Remote SQL: SELECT avalue FROM public.main_shard02 WHERE ((key = 1500)) (10 rows) Consideraciones Los Foreign Data Wrappers para Postgres son una gran extensión, pero viene con un precio con un overhead visible en cargas de trabajo transaccionales de alta intensidad.\n¡Espero que te haya gustado el artículo!\n"},{"title":"Sharding simple y manual en PostgreSQL.","href":"/es/posts/2017-03/simple-manual-sharding/","content":"\nConcepto En el concepto actual, vamos a combinar herencia de tablas foráneas con la extensión postgres_fdw, ambas siendo características ya disponibles desde la versión 9.5.\nEl particionamiento entre nodos permite una mejor localidad de datos y un modelo más escalable que mantener particiones locales. Dicho esto, los datos se dividirán en varios nodos y se organizarán usando una clave particular, que determinará en qué shard se asignarán los datos. Para el POC actual, vamos a especificar el shardKey , que es un tipo simple char(2).\nCómo se hacía esto antes Hasta hoy, la única forma de realizar hallazgos sobre este método, era desde la capa de aplicación, emitiendo consultas directamente a los nodos manteniendo cierta forma determinística o usando una tabla de catálogo:\nNOTA: los ejemplos siguientes usan código pseudo.\nDeterminístico Tabla de Catálogo query = \u0026#34;SELECT name,lastname FROM \u0026#34; + relation + partition + \u0026#34; WHERE \u0026#34; id =\u0026#34; + person_id shard = query(\u0026#34;SELECT shard FROM catalog WHERE key = \u0026#34; + person_id) query = \u0026#34;SELECT name,lastname FROM \u0026#34; + relation + shard + \u0026#34; WHERE \u0026#34; id =\u0026#34; + person_id Cómo vamos a implementar esto ahora Como las tablas foráneas (FT) no contienen datos por sí mismas, es posible mantener copias alrededor de todas las bases de datos involucradas y también en instancias separadas si esto es necesario.\nTodas las operaciones contra la tabla se harán a través de la tabla padre de las tablas FT del árbol y Postgres mismo determinará la FT de destino usando la característica de exclusión de restricciones, que se detallará más adelante.\nPara HA, estás limitado en los nodos de datos para implementar cualquier otra solución de replicación disponible en la versión core. Para ser justos, 9.6 soporta streaming replication y logical decoding, que es usado por la herramienta pglogical para proporcionar replicación lógica avanzada por tabla.\n[Fig. 1] Sharding manual con implementación actual de FDW. Tablas foráneas Las tablas foráneas no contienen datos por sí mismas y solo hacen referencia a una tabla externa en una base de datos Postgres diferente. Hay muchas extensiones diferentes permitiendo tablas externas en diferentes soluciones de almacenamiento de datos, pero en este artículo particular vamos a enfocarnos en postgres_fdw ya que queremos explorar más sobre condition pushdowns, que hace que las consultas contra estas tablas sean más performantes en consultas más complejas.\nUn benchmark más extenso se puede encontrar en mi siguiente artículo.\nEl framework subyacente para los Foreign Data Wrappers, soporta tanto operaciones de lectura como de escritura. postgres_fdw no es la excepción y también soporta condition pushdown para evitar escaneos grandes en las tablas fuente.\nEn cada base de datos que contiene la FT, necesitas invocar la creación de la extensión:\nCREATE EXTENSION postgres_fdw; Las FT tienen dos elementos principales, necesarios para apuntar correctamente tanto en la fuente como en los privilegios de usuario. Si eres lo suficientemente paranoico, preferirás usar usuarios sin privilegios con grants limitados sobre las tablas que usas.\n1) Creación de servidor 2) User Mapping CREATE SERVER shard1_main FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;5434\u0026#39;,dbname \u0026#39;shard1\u0026#39;); CREATE SERVER shard2_main FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;5435\u0026#39;,dbname \u0026#39;shard2\u0026#39;); -- Slaves CREATE SERVER shard1_main_replica FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;7777\u0026#39;,dbname \u0026#39;shard1\u0026#39;); CREATE SERVER shard2_main_replica FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;8888\u0026#39;,dbname \u0026#39;shard2\u0026#39;); -- User mapping CREATE USER MAPPING FOR postgres SERVER shard1_main OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard2_main OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard1_main_replica OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard2_main_replica OPTIONS(user \u0026#39;postgres\u0026#39;); La definición de FT es bastante directa si no queremos hacer ningún filtrado adicional de columnas:\nCREATE TABLE main (shardKey char(2), key bigint, avalue text); CREATE FOREIGN TABLE main_shard01 (CHECK (shardKey = \u0026#39;01\u0026#39;)) INHERITS (main) SERVER shard1_main; CREATE FOREIGN TABLE main_shard02 (CHECK (shardKey = \u0026#39;02\u0026#39;)) INHERITS (main) SERVER shard2_main; FDWs escribibles Incluso si no recomiendo el siguiente enfoque, puede ser muy fácil centralizar las escrituras a los shards a través de la FT. Aunque, requiere codificar un trigger para manejar esto. Actualmente, el nivel mínimo de transacción para tablas foráneas es REPEATABLE READ, pero probablemente cambiará en versiones futuras.\nUn enfoque muy simplista para un trigger INSERT sería como el siguiente:\nCREATE OR REPLACE FUNCTION f_main_part() RETURNS TRIGGER AS $FMAINPART$ DECLARE partition_name text; BEGIN partition_name := \u0026#39;main_shard\u0026#39; || NEW.shardKey; EXECUTE \u0026#39;INSERT INTO \u0026#39; || quote_ident(partition_name) || \u0026#39; SELECT ($1).*\u0026#39; USING NEW ; RETURN NULL; END; $FMAINPART$ LANGUAGE plpgsql; CREATE TRIGGER t_main BEFORE INSERT ON main FOR EACH ROW EXECUTE PROCEDURE f_main_part(); Datos en shards Como los shards contienen datos, la declaración termina siendo una tabla común con el sufijo necesario para localización:\nCREATE TABLE main_shard01( shardKey char(2), key bigint, avalue text, CHECK(shardKey=\u0026#39;01\u0026#39;)); CREATE INDEX ON main_shard01(key); Una prueba simple podría hacerse emitiendo:\nproxy=# INSERT INTO main SELECT \u0026#39;0\u0026#39; || round(random()*1+1),i.i,random()::text FROM generate_series(1,20000) i(i) ; INSERT 0 0 Probablemente estés intuyendo que la declaración anterior inserta datos en ambos nodos, y el trigger derivará la fila en consecuencia al shard correspondiente.\nNOTA: el número de shard se genera por random()*1+1 que redondea la salida entre 1 y 2.\nAgárralos de las columnas ocultas Consultar datos puede ser bastante transparente, como se muestra a continuación. El tableoid en este caso particular puede ser engañoso, ya que los oid reportados son los de los nodos, no la máquina local. Se usa solo para mostrar que son efectivamente diferentes tablas:\nproxy=# select tableoid,count(*) from main group by tableoid; tableoid | count ----------+------- 33226 | 104 33222 | 96 (2 rows) Por ejemplo, recuperar una sola fila es fácil como:\nproxy=# SELECT avalue FROM main WHERE key = 1500 and shardKey = \u0026#39;01\u0026#39;; avalue ------------------- 0.971926014870405 (1 row) Detrás de escena, la consulta empujada a los servidores remotos contiene el filtro correspondiente ((key = 1500)) y localmente, la exclusión de restricciones permite evitar escaneos adicionales en la otra FT hija.\nproxy=# explain (VERBOSE true)SELECT avalue FROM main WHERE key = 1500 and shardKey = \u0026#39;01\u0026#39;; QUERY PLAN -------------------------------------------------------------------------------- Append (cost=0.00..131.95 rows=2 width=32) -\u0026gt; Seq Scan on public.main (cost=0.00..0.00 rows=1 width=32) Output: main.avalue Filter: ((main.key = 1500) AND (main.shardkey = \u0026#39;01\u0026#39;::bpchar)) -\u0026gt; Foreign Scan on public.main_shard01 (cost=100.00..131.95 rows=1 width=32) Output: main_shard01.avalue Remote SQL: SELECT avalue FROM public.main_shard01 WHERE ((key = 1500)) AND ((shardkey = \u0026#39;01\u0026#39;::bpchar)) (7 rows) Incluso si no queremos proporcionar el shardKey, el filtro key se empujará a través de todos los nodos shard. Si tus claves no son únicas entre shards, obtendrás un conjunto de resultados de múltiples filas.\nproxy=# explain (VERBOSE true)SELECT avalue FROM main WHERE key = 1500; QUERY PLAN -------------------------------------------------------------------------------- Append (cost=0.00..256.83 rows=15 width=32) -\u0026gt; Seq Scan on public.main (cost=0.00..0.00 rows=1 width=32) Output: main.avalue Filter: (main.key = 1500) -\u0026gt; Foreign Scan on public.main_shard01 (cost=100.00..128.41 rows=7 width=32) Output: main_shard01.avalue Remote SQL: SELECT avalue FROM public.main_shard01 WHERE ((key = 1500)) -\u0026gt; Foreign Scan on public.main_shard02 (cost=100.00..128.41 rows=7 width=32) Output: main_shard02.avalue Remote SQL: SELECT avalue FROM public.main_shard02 WHERE ((key = 1500)) (10 rows) Consideraciones Los Foreign Data Wrappers para Postgres son una gran extensión, pero viene con un precio con un overhead visible en cargas de trabajo transaccionales de alta intensidad.\n¡Espero que te haya gustado el artículo!\n"},{"title":"Conectando Postgres y Kafka crudamente","href":"/es/posts/2017-02/kafka-and-copy-in-pg/","content":"\nApache Kafka y Postgres: Capacidades de transacción y reportes Apache Kafka es una plataforma de streaming distribuida bien conocida para procesamiento de datos y mensajería consistente. Te permite centralizar consistentemente streams de datos para varios propósitos consumiéndolos y produciéndolos.\nUno de los ejemplos de una buena implementación, es la implementación del pipeline de datos de Mozilla, particularmente ya que muestra Kafka como un punto de entrada del flujo de datos. Esto te permite conectar nuevos almacenes de datos debajo de su stream, facilitando el uso de diferentes formatos de almacenamiento de datos ( como DRBMS o Document, etc. ) para recuperar y escribir datos eficientemente.\nPostgres Bottled water es un enfoque diferente que merece una mención. En este caso, las instancias de Postgres son los productores, los brokers consumen los streams y mantienen el almacén de mensajes disponible para cualquier acción. La ventaja aquí son las bien conocidas capacidades ACID de Postgres , combinadas con características SQL avanzadas. Este proyecto es una extensión, lo que significa que es posible usar nuevas características próximas de Postgres fácilmente portables.\nTambién es posible, consumir y producir datos a un broker usando una nueva característica que extendió la herramienta COPY para ejecutar comandos shell para operaciones de entrada/salida. Un buen resaltado de esta característica se puede leer aquí.\nkafkacat y librdkafka kafkacat es una herramienta basada en la biblioteca del mismo autor librdkafka que hace exactamente lo que dice su nombre: producir y consumir de un broker Kafka como el comando cat .\nProduciendo al broker Kafka Produciendo datos falsos al broker Kafka, compuestos por key y payload:\n# Random text randtext() {cat /dev/urandom | tr -dc \u0026#39;a-zA-Z0-9\u0026#39; | fold -w 32 | head -n 1} while (true) ; do for i in $(seq 1 50) do echo \u0026#34;$(uuidgen);$(randtext)\u0026#34; done | kafkacat -P -b localhost:9092 -qe -K \u0026#39;;\u0026#39; -t PGSHARD sleep 10 done La opción -K define el delimitador entre la key y el payload, -t define el topic para el que quieres producir. Originalmente, este topic ha sido creado con 3 particiones (0-2), lo que nos permitirá consumir datos en diferentes canales, abriendo la puerta para paralelización.\nLas Keys no son obligatorias al producir a un broker, y en realidad para ciertas soluciones puedes omitirlas.\nConsumiendo y Produciendo dentro de una instancia Postgres La sintaxis general será algo cercano a:\nCOPY main(group_id,payload) FROM PROGRAM \u0026#39;kafkacat -C -b localhost:9092 -c100 -qeJ -t PGSHARD -X group.id=1 -o beginning -p 0 | awk \u0026#39;\u0026#39;{print \u0026#34;P0\\t\u0026#34; $0 }\u0026#39;\u0026#39; \u0026#39;; El código haciendo piping a un awk no es estrictamente necesario y es solo para mostrar la flexibilidad de la característica. Cuando usas la opción -J, la salida se imprimirá en formato json, conteniendo toda la información del mensaje, incluyendo partición, key y mensaje.\nLa opción -c limitará la cantidad de filas en la operación. Como COPY es transaccional, ten en cuenta que cuanto mayor sea la cantidad de filas, mayor será la transacción y los tiempos de COMMIT se verán afectados.\nConsumiendo topics incrementalmente Consumiendo las particiones del topic desde el beginning y estableciendo un límite de 100 documentos es fácil como:\nbin/psql -p7777 -Upostgres master \u0026lt;\u0026lt;EOF COPY main(group_id,payload) FROM PROGRAM \u0026#39;kafkacat -C -b localhost:9092 -c100 -qeJ -t PGSHARD -X group.id=1 -o beginning -p 0 | awk \u0026#39;\u0026#39;{print \u0026#34;P0\\t\u0026#34; \\$0 }\u0026#39;\u0026#39; \u0026#39;; COPY main(group_id,payload) FROM PROGRAM \u0026#39;kafkacat -C -b localhost:9092 -c100 -qeJ -t PGSHARD -X group.id=1 -o beginning -p 1 | awk \u0026#39;\u0026#39;{print \u0026#34;P1\\t\u0026#34; \\$0 }\u0026#39;\u0026#39; \u0026#39;; COPY main(group_id,payload) FROM PROGRAM \u0026#39;kafkacat -C -b localhost:9092 -c100 -qeJ -t PGSHARD -X group.id=1 -o beginning -p 2 | awk \u0026#39;\u0026#39;{print \u0026#34;P2\\t\u0026#34; \\$0 }\u0026#39;\u0026#39; \u0026#39;; EOF Y luego usando stored, para consumir desde el último offset consumido por el consumidor en el grupo:\nbin/psql -p7777 -Upostgres master \u0026lt;\u0026lt;EOF COPY main(group_id,payload) FROM PROGRAM \u0026#39;kafkacat -C -b localhost:9092 -c100 -qeJ -t PGSHARD -X group.id=1 -o stored -p 0 | awk \u0026#39;\u0026#39;{print \u0026#34;P0\\t\u0026#34; \\$0 }\u0026#39;\u0026#39; \u0026#39;; COPY main(group_id,payload) FROM PROGRAM \u0026#39;kafkacat -C -b localhost:9092 -c100 -qeJ -t PGSHARD -X group.id=1 -o stored -p 1 | awk \u0026#39;\u0026#39;{print \u0026#34;P1\\t\u0026#34; \\$0 }\u0026#39;\u0026#39; \u0026#39;; COPY main(group_id,payload) FROM PROGRAM \u0026#39;kafkacat -C -b localhost:9092 -c100 -qeJ -t PGSHARD -X group.id=1 -o stored -p 2 | awk \u0026#39;\u0026#39;{print \u0026#34;P2\\t\u0026#34; \\$0 }\u0026#39;\u0026#39; \u0026#39;; EOF Cada línea COPY, puede ejecutarse en paralelo en diferentes instancias Postgres, haciendo este enfoque flexible y fácilmente escalable a través de un conjunto de servidores.\nEsto no es completamente consistente, ya que una vez que el offset es consumido, será marcado en el broker, ya sea si la transacción falla en el lado de Postgres puede potencialmente llevar a datos faltantes.\nProduciendo mensajes fuera de las instancias Postgres De la misma manera es posible consumir cambios, es posible hacer lo mismo para producir datos al broker. Esto puede ser increíblemente útil para micro agregaciones, hechas sobre los datos crudos consumidos del broker.\nEl ejemplo siguiente muestra cómo ejecutar una consulta simple con una agregación muy simplista y publicarla en formato JSON al broker:\nmaster=# COPY (select row_to_json(row(now() ,group_id , count(*))) from main group by group_id) TO PROGRAM \u0026#39;kafkacat -P -b localhost:9092 -qe -t AGGREGATIONS\u0026#39;; COPY 3 Si tienes una granja de servidores y quieres buscar el contenido del topic usando una key, puedes hacer el siguiente ajuste:\nCOPY (select inet_server_addr() || \u0026#39;;\u0026#39;, row_to_json(row(now() ,group_id , count(*))) from main group by group_id) TO PROGRAM \u0026#39;kafkacat -P -K \u0026#39;\u0026#39;;\u0026#39;\u0026#39; -b localhost:9092 -qe -t AGGREGATIONS\u0026#39;; Así es como se ven los payloads publicados (sin key):\n➜ PG10 kafkacat -C -b localhost:9092 -qeJ -t AGGREGATIONS -X group.id=1 -o beginning {\u0026#34;topic\u0026#34;:\u0026#34;AGGREGATIONS\u0026#34;,\u0026#34;partition\u0026#34;:0,\u0026#34;offset\u0026#34;:0,\u0026#34;key\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;payload\u0026#34;:\u0026#34;{\\\u0026#34;f1\\\u0026#34;:\\\u0026#34;2017-02-24T12:34:13.711732-03:00\\\u0026#34;,\\\u0026#34;f2\\\u0026#34;:\\\u0026#34;P1\\\u0026#34;,\\\u0026#34;f3\\\u0026#34;:172}\u0026#34;} {\u0026#34;topic\u0026#34;:\u0026#34;AGGREGATIONS\u0026#34;,\u0026#34;partition\u0026#34;:0,\u0026#34;offset\u0026#34;:1,\u0026#34;key\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;payload\u0026#34;:\u0026#34;{\\\u0026#34;f1\\\u0026#34;:\\\u0026#34;2017-02-24T12:34:13.711732-03:00\\\u0026#34;,\\\u0026#34;f2\\\u0026#34;:\\\u0026#34;P0\\\u0026#34;,\\\u0026#34;f3\\\u0026#34;:140}\u0026#34;} {\u0026#34;topic\u0026#34;:\u0026#34;AGGREGATIONS\u0026#34;,\u0026#34;partition\u0026#34;:0,\u0026#34;offset\u0026#34;:2,\u0026#34;key\u0026#34;:\u0026#34;\u0026#34;,\u0026#34;payload\u0026#34;:\u0026#34;{\\\u0026#34;f1\\\u0026#34;:\\\u0026#34;2017-02-24T12:34:13.711732-03:00\\\u0026#34;,\\\u0026#34;f2\\\u0026#34;:\\\u0026#34;P2\\\u0026#34;,\\\u0026#34;f3\\\u0026#34;:155}\u0026#34;} \u0026hellip; y con key:\n{\u0026#34;topic\u0026#34;:\u0026#34;AGGREGATIONS\u0026#34;,\u0026#34;partition\u0026#34;:0,\u0026#34;offset\u0026#34;:3,\u0026#34;key\u0026#34;:\u0026#34;127.0.0.1/32\u0026#34;,\u0026#34;payload\u0026#34;:\u0026#34;\\t{\\\u0026#34;f1\\\u0026#34;:\\\u0026#34;2017-02-24T12:40:39.017644-03:00\\\u0026#34;,\\\u0026#34;f2\\\u0026#34;:\\\u0026#34;P1\\\u0026#34;,\\\u0026#34;f3\\\u0026#34;:733}\u0026#34;} {\u0026#34;topic\u0026#34;:\u0026#34;AGGREGATIONS\u0026#34;,\u0026#34;partition\u0026#34;:0,\u0026#34;offset\u0026#34;:4,\u0026#34;key\u0026#34;:\u0026#34;127.0.0.1/32\u0026#34;,\u0026#34;payload\u0026#34;:\u0026#34;\\t{\\\u0026#34;f1\\\u0026#34;:\\\u0026#34;2017-02-24T12:40:39.017644-03:00\\\u0026#34;,\\\u0026#34;f2\\\u0026#34;:\\\u0026#34;P0\\\u0026#34;,\\\u0026#34;f3\\\u0026#34;:994}\u0026#34;} {\u0026#34;topic\u0026#34;:\u0026#34;AGGREGATIONS\u0026#34;,\u0026#34;partition\u0026#34;:0,\u0026#34;offset\u0026#34;:5,\u0026#34;key\u0026#34;:\u0026#34;127.0.0.1/32\u0026#34;,\u0026#34;payload\u0026#34;:\u0026#34;\\t{\\\u0026#34;f1\\\u0026#34;:\\\u0026#34;2017-02-24T12:40:39.017644-03:00\\\u0026#34;,\\\u0026#34;f2\\\u0026#34;:\\\u0026#34;P2\\\u0026#34;,\\\u0026#34;f3\\\u0026#34;:716}\u0026#34;} Manipulación básica de topics Si eres nuevo en Kafka, encontrarás útil contar con algunos ejemplos de comandos para jugar con tu broker local.\nIniciando todo:\nbin/zookeeper-server-start.sh config/zookeeper.properties 2\u0026gt; zookeper.log \u0026amp; bin/kafka-server-start.sh config/server.properties 2\u0026gt; kafka.log \u0026amp; Creando topics y otros:\nbin/kafka-topics.sh --list --zookeeper localhost:2181 bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 3 --topic PGSHARD bin/kafka-topics.sh --delete --zookeeper localhost:2181 --topic PGSHARD bin/kafka-topics.sh --create --zookeeper localhost:2181 --replication-factor 1 --partitions 1 --topic AGGREGATIONS bin/kafka-topics.sh --delete --zookeeper localhost:2181 --topic AGGREGATIONS NOTA: Para eliminar topics, necesitas habilitar delete.topic.enable=true en el archivo server.properties.\n¡Espero que encuentres esto útil!\n"},{"title":"Resaltando nuevas características de Postgres 10: Replicación Lógica y Particionamiento.","href":"/es/posts/2017-02/postgres-10-logrep-y-part/","content":"\n¡Hola! En este artículo vamos a explorar dos de las características principales comprometidas en la próxima release de PostgreSQL: Replicación Lógica y Particionamiento. No hace falta decir que estas características aún no están disponibles en la release estable, así que están propensas a cambiar o extenderse.\n¡Advertencia publicitaria! El artículo actual es solo un vistazo de la próxima charla Demystifying Logical Replication on PostgreSQL en Percona Live Santa Clara 2017. ¡Consigue tus tickets!\nReplicación Lógica El mecanismo actual de replicación lógica es solo basado en filas. Si estás alrededor del mundo MySQL notarás que el modo statement no está soportado. Si no estás familiarizado con la diferencia entre los modos, TL;DR no importa cuántas filas estén involucradas en la consulta fuente, se enviarán como filas individuales a los slaves. Es decir, una declaración única de múltiples filas como un INSERT en la fuente producirá una entrada por fila modificada .\nEsto es algo que puedes querer tener en consideración al hacer cargas masivas, ya que hay otras herramientas/técnicas que podrían ser un mejor ajuste además de hacer stream de todo desde el master usando el stream de replicación lógica.\nGeneralmente hablando, consiste en tres elementos visibles, también detallados en la imagen a continuación:\nuna Publication (fuente) una Subscription (consumidor) y un Logical Replication Slot Lo más importante y probablemente lo más complejo es el Logical Replication Slot. La magia se hace internamente a través del plugin pgoutput, que es la pieza de código a cargo de traducir los registros WAL (pg_wal) en entradas en el logical log (pg_logical).\nEl panorama completo puede resumirse así: Los consumidores se suscriben a un único Publisher usando un slot, que contiene el snapshot (LSN) de la base de datos (el punto en el tiempo dado del cluster). El slot proporcionará la información al motor sobre el punto en el tiempo desde el cual los cambios deben replicarse.\nEn este punto, es importante notar que la característica completa no está completamente comprometida y se espera contar con una opción WITH COPY DATA en la creación del evento de creación de eventos de suscripción para sincronizar datos desde la fuente. Actualmente, el parche tiene algunos bugs y está en proceso de revisión.\nAunque todo el tema es interesante, todo lo relacionado con Logical Decoding se omitirá en este artículo. Puedes hacer más que solo replicación Postgres-to-Postgres.\nParticionamiento En las versiones pasadas, era posible alcanzar un enfoque de particionamiento muy flexible combinando herencia y triggers basados en múltiples lenguajes. La implementación actual no permite mezclar herencia y particionamiento pero aún tiene cierta flexibilidad para desacoplar y acoplar particiones, usando una sintaxis explícita.\nEn el ejemplo actual, vamos a crear tres particiones sin datos, solo para mantener el foco solo en el POC.\nPOC El concepto actual funciona alrededor de tener slaves con una política de retención diferente de cada particionamiento replicando cada una en diferentes destinos y filtrando las operaciones DELETE. Como adición, somos capaces de crear una estructura dummy, para apuntar a cada particionamiento externo para reportes o consultas de datos históricos.\nEl concepto tiene tres tipos de nodos/bases de datos:\nUn proxy (manteniendo solo Foreign Data Wrappers apuntando a tablas hijas en herencia de una tabla dummy) Un master (Conteniendo todas las particiones) Bases de datos Shard (Solo manteniendo la información de shard correspondiente) Más o menos, usando los comandos en este artículo, deberías terminar con una imagen como esta:\nComo probablemente notarás, al eliminar filas en la base de datos fuente y filtrar eventos DELETE en el momento de publicación, terminarás con slaves manteniendo más datos, permitiendo consultas de marcos de tiempo más grandes. Esto es particularmente útil para dividir consultas BI en diferentes capas dependiendo de las especificaciones de rangos de fechas, ahorrando propósitos de almacenamiento en la fuente o manteniendo también un tamaño de tabla más mantenible. Las consultas contra archivo se pueden hacer directamente en los nodos o a través de la implementación proxy mencionada adelante.\nParticionamiento en la base de datos fuente/punto de entrada La base de datos master mantendrá las definiciones y los datos más recientes. El concepto actual, se alimenta de un topic del broker Apache Kafka que está particionado en tres. Vamos a alimentar esta tabla con streams usando el comando COPY. El artículo explicando cómo se hizo esto está aquí.\nEl DDL de las tablas de la base de datos master actual es:\nCREATE TABLE main (group_id char(2), stamp timestamp without time zone DEFAULT now(), payload jsonb) PARTITION BY LIST(group_id); CREATE TABLE main_shard0 PARTITION OF main FOR VALUES IN (\u0026#39;P0\u0026#39;); CREATE TABLE main_shard1 PARTITION OF main FOR VALUES IN (\u0026#39;P1\u0026#39;); CREATE TABLE main_shard2 PARTITION OF main FOR VALUES IN (\u0026#39;P2\u0026#39;); CREATE INDEX ix_main_shard_p0_key ON main_shard0 (stamp,(payload-\u0026gt;\u0026gt;\u0026#39;key\u0026#39;)); CREATE INDEX ix_main_shard_p1_key ON main_shard1 (stamp,(payload-\u0026gt;\u0026gt;\u0026#39;key\u0026#39;)); CREATE INDEX ix_main_shard_p2_key ON main_shard2 (stamp,(payload-\u0026gt;\u0026gt;\u0026#39;key\u0026#39;)); La columna group_id mantiene el número de partición del topic del cual los datos han sido consumidos del broker Kafka.\nAhora, es momento de publicarlas dentro del filtrado de eventos correspondiente. En este punto, no hay ningún slot de replicación asociado con las publicaciones:\nCREATE PUBLICATION P_main_P0 FOR TABLE main_shard0 WITH (NOPUBLISH DELETE); CREATE PUBLICATION P_main_P1 FOR TABLE main_shard1 WITH (NOPUBLISH DELETE); CREATE PUBLICATION P_main_P2 FOR TABLE main_shard2 WITH (NOPUBLISH DELETE); Por el estado actual de los últimos commits en PostgreSQL, Logical Replication no soporta filtrado por valor de columna como lo hace la herramienta pglogical. Aunque es posible filtrar por declaración de evento, que aún es bastante útil para nuestro propósito (NOPUBLISH|PUBLISH) como se describe arriba.\nCreando los nodos La definición de tabla en los nodos debería ser directa:\nCREATE TABLE main_shard0 (group_id char(2), stamp timestamp without time zone, payload jsonb); Ahora necesitamos crear la SUBSCRIPTION para alimentarse de la PUBLICATION correspondiente en la base de datos master. Como la implementación actual del evento SUBSCRIPTION no soporta con copy data y las particiones están vacías, vamos a crear un slot de replicación lógica en la fuente. Esto se hace fácilmente usando la cláusula CREATE SLOT. Esto significa que establecerá la posición LSN desde la cual los cambios deben aplicarse al destino:\nCREATE SUBSCRIPTION P_main_P0 CONNECTION \u0026#39;port=7777 user=postgres dbname=master\u0026#39; PUBLICATION P_main_P0 WITH (CREATE SLOT); Es notable notar, que después de la creación de la suscripción notarás nuevos workers a cargo de enviar y recibir esos cambios, como se describe en la imagen arriba.\nComo no es el alcance de este artículo, voy a omitir la explicación de los [logical|streaming] replication slots para mantener esto legible. Aunque, es un concepto central de la característica de replicación.\nConsultando desde una base de datos externa Este ejemplo no tiene otro propósito que mostrar una característica ya existente (aunque mejorada en versiones recientes) en acción. Pero muy especialmente voy a resaltar el INHERIT en una FOREIGN TABLE.\nEl siguiente DLL reside en una base de datos proxy, que no mantiene ningún dato de las particiones y solo está destinada a mostrar algunas capacidades relativamente nuevas de Postgres.\nCREATE EXTENSION postgres_fdw; CREATE SERVER shard0 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;7777\u0026#39;,dbname \u0026#39;shard0\u0026#39;); CREATE SERVER shard1 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;8888\u0026#39;,dbname \u0026#39;shard1\u0026#39;); CREATE SERVER shard2 FOREIGN DATA WRAPPER postgres_fdw OPTIONS(host \u0026#39;127.0.0.1\u0026#39;,port \u0026#39;9999\u0026#39;,dbname \u0026#39;shard2\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard0 OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard1 OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE USER MAPPING FOR postgres SERVER shard2 OPTIONS(user \u0026#39;postgres\u0026#39;); CREATE TABLE main (group_id char(2), payload jsonb); CREATE FOREIGN TABLE main_shard0 (CHECK (group_id = \u0026#39;P0\u0026#39;))INHERITS (main) SERVER shard0; CREATE FOREIGN TABLE main_shard1 (CHECK (group_id = \u0026#39;P1\u0026#39;))INHERITS (main) SERVER shard1; CREATE FOREIGN TABLE main_shard2 (CHECK (group_id = \u0026#39;P2\u0026#39;))INHERITS (main) SERVER shard2; Como puedes apreciar, estamos combinando herencia, verificaciones de restricciones y foreign data wrappers para evitar consultas a tablas remotas que no coinciden con el filtro group_id. También, adjunté un EXPLAIN como prueba de que ninguna de las otras tablas foráneas ha sido examinada.\nproxy=# SELECT * FROM main WHERE payload-\u0026gt;\u0026gt;\u0026#39;key\u0026#39; = \u0026#39;847f5dd2-f892-4f56-b04a-b106063cfe0d\u0026#39; and group_id = \u0026#39;P0\u0026#39;; group_id | payload ----------+-------------------------------------------------------------------- P0 | {\u0026#34;key\u0026#34;: \u0026#34;847f5dd2-f892-4f56-b04a-b106063cfe0d\u0026#34;, \u0026#34;topic\u0026#34;: \u0026#34;PGSHARD\u0026#34;, \u0026#34;offset\u0026#34;: 47, \u0026#34;payload\u0026#34;: \u0026#34;PXdmzb3EhEeNDdn5surg2VNmEdJoIys9\u0026#34;, \u0026#34;partition\u0026#34;: 0} (1 rows) proxy=# EXPLAIN SELECT * FROM main WHERE payload-\u0026gt;\u0026gt;\u0026#39;key\u0026#39; = \u0026#39;847f5dd2-f892-4f56-b04a-b106063cfe0d\u0026#39; AND group_id = \u0026#39;P0\u0026#39;; QUERY PLAN -------------------------------------------------------------------------------- Append (cost=0.00..135.07 rows=2 width=44) -\u0026gt; Seq Scan on main (cost=0.00..0.00 rows=1 width=44) Filter: ((group_id = \u0026#39;P0\u0026#39;::bpchar) AND ((payload -\u0026gt;\u0026gt; \u0026#39;key\u0026#39;::text) = \u0026#39;847f5dd2-f892-4f56-b04a-b106063cfe0d\u0026#39;::text)) -\u0026gt; Foreign Scan on main_shard0 (cost=100.00..135.07 rows=1 width=44) Filter: ((payload -\u0026gt;\u0026gt; \u0026#39;key\u0026#39;::text) = \u0026#39;847f5dd2-f892-4f56-b04a-b106063cfe0d\u0026#39;::text) (5 rows) ¡Espero que te haya gustado el artículo!\n"},{"title":"binlogtop","href":"/es/toolbox/2017/binlogtop/","content":"Currently binlogtop is in archived status and very alpha state, although there is a python script doing sort of the same thing is: binlogEventStats.\nThis tool was used for debugging Binary Log bugs on large transactions that were split between more than one log.\nbinlogtop just do a real-time streaming statistics for helping on these case detections.\nWhat has inside?\nGolang Python "},{"title":"MyRocks Views","href":"/es/toolbox/2017/myrocks-views/","content":"\nMyRocks is an storage engine available also in MongoDB, focused on performance and space saving. It is a LSM tree, with Bloom filtering for unique keys, providing steady performance in limited amount of cache. Installing can be done through a 5.6 fork, repository here.\nInstalling is easy as importing the sql file into your database.\nRepository can be found here. What\u0026rsquo;s inside?\nSQL "},{"title":"Corrección de Go-Plus y Atom GOPATH","href":"/es/posts/2016-10/goplus-and-atom-gopath-fix-howto/","content":"\nEl contexto Golang es un lenguaje increíble, pero lo encontré bastante inestable dentro de las variables de entorno (al menos en macOS Sierra/El Capitan). gvm es tu amigo por cierto, y me ayudó a corregir algunos de los problemas instalando el último release candidate de la serie 1.7.1.\nTen en cuenta que si quieres actualizar tu macOS a Sierra, necesitarás hacer backup de todas tus variables de entorno y reinstalar gvm.\nAtom tiene un plugin para Golang, que es go-plus, y si estás leyendo esto es porque la documentación alrededor no es muy útil.\nEl problema ¡GOPATH no está siendo cargado! También puedes ver varios errores cuando Atom está intentando obtener paquetes usando go-get. Tampoco la variable de entorno GOBIN.\nTambién, he estado teniendo problemas con lo siguiente (gocode es uno de los paquetes para el plugin de Atom, pero sucede con cualquier paquete):\ngo install github.com/nsf/gocode: open /bin/gocode: operation not permitted La solución La solución para GOPATH es simple. Hay una advertencia cuando esto sucede pero ha sido agregada recientemente, con la PISTA de iniciar desde la línea de comandos para corregir esto.\nEso es fácil. Un atom \u0026amp; desde terminal debería corregir esto cargando las variables de entorno. Sin embargo, ten en cuenta que GOBIN necesita estar en el path! Puedes necesitar crear una carpeta bin en tu workspace de go. También, no olvides agregar esas variables en tu archivo shell .*rc (.bashrc, .zshrc, .profile).\np.ej.\nmkdir -p ~/go/bin export GOPATH=$HOME/go export GOBIN=$HOME/go/bin nohup atom \u0026amp; ¡Espero que corrija tu día!\n"},{"title":"Nueva característica pg-stat-ramdisk-size de PostgreSQL RDS y sus cálculos","href":"/es/posts/2016-09/rds-pg-stat-ramdisk-size-on-rds/","content":" NOTA IMPORTANTE: Esto ya ha sido abordado en el core de PostgreSQL, pero esta opción aún está disponible en RDS.\n¿Qué cambia y por qué es tan importante? Rastrear contadores de bases de datos y no solo tablas en Postgres no es barato, pero desde hace algún tiempo había workarounds involucrando la configuración de un ramdisk para colocar el directorio apuntado por la variable GUC stat_temp_directory. Ese directorio coloca un global.stat y archivos de estadísticas por base de datos llamados como db_\u0026lt;oidOfDB\u0026gt;.stat. Aunque el mecanismo para escribir en estos archivos evita flushes extra o innecesarios, es muy intensivo en escritura.\nEste cambio no requiere ningún downtime (en instalaciones standalone), ya que un simple reload forzará al Stat Collector a reescribir los archivos en la carpeta. Hay un blog bastante claro sobre poner stat_temp_directory en un ramdisk.\nEl problema reside en la falta de privilegios de RDS para manipular contenido de archivos o directorios, lo que no te permite verificar el tamaño actual y establecer un valor apropiado. Aunque, probablemente querrás saber que hay un límite de 1 GB para esta configuración en RDS.\nSi no quieres más detalles y quieres aliviar tu almacenamiento, configúralo a 256 MB y continúa con tu vida. Aunque es una configuración grande (el siguiente párrafo explica por qué), no querrás quedarte corto en esto.\nDespués de aplicar el cambio sobre pg_stat_ramdisk_size, verás que la ubicación en RDS ha cambiado:\nshow stats_temp_directory; stats_temp_directory --------------------------- /rdsdbramdisk/pg_stat_tmp TL;DR ¿Cuál es el tamaño esperado del stat_temp_directory? Antes de avanzar, detallemos la estructura de las entradas para el archivo de estadísticas:\nEstructura/Constante Tamaño PGSTAT_FILE_FORMAT_ID 1 byte PgStat_StatTabEntry 164 bytes PgStat_StatFuncEntry 28 bytes closingChar \u0026lsquo;E\u0026rsquo; describers char (T o F en este caso) Primero que nada, como se explicará más adelante, no todas las tablas, índices y funciones se escriben en el archivo de estadísticas de la base de datos. Básicamente, una fórmula básica será:\nSizeOfDBStatFile = PGSTAT_FILE_FORMAT_ID + describers + (tableCount * PgStat_StatTabEntry) + (funcCount * PgStat_StatFuncEntry) + closingChar\nPara obtener el espacio estimado necesario para las tablas actuales en cada base de datos (ten en cuenta que esto considera todas las tablas volcadas en archivo), hay una consulta que puedes ejecutar de forma segura en cada base de datos en tu instancia PostgreSQL (el archivo de estadísticas es uno por base de datos):\nSELECT count(*) * 164 \u0026#34;size in bytes\u0026#34; FROM pg_class WHERE relkind (\u0026#39;r\u0026#39;,\u0026#39;i\u0026#39;,\u0026#39;S\u0026#39;); También, necesitas hacer lo mismo dentro de pg_proc, pero en su lugar el factor será 28 bytes. Necesitarás ejecutar esto en cada base de datos, y sumarlos todos. Esto es para rastrear estadísticas de uso de funciones, que pueden deshabilitarse desde el archivo postgresql.conf con la variable track_functions. También, todos los aspectos de estadísticas de tiempo de ejecución se pueden encontrar aquí.\nEstadísticas Globales Estructura de las estadísticas globales:\nEstructura Tamaño PgStat_StatDBEntry 180 bytes PgStat_GlobalStats 92 bytes PgStat_ArchiverStats 114 bytes describer char (\u0026lsquo;D\u0026rsquo;) El archivo de estadísticas globales es más pequeño, y contiene solo las estadísticas globales y los contadores a través de bases de datos. Debería ser algo cercano a:\nPGSTAT_FILE_FORMAT_ID + describer + PgStat_GlobalStats + PgStat_ArchiverStats + (PgStat_StatDBEntry + describer) * numDatabases.\nEntonces, como puedes ver, la limitación impuesta por AWS en este respecto está muy por encima de la cantidad de datos mantenidos en este directorio en la mayoría de las bases de datos que pueden ejecutarse dentro de las expectativas de RDS.\n¿Por qué afecta a RDS? Antes de que se agregara esta característica, el stat_temp_directory tenía un lugar en la capa de almacenamiento persistente. Esto era lo mismo que cualquier otra instalación de Postgres por defecto, sin embargo debido a las características de almacenamiento de RDS el impacto podría considerarse mayor que una configuración standalone.\nSi tu aplicación es intensiva en escritura, verás el impacto en la latencia de escritura y operaciones.\nUna mirada más profunda Entonces la pregunta no tardó mucho en aparecer en la red y, no fui la excepción. ¿Hay una forma de precalcular el contenido del directorio?\nNo pude terminar con un número exacto sin embargo, puedes saber que el tamaño de los archivos está más relacionado con el número de tablas, índices, funciones y bases de datos. La siguiente estructura es el núcleo de esta implementación. Es tan importante que en realidad tiene un PGSTAT_FILE_FORMAT_ID definido que también se escribe en los archivos de estadísticas.\nTodas las estructuras para estos contenidos de archivos se colocan en el header include/pgstat.h y su implementación se hace en postmaster/pgstat.c (ya que es un worker de inicio). Cada campo que se usa para contadores usa int64 y hay algunos timestampz (64 bits también) con Oid como excepción, que está representado por 32 bits (unsigned int).\nLos backends se comunican con el collector a través de la estructura StatMsgType, cuando es diferente de una estructura cero PgStat_TableCounts. Las estructuras se mantienen en memoria local del backend mientras acumulan contadores. Entonces, eso significa que no todas las tablas, índices y funciones tendrán una entrada.\n¿Qué backends pueden solicitar una escritura de archivo? Todos los backends, el archiver, el bgwriter. Todos ellos usan la misma estructura para pasar los cambios (PgStat_Msg).\nHay 2 funciones para escribir (pgstat_write_db_statsfile, pgstat_write_statsfiles) y 2 para leer (pgstat_read_db_statsfile ,pgstat_read_statsfiles ) cada una de esas controlando ya sea el db_\u0026lt;oid\u0026gt;.stat y global.stat.\nReferencias PgStat_StatDBEntry La estructura HTAB es opaca, y mantiene un mapa hash de tablas y funciones para ser recolectadas. No nos importa el tamaño de estos mapas ya que no se escribirán en el archivo de estadísticas de todos modos. La entrada completa de la base de datos es 22 * 64 bit valores + 1 * 32 bits, por base de datos (180 bytes).\n#define PGSTAT_FILE_FORMAT_ID 0x01A5BC9D typedef struct PgStat_StatDBEntry { /* NOTA: El tipo oid está actualmente implementado como un entero sin signo de cuatro bytes. typedef unsigned int Oid; */ Oid databaseid; PgStat_Counter n_xact_commit; PgStat_Counter n_xact_rollback; PgStat_Counter n_blocks_fetched; PgStat_Counter n_blocks_hit; PgStat_Counter n_tuples_returned; PgStat_Counter n_tuples_fetched; PgStat_Counter n_tuples_inserted; PgStat_Counter n_tuples_updated; PgStat_Counter n_tuples_deleted; TimestampTz last_autovac_time; PgStat_Counter n_conflict_tablespace; PgStat_Counter n_conflict_lock; PgStat_Counter n_conflict_snapshot; PgStat_Counter n_conflict_bufferpin; PgStat_Counter n_conflict_startup_deadlock; PgStat_Counter n_temp_files; PgStat_Counter n_temp_bytes; PgStat_Counter n_deadlocks; PgStat_Counter n_block_read_time; /* tiempos en microsegundos */ PgStat_Counter n_block_write_time; TimestampTz stat_reset_timestamp; TimestampTz stats_timestamp; /* tiempo de actualización del archivo de estadísticas de la base de datos */ /* * las tablas y funciones deben estar al final en la estructura, porque no escribimos * los punteros al archivo de estadísticas. */ HTAB *tables; // definido en utils/hsearch.h HTAB *functions; } PgStat_StatDBEntry; Estructuras En general, este es el tamaño de estructura de cada una:\nEstructura Detalle Total PgStat_StatTabEntry 20 * 64 bits y 1 * 32 Oid (164 bytes) PgStat_StatFuncEntry 3 * 64 bits y 1 * 32 Oid (28 bytes) PgStat_GlobalStats 11 * 64 bits, 8 bytes + 1 * 32 bit, 4 bytes (92 bytes) PgStat_ArchiverStats 4 * 8bytes, 2 char 41 bytes. (114 bytes) ¡Espero que hayas disfrutado el artículo!\n"},{"title":"HOWTO Percona Server con Docker para DBAs","href":"/es/posts/2016-07/percona-docker/","content":"\nAntes de iniciar el contenedor Este artículo no es una explicación introductoria de docker, sin embargo su alcance es para principiantes de docker. Puedes considerarlo como una extensión de la bien documentada documentación de Percona docker hub. Para el código fuente de la imagen, el repositorio está en github.\nAquí está todo lo que necesitas hacer para comenzar:\ndocker run --name percona57 -e MYSQL_ROOT_PASSWORD=\u0026lt;a_password\u0026gt; -d percona:5.7 Para verificar el log de estado del contenedor, puedes ejecutar docker logs percona57.\nLogs adicionales de MySQL Iniciar el contenedor es bastante fácil, pero si no estás muy acostumbrado a Docker, te encontrarás un poco perdido si quieres habilitar logging u otras características.\nPor ejemplo, un contenedor con logging completo se iniciará con esto:\ndocker run --name percona57 -v /var/log/mysql:/var/log/mysql -e MYSQL_ROOT_PASSWORD=mysql -d percona:5.7 --general-log=1 --slow-query-log=1 --long-query-time=0 --log_slow_verbosity=\u0026#39;full, profiling, profiling_use_getrusage\u0026#39; Nota que log_slow_verbosity solo es aplicable para la release de Percona, y agrega salida extra que resulta muy útil cuando haces revisiones de consultas complejas. Como puedes apreciar, todas las opciones se pasan después del nombre de la imagen (percona:5.7).\nAhora, la pregunta es: ¿dónde están los logs? Generalmente, puedes acceder al contenedor usando docker exec -it percona57 bash y ver los logs dentro de él, aunque esta no es la forma más cómoda de hacer esto.\nEn el ejemplo a continuación, usaremos jq (un parser json muy útil).\n3laptop ~ # docker ps CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES cb740be0743c percona:5.7 \u0026#34;docker-entrypoint.sh\u0026#34; 35 minutes ago Up 35 minutes 3306/tcp percona57 3laptop ~ # docker inspect percona57 | jq .[].Mounts [ { \u0026#34;Propagation\u0026#34;: \u0026#34;rprivate\u0026#34;, \u0026#34;RW\u0026#34;: true, \u0026#34;Mode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Destination\u0026#34;: \u0026#34;/var/log/mysql\u0026#34;, \u0026#34;Source\u0026#34;: \u0026#34;/var/log/mysql\u0026#34; }, { \u0026#34;Propagation\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;RW\u0026#34;: true, \u0026#34;Mode\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;Driver\u0026#34;: \u0026#34;local\u0026#34;, \u0026#34;Destination\u0026#34;: \u0026#34;/var/lib/mysql\u0026#34;, \u0026#34;Source\u0026#34;: \u0026#34;/var/lib/docker/volumes/ceda51de62dac317fcafe9dd9e8f9b6f1dc5d70874466b3faf7cdfbcbbc91154/_data\u0026#34;, \u0026#34;Name\u0026#34;: \u0026#34;ceda51de62dac317fcafe9dd9e8f9b6f1dc5d70874466b3faf7cdfbcbbc91154\u0026#34; } ] 3laptop ~ # ls -l /var/lib/docker/volumes/ceda51de62dac317fcafe9dd9e8f9b6f1dc5d70874466b3faf7cdfbcbbc91154/_data ... -rw-r----- 1 maxscale docker 26886023 Jul 2 19:10 cb740be0743c.log -rw-r----- 1 maxscale docker 268834670 Jul 2 19:10 cb740be0743c-slow.log ... Los logs (general y slow) están usando el container id en el nombre del archivo, que se puede apreciar al ejecutar docker ps.\nAcceso a través de la red Obviamente, cuando usas docker en producción, no quieres acceder localmente. Para obtener el host de nuestro contenedor (y todos los contenedores en ejecución), podemos hacer los siguientes comandos:\n3laptop ~ # docker network ls NETWORK ID NAME DRIVER 5fddd2e1c80a bridge bridge e4e0c655e1aa host host 565f4a23d95a none null 3laptop ~ # docker network inspect 5fddd2e1c80a | jq .[].Containers { \u0026#34;cb740be0743cd662c700f73586fe481dc25e4eb27ef94e075c4668a5421eca13\u0026#34;: { \u0026#34;IPv6Address\u0026#34;: \u0026#34;\u0026#34;, \u0026#34;IPv4Address\u0026#34;: \u0026#34;172.17.0.2/16\u0026#34;, \u0026#34;MacAddress\u0026#34;: \u0026#34;02:42:ac:11:00:02\u0026#34;, \u0026#34;EndpointID\u0026#34;: \u0026#34;6dbd28900efe2c6f6edffcbbec0ac7d6446b4336e6e31f018f18d00f1005a812\u0026#34;, \u0026#34;Name\u0026#34;: \u0026#34;percona57\u0026#34; } } Podemos ver que nuestro contenedor percona57 está ejecutándose sobre la dirección IP 172.17.0.2. Para acceder, solo necesitas hacer como de costumbre:\n3laptop ~ # mysql -h 172.17.0.2 -p .... mysql\u0026gt; "},{"title":"patroni-consul-docker","href":"/es/toolbox/2016/patroni_docker/","content":"Patroni and Consul Source at patroni-consul-docker.\n"},{"title":"Random data for PostgreSQL","href":"/es/toolbox/2017/random-databases-postgres/","content":"\nUsually, you want to test code, try new features or just need decent content when generating random data. Random databases for Postgres addresses this need by simple SQL scripts that can be entirely generated inside your database.\nIt isn\u0026rsquo;t a sophisticated repository, although it is practical for quick usage.\nWhat has inside?\nPure SQL If you want to indicate issues, go for it at report issues.\n"},{"title":"Resumen de Full Text Search de InnoDB en MySQL 5.7.","href":"/es/posts/2016-04/fts-in-mysql-5-7-with-innodb/","content":"\nAplicación principal: Resumen de Full Text Search de InnoDB (con Shiny/R)\nCréditos Autor: Emanuel Calvo\nEmpresa: Pythian\nGracias a Valerie Parham-Thompson @ Pythian y Daniel Prince @ Oracle.\nRepositorio disponible en Github.\nPara el artículo completo y la aplicación Shinyapp está disponible aquí.\nAlgunos pensamientos iniciales Hace un par de días uno de nuestros clientes llegó con una pregunta respecto a FTS sobre el motor InnoDB. Aunque la pregunta no se responde en el artículo actual, llegué a la conclusión de que FTS a veces se malinterpreta.\nEl punto de este artículo es mostrar dinámicamente cómo funcionan los algoritmos de búsqueda, usando datos no ficticios (las fuentes de datos se descargaron del proyecto Gutenberg ) dentro de una interfaz fácil.\nPara mostrar los efectos de los tamaños de campo sobre el algoritmo de expansión de consulta, verás dos tablas principales (bookContent y bookContentByLine) ambas conteniendo los mismos libros en diferentes enfoques: por línea y por párrafo. Verás el ruido generado por el algoritmo QUERY EXPANSION cuando las frases son demasiado grandes.\nEl artículo actual ha sido desarrollado usando Shiny/R para permitirte ver los efectos de los algoritmos.\nPor el bien de la simplicidad, en este artículo no pasaremos por los parsers FTS. Probablemente eso sería material para un post futuro.\n¿Por qué considero que FTS a veces se malinterpreta? FTS es una tecnología que puede usarse para cualquier propósito, no solo búsquedas simples. Hay un mito de que FTS solo debería colocarse en clusters para ese propósito, con lo cual estoy de acuerdo. Sin embargo, ciertas reglas de negocio requieren búsquedas complejas, y tener tal característica puede ser una victoria.\nLos RDBMS no son un buen lugar para cantidades masivas de consultas FTS, sin usar ninguna de las capacidades de join que ofrecen, o las quejas ACID.\nComo dije arriba, FTS es totalmente aceptable en RDBMS, si estás usando al menos una característica crítica de RDBMS, requerida por tu modelo de negocio.\n¡Acción! Aquí hay un ejemplo de cómo los ranks difieren entre algoritmos y tamaños de campo usando la palabra \u0026lsquo;country\u0026rsquo;:\nPara comenzar a mostrar los efectos de los algoritmos, el siguiente ejemplo busca la palabra \u0026lsquo;country\u0026rsquo; usando query expansion. Esto significa que no estamos buscando solo las coincidencias exactas, sino también las entradas que aparecen más cuando se ha encontrado la coincidencia exacta.\nEn la cláusula SELECT verás ambas expresiones FTS usando NATURAL LANGUAGE con expansión de consulta y modos BOOLEAN respectivamente.\nset global innodb_ft_aux_table = \u0026#39;ftslab/bookContentByLine\u0026#39;; SELECT content, bookid, group_concat(it.POSITION) as pos, round(MATCH(content) AGAINST (\u0026#34;country\u0026#34; IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION)) as QERank, round(MATCH(content) AGAINST (\u0026#34;country\u0026#34; IN BOOLEAN MODE)) as BoolRank FROM bookContentByLine bl join information_schema.INNODB_FT_INDEX_TABLE it ON (bl.FTS_DOC_ID = it.DOC_ID) WHERE MATCH(content) AGAINST (\u0026#34;country\u0026#34; IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION) AND it.WORD = \u0026#39;country\u0026#39; GROUP BY FTS_DOC_ID ORDER BY 4 DESC LIMIT 10 ; Resultado:\n+-----------------------------------------------------------------------------+--------+-----+--------+--------+ |content | bookid | pos | QERank |BoolRank| +-----------------------------------------------------------------------------+--------+-----+--------+--------+ |\u0026#34;country in September, migrating into Holland, and leave their mates behind\u0026#34; | 15707 | 1 | 105 | 7 | |\u0026#34;unsatisfied desire to serve his country, the two prevalent enthusiasms at\u0026#34; | 15707 | 33 | 98 | 7 | |\u0026#34;Language, Vol. I. p. 212. In this country, where four or five horses travel\u0026#34;| 15707 | 35 | 93 | 7 | |\u0026#34;inflicting immense damage upon the country. Whereupon the Florentines\u0026#34; | 1232 | 36 | 89 | 7 | |\u0026#34;made for a country of twenty or thirty millionsâ€™ population, can be laid\u0026#34; | 39064 | 12 | 89 | 7 | |\u0026#34;The spiders of this country manufacture nets of various forms, adapted to\u0026#34; | 15707 | 21 | 87 | 7 | |\u0026#34;a velvet-covered arm-chair at my head! This country is too decadent\u0026#34; | 33415 | 45 | 86 | 7 | |\u0026#34;country may be ennobled, and under its auspices may be verified that\u0026#34; | 1232 | 1 | 84 | 7 | |\u0026#34;name. The writer of this unpublished pamphlet sees his country in a\u0026#34; | 39064 | 56 | 84 | 7 | |\u0026#34;In our country, Mr. Pennant informs us, that some quails migrate, and\u0026#34; | 15707 | 8 | 83 | 7 | |\u0026#34;all the morning in passing over the adjacent country.\u0026#34; (Voyage to Senegal,\u0026#34; | 15707 | 46 | 82 | 7 | |\u0026#34;the electoral system of the country. Immediately an outcry burst out\u0026#34; | 39064 | 29 | 82 | 7 | |\u0026#34;country, under a most excellent president, wherein all cities had their\u0026#34; | 1232 | 1 | 81 | 7 | |\u0026#34;Though in this country horses shew little vestiges of policy, yet in the\u0026#34; | 15707 | 16 | 81 | 7 | |\u0026#34;country districts. As Lucca had five gates, he divided his own country\u0026#34; | 1232 | 1,63| 80 | 14 | +-----------------------------------------------------------------------------+--------+-----+--------+--------+ 15 rows in set (1,16 sec) El ruido generado por la expansión de consulta es esperado y descrito en la documentación oficial aquí.\nEl caso interesante es la siguiente fila, que tiene 2 ocurrencias exactas y no es el rank más alto usando expansión de consulta . Recuerda, esto es esperado.\nTexto: \u0026#34;country districts. As Lucca had five gates, he divided his own country\u0026#34; bookid: 1232 pos: 1,63 QERank: 80 BoolRank: 14 Esto es aún peor cuando se usan oraciones grandes. En el ejemplo siguiente verás la misma consulta, contra la tabla almacenando por párrafo. El rank booleano muestra algunas de las entradas muy por encima de otras, sin embargo la expansión de consulta ubica en la parte superior registros que no necesariamente tienen muchas coincidencias exactas.\nSET GLOBAL innodb_ft_aux_table = \u0026#39;ftslab/bookContent\u0026#39;; SELECT bookid, FTS_DOC_ID, group_concat(it.POSITION) as positions, round(MATCH(content) AGAINST (\u0026#34;country\u0026#34; IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION)) as QERank, round(MATCH(content) AGAINST (\u0026#34;country\u0026#34; IN BOOLEAN MODE)) as BooleanRank, length(content) as len FROM bookContent bl join information_schema.INNODB_FT_INDEX_TABLE it ON (bl.FTS_DOC_ID = it.DOC_ID) WHERE MATCH(content) AGAINST (\u0026#34;country\u0026#34; IN NATURAL LANGUAGE MODE WITH QUERY EXPANSION) AND it.WORD = \u0026#39;country\u0026#39; GROUP BY FTS_DOC_ID ORDER BY QERank DESC LIMIT 10 ; Resultado:\n+--------+------------+-----------------+--------+-------------+-------+ | bookid | FTS_DOC_ID | positions | QERank | BooleanRank | len | +--------+------------+-----------------+--------+-------------+-------+ | 16452 | 17637 | 942,2552,9084 | 32494 | 10 | 51790 | | 16452 | 17827 | 31699 | 30232 | 3 | 51701 | | 16452 | 17761 | 667,47646 | 29517 | 7 | 50264 | | 16452 | 17791 | 13566 | 28888 | 3 | 49129 | | 16452 | 17927 | 23259,7044 | 26731 | 7 | 48983 | | 16452 | 17839 | 9012,199 | 24933 | 7 | 44451 | | 16452 | 17815 | 29318 | 24745 | 3 | 44011 | | 16452 | 17729 | 895,16485,24034 | 23305 | 10 | 42612 | | 16452 | 17621 | 1765 | 19935 | 3 | 36698 | | 16452 | 17803 | 3942 | 17552 | 3 | 30586 | +--------+------------+-----------------+--------+-------------+-------+ 10 rows in set (1,88 sec) La expansión de consulta es útil cuando intentas buscar qué entradas contienen más palabras que aparecen frecuentemente dentro del término de búsqueda. Tener campos de texto grandes aumenta la probabilidad de tener más palabras que aparecen entre el término de búsqueda. En el caso de la tabla bookContent (tabla por párrafo), el tamaño promedio de campo es r rs$len caracteres.\nLa INNODB_FT_INDEX_TABLE Hay una forma de jugar con el contenido de los índices FTS. Como puedes haber notado en los ejemplos anteriores, usé la declaración set global innodb_ft_aux_table = 'ftslab/bookContent'; , que carga el contenido del índice a memoria para una consulta fácil.\nSi usas RDS, la opción innodb_ft_aux_table no está disponible ya que es GLOBAL y requiere privilegios SUPER.\np.ej. Puedes obtener fácilmente los tokens más frecuentes:\nSELECT WORD,count(*) FROM information_schema.INNODB_FT_INDEX_TABLE group by WORD having count(*) \u0026gt; 1000 order by 2 limit 10; Resultado:\n+--------+----------+ | WORD | count(*) | +--------+----------+ | should | 1023 | | yet | 1027 | | any | 1070 | | like | 1071 | | been | 1073 | | first | 1080 | | nor | 1087 | | your | 1106 | | thou | 1130 | | shall | 1164 | +--------+----------+ 10 rows in set (5,40 sec) Probablemente no es una información muy útil ya que la mayoría de estas palabras aparecen demasiado frecuentemente y son verbos modales, adverbios, pronombres, determinantes, etc. Podría ser el caso de que no estés interesado en indexar esas palabras. Si ese es el caso puedes agregarlas como stopwords en tu propia tabla de stopwords. Especialmente si estás más interesado en búsquedas booleanas, perdiendo alguna parte de las expresiones del lenguaje. Construí una consulta para esta situación para permitirnos construir la tabla de stopwords usando las palabras actuales que queremos agregar al filtrado:\n(ftslab) \u0026gt; select group_concat(WORD) FROM (select distinct WORD FROM information_schema.INNODB_FT_INDEX_TABLE group by WORD having count(*) \u0026gt; 1000) d\\G *************************** 1. row *************************** group_concat(WORD): all,and,any,been,but,can,first,had,has,have,her,him,his,into, its,like,may,more,nor,not,now,one,only,other,our,said,shall,she,should,some,such, than,their,them,then,there,these,they,those,thou,thus,thy,time,were,which,would, yet,you,your 1 row in set (5,28 sec) Construyamos nuestra tabla de filtro usando tanto entradas predeterminadas como nuevas y manteniendo el orden alfabético:\nCREATE TABLE bookContentByLine_stopwords(value VARCHAR(30)) ENGINE = INNODB; INSERT INTO bookContentByLine_stopwords SELECT value FROM ( SELECT value FROM INFORMATION_SCHEMA.INNODB_FT_DEFAULT_STOPWORD UNION SELECT DISTINCT WORD as value FROM information_schema.INNODB_FT_INDEX_TABLE GROUP BY WORD having count(*) \u0026gt; 1000 ) allEntries ORDER BY value ASC; DROP INDEX ftscontent ON bookContentByLine; SET GLOBAL innodb_ft_server_stopword_table = \u0026#39;ftslab/bookContentByLine_stopwords\u0026#39;; CREATE FULLTEXT INDEX ftscontent ON bookContentByLine(content); Verificar el contenido del índice es fácil como:\n(ftslab) \u0026gt; select * from information_schema.INNODB_FT_INDEX_TABLE WHERE lower(WORD) like \u0026#39;%country%\u0026#39;; +------------------+--------------+-------------+-----------+--------+----------+ | WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION | +------------------+--------------+-------------+-----------+--------+----------+ | country | 149 | 787 | 28 | 733 | 265 | | country | 149 | 787 | 28 | 733 | 1342 | | countrydistricts | 733 | 733 | 1 | 733 | 816 | | thecountry | 249 | 733 | 2 | 733 | 750 | +------------------+--------------+-------------+-----------+--------+----------+ 4 rows in set (0,08 sec) (ftslab) \u0026gt; select * from information_schema.INNODB_FT_INDEX_TABLE WHERE DOC_ID = 155 AND lower(WORD) like \u0026#39;%country%\u0026#39;; +---------+--------------+-------------+-----------+--------+----------+ | WORD | FIRST_DOC_ID | LAST_DOC_ID | DOC_COUNT | DOC_ID | POSITION | +---------+--------------+-------------+-----------+--------+----------+ | country | 149 | 787 | 28 | 155 | 31 | | country | 149 | 787 | 28 | 155 | 495 | | country | 149 | 787 | 28 | 155 | 158 | | country | 149 | 787 | 28 | 155 | 525 | +---------+--------------+-------------+-----------+--------+----------+ 4 rows in set (0,09 sec) En el ejemplo mostrado antes no hay intención de comparar puntuaciones de rank ya que están basadas en algoritmos diferentes. La idea allí es mostrar que QUERY EXPANSION puede tener resultados no deseados en algunos casos debido a su mecanismo.\nAvanzando en elegir stop words El artículo completo es increíblemente interesante. En resumen, dice que la palabra más frecuente ocurrirá aproximadamente el doble de veces que la segunda palabra más frecuente, tres veces más a menudo que la tercera palabra más frecuente, y así sucesivamente (la distribución de frecuencia de rank es una relación inversa).\nLa idea aquí es medir cuánto índice ahorramos cortando esas palabras que son extremadamente frecuentes y no agregan un significado necesario a la búsqueda.\nConsideraciones y recomendaciones Usa QUERY EXPANSION solo si estás interesado en buscar relaciones sobre coincidencias exactas. Recuerda que el tamaño del campo es crucial al usar esto. FTS no es el mejor ajuste para coincidencias exactas de strings en columnas únicas. No querrás usar FTS para buscar emails en una sola columna, campos de nombre y apellido, p.ej. Para esos, probablemente usarás otras técnicas como búsquedas inversas o operador de coincidencia exacta (=). Mantén tus índices FTS cortos. No agregues TODAS las columnas de texto. Parsea primero desde tu aplicación la búsqueda del usuario y adapta la consulta. Si estás usando BOOLEAN MODE, puedes usar la puntuación de rank para filtrar filas. MySQL es lo suficientemente inteligente para optimizar las funciones FTS para evitar ejecuciones dobles. Puedes hacer esto usando algo como: match(content,title) against (\u0026quot;first (\u0026lt;second \u0026gt;third)\u0026quot;) \u0026gt; 1 Generalmente, puntuaciones menores que 1 pueden ignorarse cuando se usan búsquedas en modo booleano o natural. OPTIMIZE TABLE hace un rebuild de la tabla. Para evitar esto, configura innodb_optimize_fulltext_only=1 para hacer un mantenimiento incremental en la tabla. Recuerda que NATURAL LANGUAGE MODE no toma los operandos como BOOLEAN MODE. Esto afecta la puntuación de rank (prueba +bad (thing) p.ej.) Si planeas ordenar por rank, no es necesario especificar la cláusula ORDER BY ya que InnoDB hace el orden después de recuperar los doc ids . También, el comportamiento es diferente del predeterminado ya que devuelve los más pesados en la parte superior (como un ORDER BY rank DESC). Si vienes de la implementación FTS de MyISAM, recuerda que la puntuación de rank es diferente. Crea el índice FULLTEXT después de que los datos se carguen InnoDB bulk load. Al restaurar backups FTS, probablemente encontrarás el \u0026ldquo;ERROR 182 (HY000) at line nn: Invalid InnoDB FTS Doc ID\u0026rdquo;. Intenta evitar usar más de una expresión FTS en la cláusula where. Ten en cuenta que esto afecta el orden en los resultados y consume una cantidad considerable de CPU. InnoDB ordena por la última expresión en la cláusula WHERE. WL#7123 También, si evitas la información de rank en la proyección (cláusula SELECT) y usas otras agregaciones como count(*), usará los hints FT \u0026ldquo;no ranking\u0026rdquo;. El hint limit no se usará si se invoca explícitamente un ORDER BY y la cláusula MATCH en la proyección. explain select * from bookContentByLine where match(content) against (\u0026#34;+home\u0026#34; IN BOOLEAN MODE) ORDER BY FTS_DOC_ID LIMIT 10\\G select_type: SIMPLE table: bookContentByLine type: fulltext Extra: Using where; Ft_hints: no_ranking; Using filesort explain select * from bookContentByLine where match(content) against (\u0026#34;+home\u0026#34; IN BOOLEAN MODE) LIMIT 10\\G table: bookContentByLine type: fulltext Extra: Using where; Ft_hints: no_ranking, limit = 10 explain select count(content) from bookContentByLine where match(content) against (\u0026#34;+home\u0026#34; IN BOOLEAN MODE) \\G table: bookContentByLine type: fulltext Extra: Using where; Ft_hints: no_ranking Si planeas usar la columna FTS_DOC_ID con la opción AUTO_INCREMENT, ten en cuenta que hay una limitación respecto a esto. Debes declarar una restricción PRIMARY KEY de columna única o como índice UNIQUE. También, el tipo de datos está restringido como bigint unsigned. p.ej: CREATE TABLE test ( FTS_DOC_ID bigint unsigned AUTO_INCREMENT, mainPk bigint, other text, PRIMARY KEY(mainPk), UNIQUE(FTS_DOC_ID) ); FT_QUERY_EXPANSION_LIMIT Esta variable controla el número de coincidencias principales cuando se usa WITH QUERY EXPANSION (afecta solo MyISAM).\nreferencia\nBug 80347 - Invalid InnoDB FTS Doc ID Enviado https://bugs.mysql.com/bug.php?id=80347\nemanuel@3laptop ~/sandboxes/rsandbox_5_7_9 $ ./m dumpTest \u0026lt; full.dump ERROR 182 (HY000) at line 73: Invalid InnoDB FTS Doc ID emanuel@3laptop ~/sandboxes/rsandbox_5_7_9 $ ./m dumpTest \u0026lt; ddl.dump emanuel@3laptop ~/sandboxes/rsandbox_5_7_9 $ ./m dumpTest \u0026lt; onlyData.dump emanuel@3laptop ~/sandboxes/rsandbox_5_7_9 $ ./m dumpTest \u0026lt; full.dump ERROR 182 (HY000) at line 73: Invalid InnoDB FTS Doc ID mysqldump no es muy inteligente si usas FTS_DOC_ID:\n2016-02-13T22:11:53.125300Z 19 [ERROR] InnoDB: Doc ID 10002 is too big. Its difference with largest used Doc ID 1 cannot exceed or equal to 10000 Toma dumps sin considerar la restricción codificada en innobase/row/row0mysql.cc:\nLas diferencias entre Doc IDs están restringidas dentro de un entero de 4 bytes. Ver fts_get_encoded_len() La corrección para esto es hacer backup de la tabla por chunks de 10000 documentos.\nFine tuning Fine tuning Rendimiento\nCaracterísticas introducidas Mantenimiento innodb_optimize_fulltext_only\nInternos de parsers Escribiendo plugins parser FTS\nVersión Este documento R Markdown está hecho interactivo usando Shiny. A diferencia del flujo de trabajo más tradicional de crear reportes estáticos, ahora puedes crear documentos que permiten a tus lectores cambiar las suposiciones subyacentes a tu análisis y ver los resultados inmediatamente.\nPara aprender más, ver Documentos Interactivos.\n"},{"title":"Trucos de Stream XtraBackup para acelerar transferencias de backup","href":"/es/posts/2016-04/multistream/","content":"Descripción del problema Algunos sistemas tienen un bug que hace que xbstream falle al crear el directorio de destino, necesitas forzar su creación. Esta es la razón, que en este post, agregaré pasos adicionales antes de abrir el proceso xbstream.\nxtrabackup_56: Error writing file \u0026#39;UNOPENED\u0026#39; (Errcode: 32 - Broken pipe) xb_stream_write_data() failed. Actualización: ha pasado mucho tiempo desde que las nuevas versiones no han sido probadas por mí, así que este bug podría estar corregido ahora.\nComprimiendo y descomprimiendo para superar redes lentas En el siguiente ejemplo, vamos a trabajar alrededor de este bug, hacer stream del backup, descomprimir, y aplicar los eventos de binary logs. Si tienes muchos cores, podrías preferir comprimir y descomprimir agresivamente, especialmente si tu transferencia de red no es tan rápida.\ninnobackupex --parallel 50 --slave-info \\ --ibbackup=/usr/bin/xtrabackup --tmpdir=/srv/ --stream=xbstream /srv/ \\ | pigz | ssh host.com \u0026#34;(cd /srv/mysql; gzip -dc | xbstream -x)\u0026#34; Usando xbstream sería algo así:\ninnobackupex --no-lock --slave-info --socket=/data/mysql/mysql.sock \\ --stream=xbstream --safe-slave-backup --tmpdir=/data/temp --user= \\ --password=\u0026#34;\u0026lt;password\u0026gt;\u0026#34; --parallel=4 ./ | pigz | \\ ssh host.com \u0026#34;(mkdir /data/mysql 2\u0026gt; /dev/null; cd /data/mysql; gzip -dc \\ | xbstream -x /data/mysql; chown -R mysql: /data/mysql) \\ \u0026amp;\u0026amp; innobackupex --apply-log /data/mysql \u0026amp;\u0026amp; chown -R mysql: /data/mysql\u0026#34; Haciendo stream a más de un host Hacer stream del mismo backup a 2 hosts diferentes es posible, y el truco se puede hacer de esta manera:\ntime innobackupex --no-lock --slave-info --socket=/data/mysql/mysql.sock \\ --stream=xbstream --safe-slave-backup --tmpdir=/data/temp --user=myuser \\ --password=\u0026#34;\u0026lt;password\u0026gt;\u0026#34; --parallel=4 ./ | \\ tee \u0026gt;(ssh root@host1 \u0026#34;(mkdir /data/mysql 2\u0026gt; /dev/null; cd /data/mysql; xbstream -x /data/mysql; chown -R mysql: /data/mysql) \\ \u0026amp;\u0026amp; innobackupex --apply-log /data/mysql\u0026#34;) \\ \u0026gt;(ssh root@host2 \u0026#34;(mkdir /data/mysql 2\u0026gt; /dev/null; cd /data/mysql; xbstream -x /data/mysql; chown -R mysql: /data/mysql) \\ \u0026amp;\u0026amp; innobackupex --apply-log /data/mysql \u0026amp;\u0026amp; chown -R mysql: /data/mysql \u0026#34;) \u0026gt; /dev/null Con un usuario anonimizado:\ninnobackupex --no-lock --slave-info \\ --tmpdir=/tmp/xtra --stream=xbstream --safe-slave-backup --parallel=4 ./ \\ | tee \u0026gt;(ssh host1.com \u0026#34;(mkdir /var/lib/mysql/data/ 2\u0026gt; /dev/null; cd /var/lib/mysql/data/; xbstream -x /var/lib/mysql/data/ ) \\ \u0026amp;\u0026amp; innobackupex --apply-log /var/lib/mysql/data/\u0026#34;) \\ \u0026gt;(ssh host2.com \u0026#34;(mkdir /var/lib/mysql/data/ 2\u0026gt; /dev/null; cd /var/lib/mysql/data/; xbstream -x /var/lib/mysql/data/ ) \\ \u0026amp;\u0026amp; innobackupex --apply-log /var/lib/mysql/data/ \u0026#34;) \u0026gt; /dev/null "},{"title":"Inyección de datos multi-fuente a Postgres RDS con soporte de encriptación y FTS","href":"/es/posts/2016-03/multi-source-data-injection-to-postgres-rds-with-encryption-and-fts-support/","content":"\nPatrocinado: Pythian Inc.\nℹ️ \u0026nbsp; Todo el trabajo en este artículo es un PoC. ℹ️ \u0026nbsp; Ver cumplimiento HIPAA. KMS/RDS El POC en este artículo fue desarrollado antes del lanzamiento del servicio de gestión de claves para RDS.\nDesaconsejo totalmente usar el enfoque actual para encriptar datos. Usa REST.\nIntroducción He estado tratando con un problema que llegó a mi escritorio de personas de la comunidad, respecto a RDS y reglas HIPAA. Había un escenario confuso sobre si PostgreSQL estaba usando FTS y encriptación en RDS. Hay muchos detalles respecto a la arquitectura, sin embargo creo que no será necesario profundizar muy profundamente para entender los conceptos básicos del motivo del presente artículo.\nLas reglas HIPAA son complejas y si necesitas lidiar con ellas, probablemente necesitarás pasar por una lectura cuidadosa.\ntl;dr? nos dicen que almacenemos datos encriptados en servidores que no están en las instalaciones. Y ese es el caso de RDS. Sin embargo, todas las comunicaciones están encriptadas usando protocolo SSL, pero no es suficiente para cumplir con las reglas HIPAA.\nLos recursos de CPU en RDS son caros y no estables a veces, lo que hace que las características de encriptación y FTS no sean muy adecuadas para este tipo de servicio. No digo que no puedas implementarlas, solo ten en cuenta que una CPU estándar contra vCPU podría tener mucha diferencia. Si quieres hacer benchmark de tu CPU local contra vCPU de RDS, puedes ejecutar la siguiente consulta dentro de psql en ambas instancias:\n\\o /dev/null \\timing SELECT convert_from( pgp_sym_decrypt_bytea( pgp_sym_encrypt_bytea(\u0026#39;Text to be encrypted using pgp_sym_decrypt_bytea\u0026#39; || gen_random_uuid()::text::bytea,\u0026#39;key\u0026#39;, \u0026#39;compress-algo=2\u0026#39;), \u0026#39;key\u0026#39;), \u0026#39;SQL-ASCII\u0026#39;) FROM generate_series(1,10000); Hay muchas cosas y funciones que puedes combinar del paquete pgcrypto (verás que el repositorio contempla todas ellas). Intentaré publicar otro blog post respecto a este tipo de benchmarks. Mientras tanto, esta consulta debería ser suficiente para tener una idea aproximada de la diferencia de rendimiento entre la instancia RDS vCPU y tus CPUs del servidor.\nConceptos básicos de arquitectura Para este POC vamos a almacenar FTS y claves GPG localmente, en una instancia PostgreSQL simple y, usando un trigger, encriptar y subir transparentemente a RDS usando el FDW estándar (Foreign Data Wrappers).\nTen en cuenta que la comunicación RDS ya está encriptada vía SSL cuando los datos fluyen entre servidor/cliente. Es importante aclarar esto, para evitar confusiones entre encriptación de comunicación y almacenar datos encriptados.\nEl trigger simple dividirá los datos no encriptados entre una tabla local almacenando en una columna tsvector (jsonb en el TODO), encriptará y empujará los datos encriptados a RDS usando FDW (el paquete postgres_fdw estándar).\nUna vista de vuelo simple de la idea se puede observar en la imagen a continuación.\nFuente\nEstructura RDS y estructura local reflejada con FDW La estructura del esquema de la instancia RDS contiene una tabla padre, un trigger de particionamiento y su trigger:\nCREATE SCHEMA enc_schema; SET search_path TO enc_schema; -- Encriptando localmente, por eso no necesitamos referenciar la clave aquí. CREATE TABLE enc_schema.__person__pgp ( id bigint, source varchar(8), partial_ssn varchar(4), -- Campo no encriptado para otros propósitos de búsqueda rápida ssn bytea, keyid varchar(16), fname bytea, lname bytea, description bytea, auth_drugs bytea, -- Este es un vector de texto encriptado patology bytea, PRIMARY KEY(id,source) ); CREATE INDEX ON enc_schema.__person__pgp (partial_ssn); CREATE OR REPLACE FUNCTION basic_ins_trig() RETURNS trigger LANGUAGE plpgsql AS $basic_ins_trig$ DECLARE compTable text := TG_RELID::regclass::text ; childTable text := compTable || \u0026#39;_\u0026#39; || NEW.source ; statement text := \u0026#39;INSERT INTO \u0026#39; || childTable || \u0026#39; SELECT (\u0026#39; || QUOTE_LITERAL(NEW) || \u0026#39;::\u0026#39; || compTable || \u0026#39;).*\u0026#39; ; createStmt text := \u0026#39;CREATE TABLE \u0026#39; || childTable || \u0026#39;(CHECK (source =\u0026#39; || quote_literal(NEW.source) || \u0026#39;)) INHERITS (\u0026#39; || compTable || \u0026#39;)\u0026#39;; indexAdd1 text := \u0026#39;CREATE INDEX ON \u0026#39; || childTable || \u0026#39;(source,id)\u0026#39; ; indexAdd2 text := \u0026#39;CREATE INDEX ON \u0026#39; || childTable || \u0026#39;(source,ssn)\u0026#39; ; BEGIN BEGIN EXECUTE statement; EXCEPTION WHEN undefined_table THEN EXECUTE createStmt; EXECUTE indexAdd1; EXECUTE indexAdd2; EXECUTE statement; END; RETURN NULL; END; $basic_ins_trig$; CREATE TRIGGER part_person_pgp BEFORE INSERT ON __person__pgp FOR EACH ROW EXECUTE PROCEDURE basic_ins_trig() ; No vamos a usar la columna partial SSN en los ejemplos, pero la encontré muy útil para hacer búsquedas RDS sobre datos encriptados sin caer en la necesidad de desencriptar sobre la marcha el SSN. Los últimos 4 dígitos del SSN no proporcionan información útil si se roban.\nTambién, la magia de la inyección de datos multi-fuente viene de la clave compuesta usando un bigint y una etiqueta de fuente.\nBásicamente, puedes pensar en los nodos locales como proxies. Puedes insertar datos en cada nodo, pero los datos apuntarán a la instancia RDS.\nSi estás planeando gestionar grandes cantidades de datos, puedes particionar la tabla en RDS, permitiendo una mejor organización para la gestión de datos.\nNo verás índices sobre datos encriptados\nEstructura de nodos locales:\nCREATE DATABASE fts_proxy; -- conectar usando \\c fts_proxy en psql -- La salsa CREATE EXTENSION postgres_fdw; CREATE EXTENSION pgcrypto; CREATE SERVER RDS_server FOREIGN DATA WRAPPER postgres_fdw OPTIONS (host \u0026#39;dbtest1.chuxsnuhtvgl.us-east-1.rds.amazonaws.com\u0026#39;, port \u0026#39;5432\u0026#39;, dbname \u0026#39;dbtest\u0026#39;); CREATE USER MAPPING FOR postgres SERVER RDS_server OPTIONS (user \u0026#39;dbtestuser\u0026#39;, password \u0026#39;\u0026lt;shadowed\u0026gt;\u0026#39;); CREATE FOREIGN TABLE __person__pgp_RDS ( id bigint, source varchar(8), partial_ssn varchar(4), -- Campo no encriptado para otros propósitos de búsqueda rápida ssn bytea, keyid varchar(16), fname bytea, lname bytea, description bytea, auth_drugs bytea, -- Este es un vector de texto encriptado patology bytea ) SERVER RDS_server OPTIONS (schema_name \u0026#39;enc_schema\u0026#39;, table_name \u0026#39;__person__pgp\u0026#39;); Misma tabla. Cada vez que queramos tratar con la tabla RDS, lo haremos usando la tabla __person__pgp_RDS, que es solo una tabla de mapeo. Podemos consultar esta tabla como cualquier otra tabla usual.\nPara propósitos de prueba, también creé una tabla con la misma estructura que la anterior con el nombre de tabla __person_rds_RDS_URE y agregué la opción use_remote_estimate 'true'. Cuando está habilitado, postgres_fdw obtiene el conteo de filas y estimaciones del servidor remoto.\nInsertando claves localmente Solo para evitar un artículo extendido, omitiré los comandos de creación de claves GPG aquí. Por favor sigue las instrucciones en el enlace en la sección de referencia sobre claves.\nPodemos insertar las claves de varias formas, pero encontré muy conveniente usar características de psql para hacerlo. Una vez que las claves están en su lugar puedes usar el comando \\lo_import:\npostgres=# \\lo_import /var/lib/postgresql/9.4/main/private.key lo_import 33583 postgres=# \\lo_import /var/lib/postgresql/9.4/main/public.key lo_import 33584 Los siguientes pasos son muy directos. En un escenario real, probablemente no querrás subir claves privadas a la tabla, solo por propósitos prácticos de este artículo voy a hacerlo (solo para desencriptar datos en la consulta SELECT).\npgp_key_id devolverá la misma clave sin importar si usas clave privada o pública.\nCREATE TABLE keys ( keyid varchar(16) PRIMARY KEY, pub bytea, priv bytea ); INSERT INTO keys VALUES ( pgp_key_id(lo_get(33583)) ,lo_get(33584), lo_get(33583)); Dividiendo datos a FTS, encriptar y empujar a RDS Ahora, aquí es cuando comienza la parte complicada. Vamos a lograr algunas funcionalidades:\nVamos a simular routing usando herencia en los registros FTS. Eso nos permitirá dividir datos como queramos y, replicar usando la característica Logical Decoding entre los nodos. No incluiré esto en el artículo actual solo para evitar que sea extenso. Vamos a encriptar usando la clave que seleccionamos en la consulta de inserción. Si quieres una clave por tabla, encontrarás más fácil hardcodear el id de clave en _func_get_FTS_encrypt_and_push_to_RDS. Una vez que los registros están encriptados, la función insertará esos registros en la tabla foránea (RDS). Al consultar la tabla FTS, podremos determinar la fuente (algo como la técnica routing, encontrarás esto familiar si jugaste con ElasticSearch). Eso nos permite hacer la búsqueda FTS transparente para la aplicación, apuntando siempre a la tabla padre. :dogewow: ¿No es genial Postgres? :o\nEstructuras de tabla FTS -- Tabla padre CREATE TABLE local_search ( id bigint PRIMARY KEY, _FTS tsvector ); CREATE INDEX fts_index ON local_search USING GIST(_FTS); -- Tabla hija, sufijo local_search_\u0026lt;source\u0026gt; CREATE TABLE local_search_host1 () INHERITS (local_search); CREATE INDEX fts_index_host1 ON local_search_host1 USING GIST(_FTS); Haciendo esto, evitas tener una columna con un valor constante en la tabla, consumiendo espacio innecesario. Puedes tener con este método, diferentes nombres y tablas a través del cluster, pero siempre usando la misma consulta contra local_search. Puedes map/reduce los datos si quieres a través de los nodos, con la misma consulta.\nNo es necesario tener solo 1 fuente o ruta por nodo. El único requisito para esto es tener diferentes rutas por nodo (combinar fuente y ruta podría aumentar la complejidad, sin embargo es posible).\nCódigo principal CREATE SEQUENCE global_seq INCREMENT BY 1 MINVALUE 1 NO MAXVALUE; CREATE TABLE __person__pgp_map ( keyid varchar(16), source varchar(8), ssn bigint, fname text, lname text, description text, auth_drugs text[], -- Este es un vector de texto encriptado patology text ); CREATE OR REPLACE FUNCTION _func_get_FTS_encrypt_and_push_to_RDS() RETURNS \u0026#34;trigger\u0026#34; AS $$ DECLARE secret bytea; RDS_MAP __person__pgp_RDS%ROWTYPE; FTS_MAP local_search%ROWTYPE; BEGIN SELECT pub INTO secret FROM keys WHERE keyid = NEW.keyid; RDS_MAP.source := NEW.source; RDS_MAP.fname := pgp_pub_encrypt(NEW.fname, secret); RDS_MAP.lname := pgp_pub_encrypt(NEW.lname, secret); RDS_MAP.auth_drugs := pgp_pub_encrypt(NEW.auth_drugs::text, secret); RDS_MAP.description := pgp_pub_encrypt(NEW.description, secret); RDS_MAP.patology := pgp_pub_encrypt(NEW.patology, secret); RDS_MAP.ssn := pgp_pub_encrypt(NEW.ssn::text, secret); RDS_MAP.partial_ssn := right( (NEW.ssn)::text,4); RDS_MAP.id := nextval(\u0026#39;global_seq\u0026#39;::regclass); RDS_MAP.keyid := NEW.keyid; FTS_MAP.id := RDS_MAP.id; FTS_MAP._FTS := (setweight(to_tsvector(NEW.fname) , \u0026#39;B\u0026#39; ) || setweight(to_tsvector(NEW.lname), \u0026#39;A\u0026#39;) || setweight(to_tsvector(NEW.description), \u0026#39;C\u0026#39;) || setweight(to_tsvector(NEW.auth_drugs::text), \u0026#39;C\u0026#39;) || setweight(to_tsvector(NEW.patology), \u0026#39;D\u0026#39;) ) ; -- Ambas tablas contienen mismo id,source INSERT INTO __person__pgp_RDS SELECT (RDS_MAP.*); EXECUTE \u0026#39;INSERT INTO local_search_\u0026#39; || NEW.source || \u0026#39; SELECT (\u0026#39; || quote_literal(FTS_MAP) || \u0026#39;::local_search).* \u0026#39;; RETURN NULL; END; $$ LANGUAGE plpgsql; CREATE TRIGGER trigger_befInsRow_name_FTS BEFORE INSERT ON __person__pgp_map FOR EACH ROW EXECUTE PROCEDURE _func_get_FTS_encrypt_and_push_to_RDS(); Esta función hace todo. Inserta los datos en RDS y divide los datos en la tabla hija FTS correspondiente. Por propósitos de rendimiento, no quise capturar excepciones en el momento de inserción (si la tabla hija no existe, p.ej.), pero también puedes agregar esta característica con un bloque de excepción como sigue:\nBEGIN EXECUTE \u0026#39;INSERT INTO local_search_\u0026#39; || NEW.source || \u0026#39; SELECT (\u0026#39; || quote_literal(FTS_MAP) || \u0026#39;::local_search).* \u0026#39;; EXCEPTION WHEN undefined_table THEN EXECUTE \u0026#39;CREATE TABLE local_search_\u0026#39; || NEW.source || \u0026#39;() INHERITS local_search\u0026#39;; END; Lo mismo se puede hacer sobre la tabla foránea. Más información en \u0026ldquo;Class HV — Foreign Data Wrapper Error (SQL/MED)\u0026rdquo; (HV00R -fdw_table_not_found).\nRevisa \u0026ldquo;Appendix A. PostgreSQL Error Codes\u0026rdquo; en el manual oficial para referencias sobre códigos de error.\nInsertando datos En el momento de inserción, vamos a empujar datos a través de una tabla de mapeo. La razón de esto es que todos los datos encriptados se almacenan en tipo de datos bytea, y queremos tener consultas claras en su lugar.\nUna consulta de datos aleatorios se verá como:\nINSERT INTO __person__pgp_map SELECT \u0026#39;host1\u0026#39;, -- source: host1 -- Puedes hacer esto mejor obteniendo estos datos de una ubicación -- persistente \u0026#39;76CDA76B5C1EA9AB\u0026#39;, round(random()*1000000000), (\u0026#39;{Romulo,Ricardo,Romina,Fabricio,Francisca,Noa,Laura,Priscila,Tiziana,Ana,Horacio,Tim,Mario}\u0026#39;::text[])[round(random()*12+1)], (\u0026#39;{Perez,Ortigoza,Tucci,Smith,Fernandez,Samuel,Veloso,Guevara,Calvo,Cantina,Casas,Korn,Rodriguez,Ike,Baldo,Vespi}\u0026#39;::text[])[round(random()*15+1)], (\u0026#39;{some,random,text,goes,here}\u0026#39;::text[])[round(random()*5+1)] , get_drugs_random(round(random()*10)::int), (\u0026#39;{Anotia,Appendicitis,Apraxia,Argyria,Arthritis,Asthma,Astigmatism,Atherosclerosis,Athetosis,Atrophy,Abscess,Influenza,Melanoma}\u0026#39;::text[])[round(random()*12+1)] FROM generate_series(1,50) ; ¿Viste el comentario interno? Bueno, probablemente quieras dividir por customer o cualquier otro alias. Estoy usando este texto hardcodeado feo solo para evitar un artículo largo.\nTambién, si quieres evitar hardcodear tanto como sea posible, puedes considerar usar una función que devuelva el nombre del host o etiqueta de routing.\nConsultando los datos ¡Casi terminamos! Ahora podemos hacer algunas consultas. Aquí hay algunos ejemplos:\nLimitando las coincidencias:\n# SELECT convert_from(pgp_pub_decrypt(ssn::text::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name) # FROM __person__pgp_rds as rds JOIN # keys ks USING (keyid) # WHERE rds.id IN ( # select id # from local_search # where to_tsquery(\u0026#39;Asthma | Athetosis\u0026#39;) @@ _fts LIMIT 5) # AND rds.source = \u0026#39;host1\u0026#39;; source | convert_from --------+-------------- host1 | 563588056 (1 row) Todas las coincidencias y verificación doble de dónde vinieron los datos:\n# SELECT ls.tableoid::regclass, rds.source, # convert_from(pgp_pub_decrypt(ssn::text::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name) # FROM local_search ls JOIN # __person__pgp_rds as rds USING (id), # keys ks # WHERE to_tsquery(\u0026#39;Asthma | Athetosis\u0026#39;) @@ ls._fts; tableoid | source | convert_from -------------------+--------+-------------- local_search_host1 | host1 | 563588056 (1 row) Y, no podemos terminar el artículo sin mostrar cómo usar el ranking (¿viste esas funciones setweight usadas en la función? ¡Lo entendiste!):\n# SELECT rds.id, # convert_from(pgp_pub_decrypt(fname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), # convert_from(pgp_pub_decrypt(lname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), # ts_rank( ls._FTS, query ) as rank # FROM local_search ls JOIN # __person__pgp_rds as rds ON (rds.id = ls.id AND rds.source = \u0026#39;host1\u0026#39;) JOIN # keys ks USING (keyid), # to_tsquery(\u0026#39;Mario | Casas | (Casas:*A \u0026amp; Mario:*B) \u0026#39;) query # WHERE # ls._FTS @@ query # ORDER BY rank DESC; id | convert_from | convert_from | rank ----+--------------+--------------+---------- 43 | Mario | Casas | 0.425549 61 | Ana | Casas | 0.303964 66 | Horacio | Casas | 0.303964 (3 rows) Recuerda, piensa que esta consulta está haciendo FTS, desencriptación y ranking en solo una consulta, sobre un servidor local y un servidor remoto. ¡No puedes decir que PostgreSQL no es lo suficientemente hipster!\nNo puedo continuar el artículo sin mostrar el plan de consulta ejecutado por el host local (usando buffers, analyze y opciones verbose):\nEXPLAIN (buffers,verbose,analyze) SELECT rds.id, convert_from(pgp_pub_decrypt(fname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), convert_from(pgp_pub_decrypt(lname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), ts_rank( ls._FTS, query ) as rank FROM local_search ls JOIN __person__pgp_rds as rds ON (rds.id = ls.id AND rds.source = \u0026#39;host1\u0026#39;) JOIN keys ks USING (keyid), to_tsquery(\u0026#39;Mario | Casas | (Casas:*A \u0026amp; Mario:*B) \u0026#39;) query WHERE ls._FTS @@ query ORDER BY rank DESC; .... -\u0026gt; Materialize (cost=100.00..117.09 rows=3 width=122) (actual time=62.946..62.971 rows=50 loops=9) Output: rds.id, rds.fname, rds.lname, rds.keyid -\u0026gt; Foreign Scan on public.__person__pgp_rds rds (cost=100.00..117.07 rows=3 width=122) (actual time=566.495..566.520 rows=50 loops=1) Output: rds.id, rds.fname, rds.lname, rds.keyid Remote SQL: SELECT id, keyid, fname, lname FROM enc_schema.__person__pgp WHERE ((source = \u0026#39;host1\u0026#39;::text)) ... Planning time: 4.931 ms Execution time: 2115.919 ms (45 rows) Del extracto del Plan de Consulta anterior, podemos ver que el particionamiento en RDS es transparente para la consulta. El nodo de ejecución a cargo de extraer datos del RDS es el Foreign Scan, que también proporciona la consulta ejecutada remotamente.\nEspera un minuto. Parece que el SQL remoto es de alguna manera peligroso de ejecutar. ¡No está usando el id! Hay una razón para eso, y está relacionada con cómo postgres recopila las estadísticas de la tabla foránea. Si uso las estimaciones remotas podemos ver cómo cambia el SQL remoto en el Plan de Consulta:\nEXPLAIN (ANALYZE, VERBOSE, BUFFERS) SELECT rds.id, convert_from(pgp_pub_decrypt(fname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), convert_from(pgp_pub_decrypt(lname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), ts_rank( ls._FTS, query ) as rank FROM local_search ls, __person__pgp_rds_URE rds JOIN keys ks USING (keyid), to_tsquery(\u0026#39;Mario | Casas | (Casas:*A \u0026amp; Mario:*B) \u0026#39;) query WHERE rds.id = ls.id AND rds.source = \u0026#39;host1\u0026#39; AND ls._FTS @@ query ORDER BY rank DESC; Plan de Consulta (nodo de ejecución Foreign Scan):\n... -\u0026gt; Foreign Scan on public.__person__pgp_rds_ure rds (cost=100.01..108.21 rows=2 width=1018) (actual time=250.334..250.336 rows=1 loops=31) Output: rds.id, rds.source, rds.partial_ssn, rds.ssn, rds.keyid, rds.fname, rds.lname, rds.description, rds.auth_drugs, rds.patology Remote SQL: SELECT id, keyid, fname, lname FROM enc_schema.__person__pgp WHERE ((source = \u0026#39;host1\u0026#39;::text)) AND (($1::bigint = id)) ... Las tablas foráneas también necesitan que se actualicen las estadísticas locales. En los siguientes ejemplos hay 3 consultas: usando use_remote_estimate, sin ANALYZE previo y sin use_remote_estimate y una consulta usando las estimaciones locales (__person_pgp_rds) después de emitir ANALYZE y sin URE.\nfts_proxy=# \\o /dev/null fts_proxy=# SELECT rds.id, convert_from(pgp_pub_decrypt(fname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), convert_from(pgp_pub_decrypt(lname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), ts_rank( ls._FTS, query ) as rank FROM local_search ls, __person__pgp_rds_URE rds JOIN keys ks USING (keyid), to_tsquery(\u0026#39;Mario | Casas | (Casas:*A \u0026amp; Mario:*B) \u0026#39;) query WHERE rds.id = ls.id AND rds.source = \u0026#39;host1\u0026#39; AND ls._FTS @@ query ORDER BY rank DESC; Time: 12299,691 ms fts_proxy=# SELECT rds.id, convert_from(pgp_pub_decrypt(fname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), convert_from(pgp_pub_decrypt(lname::bytea, ks.priv,\u0026#39;\u0026#39;::text)::bytea,\u0026#39;SQL_ASCII\u0026#39;::name), ts_rank( ls._FTS, query ) as rank FROM local_search ls, __person__pgp_rds rds JOIN keys ks USING (keyid), to_tsquery(\u0026#39;Mario | Casas | (Casas:*A \u0026amp; Mario:*B) \u0026#39;) query WHERE rds.id = ls.id AND rds.source = \u0026#39;host1\u0026#39; AND ls._FTS @@ query ORDER BY rank DESC; Time: 20249,719 ms -- DESPUÉS DE ANALYZE en la TABLA FORÁNEA __person_pgp_rds (en el servidor local) Time: 1656,912 ms Después de analizar ambas tablas foráneas, la diferencia de tiempo de ejecución se calculó en 11% a favor de usar estimaciones locales.\nNOTA sobre UPDATES: es necesario codificar el trigger UPDATE también, para desencriptar, modificar y re-encriptar los datos.\nEl tipo de datos Json/jsonb está aquí para ayudar Puedes colapsar todos los datos y usar tipo de datos json en la tabla de mapeo y foránea, permitiéndote evitar el dolor de apuntar y desencriptar datos por columna.\nColoca todas las columnas encriptadas en una columna bytea en RDS. La tabla de mapeo se verá como sigue:\nCREATE TABLE __person__pgp_map ( keyid varchar(16), source varchar(8), ssn bigint, data jsonb ); En el momento de inserción, solo usa una columna json en lugar de por columna. Ten en cuenta que necesitarás lidiar dentro del contenido json. Encontré usar esto más fácil para insertar, pero el FTS necesita algo de limpieza para evitar insertar nombres de columnas en el campo _fts en las tablas local_search. También, para actualizaciones, el tipo de datos jsonb necesitará trabajo extra al extraer atributos.\nFunciones adicionales usadas aquí En la declaración de inserción anterior, verás una función definida por el usuario que obtiene un vector de longitud aleatoria de drogas. Está implementada usando el siguiente código:\nCREATE TABLE drugsList ( id serial PRIMARY KEY, drugName text); INSERT INTO drugsList(drugName) SELECT p.nameD FROM regexp_split_to_table( \u0026#39;Acetaminophen Adderall Alprazolam Amitriptyline Amlodipine Amoxicillin Ativan Atorvastatin Azithromycin Ciprofloxacin Citalopram Clindamycin Clonazepam Codeine Cyclobenzaprine Cymbalta Doxycycline Gabapentin Hydrochlorothiazide Ibuprofen Lexapro Lisinopril Loratadine Lorazepam Losartan Lyrica Meloxicam Metformin Metoprolol Naproxen Omeprazole Oxycodone Pantoprazole Prednisone Tramadol Trazodone Viagra Wellbutrin Xanax Zoloft\u0026#39;, \u0026#39;\\n\u0026#39;) p(nameD); CREATE OR REPLACE FUNCTION get_drugs_random(int) RETURNS text[] AS $BODY$ WITH rdrugs(dname) AS ( SELECT drugName FROM drugsList p ORDER BY random() LIMIT $1 ) SELECT array_agg(dname) FROM rdrugs ; $BODY$ LANGUAGE \u0026#39;sql\u0026#39; VOLATILE; Referencias Un tutorial muy increíble sobre FTS para PostgreSQL se puede encontrar aquí.\nFuente para lista de drogas Fuente para enfermedades Empezando con claves GPG Herramienta de línea de comandos AWS Discusión en la lista de correo de la comunidad aquí.\n"},{"title":"twitter_parser","href":"/es/toolbox/2014/twitter_parser/","content":"twitter_parser Source at twitter_parser\nThis small extension was coded for a presentation in Brazil, about injecting data from queues into the database and parse tweets comments before they are stored in the database.\nUpdate: the code as of now might not work under new PG versions, but it shouldn\u0026rsquo;t be such a big effort to bring it up to date.\n"}]