Lecciones aprendidas de la migración a Python 3

A medida que se aproxima la fecha de fin de vida de Python 2, muchos desarrolladores deberán considerar migrar sus compilaciones a Python 3. Me senté con Dan Palmer para analizar la migración reciente de Thread a Python 3.

Las oficinas de Thread en Londres. Este artículo fue publicado originalmente en able.bio

Thread es un servicio de estilo personal en línea. Cuando se registra, su sitio web le hace una serie de preguntas introductorias sobre sus estilos preferidos, tamaños, presupuesto, artículos de su propiedad, etc., que sus estilistas personales utilizan para proporcionar recomendaciones sorprendentemente adaptadas. Con sede en Londres con alrededor de 50 empleados, el sitio web de comercio electrónico impulsado por Django de Thread recibe aproximadamente 850,000 visitas por mes.

Rhett: ¿Cómo es la arquitectura del sitio de Thread?

Dan: Thread ha sido fuertemente impulsado por Django desde el primer día. El equipo fundador tiene mucha experiencia con Django, por lo que el proyecto fue diseñado para escalar a medida que aumentaba su complejidad. Nuestro servicio de recomendaciones está construido con Flask utilizando scikit-learn , hay mucha ciencia de datos utilizada en Thread, y también tenemos algunos servicios más pequeños que alimentan las máquinas de estado y algunos servicios de monitoreo, pero sobre todo es un monolito de Django. Monolith tiene un mal término como término, pero creo que Django se presta muy bien para dividir las cosas en aplicaciones y crear esta agradable jerarquía de lógica y modelos.

Rhett: ¿Por qué decidiste migrar a Python 3?

Dan: he estado en Thread durante cuatro años y fue bastante claro después del primer año que Python 2 iba a desaparecer con el tiempo. Creo que durante mucho tiempo hubo un limbo en el que las personas no estaban convencidas de que Python 3 fuera el futuro y la comunidad estaba considerando ejecutar ambas versiones a largo plazo, de una manera que supongo que es lo que ha estado sucediendo hasta la fecha. Recuerdo el primer PyCon Reino Unido al que asistí en 2015 y ese año mucha gente hablaba de migrar sus bases de código más grandes a Python 3. Al año siguiente, no mucha gente hablaba de eso, mucha gente parecía haberlo hecho. eso.

Para 2017, teníamos muy pocas dependencias que fueran compatibles solo con Python 2. Estos eran muy pequeños o tenían horquillas Python 3 a las que podíamos movernos. La fecha de fin de vida de Python 2 había sido confirmada (1 de enero de 2020) y Django también se había comprometido a utilizar Python 3. También ejecutamos nuestros sistemas en Debian y anunciaron una fecha de finalización para el soporte de Python 2, que fue un año antes. Esto se debió a su ciclo de lanzamiento, no querían tener un período durante el cual apoyaran Python 2 mientras el equipo de Python ya no lo apoyaba oficialmente.

Así que supongo que podríamos haber dejado la migración hasta el próximo año, pero nos esforzamos mucho para asegurarnos de que tengamos un buen código, de que estamos escribiendo código en buenas formas de mantenimiento a largo plazo y, finalmente, eso es lo que Python 3 realmente hace por Python . Hace que sea más fácil escribir un buen código y hubo lugares en los que sentíamos el dolor de no tener eso, particularmente con cosas como los sistemas de tipo, que tenían que administrarse con mypy , y gran parte del soporte Unicode que tenemos nunca tuvimos que ocuparnos de todo porque en este momento solo estamos disponibles en el Reino Unido. Sin embargo, de vez en cuando surgía algo y obteníamos un producto que tenía un símbolo Unicode en él que no habíamos abastecido en alguna parte o que ingeríamos un archivo CSV de algún proveedor y no funcionaba.

Tenemos algo llamado Tech 10% en Thread, que es como el 20% de Google, por lo que los miércoles por la tarde es cuando los desarrolladores pueden trabajar en lo que creen que es importante. Entonces, durante ese tiempo, asumí la tarea de comenzar a movernos a Python 3.

Rhett: ¿Cómo planeó y se preparó para migrar a Python 3?

Dan: Curiosamente, una de las cosas que no hicimos tan bien como pudimos haber sido fue planear correctamente. Como la preparación solo estaba ocurriendo en el Tech 10% time, no teníamos todo el equipo trabajando en ello y no obtuvo la planificación minuciosa que obtendría cualquier otra gran pieza de tecnología en Thread, eso es algo que creo hemos aprendido para el futuro

Habiendo dicho eso, teníamos un plan. Comenzamos por actualizar nuestras dependencias para que pudieran trabajar en Python 3. Después de eso, comenzamos a adaptar nuestras prácticas de codificación para que crearan código compatible con versiones anteriores, usamos bastantes linters Flake8 y otros complementos para eso .

