Caché interna Drupal

Drupal Cache Bins: Almacenamiento en Caché a Bajo Nivel para APIs de Alto Rendimiento

Guía práctica de los cache bins de Drupal: cómo usar la Cache API, crear bins personalizados con su propia tabla, invalidar por tags y configurar backends distintos por bin.

por Santi López ·

Más allá del caché de página

En resumen: La Cache API de Drupal permite crear bins de caché personalizados con su propia tabla, invalidar con cache tags y asignar backends distintos por bin (Redis, MySQL, APCu). Ideal para aislar el tráfico de APIs REST de alto volumen.

  • Crea un cache bin personalizado desde services.yml con su propia tabla
  • Invalida la caché de forma quirúrgica con cache tags
  • Asigna backends distintos por bin (Redis para renderizado, MySQL para persistencia)
  • Aísla APIs REST de alto volumen del resto del sistema

Cuando hablamos de caché en Drupal, lo primero que viene a la mente es el caché de página o el caché de renderizado. Pero Drupal ofrece una capa mucho más potente y granular: los cache bins, el sistema de almacenamiento de bajo nivel que permite cachear cualquier tipo de dato de forma aislada y con control total sobre su ciclo de vida.

Cada cache bin es un contenedor independiente que puede almacenar datos en su propio backend (base de datos, Redis, Memcache, APCu) y tiene su propia política de invalidación. Esto te permite, por ejemplo, tener la caché de renderizado en Redis para máxima velocidad y un bin personalizado en base de datos para datos que necesitan persistencia.

Los cache bins por defecto

Drupal incluye más de una docena de bins predefinidos, cada uno con un propósito específico:

BinPropósitoVolatilidad
cache.defaultDatos genéricos sin clasificarMedia
cache.renderFragmentos HTML renderizadosAlta
cache.dataDatos volátiles de corta duraciónAlta
cache.entityMetadatos de entidades (campos, bundles)Baja
cache.discoveryPlugins, derivados, hooksMuy baja
cache.configConfiguración del sitioMuy baja
cache.bootstrapDatos necesarios en el arranqueMuy baja
cache.pagePáginas completas para usuarios anónimosAlta
cache.staticCaché en memoria, solo dura una peticiónEfímera
cache.menuÁrboles de menú, enlaces activosBaja

La decisión clave no es solo qué bin usar, sino dónde almacenar cada uno. Por defecto todos van a la tabla cache_* en base de datos, pero puedes redirigir bins concretos a Redis o Memcache mientras otros se quedan en MySQL.

Caché por defecto

Cuando usas \Drupal::cache() sin parámetros o inyectas el servicio cache.default, en ambos casos estás usando el bin default. El código del método cache() lo deja claro:

public static function cache($bin = 'default') {
  return static::getContainer()->get('cache.' . $bin);
}

El bin default se corresponde con la tabla cache_default en la base de datos. Si inspeccionas la columna data, verás los datos serializados en el formato nativo de PHP:

a:10:{i:8395;s:21:"Mos Pagus Pala Quibus";i:14329;s:25:"Appellatio Minim Odio Sed";i:2216;s:16:"Amet Magna Pecus";i:12003;s:6:"Iustum";i:19524;s:8:"Rusticus";i:11198;s:30:"Aptent Defui Hendrerit Refoveo";i:11461;s:19:"Defui Probo Vindico";i:14570;s:16:"Genitus Praesent";i:18816;s:4:"Ideo";i:12606;s:20:"Blandit Eligo Molior";}

Otros contenedores de caché

Si buscas los servicios disponibles filtrando por tag cache.bin, verás todos los bins que Drupal registra. Cada uno tiene su tabla correspondiente:

BinTabla en base de datos
cache.defaultcache_default
cache.rendercache_render
cache.datacache_data
cache.entitycache_entity
cache.discoverycache_discovery
cache.configcache_config
cache.bootstrapcache_bootstrap
cache.pagecache_page
cache.staticEn memoria (sin tabla)
cache.menucache_menu

Límite de registros por tabla

Por defecto, el número de registros en cada tabla de caché está limitado a 5000 filas. Esto evita que las tablas crezcan indefinidamente y terminen ralentizando o llenando el espacio disponible en el servidor.

Puedes cambiar este límite globalmente o por bin concreto desde settings.php:

// Valor por defecto para todos los contenedores.
$settings['database_cache_max_rows']['default'] = 100000;

// Sin límite para el bin 'dynamic_page_cache'.
$settings['database_cache_max_rows']['bins']['dynamic_page_cache'] = -1;

El problema: cuando la tabla cache se descontrola

En un proyecto real con Drupal exponiendo una API REST de alto volumen, me encontré con un problema concreto: las respuestas de la API generaban una cantidad masiva de entradas en cache_default. Cada petición cacheada escribía en la misma tabla que usan decenas de módulos para sus propios datos. El resultado era una tabla de caché con millones de filas, lentitud en las consultas y un cache_clear_all() que bloqueaba la base de datos durante segundos.

