Supongo que no hace falta explicar que es Facebook ni decir que los últimos datos públicos indican que hay 350 millones de personas que lo usan con más o menos asiduidad, que dicho así tampoco es mucho, pero si todo Estados Unidos sólo son 308 millones, imagínate el poder que puede tener alguien con acceso directo a más gente que el país con más poder de la tierra (y defino poder como el impacto que tienen sus decisiones en el resto)
Vale Facebook es grande, pero la gente se pasa el día jugando a las granjas con vaquitas y tal, pues FarmVille tiene más de 75 millones de usuarios únicos mensuales que tienen su granjita con sus animalitos y su propia economía, más gente que toda Francia, pero todos estos tienen una cierta edad, tienen capacidad para gastar y un nivel socio económico medio-alto.
Bueno después de toda esta introducción a lo qué iba, la aplicaciones para Facebook tienen una gran capacidad de crecimiento y el rendimiento y escalabilidad de las mismas es crítico, así que os voy a explicar el caso de Gracias,Amigo!,una pequeña (de momento) aplicación para Facebook que todavía está en Beta pero algunos usuarios estaban notando demasiada lentitud.
Primero de todo es identificar cuales son las métricas más adecuadas para valorar la mejora,instrumentar la aplicación para qué arroje datos que se puedan analizar y luego entrar a buscar las mejoras.
En este caso teniamos:
1) Objetivar la apreciación del usuario (va lento)
- El tiempo desde el click a la aplicación hasta que esta es usable tarda demasiado
- Una vez dentro tengo un rendimiento adecuado
2) Instrumentar la aplicación
- Lo primero que hicimos es calcular el tiempo de proceso en el servidor, la forma más fácil y escalable es meter al inicio del codigo una variable con la fecha actual y antes de entregar la respuesta (un redirect 302) hacer la diferencia con el tiempo actual y pasar los mili-segundos como parámetro de la URL destino.
- Así tenemos que no introducimos un cuello de botella adicional (escritura a fichero/base de datos), tenemos datos de todos los accesos y son visibles mirando los logs de los servidores Web o también en Google Analytics!
3) Analizar los números y buscar objetivos
- Vemos que el tiempo medio de carga se aproxima a los 10 segundos
- Más o menos un tercio se lo «come» Facebook cargando todo el entorno, aquí o la mejora pasa porque Facebook optimiza o porque el usuario adquiere más capacidad de proceso ancho de banda
- Otra mitad nos la comemos preguntando a Facebook por el grafo social de nuestro usuario utilizando las llamadas de la API
- Y el resto es entre cargas de imágenes, javascripts, style sheets, etc, lo mismo que antes o mejoramos el tamaño/distancia hasta el usuario o este se compra algo más rápido
- Queremos bajar de los 10 segundos hasta los 2 segundos!, reducir el tiempo de carga un 80%
Mejorar!
Vamos primero a lo fácil, la gente de Facebook ha activado un sistema para evitar recargar todo el site cuando un usuario accede a una aplicación, pues activamos QuickTransitions y nos cargamos unos 3 segundos de un plumazo (se tiene que ir con cuidado al activarlo, hay cosas que se pueden romper)
Mejora de carga para ficheros estáticos y pequeñitos, convertir algunos de ellos en sprites y concatenar Javascripts y CSS, aquí ganamos poco tiempo pero reducimos la carga en los servidores y es un proyecto que afecta más los diseñadores de front-end. También deshabilitamos el Output Caching en el IIS 7.5 y activamos expires de 30 minutos para los contenidos estáticos con lo que los clicks dentro de la aplicación serán mucho más ágiles
Y por último llegamos a la parte de la chicha del tiempo gastado, y sobretodo la que nos impide sacar más el provecho a la concurrencia en el server, el thread se queda enganchado demasiado tiempo con lo que seria muy fácil encolar peticiones por falta de Threads aunque el servidor tenga un alto % de idle. En el siguiente gráfico vemos como algo tan trivial como la velocidad de acceso del usuario puede tener un efecto perverso en las peticiones por segundo que puedan asumir nuestros servidores
Gracias a los datos de la instrumentación de la aplicación vemos que tocar la Base de Datos (un SQL 2008 con tablas pequeñas) nos aportaria poco, así que vamos a ver como mejorar el acceso al API de Facebook teniendo en cuenta que es poco cacheable
- Lanzar N Threads adicionales al de ASP.net y que cada uno de ellos haga sus llamadas al API de FB. De esta forma solo tardará lo que tarde el camino crítico de llamadas, ya que puede que algunas se tengan que hacer en serie o hacer la petición de forma asíncrona
- Optimizar las llamadas al API y en lugar de utilizar las llamadas estándar, utilizar consultas FQL, una especie de SQL sobre los datos de Facebook
Gracias, Amigo! primero crea la sesión contra Facebook, luego pregunta por los datos completos del usuario que está accediendo (para poder conocer su nombre) y por último carga el grafo social mediante dos llamada en serie, primero pregunta por los amigos que estén dados de alta en la aplicación y luego pide los nombres y foto del perfil de cada uno. Visto esto, la opción de lanzar unos Threads nos mejoraría poco, solo podríamos paralelizar las peticiones sobre el usuario actual con la de los amigos, que en el fondo son las que gastan tiempo, así que reformamos parte del código y con una sola consulta FQL obtenemos todos los datos necesarios para la aplicación con lo que hemos pasado de tres llamadas a FB (unos 400 ms entre una cosa y la otra por cabeza) a solo una que además es mucho más óptima ya que en el fondo las llamadas al API son wrappers sobre consultas FQL. Y eso que los servidores están en USA, si estuviesen en el otro lado del charco, la mejora todavía hubiese sido más importante
Así que sumando todo lo hecho hemos conseguido pasar de unos 10 segundos de carga a menos de 2 donde gran parte de la mejora se la lleva el hecho de utilizar una sola llamada y encima más eficiente y un pequeño cambio en la configuración de cómo Facebook trata las peticiones
Saludos!