Por ejemplo, hay un plugin para Flake8 que le permite prohibir ciertas importaciones, así que fuimos a través de la lista de cosas que el seis biblioteca tiene bajo six.moves así que para que las cosas se han movido en una biblioteca estándar se puede utilizar como six.moves una forma compatible entre Python 2 y Python 3. Así que prohibimos todas las importaciones de Python 2, cambiamos todo a seis y eso significaba que no estábamos importando desde el lugar equivocado, así que pudimos hacerlo de manera incremental. Muchas de esas cosas empezaron a venir en forma gradual y cada semana haríamos un pequeño RP que simplemente cambiaría un par de cosas. Creo que probablemente fueron varios meses de trabajo, pero solo fueron unas pocas horas aquí y allá y durante varios meses empezamos a llegar al punto en que el código se parecía mucho más al código de Python 3.

La etapa posterior a eso fue hacer que corriéramos nuestras pruebas. Nuestras pruebas ni siquiera se ejecutaron en Python 3 para empezar, sin mencionar el pase. Entonces, cuando teníamos todos los linters en su lugar y habíamos solucionado todos esos problemas, las pruebas estaban mucho más cerca de ejecutarse, entonces había algunos pequeños errores que podíamos solucionar para que funcionasen. Creo que la primera vez que corrieron estaban en su mayoría bien. Probablemente pasamos el 80% de nuestras pruebas, por lo que se lanzaron tres de nuestras cuatro semanas de uso del Tech al 10% de tiempo para aprobar todas las pruebas.

La mayoría de las veces fue mucho manejo de CSV y cosas así. Tenemos que tratar con varios almacenes de terceros y compañías navieras que tienen servidores FTP con archivos CSV, nunca oyeron hablar de una API HTTP, así que fue divertido. Una vez que arreglamos las pruebas, encontramos lugares donde necesitábamos más pruebas. Encontraríamos un error en particular en un sistema CSV y luego aprenderíamos de eso e iríamos y escribiríamos pruebas para lo mismo en todos nuestros otros, solo para asegurarnos de no tener los mismos problemas en todo el proceso. código base

Una vez que las pruebas pasaban, agregamos otro trabajo de CI a Jenkins que ejecutaría todas las pruebas en Python 3 y nos aseguraríamos de que pudiéramos construir el paquete. Luego, la idea era esencialmente tratar la construcción de Python 3 como un ciudadano de primera clase.

Originalmente esperábamos que fuera solo un par de semanas de que la versión de Python 3 se mantuviera estable antes de que empezáramos a enviarla, pero sabíamos que el cambio sería un gran riesgo, por lo que queríamos planearlo con anticipación. Queríamos asegurarnos de hacerlo en un momento en el que tendríamos suficientes desarrolladores en la oficina si fuera necesario, y hacerlo en un momento que no era un período crítico de ventas. Así que terminamos posponiéndolo durante aproximadamente tres meses y medio y manteniendo la compatibilidad con Python 2 y Python 3 durante ese tiempo.

Eso era propenso a errores y un poco doloroso. He estado ejecutando Python 3 en mi entorno de desarrollo durante varios meses y alrededor de un mes después de que teníamos compatibilidad con Python 3, todo el equipo de desarrollo usaba Python 3 en sus entornos de desarrollo, lo que resultó en construcciones de Python 2 Escribíamos algo que funcionaba para Python 3 sin darnos cuenta y luego lo poníamos a funcionar y la construcción fallaba. Eso causó un poco de fricción, comenzó a costarnos tiempo y por eso presionamos un poco más para que se envíe Python 3 y, finalmente, fijamos la fecha un martes de noviembre.

Digo que no queríamos que el cambio se produjera en un período de ventas elevado, pero en realidad terminamos haciéndolo la semana del Black Friday, que fue otra de las cosas que aprendimos de esto. Comunicamos muy bien sobre esto dentro del equipo de tecnología, pero no nos comunicamos bien con todos los demás. Creo que estábamos un poco más seguros de lo que debería haber sido.

El resto de la compañía no sabía que había un alto riesgo de que saliera mal, así que llegamos un martes por la mañana e intentamos impulsarlo. No funcionó y terminamos dándonos cuenta antes de las 8 de la mañana que no iba a funcionar ese día, así que retrocedimos. Luego llegamos temprano al día siguiente y lo intentamos de nuevo. Algunas cosas salieron mal pero terminaron presionando y solucionando problemas a medida que se acercaban y hemos estado en Python 3 desde entonces.

Rhett: ¿Tuviste problemas de dentición al migrar sobre Python 3?

Dan: El principal que nos hizo retroceder el martes fue básicamente un problema entre nuestras sesiones y nuestro sistema de almacenamiento en caché. Significaba que todos nuestros usuarios habrían cerrado la sesión y tendrían que volver a iniciar sesión. Tenemos nuestras sesiones almacenadas en cookies firmadas, y almacenamos algunos de nuestros datos de usuario en Memcached como clases de Python en escabeche. Cuando nos movimos a Python 3 hubo algunas incompatibilidades, hasta volver a los problemas de Unicode, que dieron como resultado una diferencia entre los bytes en cadenas y significaba que no podíamos validar las cookies que las personas tenían con los datos que habíamos almacenado en Memcached .

