Servir respuestas JSON en streaming para interfaces más rápidas

Los sitios web modernos son complejos y a menudo necesitan cargar datos en segundo plano de forma asíncrona. El streaming JSON permite hacerlo.

¿Qué es el streaming de JSON?

El streaming JSON, o JSONS, es una técnica sencilla que usamos para reducir la latencia de búsqueda de los usuarios, especialmente de quienes tienen una conexión lenta. La mayor mejora se aprecia en usuarios móviles con conexiones HTTPS, pero en realidad beneficia a todos. Al trabajar con JSONS no necesitas analizadores JSON complicados: basta con enviar JSON delimitado por líneas. La API de streaming de Twitter lleva años funcionando así.

Por qué deberías usar un flujo JSON

Nuestro comprobador de nombres de usuario en redes sociales ofrece un ejemplo sencillo. Comprobamos si el nombre que buscas está disponible en Facebook, Twitter y Pinterest mediante una petición HTTP HEAD a cada sitio. Los resultados llegan en orden aleatorio y queremos mostrarlos lo antes posible. Podríamos enviar tres peticiones HTTPS, pero eso implica tres conexiones TCP, tres negociaciones SSL y demás. Podemos evitarlo, y probablemente reutilizar una conexión HTTPS existente, si enviamos una sola petición:

https://instantdomainsearch.com/services/vanity/apple?hash=866016287

La respuesta en streaming es análoga a la codificación de transferencia fragmentada de HTTP y depende de ella. La mejor forma de ver cómo funciona es trazar la conexión con curl:

curl --trace - "https://instantdomainsearch.com/services/vanity/apple?hash=866016287"
…
<= Recv data, 45 bytes (0x2d)
0000: 32 37 0d 0a 7b 22 54 79 70 65 22 3a 22 54 77 69 27..{"Type":"Twi
0010: 74 74 65 72 22 2c 22 49 73 52 65 67 69 73 74 65 tter","IsRegiste
0020: 72 65 64 22 3a 74 72 75 65 7d 0a 0d 0a          red":true}...
{"Type":"Twitter","IsRegistered":true}
<= Recv data, 46 bytes (0x2e)
0000: 32 38 0d 0a 7b 22 54 79 70 65 22 3a 22 46 61 63 28..{"Type":"Fac
0010: 65 62 6f 6f 6b 22 2c 22 49 73 52 65 67 69 73 74 ebook","IsRegist
0020: 65 72 65 64 22 3a 74 72 75 65 7d 0a 0d 0a       ered":true}...
{"Type":"Facebook","IsRegistered":true}
<= Recv data, 52 bytes (0x34)
0000: 32 39 0d 0a 7b 22 54 79 70 65 22 3a 22 50 69 6e 29..{"Type":"Pin
0010: 74 65 72 65 73 74 22 2c 22 49 73 52 65 67 69 73 terest","IsRegis
0020: 74 65 72 65 64 22 3a 74 72 75 65 7d 0a 0d 0a 30 tered":true}...0
0030: 0d 0a 0d 0a                                     ....
{"Type":"Pinterest","IsRegistered":true}
== Info: Curl_http_done: called premature == 0
== Info: Connection #0 to host instantdomainsearch.com left intact

Puede que encuentres dogmas técnicos según los cuales cada endpoint debería devolver un único objeto. O que el JSON con formato legible puede contener saltos de línea y que, por tanto, necesitas un analizador de streaming. Ignóralos. Analizar las respuestas es tan sencillo como dividirlas por los saltos de línea. La biblioteca jsonpipe es un buen punto de partida para el código del cliente.

Cómo ver la diferencia que aporta el JSON

Una buena forma de entender lo que esto supone para un usuario móvil es usar rvictl en un Mac para observar cómo se comunica con tu servidor una radio móvil real en una red móvil real. Una conexión HTTPS con mis dos barras de cobertura 3G de Verizon tiene este aspecto; la primera columna es el tiempo en segundos:

0.000000  DNS  69  Standard query 0x5622 A instantdomainsearch.com
2.573090  DNS  85  Standard query response 0x5622 A instantdomainsearch.com A 130.211.25.142

Un momento, ¿la consulta DNS tardó 2,5 segundos en resolverse? Vaya. Quizá el módem 3G estaba en modo de suspensión. Por fin podemos establecer la conexión TCP:

2.573822  TCP  64  57709 → 443 [SYN] Seq=0 Win=65535 Len=0 MSS=1388 WS=64 TSval=727045548 TSecr=0 SACK_PERM=1
2.574404  TCP  60  443 → 57709 [SYN, ACK] Seq=0 Ack=1 Win=42540 Len=0 MSS=1430 SACK_PERM=1 TSval=2852312183 TSecr=727045548 WS=128

Y comenzar la negociación SSL/TLS:

2.575790  TLSv1.2  285  Client Hello
3.582868  TLSv1.2  1428  [TCP Fast Retransmission] Server Hello
3.585061  TLSv1.2  127  Client Key Exchange
3.585771  TLSv1.2  58  Change Cipher Spec
3.586415  TLSv1.2  97  Encrypted Handshake Message
3.587729  TLSv1.2  103  Change Cipher Spec, Hello Request, Hello Request

¡Hola! A partir de aquí, 3,5 segundos después, por fin enviamos y recibimos datos de aplicación cifrados.

El JSON reduce paquetes y aumenta la velocidad

Cada pulsación en Instant Domain Search consulta varias estructuras de datos para obtener disponibilidad, resultados de mercados, sugerencias y demás. Observamos enormes mejoras de rendimiento para los usuarios cuando dejamos de intentar establecer tantas conexiones HTTPS con cada pulsación y empezamos a transmitir los resultados por una sola conexión por consulta. Puedes ver cómo esto se traduce en muchos menos paquetes que saturan la conexión del usuario. Por si te lo preguntas, probamos un web socket, pero descubrimos que las conexiones HTTPS por consulta eran la forma más fiable de ofrecer resultados rápidos a nuestros usuarios.