Facebook Twitter Google Digg Reddit Email Imprimir
Miércoles 20 de junio de 2018
Santoral:
Florentino
Otros:
Semana:
25
Día año:
171/365 (47%)
U.F.:
27131.66
IPC:
0,3
Dolar:
641,51
Euro:
742,83
Bitcoin:
U$ 6879.638
mindicador.cl
Time4VPS.EU - VPS hosting in Europe

PHP: Cache en tablas MySQL

Optimice su applicación con un cache de los datos procesados

Introducción

Es bastante frecuente que nuestras aplicaciones PHP extraen datos de nuestra base para generar listas como Menues o Selects, extraer datos de un webservice u otros, los que en general se mantienen constantes. Cada vez que se carga dicha página, dicho bloque de datos debe ser generado, y finalmente impactar en el tiempo de respuesta del usuario o los recursos utilizados en el servidor.

La opción que se plantea aquí es construir dicho bloque una sola vez y dejarlo disponible por un tiempo en que confiamos en que el dato no cambia o no nos impacte si la información cambia en ese lapso, por ejemplo:

Aunque existen producctos específicos para cache de éste tipo, como Memcached o APC nativo de PHP, en ocaciones no se puede incluir el software en el servidor, o se prefiere no tener dependencias adicionales.

Descripción de la propuesta

La proposición consiste en disponer una tabla donde almacenaremos los datos generados basados en un nombre y una referencia de fecha que nos indique hasta cuando es válido dicho dato de cache:

