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?

Leer más...

jueves, 5 de agosto de 2010

Google Cierra Wave...Cronica de una Muerte Anunciada?

En información publicada en el blog de Google, se señala que, aun cuando había sido presentada con bombos y platillos (de hecho linkee la presentación en mi blog unos cuantos artículos atrás) y aun teniendo unas aplicaciones tremendas en cuanto a experiencia de usuario, IMHO, cerrará las cuentas y el proyecto de Google Wave. Esto debido a que, si bien varios teníamos cuenta y, personalmente, regalé bastantes invitaciones, el sistema no tuvo la aceptación que se esperaba en la gente. Particularmente creo que las redes sociales tienen mucho que ver en el tema, en cuanto, si bien no tienen la misma experiencia que tenía Wave, tienen aplicaciones con un final bastante parecido pero usando una vía distinta.

De hecho, en Facebook, donde no tengo cuenta ni tendré, puedes "compartir" fotografías. Insisto, no en la manera que lo hacía Wave, pero con el mismo efecto final. El drag and drop de Wave no ha sido replicado aun en ninguna red social, pero la gente normalmente lo ve como una ganancia secundaria o de menor importancia. El tema de ver lo que el otro escribía, en línea, si bien es un chiche, la finalidad de ello es que el mensaje completo le llegue al destinatario, lo cual lo tienen en modalidad chat las páginas de ese estilo.Y asi, el resto de las apps de Wave.
Personalmente creo que Wave era un buen producto, pero careció de difusión, por un lado. Por otro lado, y aqui es donde creo que falló la visión y la ambición superó la realidad, Wave estaba diseñado para ser un protocolo, en realidad, era o es un protocolo de comunicación. De hecho la idea era que otras empresas crearan sus clientes para ese protocolo. Ese protocolo tenía la finalidad de reemplazar el IMAP, POP3 y demases protocolos de correo electrónico. Ahi es donde estuvo el error. Inicialmente debieron buscar la manera de integrarse a esos protocolos, en vez de desbancarlos, puesto que éstos llevan bastante tiempo con nosotros y serán muy difíciles de erradicar, por no decir casi imposible, a menos que alguna revolución tecnológica nos encamine a otro lado.
Pero como siempre, Google no pierde todo lo que hace y en el mismo artículo del blog de Google se recuerda que el código de varias aplicaciones es código abierto, por lo que cualquier otro individuo podrá añadirlo a sus aplicaciones. Además Google lo integrará con Gmail y en otras apps propias de ellos.
Leer más...