(Intentado) Cacheando sentencias SQL con PHP

  • Autor Autor Miguel92
  • Fecha de inicio Fecha de inicio
Miguel92

Miguel92

Alfa
Verificación en dos pasos activada
Verificado por Whatsapp
¡Usuario con pocos negocios! ¡Utiliza siempre saldo de Forobeta!
Hola a todos, estoy intentando "cachear las sentencias sql(mejor dicho el resultado de la sentencia, y no la sentencia misma perdón por eso)", esto para evitar demasiadas consultas a la base de datos, pero me he percatado que por ejemplo, si hay 100 o más posts/comentarios pero se muestra 20 por página al momento de cachear, creo que reemplazaría el archivo con la nueva página en el caso que un usuario navegue con las páginas, y estoy tratando de evitarlo realizando nuevos archivos más la página ejemplo:
sqlcache_funcname.cache (normal página inicial)
sqlcache_funcname_page2.cache (página 2)
sucesivamente...

Ya que estoy tratando de evitar el uso de librerías de terceros.
Código
PHP:
<?php

class sqlCache {

    # Nombre de la carpeta donde se almacenarán los archivos
    protected $storage = 'cacheSQL';

    # Prefijo que tendrán los archivos
    protected $prefix = 'sqlcache_';

    # La extensión que tendrán los archivos
    protected $extension = '.cache';

    // El tiempo de actualización de la caché en segundos.
    protected $upgrade = 300; # 5 Min.

    public function __construct() {
        $this->storage = $this->setStorage();
    }

    private function setStorage() {
        $storage = TS_STORAGE . $this->storage . TS_PATH;
        if(!is_dir($storage)) {
            mkdir($storage, 0777, true);
        }
        return $storage;
    }

    private function getTimeSqlCached(string $file = '') {
      // Verificar si el archivo existe antes de intentar obtener su tiempo de modificación
      if (file_exists($file)) {
         return (time() - filemtime($file) < $this->upgrade);
      }
      return false;
   }

    public function verifySqlCached(string $cache_key = '') {
        return file_exists($this->storage . $this->prefix . $cache_key . $this->extension);
    }

    public function sqlCached(string $cache_key = '', array $data = []) {
      $filename = $this->storage . $this->prefix . $cache_key . $this->extension;

      // Comparar el contenido actual de la base de datos con el caché
      $isCacheValid = $this->verifySqlCached($cache_key) && $this->getTimeSqlCached($filename);
      $currentData = $isCacheValid ? unserialize(file_get_contents($filename)) : null;

      if (!$isCacheValid || $currentData !== $data) {
         // Guardar los nuevos datos en caché si están obsoletos o diferentes
         file_put_contents($filename, serialize($data));
      } else {
         $data = $currentData; // Mantener los datos actuales si el caché es válido
      }

      return $data;
   }

    public function getSqlCached(string $namekey = '') {
      $filename = $this->storage . $this->prefix . $namekey . $this->extension;
      if ($this->verifySqlCached($namekey) && !$this->getTimeSqlCached($filename)) {
         return unserialize(file_get_contents($filename));
      } else return null;
   }

   # No estoy seguro sobre $key .= ....
   public function setCache(string $key = '', array $data = [], int $max = 0) {
       $key .= ($max !== $page OR $max <= 0) ? '' : "_p{$page}";
       if(!$this->verifySqlCached($key)) {
           return $this->sqlCached($key, $data);
       } else {
           return $this->getSqlCached($key);
       }
   }


    /**
     * Obtiene los datos almacenados en caché o los genera y almacena en caché si no están disponibles o son obsoletos.
     *
     * @param string $cacheKey La clave de la caché utilizada para identificar los datos.
     * @param int $upgrade El tiempo de expiración de la caché en segundos. Por defecto 3600 (1 hora).
     * @return mixed Los datos recuperados de la caché o generados si no están disponibles o son obsoletos.
    */
    public function getCachedData($cacheKey) {
        // Existe el archivo
        if ($this->upgrade && file_exists($cacheFile) && (time() - filemtime($cacheFile) < $upgrade)) {
            return unserialize(file_get_contents($cacheFile));
        } else {
            unlink($cacheFile);
          $data = $this->$cacheKey(); # Obtiene el resultado de la funcion
          file_put_contents($cacheFile, serialize($data));
          return $data;
       }
    }

}
$sqlCache = new sqlCache;

