Cómo migré una base de datos transaccional PostgreSQL de 3TB.

Geronimo Velasco
17 min readAug 3, 2023

--

Cómo migré una base de datos transaccional de PostgreSQL de 3TB.

En el acelerado mundo de las startups, no es raro que equipos pequeños de desarrolladores se encarguen de un proyecto completo. Experimenté esto de primera mano como desarrollador full stack en una consultora de transporte, donde me convertí en el único responsable de un proyecto crítico. Con mis compañeros de equipo habiendo pasado a otras empresas, la responsabilidad de enfrentar los desafíos del proyecto recayó completamente en mis hombros.

Nuestro proyecto se enfocaba en gestionar transacciones dentro de sistemas de transporte, procesar datos entrantes para crear informes e información esencial. Al lidiar con un flujo constante de nuevas transacciones, quedó claro que necesitábamos una solución de base de datos confiable y eficiente.

En medio del ajetreo diario de administrar el sistema, recibimos una solicitud de cliente que cambiaría el juego: migrar nuestra colosal base de datos transaccional PostgreSQL de 3TB de AWS a Azure DevOps. El motivo detrás de este movimiento era cristalino: consolidar todo nuestro sistema dentro del ecosistema de Azure, optimizando operaciones y liberando el potencial de crecimiento.

Sin embargo, el reloj estaba en marcha; nos enfrentábamos a un desafiante reto. El cliente estableció un plazo ajustado, otorgándonos apenas 3 semanas para llevar a cabo toda la migración. La urgencia de la tarea agregó una capa adicional de presión, ya que sabíamos que el éxito del proyecto dependía de una planificación meticulosa, una ejecución impecable y un toque de innovación.

En este artículo, compartiré mis valiosas experiencias e ideas obtenidas durante el proceso de migración. Aunque no soy un experto en PostgreSQL, mi trayecto como un desarrollador solitario navegando esta tarea compleja estuvo lleno de desafíos, descubrimientos significativos y el desarrollo de mejores prácticas que creo serán de gran valor para otros que enfrenten situaciones similares. Migrar una base de datos de este tamaño requirió una planificación cuidadosa, toma de decisiones estratégicas y una comprensión profunda de las complejidades involucradas.

Fasé de preparación

La fase de preparación resultó ser una de las etapas más complicadas de la migración. La historia del proyecto, con intentos fallidos previos de migrar la base de datos utilizando pg_dump cuando tenía un tamaño de 2TB, me dejó incierto sobre cómo proceder. Aunque tenía un conocimiento general del tamaño de la base de datos y había desarrollado funciones y consultas complejas para ella, la magnitud de más de 200 tablas hizo que la tarea fuera mucho más compleja de lo que imaginé inicialmente.

Para abordar este desafío, el primer paso fue crear un mapeo completo de toda la base de datos. Documenté meticulosamente las columnas críticas, índices, relaciones, restricciones, disparadores y las tablas afectadas por ellos, así como las secuencias esenciales. Este mapeo nos proporcionó una imagen clara de lo que tenía por delante, lo que me permitió analizar y prepararme para la migración de la mejor manera posible.

Además, me gustaría agregar que este proceso me llevó casi una semana completarlo. Durante este tiempo, una preocupación crucial que pesaba sobre nosotros era el flujo continuo de nuevas transacciones, lo que causaba que la base de datos principal creciera constantemente. Los métodos tradicionales de copia de seguridad y restauración podrían llevar a una desincronización de datos, y detener la producción durante la migración no era una opción debido a la naturaleza crítica de nuestros servicios.

Al no estar familiarizado con las copias de seguridad incrementales y enfrentar limitaciones de tiempo, discutí la situación con el líder técnico y juntos ideamos un enfoque alternativo. El plan consistía en redirigir las nuevas transacciones entrantes a otra base de datos mientras yo me centraba en migrar la base de datos existente de 3TB. Este enfoque significaba que teníamos que manejar la migración no solo de la base de datos de 3TB, sino también de la nueva que estaba gestionando las transacciones entrantes.

A pesar de su complejidad, este enfoque alternativo prometía ahorrar tiempo y esfuerzo al migrar la base de datos más pequeña por separado. A pesar de los desafíos, logramos completar la migración con éxito, asegurando una interrupción mínima en las operaciones en curso. La experiencia me enseñó valiosas lecciones sobre cómo manejar proyectos de migración de bases de datos a gran escala de manera eficiente y responsable.

