<commit_before>---
title: Optimizando sockets con node.js
footer: Original publicado en [GodTIC](http://www.godtic.com/blog/2013/11/24/optimizando-sockets-con-node-js/) el 2013-11-24.
---

![Crédito: <NAME>](pics/optimizando-sockets-portada.jpg "Gráfica de rendimiento")

Este post es una especie de segunda parte de [Node.js: ¿rápido como el rayo?](nodejs-rapido-como-el-rayo). En esta ocasión vamos a intentar superarnos para mejorar el rendimiento de nuestro servidor nodecached, un proyecto para hacer un clon de memcached en node.js.

## Punto de partida

Nuestro punto de partida es muy sencillo: el [memcached original](http://www.memcached.org/) nos da más de 70 mil peticiones por segundo (70 krps, por _kilo requests per second_), mientras que nodecached difícilmente pasa de 30k. ¿Es posible mejorar esta situación? Vamos a intentarlo.

La función a optimizar es [`readData()`](https://github.com/alexfernandez/nodecached/blob/master/lib/server.js#L141) en la clase `Connection`, que citamos aquí para referencia:


```
    function readData(data)
    {
        [...]
        var message = data.toString();
        var line = message.substringUpTo('\r\n');
        var rest = message.substringFrom('\r\n');
        var result = parser.readLine(line);
        if (rest)
        {   
            [...]
            result = parser.readLine(rest);
        }
        [...]
        if (result)
        {
            self.socket.write(result + '\r\n');
        }   
    }
```

Es aquí donde vamos a pasar todo el tiempo de este post. Quitando el manejo de algunas opciones, lo que hace esta función es recibir datos del socket, convertirlos en cadena, partirlos en líneas y parsearlos. El resultado del parseo se escribe en el socket y se termina. Es un código síncrono muy fácil de seguir.

### Granularidad

Vamos a ver primero de qué tiempos estamos hablando. Nuesta última prueba exitosa con nodecached, usando [mc-benchmark](https://github.com/antirez/mc-benchmark), nos dio estos resultados:

```
$ ./mc-benchmark -p 11311 -c 100 -n 100000
====== SET ======
  100000 requests completed in 3.82 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1

[...]
26205.45 requests per second

====== GET ======
  100000 requests completed in 2.99 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1

[...]
33467.20 requests per second
```

Si somos capaces de responder a 26.2 krps, quiere decir que cada petición viene a tardar 1s/26.2k ~ 38 µs. 38 microsegundos suena bien, ¿no?. Para los get es aún menos: 1s/33.5k ~ 0.000029851 s ~ 30µs, o sea ¡unos 30 microsegundos!

Pero esto no es nada comparado con memcached:

```
$ ./mc-benchmark -p 11215 -c 50 -n 100000
====== SET ======
  100016 requests completed in 1.19 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

[...]
83906.04 requests per second

====== GET ======
  100012 requests completed in 0.95 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

[...]
105054.62 requests per second
```

Por algún motivo los resultados son ahora escandalosamente mejores que los que obtuvimos en [la última ocasión](http://www.godtic.com/blog/2013/11/12/node-js-rapido-como-el-rayo/), lo cual nos debe llevar a una desconfianza sana hacia los resultados que podamos obtener ahora, por buenos o malos que sean. En cualquier caso, los tiempos son ahora mucho mejores: 1/105k ~ 9.5 µs, o sea menos de 10 microsegundos. Y un pelín más para get, unos 12 µs.

La situación es la de la siguiente gráfica: tenemos un montón de “espacio de mejora” por delante.

![Rendimiento inicial](pics/optimizando-sockets-antes.png "Gráfica de rendimiento inicial")

Una curiosidad: node.js no colapsa las dos CPUs de mi máquina, siempre deja parte de la CPU disponible para otras tareas.

!(pics/optimizando-sockets-cpu-nodejs.png "CPUs con node.js")

Mientras que memcached es mucho más glotón y satura completamente las dos CPUs.

!(pics/optimizando-sockets-cpu-memcached.png "CPUs con memcached")

### Buffers y Strings

Uno de los consejos que aparecen bastante al optimizar node.js es evitar las conversiones de [Buffer](http://nodejs.org/api/buffer.html) a [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), ya que pueden llevar mucho tiempo. ¿Cuánto exactamente? Vamos a verlo.

En nuestro código tenemos la siguiente línea en [`lib/server.js`](https://github.com/alexfernandez/nodecached/blob/master/lib/server.js) (en negrita):

```
    function readData(data)
    {
        [...]
        var message = String(data);
        var line = message.substringUpTo('\r\n');
        [...]
    }
```

Vamos a usar el temporizador de alta resolución [`process.hrtime()`](http://nodejs.org/api/process.html#process_process_hrtime) para ver el tiempo exacto que tarda esta operación:
<commit_msg>Bugs en imágenes.<commit_after>---
title: Optimizando sockets con node.js
footer: Original publicado en [GodTIC](http://www.godtic.com/blog/2013/11/24/optimizando-sockets-con-node-js/) el 2013-11-24.
---

![Crédito: <NAME>](pics/optimizando-sockets-portada.jpg "Gráfica de rendimiento")

Este post es una especie de segunda parte de [Node.js: ¿rápido como el rayo?](nodejs-rapido-como-el-rayo). En esta ocasión vamos a intentar superarnos para mejorar el rendimiento de nuestro servidor nodecached, un proyecto para hacer un clon de memcached en node.js.

## Punto de partida

Nuestro punto de partida es muy sencillo: el [memcached original](http://www.memcached.org/) nos da más de 70 mil peticiones por segundo (70 krps, por _kilo requests per second_), mientras que nodecached difícilmente pasa de 30k. ¿Es posible mejorar esta situación? Vamos a intentarlo.

La función a optimizar es [`readData()`](https://github.com/alexfernandez/nodecached/blob/master/lib/server.js#L141) en la clase `Connection`, que citamos aquí para referencia:


```
    function readData(data)
    {
        [...]
        var message = data.toString();
        var line = message.substringUpTo('\r\n');
        var rest = message.substringFrom('\r\n');
        var result = parser.readLine(line);
        if (rest)
        {   
            [...]
            result = parser.readLine(rest);
        }
        [...]
        if (result)
        {
            self.socket.write(result + '\r\n');
        }   
    }
```

Es aquí donde vamos a pasar todo el tiempo de este post. Quitando el manejo de algunas opciones, lo que hace esta función es recibir datos del socket, convertirlos en cadena, partirlos en líneas y parsearlos. El resultado del parseo se escribe en el socket y se termina. Es un código síncrono muy fácil de seguir.

### Granularidad

Vamos a ver primero de qué tiempos estamos hablando. Nuesta última prueba exitosa con nodecached, usando [mc-benchmark](https://github.com/antirez/mc-benchmark), nos dio estos resultados:

```
$ ./mc-benchmark -p 11311 -c 100 -n 100000
====== SET ======
  100000 requests completed in 3.82 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1

[...]
26205.45 requests per second

====== GET ======
  100000 requests completed in 2.99 seconds
  100 parallel clients
  3 bytes payload
  keep alive: 1

[...]
33467.20 requests per second
```

Si somos capaces de responder a 26.2 krps, quiere decir que cada petición viene a tardar 1s/26.2k ~ 38 µs. 38 microsegundos suena bien, ¿no?. Para los get es aún menos: 1s/33.5k ~ 0.000029851 s ~ 30µs, o sea ¡unos 30 microsegundos!

Pero esto no es nada comparado con memcached:

```
$ ./mc-benchmark -p 11215 -c 50 -n 100000
====== SET ======
  100016 requests completed in 1.19 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

[...]
83906.04 requests per second

====== GET ======
  100012 requests completed in 0.95 seconds
  50 parallel clients
  3 bytes payload
  keep alive: 1

[...]
105054.62 requests per second
```

Por algún motivo los resultados son ahora escandalosamente mejores que los que obtuvimos en [la última ocasión](http://www.godtic.com/blog/2013/11/12/node-js-rapido-como-el-rayo/), lo cual nos debe llevar a una desconfianza sana hacia los resultados que podamos obtener ahora, por buenos o malos que sean. En cualquier caso, los tiempos son ahora mucho mejores: 1/105k ~ 9.5 µs, o sea menos de 10 microsegundos. Y un pelín más para get, unos 12 µs.

La situación es la de la siguiente gráfica: tenemos un montón de “espacio de mejora” por delante.

![Rendimiento inicial](pics/optimizando-sockets-antes.png "Gráfica de rendimiento inicial")

Una curiosidad: node.js no colapsa las dos CPUs de mi máquina, siempre deja parte de la CPU disponible para otras tareas.

![CPUs con Node.js](pics/optimizando-sockets-cpu-nodejs.png "CPUs con node.js")

Mientras que memcached es mucho más glotón y satura completamente las dos CPUs.

![CPUs con memcached](pics/optimizando-sockets-cpu-memcached.png "CPUs con memcached")

### Buffers y Strings

Uno de los consejos que aparecen bastante al optimizar node.js es evitar las conversiones de [Buffer](http://nodejs.org/api/buffer.html) a [String](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String), ya que pueden llevar mucho tiempo. ¿Cuánto exactamente? Vamos a verlo.

En nuestro código tenemos la siguiente línea en [`lib/server.js`](https://github.com/alexfernandez/nodecached/blob/master/lib/server.js) (en negrita):

```
    function readData(data)
    {
        [...]
        var message = String(data);
        var line = message.substringUpTo('\r\n');
        [...]
    }
```

Vamos a usar el temporizador de alta resolución [`process.hrtime()`](http://nodejs.org/api/process.html#process_process_hrtime) para ver el tiempo exacto que tarda esta operación:

{'fraction_non_alphanumeric': 0.10794633642930856, 'fraction_numerical': 0.03756449948400413, 'mean_word_length': 3.463841547673883, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 8, 'lorem ipsum': 0, 'www.': 6, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '4091687', 'n_tokens_mistral': 3496, 'n_tokens_neox': 3180, 'n_words': 1120}