lunes, 31 de enero de 2011

StatementTimeout / QueryTimeout con PostgreSQL

Si no esperas lo inesperado no lo reconocerás cuando llegue.
- Heráclito de Efeso (540 AC-470 AC) Filósofo griego

Como comentaba en "las 8 falacias de los sistemas distribuídos", en los entornos de producción debemos tener en cuenta que pueden suceder hechos que no se producen en nuestros entornos de desarrollo, integración o preproducción... y ciertamente ocurren. En estos entornos son necesarios procesos y tareas concurrentes inherentes a su propia naturaleza y ambiente de "explotación" (tareas de mantenimiento tanto programados -backups-, como no programados -recuperaciones de datos-, arranque eventual de otras -nuevas- aplicaciones, procesos de datawarehousing, etc).

Uno de estos sucesos típicos suele ser el inexplicable y aleatorio bloqueo de una sesión o de toda una aplicación entera. Tras el correspondiente susto y examen exhaustivo, detectamos la causa en una consulta de base de datos que, eventualmente, tarda demasiado. En algunas ocasiones las causas son muy poco evidentes y muy difíciles de detectar, ya que sólo se producen por la coincidencia de determinados eventos (periódicos o no), e incluso en un determinado orden.

¿Y por qué tarda tanto una consulta que en nuestro entorno de preproducción se lleva apenas unas pocas decenas de milisegundos? Pues típicamente porque ocurre un bloqueo en la base de datos debido a una transacción de larga duración. Si las cosas se complican, puede existir incluso un deadlock (bloqueo mutuo) que deje nuestro sistema completamente bloqueado y produzca un efecto dominó en otras aplicaciones dependientes de la misma base de datos.

Obviamente, la solución pasa por detectar la casuística concreta y evitarla, pero mientras buscamos la causa o la solución, necesitamos mantener nuestro sistema funcionando con normalidad. Para protegernos a este tipo de eventualidades, se debe establecer un valor de timeout en nuestras consultas y transacciones a bases de datos que impida que una consulta se quede indefinidamente esperando el resultado y, por tanto, todas las sesiones de la aplicación que llegan a ese punto. En los entornos JEE, donde se configuran pools de conexiones a bases de datos que permiten un uso eficiente de recursos y conexiones de bases de datos, la ausencia de estos timeouts en esas condiciones pueden suponer el agotamiento de las conexiones del pool, debido a que no se liberan las conexiones y, por tanto, la imposibilidad de que nuevas sesiones de la aplicación puedan funcionar.

Configuración de Statement Timeout en Glassfish
En la mayoría de los servidores de aplicaciones y contenedores web JEE, existe una forma de indicar este parámetro para cada uno de nuestros pools, permitiendo especificar distintos parámetros en función de las características de las aplicaciones o de la duración de las transacciones. En el caso de Glassfish, por ejemplo, es a través del atributo StatementTimeout del pool (que internamente realiza las llamadas a setQueryTimeout() del driver.


Usando PostgreSQL, este atributo sin embargo nos ha dado una desagradable sorpresa:

Caused by: org.postgresql.util.PSQLException: Method org.postgresql.jdbc4.Jdbc4PreparedStatement.setQueryTimeout(int) is not yet implemented.
        at org.postgresql.Driver.notImplemented(Driver.java:753)
        at org.postgresql.jdbc2.AbstractJdbc2Statement.setQueryTimeout(AbstractJdbc2Statement.java:635)
        at com.sun.gjc.spi.base.ConnectionHolder.prepareStatement(ConnectionHolder.java:477)
        at oracle.toplink.essentials.internal.databaseaccess.DatabaseAccessor.prepareStatement(DatabaseAccessor.java:1162)
        at oracle.toplink.essentials.internal.databaseaccess.DatabaseCall.prepareStatement(DatabaseCall.java:612)
        at oracle.toplink.essentials.internal.databaseaccess.DatabaseAccessor.basicExecuteCall(DatabaseAccessor.java:485)

Pues si, aunque parezca increíble, el driver jdbc de Postgresql no implementa aún setQueryTimeout(), al menos hasta la versión disponible en el momento de escribir este artículo (versión Version 9.0-801 de 2010-09-20).

Establecimiento de propiedades de la conexión en el pool de conexiones de Glassfish
La solución es establecer el parámetro de conexión SocketTimeout, que especifica en segundos el tiempo máximo que se espera respuestas del servidor antes de cerrar la conexión. Este parámetro tiene el mismo efecto que el atributo StatementTimeout / QueryTimeout que comentaba anteriormente.


Para nuestros scripts PL/pgSQL, podemos establecer directamente el parámetro statement_timeout al inicio:

SET statement_timeout TO 5000; -- para 5 segundos

 <...resto del PL>

RESET statement_timeout; -- reset

En general, establecer un valor de timeout en operaciones síncronas es una buena práctica para evitar sorpresas desagradables.

Related Posts Plugin for WordPress, Blogger...
cookieassistant.com