Después de realizar la actualización automática de mi Ubuntu 9.04 a Ubuntu 9.10 “Karmic Koala” (pésima idea, por cierto) algunos botones de mi Eclipse 3.5 Galileo dejaron de funcionar. Los botones no responden al click del mouse, en cambio sí a los shortcuts del teclado.
Existe un bug registrado aquí. Está solucionado para la version 3.6M2 pero de todas maneras existe una solución para conservar tu versión actual.
La idea es ejecutar Eclipse mediante el siguiente script:
#!/bin/sh export GDK_NATIVE_WINDOWS=1 /home/santiago/eclipse-3.5/eclipse
Asumiendo que el directorio de instalación de eclipse sea: /home/santiago/eclipse-3.5/
Espero que les sea de ayuda.
Hace un tiempo enfrentando un par de problemas de performance en uno de los sitios que administro me topé con algo raro. Tenía que mejorar el acceso algunas partes del sitio y ya estaba hecho todo lo “básico” en cuanto a tunning y las respuestas no parecían llegar. Obviamente como cualquier DBA haría, me concentré en la Cache. Al analizarla vi que todo parecía normal. El hit-rate estaba por encima de lo esperado y todavía le quedaba espacio libre para seguir cacheando. Cómo optimizar más entonces?
Bien, buscando un poco econtré el siguiente artículo del Sr. Robin Schumascher: http://dev.mysql.com/tech-resources/articles/mysql-query-cache.html
Si alguien no conoce mucho de la cache de consultas (query cache) de MySQL se lo recomiendo. Dice algunas cosas básicas y explica distintas estrategias. No quiero pasar a explicar qué es la “query cache” de MySQL, ya que en artículo de Robin está muy bien explicado y en el manual de MySQL también. Simplemente voy a dar unos conceptos básicos para poder seguir con el artículo.
La “cache de consultas” (query cache) de MySQL mantiene una copia de los resultados arrojados por consultas ejecutadas en la base de datos. Por ejemplo, si realizamos una consulta a una base de datos de un foro, pidiendole usuarios (SELECT * FROM users) una vez devuelto el resultado se almacena en memoria. La proxima vez que ejecutemos la misma consulta, en vez de preguntarle a la BD y ejecutar las operaciones respectivas, nos arrojará el resultado cacheado.
Bien, esto no siempre es así. Existen algunas limitaciones, las cuales Robin comenta, y es muy importante tenerlas en cuenta.
Por ejemplo, si cambiamos las mayúsculas de la consulta anterior y consultamos lo siguiente: select * from users. esa consulta NO es igual a la anterior, por lo tanto no va a resolverse a través de la cache.
Otra limitación importante es que, si algún dato del conjunto cacheado se actualiza, la cache se invalida totalmente. Osea que si ejecutamos cualquier sentencia DML (por ejemplo un UPDATE o INSERT), la cache de usuarios se borra y la próxima vez que ejecutemos un SELECT el motor no va a poder utilizar la cache. De ahora en más me voy a concentrar en dicha limitación.
Cómo podemos entonces evitar que cada vez que se ejecute una sentencia DML se nos borre la cache? Bueno, la única forma es NO ejecutando dicha sentencia DML. Pero qué sucede si tenemos que ejecutar dicha consulta sí o sí? Bueno, entonces vamos a tener que tenerlo en cuenta a la hora del diseño. Para clarificar todo un poco más voy a plantear un ejemplo práctico.
Supongamos que estamos creando una BD de un foro. Tenemos varias tablas, y entre ellas tenemos la tabla “users”. Es probable que la tabla users contenga datos similares a estos: user_id,username, password, avatar, firma, etc. Si queremos que nuestro foro tenga un buen control y deseamos obtener datos más precisos sobre los usuarios podemos tener un campo con el numero de IP con la que se conecto dicho usuario por última vez. Aclaro que es un ejemplo muy simplificado para poder ilustrar el tema. Entonces, la tabla nos quedaría algo así:
——————
| USERS |
| #* user_id |
| * username |
| * password |
| * avatar |
| * firma |
| * ultima_IP |
——————
Cada vez que un usuario se loguea se actualiza el campo “ultima_IP” con la dirección actual. Imaginando el comportamiento básico de un foro, podemos conjeturar que cada vez que se consulta un Post, se pide a la Base de Datos información del usuario, como la firma, la imagen “avatar”, etc. Lo mas coherente sería que se cacheen estos datos, con el objetivo de no sobrecargar a la BD con información relativamente estática.
Supongamos ahora que nuestro foro funciona de la siguiente manera. Cuando se pide leer un post se obtiene una lista con TODOS los usuarios del foro. Una vez obtenido el registro completo buscamos los usuarios que realizaron entradas en dicho post dentro de la aplicación mediante un bucle. Esto no es lo recomendado, pero nuevamente aclaro, está simplificado para ilustrar el concepto. Recapitulando, cada vez que queremos ver un post realizamos la siguiente consulta: SELECT * FROM users. Acto seguido mediante un bucle obtenemos los usuarios que nos interesan y mostramos la información necesaria.
Supuestamente, cada vez que se accede a un Post, la información va a ser obtenida a través de la cache ya que siempre realizamos la misma consulta, cualquiera sea el Post. Qué sucede cuando un usuario se loguea? Como dije anteriormente, cada vez que se loguea el usuario se actualiza su campo “ultima_IP” con el valor correspondiente mediante una consulta similar a esta: “UPDATE users SET ultima_IP = X WHERE user_id = Y”. Dicha consulta va a invalidar la cache que estabámos utilizando antes y cuando se quiera acceder nuevamente a un Post la consulta no va a poder ser satisfecha por dicha cache, sino que va a tener que resolverse mediante las estrategias comunes. Pareciera que no podemos realizar ninguna mejora a este comportamiento. Por un lado, es un requerimiento necesario mostrar la información de usuarios en los Posts, y por otro, por cuestiones de seguridad debemos tener la IP de todos los usuarios. Qué soluciones existen para mejorar entonces?
Deben existir muchas, a mi se me ocurrió la siguiente:
Dividimos la tabla users en dos. Por un lado almacenamos la información relativamente “estática” como el nombre de usuario, el avatar, la firma, etc. Digo estática, porque esta información cambia con menos frecuencia que “ultima_ip” que cambia cada vez que el usuario se loguea. Siguiendo, creamos otra tabla conteniendo la información dinámica, por ejemplo la tabla “users_ip_addr” que contendrá simplemente dos campos, el id de usuario (user_id), que a la vez será la PK y la dirección IP correspondiente. Tendríamos entonces el siguiente esquema:
—————— ———————-
| USERS | | USERS_IP_ADDR |
| #* user_id | | #* user_id |
| * username | ——— | * ultima_IP |
| * password | ————————-
| * avatar |
| * firma |
——————
Ahora, cada vez que un usuario se loguea, la consulta será algo similar a: “UPDATE users_ip_addr SET ultima_IP = X WHERE user_id = Y”. Vemos que la tabla USERS no entró en juego, por lo tanto la cache no será invalidada. Bajo este esquema y dado el funcionamiento explicado anteriormente, podemos decir que las consultas para obtener usuarios podrán ser satisfechas mediante la cache un número mayor de veces.
Es importante remarcar que esto es una solución relativamente “casera” y que no se aplica en todos los casos. Está basada en la Query Cache de MySQL que cachea el conjunto de datos, si quisiéramos usarla en otro motor que no lo hiciera no tendría sentido. Simplemente trato de ilustrar las variantes que podemos encontrar a la hora de realizar la optimización de un sitio, más allá del tunning “básico” de la BD.
Otra cosa importante es, obviamente, tener consultas de buena calidad. No es una noticia y en el comienzo del articulo de Robin también está mencionado.
Hace un tiempo comencé a desarrollar un sitio que requería de una estructura de árbol para recorrer las categorías.
Después de buscar un tiempo, no pude encontrar ninguna API que tenga un buen desempeño y sea relativamente amigable.
Es por eso, que decidí escribirla por mi mismo. Dado mi trasfondo de Java (y el idílico romance con sus colecciones) tomé como decisión de diseño asemejarlo lo más posible a dichas estructuras.
Hoy cuelgo la primera oficialmente libre de bugs (eso espero). Como el nombre del post indica, es una Linked List (Lista Enlazada) totalmente implementada en PHP y con la misma interfaz que la símil de Java. Por lo tanto tenemos los mismos métodos para accederla, resultando los mismos resultados.
Espero dentro de poco poder subir el árbol, y si sigue en pié el proyecto, algunos grafos dirigidos.
Por último, le pido a cualquier usuario que si reconocen algun problema o bug me lo comuniquen así sigo mejorándola.
<?php
/**
* LinkedList
* @author Santiago Basulto
* @copyright Xingular.net
* @version 2009
* @access public
*/
class LinkedList extends ArrayObject {
private $_array;
public function __construct()
{
if(is_array(func_get_arg(0)))
{
$this->_array = func_get_arg(0);
}
else{
$this->_array = func_get_args();
}
parent::__construct($this->_array);
}
public function each($callback)
{
$iterator = $this->getIterator();
while($iterator->valid())
{
$callback($iterator->current());
$iterator->next();
}
}
public function add()
{
$parametros =func_get_args();
if(func_num_args()==2)
{
$this->addPosition($parametros[0],$parametros[1]);
}
else{
if(func_num_args()==1)
{
$this->addFirst($parametros[0]);
}
}
}
public function addFirst($object)
{
if(isset($object))
{
array_unshift($this->_array,$object);
parent::__construct($this->_array);
}
}
public function addLast($object)
{
$this->_array[] = $object;
parent::__construct($this->_array);
}
public function addPosition($index,$object)
{
if(count($this->_array)>=$index)
{
$aux = array();
for($i=0;$i<$index;$i++)
{
$aux[] = array_shift($this->_array);
}
$aux[]=$object;
$this->_array=array_merge($aux,$this->_array);
unset($aux);
parent::__construct($this->_array);
}
}
public function remove($index)
{
if($index<$this->size())
{
array_splice($this->_array,$index,1);
parent::__construct($this->_array);
}
}
public function removeObject($object)
{
$index = array_search($object,$this->_array);
if($index!=false)
{
array_splice($this->_array,$index,1);
parent::__construct($this->_array);
return true;
}
return false;
}
public function clear()
{
$nuevo = array();
$this->_array = $nuevo;
parent::__construct($this->_array);
}
// Clone es una palabra reservada del lenguaje.
//agregue el siguiente método por si alguien no lo sabe
public function copy()
{
$copia = clone $this;
return $copia;
}
public function without()
{
$args = func_get_args();
return array_values(array_diff($this->_array,$args));
}
public function element()
{
return $this->_array[0];
}
public function indexOf($value)
{
return array_search($value,$this->_array);
}
public function contains($value)
{
return ($this->indexOf($value)!=FALSE);
}
public function inspect()
{
echo "<pre>".print_r($this->_array, true)."</pre>";
}
public function last()
{
return $this->_array[count($this->_array)-1];
}
public function reverse($applyToSelf=false)
{
if (!$applyToSelf)
return array_reverse($this->_array);
else
{
$_array = array_reverse($this->_array);
$this->_array = $_array;
parent::__construct($this->_array);
return $this->_array;
}
}
// En php se devuelve una copia, no una referencia. Para devolver una referencia (malisima practica) es necesario
// usar el operador Referencia ( & )
public function toArray()
{
return $this->_array;
}
public function shift()
{
$_element = array_shift($this->_array);
parent::__construct($this->_array);
return $_element;
}
public function removeFirst()
{
return $this->shift();
}
public function pull()
{
return $this->shift();
}
public function pullFirst()
{
return $this->shift();
}
public function pullLast()
{
return $this->pop();
}
public function pop()
{
$_element = array_pop($this->_array);
parent::__construct($this->_array);
return $_element;
}
public function size()
{
return count($this->_array);
}
public function set($index,$value)
{
if($index<$this->size())
{
$this->_array[$index]=$value;
parent::__construct($this->_array);
}
return false;
}
public function get($index)
{
if($index<$this->size() && is_int($index))
{
return $this->_array[$index];
}
}
public function getFirst()
{
return $this->_array[0];
}
public function peek()
{
return $this->getFirst();
}
public function peekFirst()
{
return $this->getFirst();
}
public function peekLast()
{
return $this->getLast();
}
public function getLast()
{
return $this->_array[$this->size()-1];
}
}
?>
Para descargar:
proyecto en google-code
Hace unos minutos, recorriendo la web de MySQL (http://www.mysql.com/) me encontré con el siguiente paper:
- Database Tiering: Achieving Scale-Out by Combining MySQL with your Existing Database
(Disponible via pedido en el sitio de MySQL)
Son apenas unas cuantas hojas (10 para ser precisos) explicando dos casos de estudio importantes donde MySQL se ha visto involucrado para lograr un respaldo sólido, simple y económico basado en el motor MySQL.
Vale aclarar que en el paper, como ya lo mencioné, se explican dos casos de estudio. A saber, CitySearch y Sabre Holdings. De todas maneras solamente voy a profundizar en el caso de CitySearch ya que me parece una solución muy sencilla para implementar y con grandes beneficios.
La idea general es simplemente colaborar con el motor que tenga una empresa (generalmente motores Oracle, ya que las dos empresas en cuestión son de gran embergadura) añadiendo servidores con motor MySQL como front-end. Esto posibilita aliviar la carga del Data Server principal, lograr estabilidad y confiabilidad.
La estrategia principal para escalar en este caso fue, como indica el título de este post, implementar “Database Tiering” (Capas de Bases de Datos). Lograr introducir una nueva capa, en esta ocasion para el front-end de Bases de Datos, especificamente motor MySQL. De esta forma, las consultas irían dirigidas en primera instancia a un Balanceador de Carga, que consultaría directamente al motor MySQL (implementado como cluster, con dos servidores maestros, que a su vez replican a cadenas separadas de servidores esclavos) y de esta forma conseguiria realizar las consultas de forma eficiente en menos tiempo, ya que los servidores podrían estar geograficamente (estratégicamente) distribuidos. De esta forma se consigue aliviar considerablemente la carga.
En cuanto a la actualizacion y creación de datos, las consultas van dirigidas directamente al motor Oracle (obviamente las actualizaciones son mucho menores que las consultas), en este caso un 9i, que está instalado sobre un servidor que cada un minuto corre un script en Perl que replica los cambios hacia los servidores MySQL.
Evidentemente esta nueva arquitectura resulta muy eficiente, sobre todo con respecto a costos. Ya que comprar, instalar y configurar un motor MySQL es mucho más simple (por lo tanto más económico) que hacer lo mismo con un motor Oracle.
Otra gran ventaja de esta arquitectura es la tolerancia a fallas. Ya que los datos se encuentran replicados y si por alguna razon existiese algun tipo de problema realizar un nuevo set-up sería sencillo (y no se perderían datos, que es lo más importante).
Por último, y como conclusión final quiero decir que es importante saber abrir la cabeza y por lo tanto estar abierto a nuevas posibilidades. Siempre hay que cultivar la imaginación y el ingenio (sobre todo nosotros, los estudiantes/graduados ingenieriles).
Los invito a que lean el paper, bastante simple de entender por cierto, se encuentra en sitio de
MySQL, en la sección de Papers.
Santiago Basulto.-
| L | M | X | J | V | S | D |
|---|---|---|---|---|---|---|
| « Nov | ||||||
| 1 | 2 | 3 | 4 | 5 | 6 | 7 |
| 8 | 9 | 10 | 11 | 12 | 13 | 14 |
| 15 | 16 | 17 | 18 | 19 | 20 | 21 |
| 22 | 23 | 24 | 25 | 26 | 27 | 28 |