Database preparation strategy

Elegir el enfoque de migración adecuado

Después de analizar la base de datos, me encontré considerando diferentes enfoques de migración. La primera opción fue utilizar pg_dump/pg_restore, a pesar de nuestros intentos fallidos anteriores con este método. Otro enfoque consistía en utilizar Pentaho, una herramienta de integración de datos conocida por su versatilidad en el manejo de conjuntos de datos grandes. Por último, mi líder técnico sugirió un método de paginación manual utilizando el comando “offset limit”, lo cual llamó mi atención durante una demostración.

Permíteme explicarte estos tres enfoques de migración y sus respectivos resultados, así como los desafíos que enfrentamos en el camino. A pesar del atractivo inicial de cada método, finalmente nos decidimos por un enfoque que resultó ser el más adecuado para nuestra situación única.

Pentaho

El historial del proyecto con intentos fallidos utilizando pg_dump/pg_restore hizo que el enfoque de Pentaho fuera la primera opción a probar. Para determinar si Pentaho era un candidato adecuado para la migración, realicé una demostración simple, tratando de migrar la información de una tabla masiva con aproximadamente 700 millones de registros y alrededor de 40 columnas.

Después de configurar el entorno durante el día, dejé la migración en ejecución durante toda la noche para evaluar su rendimiento hasta el día siguiente.

A la mañana siguiente, al verificar el progreso de la migración, me desilusioné al descubrir la aparición del evento de espera “IODataFileRead”. Esto fue preocupante porque indicaba que la migración tenía dificultades para obtener los datos necesarios del disco. Incluso después de más de 12 horas, solo se habían migrado alrededor de 250,000 registros, dejándome sorprendido por los resultados poco impresionantes. Comencé a analizar las razones detrás de este problema.

En ese momento, mis pensamientos se centraron en el tamaño de la tabla y el alto número de columnas. Parecía que el tamaño de página predeterminado de 8 KB en la base de datos era insuficiente para manejar una tabla tan grande, lo que hacía que la base de datos obtuviera numerosas páginas, potencialmente en orden aleatorio, lo que provocaba que la obtención de datos se atascara. Como resultado, la base de datos tenía que recuperar miles de páginas de esta manera y almacenarlas en la memoria RAM antes de devolverlas, lo que ralentizaba significativamente el proceso de migración.

Después de realizar múltiples demostraciones con tablas similares, con la esperanza de encontrar una forma de hacer que el enfoque de Pentaho funcionara, lamentablemente no obtuve los resultados deseados. A medida que el reloj seguía avanzando, el tiempo se volvía un bien preciado.

Dedicé cuatro días a trabajar en este enfoque, pero a pesar de mis esfuerzos, quedó claro que no era la opción adecuada para nuestros requisitos de migración. Con el plazo acercándose y la necesidad de explorar otras opciones, tomé la difícil decisión de pasar al siguiente enfoque. Adaptarme rápidamente y tomar decisiones informadas se volvió crucial como desarrollador solitario encargado de este proyecto crítico.

La migración de una base de datos de tal envergadura requería una estrategia efectiva y un enfoque sólido. Aunque el enfoque de Pentaho no funcionó como se esperaba, este proceso de prueba y error fue valioso, ya que me permitió descartar una opción que no se ajustaba a nuestras necesidades. Siempre es importante aprender de las dificultades y ajustar el enfoque para encontrar la mejor solución.

En el siguiente paso, me embarqué en explorar el método de paginación manual utilizando “offset limit”, que fue sugerido por mi líder técnico. Este enfoque presentó ciertas ventajas, como una mayor flexibilidad y control sobre el proceso de migración. A través de esta experiencia, seguí aprendiendo y creciendo como profesional, enfrentando los desafíos con determinación y perseverancia.

El método de paginación manual utilizando “offset limit”

Mientras se ejecutaba la demostración de la migración con Pentaho, aproveché parte del tiempo libre para explorar otras posibilidades. Mi líder técnico sugirió probarlo, aunque yo estaba escéptico sobre su éxito. Sorprendentemente, durante este tiempo, me topé con algo intrigante.

Decidí crear un proyecto simple en NodeJS para obtener información de la tabla con 700 millones de registros utilizando la paginación con un offset de 100,000 registros y un valor límite de 100,000. Para mi sorpresa, cuando la consulta alcanzó aproximadamente 220 millones de registros, de repente tuvo dificultades y quedó atascada, activando el temido evento de espera “IODataFileRead”. Me quedé perplejo por esta desaceleración repentina y decidí realizar un análisis exhaustivo para entender la causa raíz.