La experiencia de Thread se trata principalmente de buscar conjuntos y aprender a vestirse, por lo que para nosotros es una experiencia de usuario mucho mejor tener un tiempo de caducidad relativamente largo en nuestras cookies. Sabemos que las personas entran y salen de la navegación, por lo que en términos de experiencia de usuario, desconectar a todos no era algo que pensamos que fuera razonable, y es por eso que decidimos no seguir adelante el martes y pasamos el día escribiendo compatibilidad hacia adelante en nuestro almacenamiento en caché y la firma de cookies para que cuando lo lanzamos al día siguiente no tengamos ese problema.

Parte del plan de despliegue fue que ejecutamos los servidores web, quitamos todo, volvimos a poner los servidores web y las colas realmente críticas, como nuestra cola de procesamiento de pago, por ejemplo. Luego, la idea fue que a lo largo del día mostraríamos las otras colas de una en una y veríamos los registros en busca de errores. Nuestro almacén abre a las 8 a. M. Y todo el software que utilizan es nuestra aplicación Django, por lo que tenía que estar funcionando para entonces, lo que fue un éxito, pero cuando comenzamos a hacer cola descubrimos que todavía había una feria número de cosas que no funcionaban exactamente como esperábamos que fueran y terminamos averiguando que al día siguiente habíamos causado que muchos artículos se agotaran, ordenados por correo. El pedido fuera de stock es cuando alguien ha comprado algo y luego le enviamos un correo electrónico para decir que en realidad no lo tenemos. Por lo general, solo ocurre en circunstancias muy especiales y tenemos que reembolsar al cliente, no es una buena experiencia. Es una medida que rastreamos muy de cerca y tratamos de minimizarla. Durante este tiempo, se incrementó y resultó que algunas de las formas en que teníamos nuestras colas ejecutándose en un proceso en serie causaban un problema en el que estábamos ejecutando múltiples de la misma cola. Todos estaban agitando las cosas muy rápidamente, causando problemas, cometiendo errores y marcando muchas cosas cuando se agotaron. Nuestro equipo de operaciones probablemente pasó dos o tres días limpiando las secuelas de eso. Ese fue probablemente el mayor problema que tuvimos.

Realizamos reuniones llamadas 5 Porqués cuando algo sale mal a gran escala para descubrir la causa raíz del problema. Terminamos ejecutando 5 porqués para este problema y finalmente llegamos a la conclusión de que la causa principal era que como equipo técnico no comunicamos todos los riesgos y todo lo que podría afectar al resto del equipo. Si hubiéramos hecho eso, tal vez hubiesen podido detectar los problemas antes, tal vez si hubiéramos mencionado los riesgos que hubieran dicho que no hicieran esto el Black Friday o habrían visto algunos de los sistemas que iban a utilizar. toque y dijo "en realidad, es posible que desee escribir algunas pruebas para esta área en particular porque es algo muy importante para nosotros".

Por lo tanto, no fue un lanzamiento sin problemas, pero al final de la semana, los únicos problemas que teníamos eran cosas menores, como los informes que solo se generan una vez a la semana, y podíamos solucionarlos fácilmente y volver a ejecutar el informe. Realmente no hemos tenido ningún problema desde entonces.

Rhett: ¿Qué recomendaría a las personas que se preparan para llevar a cabo la misma migración?

Dan: Diría que planifique los pasos para saber cómo va a hacer que su base de código sea compatible con Python 3. Invierta en herramientas que lo ayuden a saber que es compatible. Escriba pruebas, si no tiene pruebas en ciertas áreas de su código base y sabe que tratan con archivos de datos que pueden estar en diferentes formatos o cosas que Python 3 ha cambiado notablemente, yo diría que escriba más pruebas para aquellos Tipos de cosas. Pensamos que teníamos una cobertura de prueba bastante buena, pero había ciertas partes en las que encontramos que nos faltaban. Invierte en herramientas como linters y configura versiones paralelas de tu software que se ejecuta en Python 3.

Planifique cómo va a llegar a Python 3 compatible de una manera que no interrumpa al resto de su equipo de desarrollo. Eso fue algo que creo que hicimos bien, cuando fui a los desarrolladores y dije: "Oye, ¿quieres usar Python 3 en tu máquina?" Todo funcionaba básicamente y eso significaba que el resto del equipo lo consideraba fue una buena cosa y lo compró.

Creo que lo otro sería planear el lanzamiento, especialmente si tienes un servicio web, uno donde realmente importa que estés funcionando y que las personas puedan realizar transacciones. Así que planifique paso a paso con mucho cuidado lo que va a hacer y en cualquier punto dentro de eso, cómo puede darse cuenta de que no está funcionando y cómo va a retroceder de eso a un estado de trabajo conocido.

Comuníquese fuera de su equipo de ingeniería, asegúrese de que la gente sepa cuáles son los riesgos, qué puede dejar de funcionar y qué deben vigilar. Asegúrese de que todos sepan cómo identificar que algo no funciona, porque a veces es difícil de saber, especialmente si tiene un sistema grande.