La solución fue crear un cache bin personalizado con su propia tabla, aislando la caché de la API del resto del sistema. Así el volumen de la API no afectaba al rendimiento general de Drupal.

Crear un cache bin personalizado

El proceso requiere dos pasos: declarar el bin en services.yml y consumirlo desde tu código.

1. Declarar el bin

En el fichero services.yml de tu módulo custom:

services:
  cache.mi_api_cache:
    class: Drupal\Core\Cache\CacheBackendInterface
    tags:
      - { name: cache.bin }
    factory: ['@cache_factory', 'get']
    arguments: [mi_api_cache]

Esto registra un nuevo bin llamado mi_api_cache. Solo necesitarás ejecutar drush cr y Drupal creará automáticamente la tabla cache_mi_api_cache en la base de datos, completamente independiente de cache_default. Si tienes Redis configurado como backend, puedes redirigir este bin a Redis añadiendo en settings.php:

$settings['cache']['bins']['mi_api_cache'] = 'cache.backend.redis';

2. Usar el bin desde tu código

Una vez declarado, puedes consumirlo inyectando el servicio cache.mi_api_cache o llamando directamente a \Drupal::cache('mi_api_cache'):

$cache = \Drupal::cache('mi_api_cache');

// Intentar recuperar de caché.
$cid = 'api_respuesta:' . $endpoint . ':' . md5(serialize($params));
if ($cached = $cache->get($cid)) {
  return $cached->data;
}

// Generar la respuesta (costoso).
$data = $this->buildApiResponse($endpoint, $params);

// Almacenar en caché con tags y expiración.
$cache->set($cid, $data, time() + 3600, ['api_response', 'endpoint:' . $endpoint]);

return $data;

La clave está en los cache tags: al etiquetar cada entrada con ['api_response', 'endpoint:' . $endpoint], puedes invalidar selectivamente todas las respuestas de un endpoint concreto sin tocar el resto de la caché ni afectar a otros bins.

Invalidación quirúrgica con cache tags

Uno de los puntos más potentes de la Cache API de Drupal es el sistema de tags. Te permite invalidar exactamente lo que necesitas sin recurrir a cache_clear_all():

// Invalidar todas las respuestas cacheadas de la API.
\Drupal\Core\Cache\Cache::invalidateTags(['api_response']);

// Invalidar solo las respuestas de un endpoint concreto.
\Drupal\Core\Cache\Cache::invalidateTags(['endpoint:search']);

// Invalidar un item concreto por su CID.
\Drupal::cache('mi_api_cache')->delete('api_respuesta:search:' . $hash);

También puedes combinar múltiples tags en una sola invalidación:

\Drupal\Core\Cache\Cache::invalidateTags(['node:42', 'node:17', 'user:5']);

Esto te permite, por ejemplo, invalidar la caché de la API cuando se modifica una entidad concreta en Drupal, usando un hook:

/**
 * Implements hook_entity_update().
 */
function mi_modulo_entity_update(Drupal\Core\Entity\EntityInterface $entity) {
  // Cuando se actualiza una entidad de tipo 'contenido_api',
  // invalidar las respuestas cacheadas que la incluyen.
  if ($entity->getEntityTypeId() === 'node' && $entity->bundle() === 'contenido_api') {
    \Drupal\Core\Cache\Cache::invalidateTags(['api_response']);
  }
}

Backends por bin: cada dato en su sitio

La flexibilidad de los cache bins brilla cuando combinas distintos backends según las necesidades de cada bin:

// settings.php — routing de bins a backends

// Bines de alto rendimiento → Redis.
$settings['cache']['bins']['render'] = 'cache.backend.redis';
$settings['cache']['bins']['dynamic_page_cache'] = 'cache.backend.redis';
$settings['cache']['bins']['mi_api_cache'] = 'cache.backend.redis';

// Bines que necesitan persistencia tras reinicio → base de datos.
$settings['cache']['bins']['bootstrap'] = 'cache.backend.database';
$settings['cache']['bins']['discovery'] = 'cache.backend.database';
$settings['cache']['bins']['config'] = 'cache.backend.database';

// Bin volátil y masivo → APCu (sin tocar Redis ni MySQL).
$settings['cache']['bins']['data'] = 'cache.backend.apcu';

Esta granularidad te permite optimizar costes y rendimiento sin mover toda la caché a un solo backend. Redis se reserva para lo que necesita velocidad. MySQL para lo que necesita persistencia. APCu para lo que es volátil y no justifica un servicio externo.

Cuándo crear un bin personalizado

Tres señales de que necesitas tu propio bin:

  1. Volumen masivo de datos cacheados que no debería mezclarse con la caché general. Como mi caso con la API REST: millones de entradas que degradaban el rendimiento de cache_default.

  2. Políticas de expiración radicalmente distintas. Por ejemplo, datos que caducan cada minuto frente a datos que duran semanas. Separarlos evita que los cache_clear_all() agresivos afecten datos estables.

  3. Necesidad de backend específico por razones de coste o rendimiento. Quizás necesitas Redis para la caché de renderizado pero no quieres pagar la memoria extra para datos que podrían ir a base de datos.

