Planteamiento y contexto
Un servicio asíncrono a menudo responde solicitudes desde una caché en memoria sin realizar E/S; aun así, cada corrutina se convierte en una tarea y espera la planificación del bucle de eventos. El equipo propone configurar asyncio.eager_task_factory de forma global. Evalúa el balance costo-beneficio y propone un despliegue seguro.
La documentación de Python indica que la ejecución eager inicia una corrutina de forma síncrona mientras se construye su Task; la Task se planifica únicamente cuando la corrutina se bloquea. La API se añadió en Python 3.12 y modifica la semántica observable de planificación.
Qué evalúa el entrevistador
La clave está en distinguir la finalización síncrona del bloqueo asíncrono y explicar los efectos en la equidad (fairness), el orden, el momento de las excepciones, la cancelación y TaskGroup. Las respuestas sólidas restringen la optimización a límites medidos e incluyen comprobaciones de versión, feature flags, reversión y métricas del bucle de eventos.
Preguntas clarificatorias iniciales
- ¿Cuál es la versión mínima de Python en todos los despliegues?
- ¿Son estas corrutinas lecturas de caché en memoria o pueden realizar E/S de red, base de datos, archivos o bloqueos (locks)?
- ¿Depende el código del orden de creación de tareas, de la equidad del bucle o del orden de
call_soon? - ¿El manejo de fallos, la cancelación y los plazos límite (deadlines) pertenecen a un
TaskGroupo al alcance de la solicitud? - ¿El objetivo es el uso de CPU al crear tareas, la latencia de cola (tail latency) o el throughput, y cuál es la línea base?
Estructura de respuesta en 30 segundos
“No la habilitaría globalmente de entrada. Un eager factory inicia una corrutina durante la construcción de la Task, por lo que un acierto en caché puede evitar un turno de planificación en el bucle de eventos; una corrutina que se bloquea se planifica normalmente. Esto cambia el orden, el momento de las excepciones y la equidad: un bucle que crea muchas tareas síncronas puede retrasar temporizadores y otras solicitudes. Yo verificaría la versión de Python, la desplegaría en puntos de llamada de caché medidos, compararía el retraso (lag) del bucle de eventos y la latencia de cola, y mantendría la fábrica predeterminada como reversión inmediata.”
Análisis detallado paso a paso
Paso 1: Establecer los modelos predeterminado y eager
Con la fábrica predeterminada, create_task planifica la corrutina para ejecutarse pronto. Con un eager factory, la construcción la avanza de inmediato hasta que retorna, lanza una excepción o alcanza su primer await bloqueante. Una finalización síncrona puede no llegar a entrar nunca en la cola del bucle de eventos.
loop.set_task_factory(asyncio.eager_task_factory)
task = asyncio.create_task(read_cached(key))Por lo tanto, la ejecución eager es un cambio semántico observable, no simplemente un planificador más rápido.
Paso 2: Elegir el límite de la corrutina
Los buenos candidatos son operaciones cortas y con alta tasa de aciertos en caché en memoria o memoizadas. Las corrutinas que puedan realizar trabajo de red, base de datos, archivos, bloqueos o CPU no acotada deben mantener un límite de bloqueo predecible para que la construcción no monopolice la tarea actual.
Paso 3: Analizar el orden y la equidad
Cuando se crean tareas en un bucle, las corrutinas eager pueden completarse inmediatamente en el orden de creación, alterando una intercalación que antes dependía del bucle de eventos. Un lote síncrono grande puede retrasar temporizadores, callbacks de E/S y otras solicitudes. Monitorea el retraso del bucle de eventos y acota el tamaño de los lotes.
Paso 4: Manejar excepciones y cancelación
Una excepción lanzada antes del primer bloqueo puede aflorar cerca de create_task, cambiando el punto de captura y la forma del stack trace. Mantén referencias a las Task y deja que CancelledError se propague tras la limpieza. Nunca uses el modo eager para convertir una cancelación de solicitud o un fallo de caché en una respuesta exitosa.
Paso 5: Combinarlo con TaskGroup y plazos límite
TaskGroup sigue gestionando el árbol de tareas, la cancelación entre hermanas y la unión (join), pero un hijo puede haberse completado o fallado ya durante create_task. Utiliza un manejo estructurado de try/except* y un plazo límite exterior único; no asumas que cada hijo espera primero en una cola.
async with asyncio.TaskGroup() as group:
user = group.create_task(read_cached("user"))
orders = group.create_task(read_remote("orders"))Paso 6: Verificar versiones y delimitar el alcance de la funcionalidad
La fábrica está disponible a partir de Python 3.12. Python 3.14 también expone una opción eager_start en create_task. Un servicio multiversión debe validar el entorno de ejecución al inicio y preferir un experimento local en puntos de llamada antes que un cambio global incondicional de la fábrica.
Paso 7: Desplegar con métricas de reversión
Comienza en puntos de llamada exclusivos de caché detrás de un interruptor (switch). Compara la CPU, el tiempo de construcción de tareas, la latencia de cola en aciertos de caché, el retraso del bucle de eventos, la tasa de excepciones, la tasa de cancelación y el QPS posterior frente a la fábrica predeterminada. Si la equidad o los errores sufren una regresión, restaura la predeterminada sin modificar el código de negocio.
Paso 8: Probar la semántica, no solo benchmarks
Cubre el retorno síncrono, las excepciones síncronas, el primer await, la cancelación externa, múltiples fallos en TaskGroup, plazos límite, creación recursiva de tareas, equidad de temporizadores y lotes mixtos de caché/remotos. Registra eventos y valida el orden; un benchmark de throughput por sí solo no puede validar la semántica de planificación.
Respuesta modelo de alta calidad
“La ejecución eager se adapta a corrutinas de caché cortas y predecibles porque elimina un turno de planificación; es riesgosa para E/S impredecibles o rutas largas de CPU. El riesgo principal radica en la alteración del orden, la equidad y el momento de las excepciones. Yo verificaría las versiones, la habilitaría localmente detrás de un flag, conservaría la reversión al valor predeterminado y monitorearía el retraso del bucle de eventos, la latencia de cola, la cancelación y los errores. TaskGroup y un plazo límite compartido se mantienen, al igual que la cancelación y limpieza normales.”
Errores comunes
- Tratar el modo eager como si fuera inocuo para la semántica → el orden y el momento de las excepciones cambian → documenta ambos modelos de ejecución y mide.
- Habilitarlo para cada corrutina → E/S lentas o CPU bloquean la tarea actual → haz despliegues canario únicamente en rutas síncronas cortas.
- Ignorar las versiones de Python → los despliegues más antiguos fallan al iniciar → valida el entorno de ejecución y conserva la fábrica predeterminada.
- Observar únicamente el throughput promedio → la equidad sufre regresiones silenciosas → añade métricas de latencia de cola y de retraso del bucle.
- Asumir que TaskGroup no cambia → los fallos en tiempo de construcción se manejan mal → prueba fallos de hijos eager y grupos de excepciones.
- Ignorar errores de cancelación o de limpieza → las solicitudes filtran trabajo residual → preserva la cancelación y verifica las rutas de liberación de recursos.
Preguntas de seguimiento y respuestas sólidas
Pregunta de seguimiento 1: ¿Un acierto de caché síncrono sigue dejando una Task?
La Task ya puede estar completada durante la construcción. No requieras un turno del bucle de eventos; lee el objeto retornado y prueba la ruta de finalización eager explícitamente.
Pregunta de seguimiento 2: ¿El modo eager garantiza el orden de creación para la finalización?
No. Solo cambia el momento de inicio; una vez que una corrutina se bloquea, se aplica la planificación normal. Si el orden de negocio importa, coordínalo explícitamente u ordena los resultados por una clave de operación.
Pregunta de seguimiento 3: ¿Cómo evitas que un lote de aciertos de caché deje sin recursos a otras solicitudes?
Acota el lote, cede el control (yield) entre lotes cuando sea apropiado y monitorea el retraso del bucle. Aplicar el modo eager a un único punto de llamada de bajo costo es más seguro que una política global.
Pregunta de seguimiento 4: ¿Cómo se revierte tras una regresión?
Desactiva el interruptor, restaura la fábrica de tareas predeterminada, verifica que las métricas regresen a la línea base y conserva trazas que contengan la versión del entorno de ejecución y el orden de eventos para el diagnóstico.
Pregunta de seguimiento 5: ¿Cómo se relaciona eager_start con la fábrica?
eager_start es una opción explícita para una Task individual; la fábrica es una política predeterminada a nivel del bucle de eventos. Confirma la firma exacta de la versión de Python y la precedencia antes de combinarlas.