PHP:
$storage = TS_STORAGE . $this->storage . TS_PATH;
Tendría como resultado algo así 'root/storage/cacheSQL/' donde se almacenarán los archivos

Algunas explicaciones:
La función sqlCached() se encarga de crear el archivo.
La función getSqlCached() se encarga de verificar la existencia del archivo.
La función setCache() sería la que se encargaría de generar el archivo y de obtener los datos de dicho archivo.

El tema esta en setCache(), espero que me haya explicado de una forma que se pueda entender lo que quiero hacer... y desde ya muchas gracias!

(Esto se encuentra en ZCode[rama main 2.0.14]) Link actualizado a la fecha de hoy 20.03.25

Línea donde se aplica en c.posts.php #203 Link actualizado a la fecha de hoy 20.03.25

El código colocado aquí ya es diferente al de la repo!
 
Última edición:
Hola, tu plan suena sólido para manejar la cache de SQL. En cuanto al método setCache(), parece que estás en el camino correcto, estás creando el cache si no existe y leyendo de él si ya existe. Sólo ten en cuenta que este método depende de la variable $page, la cual no estoy viendo en tu código ni como parámetro. Asegúrate de pasarla o manejarla apropiadamente para evitar errores. En general, tu enfoque para cachear cada página de resultados de consultas como un archivo separado es bueno, asegúrate de manejar las excepciones y los errores para evitar problemas en la producción. ¡Buena suerte!
 
Porque no mejor intentas cachear el output del html y guardas el cache con el nombre del la ruta.

Tambien se me ocurre que puedas guardar los archivos de cache con sentenciasql+IDEvento
 
Porque no mejor intentas cachear el output del html y guardas el cache con el nombre del la ruta.

Tambien se me ocurre que puedas guardar los archivos de cache con sentenciasql+IDEvento
No la había pensado de esa forma...
 
No reinventes la rueda. Usa alguna librería. Yo uso bastante laravel para programar. Cuando necesito solo el "paquete de cachear" lo extraigo y uso solo esa parte en el nuevo proyecto.

Si insistes en hacerlo tu solo. Revisa el código fuente del paquete que te mencione antes y mira como funciona de paso aprendes de patrones de diseño.
 
Pasalo mejor a sitio estático. PHP igual consume recursos
 
No reinventes la rueda. Usa alguna librería. Yo uso bastante laravel para programar. Cuando necesito solo el "paquete de cachear" lo extraigo y uso solo esa parte en el nuevo proyecto.

Si insistes en hacerlo tu solo. Revisa el código fuente del paquete que te mencione antes y mira como funciona de paso aprendes de patrones de diseño.
Lo veré, lo quiero hacer así de esta forma pueda mejorar en programación, tratando de crear, las únicas librería que estoy usando son Smarty 4.5.4, JBBCode y Google(2FA)...
 
Lo veré, lo quiero hacer así de esta forma pueda mejorar en programación, tratando de crear, las únicas librería que estoy usando son Smarty 4.5.4, JBBCode y Google(2FA)...
Perfecto es lo mejor que puedes hacer para aprender a programar es revisar codigo ajeno, entonces haz lo que te digo. Mira la documentación de Laravel, como maneja el tema de Caching sigue la pista a todas las clases que invoca Cache::store.
 
Hace un tiempo desarrollé algo similar a lo que estás buscando, es un sistema de caché muy genérico que te permite guardar en archivos, bases de datos o cualquier soporte lo que quieras tener pre-calculado.

La documentación es un poco pobre por decirlo suavemente pero por ahí te puede dar algunas ideas. Podés verlo en https://github.com/mchojrin/dist-cache

Saludos!
 
Interesante tu implementación, parece bastante bien pensada. Aunque me surge una duda: ¿no sería más eficiente usar un sistema de caché en memoria en lugar de archivos en disco? Algo como APCu, Memcached o Redis evitaría accesos al sistema de archivos y podría mejorar el rendimiento si hay mucho tráfico. ¿Has considerado alguna de esas opciones o prefieres evitar servicios adicionales?

Al menos así es como yo lo hago.
 
Interesante tu implementación, parece bastante bien pensada. Aunque me surge una duda: ¿no sería más eficiente usar un sistema de caché en memoria en lugar de archivos en disco? Algo como APCu, Memcached o Redis evitaría accesos al sistema de archivos y podría mejorar el rendimiento si hay mucho tráfico. ¿Has considerado alguna de esas opciones o prefieres evitar servicios adicionales?