Cuándo NO necesitas un bin personalizado

  • Si tus datos caben sin problema en cache.default con una política de tags adecuada.
  • Si el volumen es bajo y no justifica la complejidad añadida.
  • Si puedes usar el bin cache.data o cache.default con tags para invalidación selectiva.

Lecciones aprendidas

  • Aísla lo que crece. Si un componente genera muchas entradas de caché, dale su propio bin. El aislamiento evita que un problema de volumen en una parte degrade el rendimiento global.
  • Los cache tags son tu herramienta de precisión. Invalidar por tags es mucho más eficiente que cache_clear_all(). Diseña tus tags pensando en las operaciones de invalidación que vas a necesitar.
  • Cada backend tiene su coste. Redis es rápido pero la memoria es cara. MySQL es barato pero más lento. APCu es gratis pero no sobrevive a reinicios. Elige por bin, no para todo.
  • Monitoriza el tamaño de cada bin. El módulo cache_metrics o consultas directas a la tabla cache_* te permiten detectar bines que crecen sin control antes de que se conviertan en un problema.
  • El orden de invalidación importa. Si invalidas por tags y luego reconstruyes inmediatamente, estás anulando el beneficio. En muchos casos es mejor invalidar y dejar que el siguiente usuario dispare la reconstrucción perezosa.

Los cache bins son una de las herramientas más infravaloradas de Drupal. Cuando los usas bien, multiplicas el rendimiento sin tocar una línea de código de negocio. Si estás cacheando respuestas de API, procesando datos costosos o simplemente quieres control fino sobre qué se almacena dónde, dominar los bins y los tags es el camino.

Si necesitas configurar Redis como backend de caché a bajo nivel, tienes la guía de Redis en Drupal. Y si estás migrando contenido desde un sistema externo, la guía de Migrate API te explica cómo importarlo con YAML y drush.

Preguntas frecuentes

¿Qué es un cache bin en Drupal?

Un cache bin es un contenedor independiente del sistema de caché de bajo nivel de Drupal que permite almacenar cualquier tipo de dato de forma aislada y con control total sobre su ciclo de vida. Cada bin puede usar su propio backend (base de datos, Redis, Memcache o APCu) y su propia política de invalidación, lo que permite, por ejemplo, tener el renderizado en Redis y un bin personalizado en base de datos.

¿Cuáles son los cache bins por defecto de Drupal?

Drupal incluye más de una docena de bins predefinidos, entre ellos `cache.default` (datos genéricos), `cache.render` (fragmentos HTML renderizados), `cache.data` (datos volátiles), `cache.entity` (metadatos de entidades), `cache.discovery` (plugins y hooks), `cache.config`, `cache.bootstrap`, `cache.page` (páginas anónimas), `cache.menu` y `cache.static` (efímera en memoria). Por defecto todos se almacenan en tablas `cache_*` de la base de datos.

¿Cómo crear un cache bin personalizado en Drupal?

Se crea en dos pasos. Primero se declara en el fichero `services.yml` del módulo personalizado un servicio `cache.mi_bin` que use `CacheBackendInterface`. Después se consume desde el código inyectando ese servicio o llamando a `\Drupal::cache('mi_bin')`. Drupal crea automáticamente la tabla correspondiente y el bin queda aislado del resto del sistema.

¿Cómo se invalida la caché con cache tags en Drupal?

Los cache tags permiten una invalidación quirúrgica: en lugar de limpiar todo el bin, se invalidan solo las entradas asociadas a un tag concreto. Se usan los métodos `invalidateTags()` del servicio cache_tags.invalidator o `\Drupal::service('cache_tags.invalidator')->invalidateTags()`. Esto es clave para que un bin cacheado (como el de una API) se refresque automáticamente cuando el contenido subyacente cambia.

¿Se pueden asignar backends distintos a cada cache bin?

Sí. Cada bin puede apuntar a un backend distinto configurándolo en `settings.php` con `$settings['cache']['bins']['mi_bin'] = 'cache.backend.redis'`. Esto permite, por ejemplo, que la caché de renderizado vaya a Redis para máxima velocidad y que un bin personalizado se quede en base de datos para datos que necesitan persistencia. Es la base de la estrategia de rendimiento en sitios institucionales.

¿Cuándo conviene crear un cache bin personalizado?

Conviene cuando un tipo de datos genera un volumen tan alto que degrada el rendimiento del bin compartido, como ocurrió con una API REST de alto volumen que llenaba `cache_default` con millones de filas. Aislarlo en su propia tabla evita que ese tráfico afecte al resto del sistema. No hace falta cuando el volumen es moderado o el dato ya tiene un bin por defecto adecuado.