DROP TABLE IF EXISTS `mycache`;
CREATE TABLE `mycache` (
  `name` varchar(50) NOT NULL,
  `data` varchar(8000) NOT NULL,
  `updated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
  `validto` timestamp NOT NULL DEFAULT '0000-00-00 00:00:00',
  PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Los fragmentos de código a generar, se invocan mediante funciones en PHP que se encarguen de buscar el dato en el cache y se es fresco (validto > now()) se usa sin mayor procesamiento. En caso contrario, nos encargamos de generar los tags html, lo almacenamos en el cache y retornamos el texto generado.

Mejor con un ejemplo

Para efectos de un ejemplo, consideraremos la generación de 2 combo <select> de 30 elementos, los que se extraen de la tabla combotbl con la siguiente estructura:

DROP TABLE IF EXISTS `combotbl`;
CREATE TABLE `combotbl` (
  `ID` int(11) NOT NULL AUTO_INCREMENT,
  `tipo` int(11) NOT NULL,
  `value` varchar(60) NOT NULL,
  `description` varchar(120) NOT NULL,
  PRIMARY KEY (`ID`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;

Los valores de cada combo se relacionan por el campo tipo, donde se determina el valor y descripción, así, cada combo se forma mediante la función creacombo:

<html>
    <head>
        <title>
            Prueba de Cache base de datos
        </title>
    </head>    
    <body>

        <h1>
            Ejemplo de cache en base de datos
        </h1>
        
        <form name="test">
            
<?php

$db = mysqli_connect('localhost','test','password','test');
if (mysqli_connect_errno()) {
  echo "Failed to connect to MySQL: " . mysqli_connect_error();
}

$tini = microtime(TRUE);

$c1 = creacombo($db,"combo1", 1) ;
$c2 = creacombo($db,"combo2", 2) ;

$tiempo = (microtime(TRUE) - $tini) * 1000; 
echo "<div>Combo1: " . $c1 . "</div>\n" ;
echo "<div>Combo2: " . $c2 . "</div>\n" ;
echo "<br /><br />" ;
echo "<div>Tiempo: $tiempo miliegundos" ;

function creacombo($db,$name,$tipo) {
    $sql = "Select * from combotbl where tipo=$tipo order by value" ;
    $result = mysqli_query($db,$sql);

    $combo = "<select id=\"$name\">\n";
    while($row = mysqli_fetch_array($result)) {
      $combo .= '<option value="'. $row['value'] . '">' .
                $row['description'] . '</option>' . "\n";
    }
    $combo .= "</select>\n" ;
    return $combo;
}

?>

        </form>
</body>
</html>

Al realizar las pruebas, el tiempo de utilizado para generar ambos combos es del órden de 1.5 milisegundos

Ahora es donde entra a operar el cache, agregamos las funciones cache_get y cache_set las que se encargarán de recuperar y guardar los datos en el cache. Ahora, para obtener los datos seguimos los siguientes pasos:

  1. Verificamos si el dato existe en el cache y se encuentra fresco, de ser así, se retorna el valor almacenado sin mayor procesamiento.
  2. Dado que el dato no existe o no está actualizado, se debe generar a partir de los datos en la base (o WebService externo)
  3. Almacenamos el valor generado en la base de cache, asignando el tiempo de duración
  4. Retornamos el valor generado

Revicemos entonces como queda la programación de la página utilizando el cache:

<html>
    <head>
        <title>
            Prueba de Cache base de datos
        </title>
    </head>    
    <body>

        <h1>
            Ejemplo de cache en base de datos
        </h1>
        
        <form name="test">
            
<?php

$db = mysqli_connect('localhost','test','password','test');
if (mysqli_connect_errno()) {
  echo "Failed to connect to MySQL: " . mysqli_connect_error();
}

$tini = microtime(TRUE);

$c1 = creacombo($db,"combo1", 1) ;
$c2 = creacombo($db,"combo2", 2) ;

$tiempo = (microtime(TRUE) - $tini) * 1000; 
echo "<div>Combo1: " . $c1 . "</div>\n" ;
echo "<div>Combo2: " . $c2 . "</div>\n" ;
echo "<br /><br />" ;
echo "<div>Tiempo: $tiempo miliegundos" ;

function creacombo($db,$name,$tipo) {

    $combo = cache_get($db, $name);
    if ( empty($combo) ) {

        $sql = "Select * from combotbl where tipo=$tipo order by value" ;
        $result = mysqli_query($db,$sql);

        $combo = "<select id=\"$name\">\n";
        while($row = mysqli_fetch_array($result)) {
          $combo .= '<option value="'. $row['value'] . '">' . 
                    $row['description'] . '</option>' . "\n";
        }
        $combo .= "</select>\n" ;
        cache_set($db, $name, $combo, 3600) ;
    } 
    return $combo;
}

function cache_get($db,$name) {
    $sql = "select * from mycache where name = '$name' and validto > now()";
    
    $result = mysqli_query($db,$sql); 
    if ( empty($result) ) {
        return "" ;
    } else {
       $row = mysqli_fetch_array($result) ;
       return $row['data'] ;
    }
}

function cache_set($db,$name,$value,$tiempo) {
    $sql = "insert into mycache set name    = '$name',
                                    validto = now()+ INTERVAL $tiempo SECOND,
                                    data    = '$value'
                   on duplicate key update
                                    validto = values(validto),
                                    data    = values(data)";
    
    $result = mysqli_query($db,$sql); 
 }

?>
            
        </form>
</body>
</html>

Al cargar la URL concache.php, obtenemos en la primera ejecución tiempos del órden de 3 milisegundos, pero en ejecuciones posteriores, nuestro tiempo baja a los 0.9 milisegundos, lo que significa un 66% más rápido.

Si necesitamos limpiar el cache, basta con eliminar el registro en la tabla o simplemente hacemos un TRUNCATE a la tabla mycache para eliminar todo el cache.

Conclusión

Podemos ver que éste mecanismo nos trae beneficios en los tiempos de respuesta para contenidos repetitivos. Mucho dependerá de los tiempos asignados para el cache y del tipo de datos almacenados, teniendo mayores beneficios de aquellos que son más costosos de obtener, como consultas externas a WebServices, o estadísticas.

Se debe tener consideración al momento de determinar el tiempo de permanencia en cache en los siguientes aspectos:

Mejor con APC, cuando sea posible

PHP incluye un módulo nativo para Cache, que maneja los datos en memoria compartida y archivos, pero requiere ser instalado. En CentOS es bastante simple:

yum install php-pecl-apc

podemos encontrar su documentación en http://cl1.php.net/manual/es/book.apc.php. Las funciones más importantes a utilizar son:

Utilizando el mismo ejemplo anterior, nuestro código PHP queda como:

<html>
    <head>
        <title>
            Prueba de Cache "APC"
        </title>
    </head>    
    <body>

        <h1>
            Ejemplo de cache APC
        </h1>
        
        <form name="test">
        
<?php

$db = mysqli_connect('localhost','test','password','test');
if (mysqli_connect_errno()) {
  echo "Failed to connect to MySQL: " . mysqli_connect_error();
}

$tini = microtime(TRUE);

$c1 = creacombo($db,"combo1", 1) ;
$c2 = creacombo($db,"combo2", 2) ;

$tiempo = (microtime(TRUE) - $tini) * 1000; 
echo "<div>Combo1: " . $c1 . "</div>\n" ;
echo "<div>Combo2: " . $c2 . "</div>\n" ;
echo "<br /><br />" ;
echo "<div>Tiempo: $tiempo miliegundos" ;

function creacombo($db,$name,$tipo) {

    $combo = apc_fetch($name);
    if ( !($combo) ) {

        $sql = "Select * from combotbl where tipo=$tipo order by value" ;
        $result = mysqli_query($db,$sql);

        $combo = "<select id=\"$name\">\n";
        while($row = mysqli_fetch_array($result)) {
          $combo .= '<option value="'. $row['value'] . '">' .
                    $row['description'] . '</option>' . "\n";
        }
        $combo .= "</select>\n" ;
        apc_store($name, $combo, 3600) ;
    } 
    return $combo;
}

?>
        </form>
</body>
</html>

Con lo que el tiempo de carga de los combo, pasa a ¡ 0.09 milisegundos !!!, 16 veces más rápido.

Si tenemos administración de nuestro servicio PHP, lo mejor será utilizar APC, teniendo en consideración que los datos almacenados en el APC son accesibles desde cualquier script PHP dentro del servidor, incluso de otros dominios, por lo que es un riesgo de seguridad si el servidor es compartido.

Escrito por: Luis Hernán de la Barra, 05/09/2014

Recuperado desde Cache

Wyzer
Luis Hernán de la Barra
E-Mail:ldelabar@wyzer.cl
Web:www.wyzer.cl