Durante mi investigación, descubrí algo interesante sobre el offset. Ejecuté el comando “EXPLAIN ANALYZE” en la consulta para obtener una comprensión más profunda del proceso de ejecución real de PostgreSQL. Esta exploración me permitió obtener una comprensión más profunda del comportamiento de la base de datos y los desafíos que enfrentaba al manejar conjuntos de datos tan grandes.

Query example

EXPLAIN ANALYZE SELECT * FROM table_name offset 200000 limit 100000;

Usar OFFSET en realidad no salta registros; más bien, recupera todos los registros y confía en PostgreSQL para filtrarlos. Esto explica por qué la consulta se estaba ralentizando, ya que no estaba recuperando solo los 100,000 registros esperados, sino todos los 220 millones de registros, solo para filtrarlos después. Fue una observación intrigante y confirmó aún más mis sospechas iniciales.

Mientras explorábamos opciones alternativas, consideramos usar el comando COPY para realizar copias de seguridad de manera paginada. Sin embargo, surgió un obstáculo desafortunado. Las tablas de la base de datos tenían índices primarios que contenían valores UUID, lo que hacía que este enfoque fuera inviable. La presencia de UUIDs perturbaba el orden de las tablas, lo que hacía impráctico utilizar eficientemente el comando COPY para la migración.

Query example

COPY (SELECT * FROM your_table WHERE id BETWEEN 1 AND 100) TO '/table-backup.csv' CSV HEADER;

Este descubrimiento nos llevó a un punto de decisión crítico, que nos obligó a reevaluar nuestro enfoque y buscar un método que pudiera funcionar eficazmente a pesar de las limitaciones impuestas por los índices principales de UUID. Como desarrollador solitario, encontrar una solución adecuada en medio de estos desafíos requirió una combinación de creatividad y experiencia técnica.

pg_dump y pg_restore

Después de una semana entera de trabajar en dos enfoques diferentes, el último enfoque que probé fue utilizando pg_dump y pg_restore. Durante este tiempo, tomé la iniciativa de investigar estos comandos para comprender mejor cómo utilizarlos de manera efectiva para un rendimiento óptimo.

Sin embargo, hubo un desafío significativo al tratar con Azure For PostgreSQL: Flexible server; no teníamos acceso como superusuario. Esta limitación planteó un problema, ya que la estrategia ideal habría sido deshabilitar los disparadores durante el proceso de migración para acelerar la restauración. Desafortunadamente, esto no fue posible debido a la falta de acceso como superusuario. Este contratiempo me obligó a buscar formas alternativas de lograr una migración eficiente.

A lo largo del proceso, mantuve al cliente informado sobre los desarrollos en curso, ya que ya habíamos superado la fecha límite inicial, haciéndoles conscientes de que la tarea era compleja. En un esfuerzo por abordar los desafíos, el cliente incluso contrató a un experto en PostgreSQL para ayudarnos, pero después de dos semanas adicionales de esfuerzos colaborativos, aún no logramos una migración exitosa. En consecuencia, el cliente extendió la fecha límite para acomodar las complejidades que encontramos.

Después de casi dos semanas de investigación intensiva y extensos experimentos con múltiples estrategias utilizando pg_dump y pg_restore, he creado con éxito un plan integral para acelerar el proceso de migración tanto para la base de datos de 3TB como para la base de datos más pequeña.

  1. Utilizando 0 compresión para pg_dump (-Z 0): Al especificar el parámetro -Z 0 durante la fase de respaldo, opté por 0 compresión, priorizando la velocidad sobre la utilización del espacio en disco. Esta decisión aceleró el proceso de respaldo, aunque requirió espacio adicional en el disco para almacenar la copia de seguridad.
  2. Empleando trabajos para respaldos y restauraciones concurrentes (-j <num>): Para mejorar aún más el rendimiento general y reducir el tiempo de migración, utilicé el procesamiento en paralelo mediante el parámetro -j <num> para manejar respaldos y restauraciones de manera concurrente para diversas tablas. Al establecer <num> como el número deseado de trabajos en paralelo, ejecuté múltiples tareas simultáneamente, lo que aumentó significativamente la eficiencia del proceso de migración.
  3. Deshabilitando disparadores durante la restauración ( — disable-triggers): Para mantener la consistencia de los datos y evitar conflictos derivados de la ejecución de disparadores, deshabilité los disparadores durante la fase de restauración utilizando el parámetro — disable-triggers. Esta precaución aseguró evitar problemas de integridad durante la restauración de datos.

