Reintentar cada fila inestable por separado
Requisito
Importa seis filas a través de un endpoint inestable: las filas pares fallan exactamente una vez antes de tener éxito. Da a cada fila su propio presupuesto de reintentos de dos intentos, ejecuta hasta tres filas a la vez, e imprime los resultados en orden de origen con el número del intento que tuvo éxito. La inyección de fallos y los retardos por fila son deterministas y están en el código; las dos versiones deben imprimir las líneas que aparecen bajo Salida esperada.
Salida esperada
row 1 (alpha) imported on attempt 1 row 2 (bravo) imported on attempt 2 row 3 (charlie) imported on attempt 1 row 4 (delta) imported on attempt 2 row 5 (echo) imported on attempt 1 row 6 (foxtrot) imported on attempt 2
Lado a lado
RxDart
FxDart
Por qué difieren
Ambos lados expresan la mitad de resiliencia igual: un envoltorio de
retry por fila, de modo que una fila inestable se re-ejecuta mientras
sus vecinas pasan sin tropiezos. RxDart lo escribe
flatMap hacia un stream interno con reintentos por fila
con maxConcurrent: 3; FxDart lo escribe
mapRetry(2, …) bajo concurrent(3),
donde cada elemento en vuelo lleva su propio presupuesto
independiente.
La diferencia es lo que sale por el otro extremo.
flatMap fusiona los streams internos en orden de
terminación — ese es su contrato — así que con tres filas en
vuelo y retardos desiguales, los resultados llegan barajados.
Recuperar el orden de origen significa etiquetar cada resultado con
su id de fila y ordenar después de toList. El operador de
RxDart que sí conservaría el orden, concatMap, lo hace
renunciando a la concurrencia — una fila a la vez. En el modelo pull,
la concurrencia ordenada es el modo nativo: concurrent(3)
evalúa tres pulls a la vez pero los entrega en orden de origen por
construcción, así que no hay nada que etiquetar ni nada que ordenar.
Veredicto FxDart — el orden es la historia. «N a la vez, reintentadas por separado, en orden» es una sola cadena en el modelo pull y un rodeo de fusionar-y-reordenar en el modelo push.
Benchmark
Caso async: la escala principal es N = 10,000, no 1,000,000. Cada elemento cuesta una vuelta del event loop en ambos lados, así que un millón de awaits reales mediría el event loop de Dart durante minutos — no el pipeline. Los retardos son de longitud cero y se conserva el límite de concurrencia del ejemplo; lo que comparan las barras es la maquinaria del pipeline.
N = 100
Tiempo Empate
Memoria pico Empate
N = 10,000
Tiempo Empate
Memoria pico Gana FxDart
Las barras son medianas de iteraciones cronometradas repetidas en procesos nuevos por lado (los N pequeños se agrupan por resolución del temporizador). Dos lados a menos del 5% entre sí — o a menos de 0.6 ms, una diferencia que nadie puede percibir — cuentan como empate; las carreras relativas ajustadas se vuelven a medir hasta 5 veces. En una app, cualquier cosa por debajo de unos pocos milisegundos es invisible para el usuario, gane la barra que gane. La memoria es el RSS pico del proceso. La VM de Dart y el dataset son idénticos en ambos lados, así que la diferencia entre las dos barras es lo que retiene el pipeline en sí.