Symfony 4, APCu y Memcached

Hoy quiero hablaros de un par de componentes que Symfony ha incorporado en sus últimas versiones y que gracias a ellos podemos conseguir mejorar significativamente el rendimiento de cualquier aplicación: APCu Cache Adapter y MemcachedAdaptar

¿Cómo hemos llegado a ellos?

En uno de los últimos proyectos en los que he trabajado he tenido la suerte de verme obligado a trabajar con consultas SQL nativas sin pasar Doctrine, ya que la aplicación debía funcionar sobre una base de datos ya preparada por el cliente y migrarla a Doctrine se convirtió en un verdadero quebradero de cabeza debido a la forma en que se relacionaban los datos; cualquier atisbo de intentar normalizarla solía terminar tal que así:

Tras esta pequeña introducción, en esta base de datos se encuentra la estructura completa de categorías de los productos en forma de árbol (padres e hijos en las columnas) y en base a ella se construye el menú de navegación de la aplicación:

Por tanto, en cada llamada sería necesario consultar la base de datos para generar el menú algo que como sospecharéis no es demasiado eficiente. Y es aquí donde entra el componente ACPu de Symfony, el cual como veréis a continuación es facilísimo de usar y nos servirá para resolver situaciones como ésta.

¿Qué necesito para ApcuAdaptar?

Básicamente lo único que te pide Symfony para usarlo es que se encuentre instalada (y activada) la extensión ACPu de PHP, algo tan sencillo como:

sudo apt-get install php7.0-apcu -y

Esta misma librería se encuentra disponible para el resto de versiones de PHP 7.x y permite emplear la antigua caché APC de PHP5 para almacenar datos.

Importante: si empezamos a usar el componente sin haber instalado la extensión, Symfony nos lanzará un error, por lo que mi recomendación es que diferenciéis entre entornos mientras estáis desarrollando de cara a evitar el fallo.

¿Y cómo se usa?

Para usar el adaptador que viene con Symfony de APCu bastará con los siguientes pasos:

use Symfony\Component\Cache\Adapter\ApcuAdapter;

Dado que yo la estoy empleando en el servicio encargado de construir el menú, en el constructor crearé un objeto de la clase ApcuAdapter :

class MenuService {
public function __construct(NodeRepository $nodeRepository) {
  $this->nodeRepository = $nodeRepository;
  $this->cache = new ApcuAdapter();
}

Hecho esto, emplearemos el objeto $cache para cachear en APCu el árbol de nodos obtenido desde base de datos:

public function getMenu($locale) {
  $menuCached = $this->cache->getItem(‘app.menu’);
  if ($menuCached->isHit()) {
    $menu = $menuCached->get();
  } else {
    $menu = $this->nodeRepository->buildMenu();
    $menuCached->set($menu);
    $this->cache->save($menuCached);
  }
  return $menu;
}

Como veis, lo único que hay que hacer es obtener del adaptador un Item asociado a la tag que queramos(en este caso app.menu) y comprobar si esa extracción fue un hit contra la caché. De ser así, ese mismo Item nos da un método get para obtener el objeto que cacheamos previamente. En el caso de que no haya habido un hit, crearemos el menú normalmente y a continuación se lo asignaremos al Item de la caché para salvarlo mediante el adaptador APCu y su método save .

Es decir, fácil y sencillo.

Y MemcachedAdapter

Por otra parte, a partir de Symfony 3.3 también podremos acceder a un adaptador para trabajar con Memcached, otro sistema que permite almacenar en memoria RAM los datos que queramos de cara a evitar realizar varias veces la misma operación.

Particularmente, yo suelo emplear Memcached si tengo que trabajar con API’s externas que son lentas y cuyos resultados sin embargo no cambian demasiado a lo largo del tiempo. En estos casos, cacheo los resultados de las llamadas mediante Memcached de modo que pueda acceder a ellos sin necesidad de volver a lanzar la consulta.

En las primeras versiones de Symfony, existía un bundle que te realizaba la integración de Memcache (ojo a la ausencia de la ‘d’ final) de forma muy rápida: LSWMemcache. Sin embargo, con la llegada de Symfony 3.3 y los CacheAdapters así como que finalmente la extensión memcached se impuso a memcache , dejó de tener sentido mantener este bundle. No obstante, quería mencionarlo ya que durante bastante tiempo fue de las pocas alternativas que había y su mantenimiento fue bastante duradero.

Volviendo al tema, gracias a Symfony 3 / 4, podremos tambiénemplear MemcachedAdapter para integrarnos directamente con el servicio Memcached de nuestro servidor y cachear ahí los datos que necesitemos:

<?php
namespace App\Service;
use Symfony\Component\Cache\Adapter\MemcachedAdapter;
class MemcacheService {
  private $memcacheClient; 
  private $cache;
  public function __construct($namespace) {
    $satName = $satConfig['name'];
    $this->memcachedClient = MemcachedAdapter::createConnection(
      'memcached://localhost:11211'
    );
    $this->cache = new MemcachedAdapter(
      $this->memcachedClient
    );
  }
  public function get($key) {
    $cacheItem = $this->cache->getItem($key);
    return ($cacheItem->isHit()) ? $cacheItem->get() : false;
  }
  public function set($key, $value) {
    $cacheItem = $this->cache->getItem($key);
    $cacheItem->set($value);
    $this->cache->save($cacheItem);
  }
  public function delete($key) {
    return $this->cache->deleteItem($key);
  }
}

Espero que os haya servido este artículo y que comencéis a emplear esta fantástica funcionalidad que Symfony nos trae de gratis.