Mi plan paso a paso fue el siguiente:

  1. Respaldar el esquema de la base de datos y migrar secuencias mientras se crean los roles de base de datos necesarios en el nuevo servidor.
  2. Eliminar restricciones clave para reducir operaciones de E/S innecesarias y mejorar la velocidad de migración.
  3. Quitar índices grandes para optimizar aún más las operaciones de E/S durante la migración.
  4. Migrar la información tabla por tabla o en grupos de tablas relacionadas. Debido a la falta de permisos de superusuario, tuve que seleccionar cuidadosamente las relaciones clave a eliminar para lograr un proceso de migración más fluido.
  5. Después de migrar la información, restaurar las restricciones, teniendo en cuenta las relaciones entre las tablas.
  6. Finalmente, restaurar los índices grandes de manera concurrente después de migrar con éxito los datos.

Una vez que finalicé el plan de migración, lo revisé con mi líder técnico y comencé a configurar el entorno para iniciar el proceso real de migración de la base de datos, lo que me permitió superar las limitaciones impuestas por la falta de permisos de superusuario.

Configuración del entorno de migración

Para configurar el entorno de migración, hemos decidido utilizar una Máquina Virtual (VM) para realizar copias de seguridad y llevar a cabo la restauración en la base de datos final. La VM se creará en la misma región que la base de datos de destino. Para garantizar una comunicación segura y eficiente entre los recursos de Azure, configuraremos los permisos necesarios para permitir que la VM se conecte a la base de datos mediante Azure VNET (Red Virtual).

El uso de Azure VNET para la comunicación entre los recursos tiene ventajas, ya que ofrece un mejor rendimiento y seguridad en comparación con la comunicación a través de Internet público.

Estos son los pasos que se han seguido para preparar la VM para la migración:

  1. Crear la VM: Se ha creado una Máquina Virtual en la misma región de Azure que la base de datos final. Esto garantiza proximidad, lo que puede mejorar la velocidad de transferencia de datos.
  2. Instalar PostgreSQL: Una vez configurada la VM, se instaló PostgreSQL en ella. Esto servirá como nuestro servidor de migración, donde almacenaremos los archivos de copia de seguridad de la base de datos y realizaremos la restauración.
  3. Configurar Permisos y Redes: Para establecer una conexión segura y eficiente entre la VM y la base de datos de destino, se configuraron los permisos y ajustes de red necesarios. Utilizando Azure VNET, podemos garantizar que la comunicación esté restringida a la red virtual, reduciendo la exposición a Internet público y mejorando la seguridad en general.
  4. Probar Conexiones: Con PostgreSQL instalado y la configuración de red establecida, se llevaron a cabo pruebas de conexión para verificar que la VM pueda comunicarse con las bases de datos de origen y destino de manera exitosa. Este paso es crucial para asegurar que el proceso de migración pueda continuar sin problemas de conectividad.

En este punto, la VM está lista para funcionar como el servidor de migración, y estamos preparados para proceder con la migración de la base de datos. Los próximos pasos implicarán tomar copias de seguridad de la base de datos de origen, transferirlas de manera segura a la VM y luego restaurar los datos en la base de datos final.

Ejecución de la migración

El día de la migración de la base de datos de 3TB, me preparé meticulosamente siguiendo el plan establecido. Antes de ejecutar cualquier operación de respaldo o restauración, me aseguré de ajustar finamente los parámetros en el postgresql.conf para optimizar el rendimiento y evitar posibles problemas, como vacíos automáticos. Consulté los parámetros recomendados de recursos de Azure, asegurándome de seguir las mejores prácticas al utilizar pg_dump y pg_restore.

Una vez que la configuración estuvo optimizada, procedí a crear scripts de respaldo y restauración, simplificando el proceso y eliminando la necesidad de entrada manual. Verifiqué dos veces los scripts para asegurarme de su precisión antes de iniciar cualquier comando.

Con todo listo, informé al equipo y al cliente sobre el inicio del proceso de migración y comencé a ejecutar cada paso descrito en el plan. Durante la migración, monitoreé de cerca el progreso, asegurándome de que cada fase se ejecutara según lo previsto. Comunicando regularmente el estado y las actualizaciones al equipo, mantuve a todos informados sobre el progreso de la migración.

