Heroku trunca las respuestas HTTP?

78

Estoy ejecutando una aplicación Flask / Gunicorn Python en un dinamómetro Heroku Cedar. La aplicación vuelve JSON responsesa sus clientes (es una API server, de verdad).

De vez en cuando, los clientes obtienen respuestas de 0 bytes. Sin embargo, no soy yo quien los devuelve. Aquí hay un fragmento del registro de mi aplicación:

14 de marzo 13:13:31 d.0b1adf0a-0597-4f5c-8901-dfe7cda9bce0 app [web.1] [2013-03-14 13:13:31 UTC] 10.104.41.136 apisrv - api_get_credits_balance (): session_token = [MASKED ]

La primera línea de arriba es yo comenzando a manejar la solicitud.

14 de marzo 13:13:31 d.0b1adf0a-0597-4f5c-8901-dfe7cda9bce0 app [web.1] [2013-03-14 13:13:31 UTC] 10.104.41.136 apisrv 1252148511 api_get_credits_balance (): regresando [{' saldo_créditos ': 0}]

La segunda línea es yo devolviendo un valor (a Flask - es un objeto "Respuesta" de Flask).

14 de marzo 13:13:31 d.0b1adf0a-0597-4f5c-8901-dfe7cda9bce0 app [web.1] "10.104.41.136 - - [14 / Mar / 2013: 13: 13: 31]" POST / get_credits_balance? Session_token = HTTP ENMASCARADO / 1.1 "200 22" - "" Appcelerator Titanium / 3.0.0.GA (iPhone / 6.1.2; iPhone OS; en_US;) "

La tercera línea es la de Gnicorn, donde se puede ver que Gunicorn obtuvo el estado de 200 y el cuerpo HTTP de 22 bytes (" 200 22").

Sin embargo, el cliente obtuvo 0 bytes. Aquí está el registro del enrutador Heroku:

14 de marzo 13:13:30 d.0b1adf0a-0597-4f5c-8901-dfe7cda9bce0 heroku [router] at = info method = POST path = / get_credits_balance? Session_token = MASKED host = matchspot-apisrv.herokuapp.com fwd = "66.87. 116.128 "dinamómetro = web.1 cola = 0 espera = 0 ms conexión = 1 ms servicio = 19 ms estado = 200 bytes = 0

¿Por qué Gunicorn devuelve 22 bytes, pero Heroku ve 0 y, de hecho, devuelve 0 bytes al cliente? ¿Es esto un error de Heroku?

Nitzan sacudido
fuente
1
¿Notaste que la marca de tiempo de heroku está antes de la marca de tiempo de tu proceso? ¿Usas gevent? Creo que algo anda mal con la sincronización.
Tigra
2
Y, sin embargo, la marca de tiempo indica 1 segundo de diferencia, no 1 1 ms ... No trabajé con heroku, por lo que son solo sugerencias. 1 ms y 1999 ms pueden darle una diferencia de 1 segundo en la marca de tiempo. El servicio 19ms también es demasiado bajo para ser cierto en el servicio en la nube. Entonces, mi punto es que probablemente haya algún tipo de tiempo de espera y de tiempo de espera en lugar de error. Heroku sirve una página vacía. Esta sugerencia es una posibilidad remota, pero tal vez debería emular una solicitud larga y ver qué sucede
Tigra
9
¿Qué tan útil fue Heroku cuando los contactó con esto (por curiosidad)?
orokusaki
6
No mucho hasta ahora. Me acerqué a ellos hace 10 días y me dijeron que los chicos de Python lo mirarían primero y que si no pueden ayudarme, los chicos de enrutamiento echarán un vistazo. 5 días después me informaron que los chicos de Python habían pasado esto a los chicos de enrutamiento, y hoy recibí un correo electrónico de un "chico de enrutamiento" diciendo que no podía recrear y pidiendo más información. Así que sí, están pasando por el proceso correcto, pero está tardando una eternidad.
Nitzan Shaked
1
Pequeña actualización: esto aún no se ha resuelto. He estado intercambiando mensajes con el soporte de Heroku, y lo mejor que puedo deducir en este momento es que no me han despedido con "está de tu parte" y están tratando de escribir una herramienta que tcpdump-capture el tráfico de la aplicación. , para "depurar casos como este".
Nitzan Shaked

Respuestas:

1

Sé que puede que me consideren un poco fuera de lugar aquí, pero hay otra opción.

Sabemos que de vez en cuando hay un error que ocurre en el tránsito, sabemos que no hay mucho que podamos hacer ahora para detener el problema. Si solo proporciona la API, deje de leer; sin embargo, si también escribe el cliente, continúe.

El error es un caso conocido y una causa conocida. El resultado de un valor de retorno vacío significa que algo salió mal. Sin embargo, el valor está disponible y fue obtenido, calculado, lo que sea ... Mi instinto como desarrollador sería tratar un resultado vacío como un error HTTP y solicitar que se reenvíen los datos. A continuación, puede realizar un seguimiento de las solicitudes de reenvío y ver con qué frecuencia sucede.

Sugeriría (aunque me parece que es el tipo de desarrollador que también piensa en esto) que cuente las solicitudes y establezca un valor sensato para responder "error de red" al usuario. Mi instinto sería volver a intentarlo de inmediato y luego esperar un poco antes de volver a intentarlo.

Por lo que describe, el primer reintento probablemente recogería los datos correctamente. Por supuesto, esto podría significar mantener las solicitudes más antiguas en la caché durante unos minutos o ejecutar la solicitud por segunda vez, según lo que parezca más apropiado.

Esto también evitaría otros errores de red punto a punto y dejaría la aplicación mucho más robusta incluso frente a problemas de conectividad.

Sé que nuestro instinto como desarrolladores es arreglar la falla conocida, pero a veces es mejor trabajar hacia un sistema que pueda funcionar a pesar de las fallas. Dicho esto, nunca está de más registrar errores y problemas e intentar solucionarlos de todos modos.

Matthew Brown alias Lord Matt
fuente
En realidad, ese no es un mal comentario (aunque probablemente debería estar en un comentario y no en una respuesta), y no creo que no lo haya pensado ... El problema es que el cliente no puede volver a emitir la solicitud, porque la solicitud puede tener efectos secundarios en el lado del servidor (como transferir dinero por segunda vez, por ejemplo). La solución para eso es que el cliente emita request_id's, y que el servidor mantenga una lista de "qué request_id's han sido atendidos en los últimos 60 segundos". Cuando un cliente recibe una respuesta de 200 con un cuerpo de 0 bytes, vuelve a emitir la solicitud con la misma identificación y el servidor no vuelve a ejecutar (continuación)
Nitzan Shaked
(cont.) todo de nuevo. Sin embargo, eso es tan feo que decidí no implementarlo.
Nitzan Shaked
Apenas soy un principiante en el almacenamiento en caché, pero me parece: envíe una cadena aleatoria como parte de la solicitud y guarde en caché el resultado. Cuando vuelve a enviar la solicitud, con la misma cadena aleatoria, obtendrá naturalmente el resultado almacenado en caché (mismo contenido, misma fuente ...), pero cuando envía una nueva solicitud legítima, tiene una nueva cadena aleatoria y, por lo tanto, no se almacena en caché resultado.
Narfanator