La mayoría de desarrolladores de aplicaciones web desean que éstas sean usadas por miles de personas (y si es a la vez, mejor). Cuando se llega a un determinado número de usuarios, es difícil que la aplicación aguante esa carga si no se han tomado ciertas decisiones. La más simple (y probablemente más cara) pasa por añadir más prestaciones a los servidores con los que trabaja nuestra app, pero evidentemente podemos encontrar soluciones ligeramente mejores.
Hoy en día es extraño encontrar una aplicación web que no haga uso de AJAX, unas lo hacen de manera inteligente y otras no tanto. Vamos a ver qué podemos hacer para reducir un poco(en caso de no haberlo hecho ya) la carga que deben soportar nuestros servidores.
El consejo básico es simple: es preferible que el contenido dinámico sea generado en el lado del cliente con Javascript a que el trabajo lo hagan nuestras máquinas. Es claro que no siempre se podrá hacer eso, pero por lo general sí, como ejemplo tenemos datos que se actualizan periódicamente o que se estructuran de forma tabular o en listados.
La justificación del consejo radica en 2 puntos, en primer lugar podemos reducir el tiempo de CPU en el servidor consumido por petición, en segundo lugar podemos reducir el ancho de banda usado si enviamos datos como JSON en vez de contenido HTML pregenerado. Así dicho suena creíble, pero tenemos que comprobarlo (me fijaré en el caso concreto de PHP).
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <?php
// Codigo que sirve petición con JSON
$link = mysql_connect('xxx', 'xxx', 'xxx');
mysql_select_db('xxx');
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
$outresult = array();
while ($outresult[] = mysql_fetch_row($dbresult));
array_pop($outresult);
echo json_encode($outresult);
mysql_close($link);
?> |
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
| <?php
// Codigo que sirve la petición con HTML
$link = mysql_connect('xxx', 'xxx', 'xxx');
mysql_select_db('xxx');
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
while ($row = mysql_fetch_row($dbresult)) {
echo '<tr><td>',$row[0],'</td><td>',$row[1],'</td><td>',$row[2],'</td></tr>';
}
mysql_close($link);
?> |
Antes de hablar sobre como se procesa la petición en el lado del cliente haré algunas observaciones. Aunque en este código no está, añadí código para medir cuantos milisegundos tardaba cada script en ejecutarse. Para aumentar la fiabilidad de las pruebas conté solo el tiempo que se tarda en procesar el resultado de la consulta a la base de datos y enviarla. Así pues excluyo el acceso a la base de datos y el cierre de la conexión de las medidas efectuadas, que son las siguientes (había 200 registros en la base de datos):
- Petición servida en JSON (10 muestras):
- Número de bytes enviados: 18830
- Pico de memoria consumida: 305128 bytes ~ 297.976 Kb
- Tiempo mínimo: 0.62799 ms
- Tiempo máximo: 1.2610 ms
- Tiempo medio: 0.9064 ms
- Desviación típica: 0.1436 ms
- Petición servida en HTML (10 muestras):
- Número de bytes enviados: 23854
- Pico de memoria consumida: 100008 bytes ~ 97.664 Kb
- Tiempo mínimo: 0.7529 ms
- Tiempo máximo: 2.4579 ms
- Tiempo medio: 1.2909 ms
- Desviación típica: 0.4956 ms
Lo más destacable de estas medidas es el número de bytes enviados, con JSON podemos ahorrar (como mínimo) un 21.06% de ancho de banda. Digo como mínimo porque en este ejemplo el código HTML generado es más bien austero, ya que no incorpora clases CSS ni identificadores para facilitar el manejo del DOM.
El segundo dato nos muestra la cantidad máxima de memoria consumida durante la petición procesada. Como era esperable, la primera versión del código consume más memoria debido al array que va construyendo y a la cadena JSON que ensambla finalmente a partir de la lista generada. En este punto hay que tener en cuenta que, si somos listos, esto no tiene por qué suponer un gran problema ya que ese mismo array podríamos (y de hecho es recomendable) guardarlo usando (por ejemplo) Memcached de forma que ahorramos posteriores consultas a la base de datos.
Respecto a los otros datos tengo que decir que no son significativos en lo tocante al asunto JSON vs HTML, pues la comparativa no es del todo justa. Sin embargo, lo que sí podemos ver es qué supone usar repetidas veces la primitiva echo frente a un solo uso. Por un lado aumentan los tiempos de ejecución, y por otro aumenta la dispersión de las medidas (ver desviación típica), lo que puede tocar un poco las narices cuando queremos hacer cálculos de cara a escalar nuestra infraestructura.
Otra versión del código que genera HTML es la siguiente:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
| <?php
$link = mysql_connect('xxx', 'xxx', 'xxx');
mysql_select_db('xxx');
$dbresult = mysql_query('SELECT nick, passwd, name FROM json');
$outresult = '';
while ($row = mysql_fetch_row($dbresult)) {
$outresult .= '<tr><td>'.$row[0].'</td><td>'.$row[1].'</td><td>'.$row[2].'</td></tr>';
}
echo $outresult;
mysql_close($link);
?> |
Los datos que arroja esta versión son los siguientes:
- Petición servida en HTML (2ª versión, 10 muestras):
- Número de bytes enviados: 23854
- Pico de memoria consumida: 126432 bytes ~ 123.469 Kb
- Tiempo mínimo: 0.3531 ms
- Tiempo máximo: 0.8111 ms
- Tiempo medio: 0.5435 ms
- Desviación típica: 0.1577 ms
Podemos extraer algunas conclusiones más de esta tercera lista de cifras. En primer lugar se nos podría ocurrir adoptar el mismo enfoque para la generación de JSON, es decir, olvidarnos de construir un array y pasar a construir un string directamente (de hecho obtendríamos mejores resultados, pues la cadena JSON es más corta que la cadena HTML). Si no vamos a usar más esos datos en un lapso de tiempo breve parece lo más acertado, pero si tenemos planeado acceder a ellos otra vez puede que sí nos convenga construir la lista como en el primer ejemplo de código.
Un detalle interesante es que teniendo un array probablemente sea más rápido generar un texto JSON con la función json_encode que concatenando strings en un bucle. El problema que tenemos (en este caso) es que el driver nativo de MySQL para PHP no permite obtener una lista con todas las filas directamente, y consecuentemente nos vemos forzados a construirla nosotros (que es menos eficiente que usar un método nativo de PHP programado en C, y de ahí que el tercer ejemplo rinda mejor que el segundo).
Tengo que admitir que no conozco por qué no hay un método nativo de PHP para obtener en forma de array todos los resultados de una consulta a una base de datos MySQL, pero creo que, en caso de no haber razones de peso... puede ser una buena jugada implementar en C una extensión del lenguaje que haga precisamente eso para ahorrar un valioso tiempo de ejecución.
En el lado del cliente las diferencias no han sido estadísticamente significativas para mi ejemplo, aunque es de esperar que los tiempos de descarga sean menores con JSON, mientras que los de proceso sean menores con HTML. El tiempo total dependerá de la potencia de cómputo del cliente y del ancho de banda de la conexión, por lo que algunos usuarios saldrán beneficiados mientras que otros puede que tarden algunos milisegundos más en obtener la página web completamente renderizada.
Sin más rodeos, resumen rápido: JSON permite reducir el ancho de banda consumido en más de un 21%, hacer un solo echo reduce el tiempo de ejecución (la reducción puede sobrepasar fácilmente el 50%, en el caso del ejemplo es un 57.89%) y la dispersión estadística de esos mismos tiempos (lo que nos puede ayudar a realizar mejores cálculos para escalar la plataforma o puede servir para que los balanceadores de carga tengan datos más fiables con los que trabajar).
Como apunte final solo quiero destacar que es importante realizar experimentos y comprobar los hechos por uno mismo, pues la tecnología evoluciona constantemente y es probable que ciertas afirmaciones no se puedan sostener en el tiempo. Además me he dejado algunas técnicas en el tintero, como usar los métodos ob_start y ob_end_clean, que nos permiten aprovechar el buffer de escritura para evitar la necesidad de concatenaciones. Supongo que viendo la extensión del artículo comprenderéis que no lo haya comentado en profundidad. Espero que os haya sido útil
.