Programación y desarrollo de software

Cómo integrar Rust gradualmente para acelerar una base de código existente sin reescribirla por completo

Lily Mara explica una metodología de refactorización de funciones mediante la interfaz de función foránea (FFI) para trasladar partes específicas de Python a Rust usando PyO3, manteniendo la aplicación existente y reduciendo los riesgos de una reescritura integral. La experiencia muestra que la aceleración a nivel de función puede superar 100 veces, mientras que la mejora en una prueba HTTP global alcanzó solo alrededor del 15 %, lo que destaca la importancia de medir a nivel del sistema.

2026-09-15
6 min de lectura
10 visitas
فريق تحرير certi.news
Cómo integrar Rust gradualmente para acelerar una base de código existente sin reescribirla por completo

En lugar de reescribir una aplicación completa en Rust, Lily Mara, Staff Engineer en Discord, propone trasladar gradualmente las funciones que más recursos consumen desde un lenguaje dinámico como Python a Rust, conectando ambas implementaciones dentro del mismo proceso. Mara presentó esta metodología en una sesión publicada por InfoQ, basándose en su experiencia en la creación de sistemas distribuidos para enviar decenas de miles de millones de notificaciones diarias a los usuarios de Discord y en su libro Refactoring to Rust.

La idea fundamental no es sustituir un lenguaje por otro simplemente porque Rust sea más rápido, sino identificar partes acotadas del sistema cuya optimización produzca el mayor impacto. Este enfoque permite reducir el alcance del cambio, probar el nuevo comportamiento frente al antiguo y conservar la implementación original en los casos en los que resulte difícil garantizar resultados idénticos.

¿Por qué no empezar con una reescritura completa?

Mara considera comprensible el entusiasmo por reconstruir por completo un sistema antiguo en un lenguaje más moderno, pero advierte que esto implica grandes riesgos prácticos. Los proyectos de reescritura pueden exceder los plazos, volverse más complejos de lo previsto o volver a introducir errores que el sistema antiguo ya había resuelto. Además, el código antiguo no siempre es complejo por su antigüedad; puede reflejar limitaciones y detalles acumulados a partir del uso real y de conocimientos institucionales difíciles de trasladar a un proyecto nuevo.

La sesión también advierte contra reducir el problema del rendimiento al lenguaje de programación. La causa de la lentitud puede estar en el esquema de la base de datos, en los patrones de consulta y almacenamiento en caché o en el diseño de los servicios, y no en el coste de ejecutar una línea de código. Por tanto, aprovechar Rust no significa que una reescritura vaya a resolver automáticamente los cuellos de botella arquitectónicos.

¿Qué significa la refactorización mediante FFI?

La metodología utiliza lo que Mara denomina FFI refactoring, es decir, reescribir una función o una pequeña parte de las funciones en otro lenguaje y conectarla con la aplicación existente mediante la interfaz de función foránea. En el ejemplo presentado, la aplicación Flask sigue recibiendo la solicitud HTTP y deserializando el JSON; después envía los datos a una función estadística escrita en Rust, devuelve los resultados a Python y envía la respuesta.

La conexión se basa en una interfaz de C, que en la práctica funciona como un lenguaje común entre numerosos sistemas y lenguajes. En el caso de Python, el proyecto PyO3 proporciona herramientas para crear un módulo importable desde Python, mientras que Maturin puede utilizarse para compilarlo. Atributos de código como pyfunction y pyclass convierten las funciones y estructuras de datos de Rust en interfaces que pueden invocarse desde Python.

Elegir la función adecuada y medir el impacto

La experiencia recomienda buscar funciones que combinen una alta frecuencia de invocación con un elevado coste de invocación. Una función puede ser relativamente barata cada vez que se ejecuta, pero funcionar con cada solicitud, como la lógica de validación situada delante de los controladores de una API. Por otro lado, puede haber operaciones poco frecuentes pero muy costosas. El criterio es el impacto acumulado en el tiempo de CPU, no la impresión de que una función concreta parezca lenta.

En el ejemplo estadístico, la implementación en Rust fue algo más de cien veces más rápida al medir la propia función desde Python, aunque la prueba ejecutó ambas aplicaciones mediante un intérprete de Python. Sin embargo, al medir un controlador HTTP completo, incluyendo Flask y la serialización y deserialización de JSON, la mejora fue de solo alrededor del 15 %. El tiempo de ejecución de la versión original de la función fue de aproximadamente 86 microsegundos, un tiempo pequeño de forma aislada, pero que puede convertirse en un coste importante al repetirse a gran escala.

Esta diferencia entre la medición parcial y la medición global es una de las lecciones más importantes de la sesión: no basta con registrar la aceleración de una función; también hay que realizar pruebas integrales que representen el flujo de uso real e incluyan la red o el marco web y la serialización de datos cuando sea necesario.

Compatibilidad funcional, pruebas y limitaciones

La reimplementación del ejemplo mostró diferencias entre los resultados de la biblioteca estadística de Python y los de la biblioteca de Rust. Una de las bibliotecas calculó los cuartiles con precisión, mientras que la otra utilizó estimaciones adecuadas para conjuntos masivos; también aparecieron pequeñas diferencias en el redondeo decimal. Por ello, no debe suponerse que sustituir una biblioteca conservará automáticamente el mismo comportamiento.

Si la equivalencia es imprescindible, se puede buscar otra biblioteca, reimplementar el algoritmo original en Rust o mantener la parte sensible en Python. La ventaja del traslado a nivel de función es que permite combinar ambos lenguajes dentro del mismo proceso, en lugar de separar el componente en un servicio independiente y añadir comunicaciones de red y nuevos costes operativos.

En cuanto a las pruebas, incluyen pruebas directas de Rust, las pruebas existentes de Python para los controladores de Flask y pruebas que comparan los resultados de ambas implementaciones, además de pruebas aleatorias de propiedades cuando resulten adecuadas. No obstante, añadir código nativo introduce complejidades operativas: los entornos de desarrollo necesitan un compilador de Rust o binarios compatibles con el sistema operativo y la arquitectura, el despliegue se vuelve más complejo y pueden aparecer nuevos errores.

En la práctica, esta metodología ofrece una vía relativamente limitada en riesgos para mejorar partes seleccionadas de sistemas existentes, pero no elimina la necesidad de realizar análisis arquitectónicos, pruebas de compatibilidad y mediciones globales. El ahorro real solo queda demostrado cuando la mejora se refleja en el flujo completo del servicio, no en un benchmark aislado de la función.

Fuente de la noticia
InfoQ - Architecture Articles
Abrir fuente original ↗
ف
Autor

فريق تحرير certi.news

De la misma categoría

También te puede interesar

Ver todas las noticias