Al menos así es como yo lo hago.
Se puede usar cualquier método de almacenamiento que quieras. Podés implementar tu propio Muc\Cache\Storage usando APCu, Memcached, Redis o cualquier medio que quieras. De hecho, la implementación de Memcached está hecha en Muc\Cache\MemcacheStorage. El código está algo viejito porque lo había hecho para php 5.4 y nunca lo actualicé, pero las ideas base siguen vigentes.
 
Hace un tiempo desarrollé algo similar a lo que estás buscando, es un sistema de caché muy genérico que te permite guardar en archivos, bases de datos o cualquier soporte lo que quieras tener pre-calculado.

La documentación es un poco pobre por decirlo suavemente pero por ahí te puede dar algunas ideas. Podés verlo en https://github.com/mchojrin/dist-cache

Saludos!
Hola, suena interesante... lo voy a ver...
 
Interesante tu implementación, parece bastante bien pensada. Aunque me surge una duda: ¿no sería más eficiente usar un sistema de caché en memoria en lugar de archivos en disco? Algo como APCu, Memcached o Redis evitaría accesos al sistema de archivos y podría mejorar el rendimiento si hay mucho tráfico. ¿Has considerado alguna de esas opciones o prefieres evitar servicios adicionales?

Al menos así es como yo lo hago.
Es que en realidad fue la única forma que había pensado. Hasta ahora lo que se me viene a la cabeza es que se almacene en "localStorage" o como mucho en "sessionStorage" pero no sé que tan viable puede ser y tal vez hace todo lo contrario, en vez de maximizar, lo hace mucho más lenta(una teoría que no comprobé 😀).
 
Yo hace poco tenia un planteamiento similar y lo aborde con guardar y leer ficheros .json

lo que hago es darle un tiempo de vida al json y un cron cada 24 h para eliminar lo jsons viejos

luego en el controller que hace las consultas al model, mediante una funcion para calcular el tiempo de vida sigo los siguientes criterios:

1. ¿existe el json? si, ¿tiene menos de X tiempo creado? si, obtengo los datos, hago json_decode y aplico un return

2. no existe el json o es mas viejo que lo establecido, aplico la consulta, luego la cargo en variable, guardo el json para el siguiente y aplico un return con los datos.

este metodo parece arcaico, pero me esta permitiendo tener una web de 20 k de usuarios diarios en un host modesto, asi que puedo asegurar que funciona, todo depende que tan bien organices los ficheros y que tan grande sea el proyecto ya que algunos host compartidos limitan mucho los inodes
 
Yo hace poco tenia un planteamiento similar y lo aborde con guardar y leer ficheros .json

lo que hago es darle un tiempo de vida al json y un cron cada 24 h para eliminar lo jsons viejos

luego en el controller que hace las consultas al model, mediante una funcion para calcular el tiempo de vida sigo los siguientes criterios:

1. ¿existe el json? si, ¿tiene menos de X tiempo creado? si, obtengo los datos, hago json_decode y aplico un return

2. no existe el json o es mas viejo que lo establecido, aplico la consulta, luego la cargo en variable, guardo el json para el siguiente y aplico un return con los datos.

este metodo parece arcaico, pero me esta permitiendo tener una web de 20 k de usuarios diarios en un host modesto, asi que puedo asegurar que funciona, todo depende que tan bien organices los ficheros y que tan grande sea el proyecto ya que algunos host compartidos limitan mucho los inodes
De hecho en otra versión del mismo ya que había realizado pruebas, había determinado si había pasado x tiempo, se borraba y se generaba uno nuevo, y en caso que existiera una nueva entrada este haría lo mismo, excepto que en vez de eliminar reemplazaba el contenido por el nuevo. (lo último mencionado esta aplicado en el código)
 
Yo hace poco tenia un planteamiento similar y lo aborde con guardar y leer ficheros .json

lo que hago es darle un tiempo de vida al json y un cron cada 24 h para eliminar lo jsons viejos

luego en el controller que hace las consultas al model, mediante una funcion para calcular el tiempo de vida sigo los siguientes criterios:

1. ¿existe el json? si, ¿tiene menos de X tiempo creado? si, obtengo los datos, hago json_decode y aplico un return

2. no existe el json o es mas viejo que lo establecido, aplico la consulta, luego la cargo en variable, guardo el json para el siguiente y aplico un return con los datos.