Gracias a la meticulosa preparación y el seguimiento del plan, logré gestionar con éxito el proceso de migración, garantizando una transición fluida y eficiente para la base de datos de 3TB. La combinación de configuración optimizada, scripts automatizados y comunicación constante contribuyó a la finalización exitosa de la migración.

Informamos al cliente que las transacciones entrantes se encolarían durante el proceso de migración de la base de datos más grande de 3TB. Mientras se llevaba a cabo la migración de la base de datos más pequeña, enfocamos nuestros esfuerzos en asegurar una migración fluida y eficiente para la más pequeña, que tomó menos tiempo en completarse.

Durante la migración de la base de datos más pequeña, continuamos gestionando las transacciones entrantes de manera efectiva y aseguramos que no se perdiera ni comprometiera ningún dato. Una vez que la migración de la base de datos más pequeña se completó con éxito, reanudamos la recepción de transacciones para ambas bases de datos.

Le comunicamos al cliente que las transacciones entrantes se pondrían en cola durante el proceso de migración de la base de datos más pequeña. Durante la migración de la base de datos más pequeña, continuamos administrando las transacciones entrantes de manera efectiva y nos aseguramos de que no se perdiera ni se comprometiera ningún dato. Una vez que se completó con éxito la migración de la base de datos más pequeña, completamos la migración, unificamos la información y reanudamos la recepción de transacciones en el nuevo entorno.

Monitoreo e Integridad de Datos

Durante el proceso de migración, garantizar la integridad de los datos y monitorear el progreso son aspectos críticos. Para lograr esto, empleé tres métodos de monitoreo:

  1. Monitoreo de Red: Supervisé la Máquina Virtual (VM) y el Servidor de la Base de Datos utilizando herramientas de monitoreo de red para observar el flujo de datos entre ellos. Al mantener un estrecho seguimiento de la actividad de la red, pude asegurarme de que la información se estuviera migrando según lo esperado e identificar cualquier problema relacionado con la red que pudiera afectar la migración.
  2. PgAdmin Dashboard: El Panel de Control de PgAdmin proporcionaba información en tiempo real sobre el progreso del proceso de pg_restore. Al monitorear el panel, pude rastrear la ejecución de las tareas de restauración, identificar cualquier evento de espera y observar la salud general de la base de datos durante la migración.

3. Consultas de Metadatos de la Base de Datos: Con frecuencia utilicé diversas consultas para verificar los metadatos de la base de datos, lo que me permitió asegurarme de que la información se migrara correctamente y verificar la integridad de los datos. Algunas de las consultas más importantes que utilicé incluyeron:

  • Tuplas Vivas y Muertas: Supervisé la cantidad de tuplas vivas y muertas en las tablas para evaluar cuánto espacio se estaba utilizando y cómo se distribuían los datos. Esto ayudó a identificar posibles problemas con la distribución de datos y la utilización del espacio durante la migración.
SELECT relname, n_live_tup, n_dead_tup 
FROM pg_stat_all_tables
WHERE n_live_tup > 0 OR n_dead_tup > 0;

-- TO SEARCH FOR A SPECIFIC TABLE
SELECT relname, n_live_tup, n_dead_tup
FROM pg_stat_all_tables
WHERE relname = 'table_name';
  • Tamaño de la Tabla: Verifiqué regularmente el tamaño de las tablas para hacer un seguimiento del progreso de la migración de datos. Esto me permitió asegurarme de que las tablas se estuvieran llenando con datos según lo esperado y ayudó a identificar cualquier discrepancia que pudiera indicar problemas de migración.
SELECT
relname AS "relation",
pg_size_pretty (
pg_total_relation_size (C .oid)
) AS "total_size"
FROM
pg_class C
LEFT JOIN pg_namespace N ON (N.oid = C .relnamespace)
WHERE
nspname NOT IN (
'pg_catalog',
'information_schema'
)
AND C .relkind <> 'i'
AND nspname !~ '^pg_toast'
and relname = 'table_name'
ORDER BY
pg_total_relation_size (C .oid) DESC
  • Tamaño de la Base de Datos: Monitorear el tamaño general de la base de datos.
SELECT pg_size_pretty(
pg_database_size( current_database() )
) AS database_size;

