Código bloqueante y no bloqueante con NodeJS

NodeJS ofrece grandes ventajas a la hora de publicar servicios web. Concretamente, «Node» permite un elevado rendimiento de las aplicaciones con un menor consumo de recursos.  Esto quiere decir que podemos atender a un mayor número de usuarios, sin que la máquina que hace de servidor se sature rápidamente. Por supuesto, esta característica ayuda al rendimiento de las aplicaciones y, por tanto, a su escalabilidad. En el blog ya hablamos de cómo desplegar una aplicación NodeJS en un Servidor Cloud

¿Cómo consigue NodeJS este elevado rendimiento? Por medio del mencionado «código no bloqueante», que es una característica del lenguaje Javascript en general, que obviamente se extiende a la plataforma NodeJS.

Qué significa bloqueante o no bloqueante

Las tareas que realizan los lenguajes de programación usualmente llevan un tiempo para realizarse. Por ejemplo, un acceso a disco o un acceso a base de datos, requieren un tiempo para procesarse. Durante el tiempo de procesamiento de determinadas tareas el proceso que las ha iniciado tiene dos opciones:

  • Quedarse en estado «suspendido» esperando hasta que la tarea termine, es decir, hasta que la base de datos devuelva el resultado o el archivo se haya podido abrir o leer.
  • Liberar el flujo de ejecución, de modo que el proceso que inició la acción puede atender otras necesidades del lenguaje.

Los lenguajes de programación generalmente se comportan de uno u otro modo. Por ejemplo, PHP es bloqueante, por lo que su comportamiento es el definido en la opción 1. Por otra parte, Javascript es no bloqueante, por lo que su comportamiento es el descrito en la opción 2.

Ventajas del modelo no bloqueante en servicios web

Que un lenguaje de programación sea bloqueante o no en realidad no tiene nada de bueno o de malo. Las dos alternativas tienen sus ventajas e inconvenientes, pero en el caso de los servicios web, en los que generalmente se debe atender a un gran número de usuarios, el hecho de no ser bloqueante puede aportar mayores ventajas.

Por ejemplo, en PHP cada solicitud requiere un proceso independiente del sistema para ser atendida. Durante todo el procesamiento de esa solicitud ese proceso se encuentra activo, aunque en algunos momentos pueda estar desocupado (por ejemplo cuando espera la respuesta de una consulta contra la base de datos). En Node en cambio, el proceso que atiende una solicitud quedará liberado cuando esté esperando la respuesta de la base de datos y podrá atender a otras solicitudes mientras tanto.

Por supuesto, cada proceso ocupa cierta memoria y recursos de la máquina, por lo que al liberarse permite que esos recursos se pongan a disposición de otras solicitudes. Ahí está la clave del rendimiento de NodeJS y las menores necesidades de infraestructura.

Sin embargo, también hay que decir que esta característica no se lleva bien con determinados tipos de aplicaciones. Por ejemplo, un cálculo matemático realizado por código Javascript no libera el proceso, es código bloqueante en Node o en cualquier otro lenguaje. Por lo tanto, hace que NodeJS no pueda dedicarse a atender a muchos usuarios a la vez, ya que este lenguaje sólo utiliza un proceso en el servidor. En estos casos para aumentar la escalabilidad se requiere usar varios procesos, lo que hace que se requiera servidores con varios núcleos, lo que no siempre es posible. Por todo ello NodeJS no sería el mejor lenguaje para aplicaciones que requieren cantidades elevadas de cálculos matemáticos.

Código bloqueante y no bloqueante

Finalizamos con una recomendación básica. Si observas la documentación de NodeJS encontrarás que muchas de sus funciones tienen dos alternativas. Por ejemplo:

  • fs.open(path[, flags[, mode]], callback)
  • fs.openSync(path[, flags, mode])

El método open del objeto fs (File System), que sirve para abrir un fichero, tiene versiones síncronas y asíncronas. La alternativa fs.open() es asíncrona, lo que significa que es no bloqueante. Sin embargo, la alternativa fs.openSync() es síncrona, lo que indica que el proceso estará esperando hasta que se complete la operación de acceso a disco.

Como imaginarás, debemos siempre decantarnos por los métodos no bloqueantes (asíncronos), ya que son capaces de aprovechar las características descritas de NodeJS. Aunque los métodos síncronos puedan resultar más sencillos de usar, sobre todo para desarrolladores acostumbrados a lenguajes bloqueantes como PHP, no debemos caer en la tentación de escogerlos, puesto que estaremos disminuyendo sensiblemente el rendimiento de las aplicaciones y aumentando los requisitos del servidor para atender a más usuarios.