este metodo parece arcaico, pero me esta permitiendo tener una web de 20 k de usuarios diarios en un host modesto, asi que puedo asegurar que funciona, todo depende que tan bien organices los ficheros y que tan grande sea el proyecto ya que algunos host compartidos limitan mucho los inodes
Puedes implementar redis o memcache y sera aun mas rapido
 
Interesante, en mi xperiencia actualmente hay varias formas d hacer 1 cache d datos y no solo del server-side. uso Laravel (a huge fan) y sé ke ya viene optimizado 'd fábrica' para exigir lo menos posible al server y eso incluye 'data cache', eso sí, dependiendo d la naturaleza d tu web deberías evaluar seriamente si 1 cache d datos procede.
En ste caso kiero compartir este link:
https://github.com/oscaralderete/store-state
dado ke se trata d 1 Svelte app ke -como es típico- hace 1 fetch d datos d 1 API, voy a mencionar akí mi web:
https://wpe.oscaralderete.com/blog
en la versión anterior, cada vez ke se hacía click ahí o se llegaba al site vía 1 post del blog, la app hacía 1 fetch d datos, incluso si del BLOG saltaba al HOME y luego volvía al BLOG el fetch se disparaba, lo cuál es terriblemente ineficiente. Buscando 1 solución a eso sin usar STORES (la alternativa clásica d Svelte para compartir datos en SPAs) pues la última versión d Svelte usa $state() y al no encontrar 1 tutorial ke lo explike decidí hacerlo yo mismo basado en el uso d STORES. Como es típico d Svelte, la solución es ridículamente simple. Lo siguiente (en el proyecto del repo) es parte del código d la página Product (o detalle del producto):

import { products } from "../store/products";
import Tools from "../tools";

console.log("products:", $products);

onMount(() => {
if ($products.length === 0) {
console.log("fetching data...");

Tools.fetch(Tools.products_endpoint, (res) => {
$products = res;

console.log("$products has been populated", $products);

product = $products.find((i) => i.id == id);
});
}
});

export let id;

let product = $products.length > 0 ? $products.find((i) => i.id == id) : null;

la parte importante es el fetch d datos (Tools.fetch() ke se ejecuta solo si el store products stá vacío '$products.length === 0'), así d simple, obviamente como es la página detalle del producto: 'let product = $products.length > 0 ? $products.find((i) => i.id == id) : null;' maneja la data del producto actual, busca el registro en $products si ya stá populated o la nulifica esperando ke el evento onMount la asigne.
Como es d suponer, la página productos tiene 1 código prácticamente =, solo ke NO SE ASIGNA la data al 'producto actual' pues no hay producto actual al tratarse d la página d todos los posts, alguien d mi ekipo me comentó con agudeza: 'oscar, sto no es falta al principio DRY?' es decir, no repitas código y no, d hecho en mi xperiencia funciona como 1 fallback, en caso d ke llegues directamente al sitio ke usa sta técnica vía 1 link d 1 post (ke podría haber publicado en tus redes sociales):
https://wpe.oscaralderete.com/blog/sincronizar-de-odoo-con-magento-24
ambas páginas: blog y post del blog deben ser capaces de hacer el populate del store. Al final 1 vez ke el store o el $state() constenga la data d posts cada vez ke retornes al blog o vayas a 1 post del blog la carga es casi inmediata.
Observaciones: en mi caso no tenemos ningún interés en ke nuestro blog posicione o ke le vaya bien respecto al SEO así ke no usamos SSR y tampoco nos interesa hacerlo, así ke si kieres algo muy para SEO no es 1 técnica conveniente; pero si trabajas procesando datos en intranets sta técnica es 1 opción a considerar.
Paginación? x supuesto, pero en ese caso sugerimos agregar a tu store 1 variable: current_page y $currentPage, y la actualización podría ser algo así:
if ($products.length === 0 || $currentPage != current_page) {
(actualiza $products y $currentPage)
}
Espero ke les sea d utilidad
 
Interesante, en mi xperiencia actualmente hay varias formas d hacer 1 cache d datos y no solo del server-side. uso Laravel (a huge fan) y sé ke ya viene optimizado 'd fábrica' para exigir lo menos posible al server y eso incluye 'data cache', eso sí, dependiendo d la naturaleza d tu web deberías evaluar seriamente si 1 cache d datos procede... (solo para acortarlo)
Entiendo el funcionamiento que planteas, pero lo único que me frena más o menos es generar el archivo cache y almacenar los datos allí todo esto pasa en el backend(PHP) y la opción con laravel no es viable, ya que no lo uso.
 
Atrás
Arriba