Utilizando estos métodos de monitoreo y ejecutando consultas esenciales, pude asegurar con confianza el éxito del proceso de migración, mantener la integridad de los datos y abordar cualquier problema de manera oportuna durante la migración.

Optimizaciones posteriores a la migración

Ajusté los valores de la configuración de PostgreSQL, ajustando finamente los parámetros del servidor postgresql.conf para lograr un mejor rendimiento. Por defecto, Azure For PostgreSQL ajusta automáticamente estos parámetros según los recursos del servidor. Sin embargo, los ajusté para optimizar aún más la configuración de la base de datos.

Pensamientos y sentimientos personales

Al principio, debo admitir que veía la migración como una tarea imposible. Incluso con la ayuda del desarrollador senior y el arquitecto de software del equipo, luchamos solo para hacer copias de seguridad de la base de datos en el pasado. Cuando el cliente hizo la solicitud, sabía que recaería sobre mis hombros manejarla, y no tenía a nadie más a quien recurrir para obtener ayuda, ya que era el único que quedaba del equipo original.

Para ser honesto, incluso los otros equipos y mis líderes técnicos tenían miedo de la solicitud. Y yo también me sentía de la misma manera. Pero luego recordé un consejo de un creador de contenido realmente bueno llamado midudev. Él dijo que si nadie más quiere tocar un proyecto, ni siquiera con un palo, podría ser una oportunidad para desafiarse a sí mismo, aprender mucho y convertirse en un mejor ingeniero de software.

Así que, con esa mentalidad, decidí abrazar el desafío de manera positiva. Sabía que no era un experto en PostgreSQL en absoluto, pero tenía algo de experiencia migrando bases de datos más pequeñas de 5GB o 20GB. Sin embargo, manejar una masiva base de datos transaccional de 3TB era una historia completamente diferente.

A pesar de mi miedo inicial, di el salto y lideré la migración. Debo decir que este desafío sacó lo mejor de mí. Me complace decir que completé con éxito la migración e incluso encontré formas de optimizar ciertos aspectos de la base de datos.

A lo largo del proceso, aprendí muchísimo. La migración realmente me ayudó a comprender los fundamentos de las bases de datos y cómo funcionan detrás de escena. Fue una experiencia increíblemente enriquecedora para mí como profesional.

Mirando hacia atrás, si me enfrentara a la misma situación nuevamente, me acercaría de la misma manera que lo hice antes. El sentido de logro y el conocimiento adquirido realmente lo valieron.

Ahora que sé un poco más sobre PostgreSQL, definitivamente consideraría usar otras herramientas de migración como Barman o pgcopydb para copias de seguridad incrementales y tareas de migración más avanzadas. La migración manual puede ser propensa a errores, y estas herramientas pueden proporcionar más automatización y confiabilidad.

He decidido escribir este artículo para compartir toda mi experiencia de migración. Creo que podría ser útil para alguien más que se encuentre en una situación similar. Compartir mi viaje y las lecciones que he aprendido podría ofrecer perspectivas valiosas y aliento a otros que enfrentan migraciones desafiantes.

Conclusión

En conclusión, migrar una base de datos masiva de 3TB fue una tarea desafiante y compleja. Requirió un conocimiento profundo de los fundamentos de las bases de datos e involucró un proceso de prueba y error. A pesar de las dificultades, aprendí lecciones valiosas y obtuve un conocimiento más profundo de PostgreSQL.

El monitoreo del progreso de la migración fue crucial para mantener la integridad de los datos y el rendimiento. La experiencia me impulsó a sobresalir y optimizar la base de datos con éxito.

Mirando hacia el futuro, estoy ansioso por explorar otras herramientas de migración para futuros proyectos. Escribir este artículo ha sido un viaje de reflexión y espero que pueda inspirar y ayudar a otros que enfrenten desafíos similares. En general, la experiencia de migración ha fortalecido mis habilidades como ingeniero de software y ha reafirmado mi dedicación para abrazar desafíos técnicos.

Referencias

Utilicé ChatGPT para ayudarme a escribir este artículo porque quería que la lectura del artículo fuera realmente agradable. Soy bastante torpe para escribir artículos bien estructurados. La primera vez que intenté escribir el artículo fue como una montaña rusa hablando de todo y nada al mismo tiempo, ¡jaja!

--

--

Geronimo Velasco

Software engineer. I used to shared my knowledge and experience with my friends. Now I started to share it here :)