jueves, 26 de agosto de 2010

Bug en PostfixAdmin con base de datos Mysql. Too many connections

He detectado un mini bug en PostfixAdmin que hace que la plataforma rechace conexiones a la base de datos, debido a que genera una saturación de conexiones al motor, Mysql en este caso. El problema radica principalmente en que las funciones de dicho programa solamente abren las conexiones a la base de datos y no las cierran una vez finalizada la query o la operación correspondiente a la base de datos. Con ello, satura el spool de conexiones del motor, dejándolo inhabilitado para operar.

Read More PostfixAdmin es una herramienta, desarrollada en PHP, que permite administrar casillas de correo electrónico para postfix, con utilidades y aplicaciones bastante interesantes, como el Backup, la generación de cuentas y test de la misma online, etc. Bastante recomendable. De hecho tiene soporte multi base de datos, etc. Para mayor información de la app pueden recurrir al sitio http://postfixadmin.sourceforge.net/ y mirar más características de éste.
Particularmente en esta ocasión comentaré acerca de un problema que se generó al utilizar esta plataforma. El proyecto requería una inyección masiva de cuentas online y para ello se modificó la estructura del programa, aprovechando que es free software, con licencia GNU. Al requerir una inyección masiva y continua de cuentas de correo en el sistema, nos percatamos, gracias al control de errores que creé para la plataforma superior que utilizaba esta herramienta, que la plataforma en un momento dejó de crear cuentas. Al revisar los errores de la plataforma, comprobamos un "Too many connections" en el log. Al revisar en Mysql las conexiones activas, vimos que había superado las 170 mil conexiones y no permitía seguir ingresando más.

mysql> show status like '%onn%';
+------------------------------------+------------+
| Variable_name                     | Value      |
+------------------------------------+------------+
| Aborted_connects                | 14            |
| Connections                          | 171083  |
| Max_used_connections      | 31           |
| Ssl_client_connects            | 0              |
| Ssl_connect_renegotiates | 0              |
| Ssl_finished_connects       | 0              |
| Threads_connected            | 4              |
+------------------------------------+------------+
7 rows in set (0.00 sec)

Lo primero que hice fue aumentar la variable max_connections que estaba en 100 (es un mysql  5.0.77) y la setee en 500, para lo cual usé
SET GLOBAL max_connections=500;
Luego de eso al ver que solamente le di algo más de tiempo antes de caer, revisé el code del postfixadmin que estaba utilizando (create-mailbox.php) y revisé la parte referente a las conexiones a la base y certifiqué que las conexiones que se abrían no eran cerradas correctamente, creando un exceso de conexiones sin utilizar y que quedaban abiertas,  impidiendo el buen funcionamiento del motor.
El programa usa una función llamada db_connect() para realizar la conexión y db_query para realizar las consultas a la base. Dichas funciones están incluídas en el archivo functions.inc.php, cuyos cambios detallo a continuación:
//
// db_query
// Action: Sends a query to the database and returns query result and number of rows
// Call: db_query (string query)
// Optional parameter: $ignore_errors = TRUE, used by upgrade.php
//
function db_query ($query, $ignore_errors = 0)
{
global $CONF;
global $DEBUG_TEXT;
$result = "";
$number_rows = "";
static $link;
$error_text = "";
if ($ignore_errors) $DEBUG_TEXT = "";

if (!is_resource($link)) $link = db_connect ();

if ($CONF['database_type'] == "mysql") $result = @mysql_query ($query, $link)
or $error_text = "
DEBUG INFORMATION:
Invalid query: " . mysql_error($link) . "$DEBUG_TEXT";
if ($CONF['database_type'] == "mysqli") $result = @mysqli_query ($link, $query)
or $error_text = "
DEBUG INFORMATION:
Invalid query: " . mysqli_error($link) . "$DEBUG_TEXT";
if ($CONF['database_type'] == "pgsql")
{
$result = @pg_query ($link, $query)
or $error_text = "
DEBUG INFORMATION:
Invalid query: " . pg_last_error() . "$DEBUG_TEXT";
}
if ($error_text != "" && $ignore_errors == 0) die($error_text);

if ($error_text == "") {
if (preg_match("/^SELECT/i", trim($query)))
{
// if $query was a SELECT statement check the number of rows with [database_type]_num_rows ().
if ($CONF['database_type'] == "mysql") $number_rows = mysql_num_rows ($result);
if ($CONF['database_type'] == "mysqli") $number_rows = mysqli_num_rows ($result);
if ($CONF['database_type'] == "pgsql") $number_rows = pg_num_rows ($result);
}
else
{
// if $query was something else, UPDATE, DELETE or INSERT check the number of rows with
// [database_type]_affected_rows ().
if ($CONF['database_type'] == "mysql") $number_rows = mysql_affected_rows ($link);
if ($CONF['database_type'] == "mysqli") $number_rows = mysqli_affected_rows ($link);
if ($CONF['database_type'] == "pgsql") $number_rows = pg_affected_rows ($result);
}
}

$return = array (
"result" => $result,
"rows" => $number_rows,
"error" => $error_text
);
if ($CONF['database_type'] == "mysql") mysql_close($link); //  <---- LINEA AGREGADA
if ($CONF['database_type'] == "mysqli") mysqli_close($link); // <--- LINEA AGREGADA
return $return;
}

Además dado que no solo db_query abría conexiones, se tuvo que modificar también la funcion escape_string.

/**
* Clean a string, escaping any meta characters that could be
* used to disrupt an SQL string. i.e. "'" => "\'" etc.
*
* @param String (or Array)
* @return String (or Array) of cleaned data, suitable for use within an SQL
*    statement.
*/
function escape_string ($string)
{
global $CONF;
// if the string is actually an array, do a recursive cleaning.
// Note, the array keys are not cleaned.
if(is_array($string)) {
$clean = array();
foreach(array_keys($string) as $row) {
$clean[$row] = escape_string($string[$row]);
}
return $clean;
}
if (get_magic_quotes_gpc ())
{
$string = stripslashes($string);
}
if (!is_numeric($string))
{
$link = db_connect();
if ($CONF['database_type'] == "mysql")
{
$escaped_string = mysql_real_escape_string($string, $link);
}
if ($CONF['database_type'] == "mysqli")
{
$escaped_string = mysqli_real_escape_string($link, $string);
}
if ($CONF['database_type'] == "pgsql")
{
// php 5.2+ allows for $link to be specified.
if (version_compare(phpversion(), "5.2.0", ">="))
{
$escaped_string = pg_escape_string($link, $string);
}
else
{
$escaped_string = pg_escape_string($string);
}
}
if ($CONF['database_type'] == "mysql") mysql_close($link); //  <---- LINEA AGREGADA
if ($CONF['database_type'] == "mysqli") mysqli_close($link); // <--- LINEA AGREGADA
}
else
{
$escaped_string = $string;
}
return $escaped_string;
}

Con ello se solucionó el problema y ya no tengo caídas en la base por saturación de conexiones. Cabe destacar que la modificación se realizó solo para mysql y mysqli, pero si se quiere agregar para PostgreSQL solo se debe añadir el close de pg en las partes comentadas. Si alguien más tuvo este problema, ahora tiene la solución...ganancias del software libre, no?

1 comentario:

  1. Gracias por la información. De casualidad no sabes como crear una conección de postfixadmin a una base de datos que está en otro servidor sobre ssl?

    ResponderEliminar