Documentation for a newer release is available. View Latest

InfiniSpan

Introducción

Infinispan es un almacén de datos en memoria de clave/valor que incluye un conjunto de características más sólido que otras herramientas del mismo nicho.

Proporciona un almacén de datos en memoria flexible que puedes configurar para adaptarlo a casos de uso como:

  • Impulsar el rendimiento de la aplicación con cachés locales de alta velocidad.

  • Optimizar bases de datos disminuyendo el volumen de operaciones de escritura.

  • Proporcionar resiliencia y durabilidad para datos consistentes a través de clústeres.

Canales de soporte

El soporte de Infinispan viene en dos formas; stackoverflow es el primer lugar para buscar posibles soluciones. Si no, hay un canal de chat ZULIP a medida que te pondrá en contacto directo con expertos de Infinispan.

Configuración de Infinispan

El CacheManager es la base de la mayoría de las características que usaremos. Actúa como un contenedor para todas las cachés declaradas, controlando su ciclo de vida y siendo responsable de la configuración global.

Infinispan incluye una forma muy sencilla de construir el CacheManager:

    @Bean
    InfinispanCacheProvider infinispanCacheProvider(final Marshaller marshaller) {
        var configuration = kubernetesStack
                ? buildClusteredConfigurationForKubernetes(marshaller)
                : buildDefaultClusteredConfiguration(marshaller);
        var cacheManager = new DefaultCacheManager(configuration);
        return new InfinispanCacheProvider(cacheManager, settings);
    }

    private GlobalConfiguration buildClusteredConfigurationForKubernetes(Marshaller marshaller) {
        return GlobalConfigurationBuilder.defaultClusteredBuilder()
                .cacheManagerName(cacheManagerName)
                .transport()
                .addProperty("stack", "kubernetes")
                .addProperty("configurationFile", "default-configs/default-jgroups-kubernetes.xml")
                .initialClusterSize(initialClusterSize)
                .initialClusterTimeout(initialClusterTimeout.getSeconds(), TimeUnit.SECONDS)
                .serialization()
                .marshaller(marshaller)
                .build();
    }

    private GlobalConfiguration buildDefaultClusteredConfiguration(Marshaller marshaller) {
        return GlobalConfigurationBuilder.defaultClusteredBuilder()
                .cacheManagerName(cacheManagerName)
                .transport()
                .initialClusterSize(initialClusterSize)
                .initialClusterTimeout(initialClusterTimeout.getSeconds(), TimeUnit.SECONDS)
                .serialization()
                .marshaller(marshaller)
                .build();
    }

Una caché se define por un nombre y una configuración. La configuración necesaria se puede construir utilizando la clase ConfigurationBuilder, ya disponible en nuestro classpath.

El ConfigurationBuilder se proporciona con el siguiente método:

    private Cache<Object, Object> buildInfinispanCache(final String name, final InfinispanCacheSetting infinispanCacheSetting) {
        log.info("Cache {} specified timeout of {} min, max of {}", name, infinispanCacheSetting.getTimeout(),
                infinispanCacheSetting.getMaxSize());

        var configBuilder = new ConfigurationBuilder();
        var cacheMode = CacheMode.valueOf(infinispanCacheSetting.getCacheMode());

        configBuilder.clustering()
                .cacheMode(cacheMode)
                .encoding().mediaType("application/json")
                .memory()
                .maxCount(infinispanCacheSetting.getMaxSize())
                .whenFull(EvictionStrategy.REMOVE)
                .expiration()
                .lifespan(infinispanCacheSetting.getTimeout().toMillis(), TimeUnit.MILLISECONDS);

        if (isRemote(cacheMode)) {
            configBuilder.clustering()
                    .stateTransfer().fetchInMemoryState(infinispanCacheSetting.getFetchInMemoryState())
                    .awaitInitialTransfer(infinispanCacheSetting.getAwaitInitialStateTransfer())
                    .timeout(infinispanCacheSetting.getStateTransferTimeout().toMillis());
        }

        if (cacheMeterRegister.metricsEnabled()) {
            configBuilder.statistics().enable();
        }

        final Cache<Object, Object> cache = cacheManager.administration().withFlags(CacheContainerAdmin.AdminFlag.VOLATILE)
                .getOrCreateCache(name, configBuilder.build());

        if (cacheMeterRegister.metricsEnabled()) {
            cacheMeterRegister.bind(cache);
        }

        setCacheLevelLogging(cache, name, cacheMode, infinispanCacheSetting);
        setClusterLevelLogging(cacheMode, infinispanCacheSetting);
        return cache;
    }

    private void setCacheLevelLogging(final Cache<Object, Object> cache, final String cacheName, final CacheMode cacheMode,
                                      final InfinispanCacheSetting infinispanCacheSetting) {

        if (isRemote(cacheMode) && infinispanCacheSetting.getClusterLogging()) {
            cache.addListener(new ClusterCacheLoggingListener(cacheName));
        }
        if (infinispanCacheSetting.getLocalLogging()) {
            cache.addListener(new LocalCacheLoggingListener(cacheName));
        }
    }

    private void setClusterLevelLogging(final CacheMode cacheMode, final InfinispanCacheSetting infinispanCacheSetting) {

        if (isRemote(cacheMode) && isLoggingEnabled(infinispanCacheSetting)) {
            cacheManager.addListener(new ClusterLoggingListener());
        }
    }

    private boolean isLoggingEnabled(final InfinispanCacheSetting infinispanCacheSetting) {
        return infinispanCacheSetting.getClusterLogging() || infinispanCacheSetting.getLocalLogging();
    }

    private boolean isRemote(final CacheMode cacheMode) {
        return cacheMode.isDistributed() || cacheMode.isReplicated();
    }

Toda la documentación sobre cómo configurar una caché de Infinispan está disponible aquí.

Todos los beans mencionados anteriormente los obtenemos de forma gratuita al añadir la dependencia de maven mencionada antes.

Sin embargo, la caché de Infinispan requiere los siguientes valores de configuración para cada caché proporcionada:

ipf.caching.infinispan.settings."${cache_name}".cache-mode=[CacheMode]
ipf.caching.infinispan.settings."${cache_name}".timeout=[Duration]
ipf.caching.infinispan.settings."${cache_name}".max-count=[Long]
ipf.caching.infinispan.settings."${cache_name}".cluster-logging=[Boolean]
ipf.caching.infinispan.settings."${cache_name}".local-logging=[Boolean]
  • cache_name - nombre de la caché que se utiliza

  • cache-mode - Los gestores de caché de Infinispan pueden crear y controlar múltiples cachés que usan diferentes modos. Por ejemplo, puedes usar el mismo gestor para cachés locales, cachés distribuidas y cachés con modo de invalidación.

  • timeout - duración durante la cual la caché permanecerá activa en memoria antes de ser expulsada.

  • max-count - especifica el número total de entradas que las cachés pueden contener antes de que Infinispan realice la expulsión

  • cluster-logging - instancia un ClusterCacheLoggingListener

  • local-logging - instancia un LocalCacheLoggingListener

Un ejemplo:

ipf.caching.infinispan.settings.cache1.cache-mode=REPL_ASYNC
ipf.caching.infinispan.settings.cache1.timeout=15m
ipf.caching.infinispan.settings.cache1.max-count=15000
ipf.caching.infinispan.settings.payment-data.cluster-logging=true
ipf.caching.infinispan.settings.payment-data.local-logging=true
  • cache-mode - puede establecerse en uno de los siguientes:

    • LOCAL - Los datos no se replican

    • REPL_ASYNC - Datos replicados de forma asíncrona

    • REPL_SYNC - Datos replicados de forma síncrona

    • DIST_SYNC

    • DIST_ASYNC

En caso de que el modo de caché sea distributed o replicated, se requiere la siguiente configuración adicional:

ipf.caching.infinispan.settings."${cache_name}".fetch-in-memory-state=[Boolean]
ipf.caching.infinispan.settings."${cache_name}".await-initial-state-transfer=[Boolean]
ipf.caching.infinispan.settings."${cache_name}".state-transfer-timeout=[Duration]
ipf.caching.infinispan.settings."${cache_name}".global-state-persistence-location=[String]

Más detalles sobre los campos:

  • fetch-in-memory-state - Si es verdadero, la caché obtendrá datos de las cachés vecinas cuando se inicie, de modo que la caché comience "caliente", aunque afectará al tiempo de arranque. En modo distribuido, el estado se transfiere también entre cachés en ejecución, a medida que cambia la propiedad de las claves (por ejemplo, porque una caché abandonó el clúster). Desactivar esta opción significa que a veces una clave tendrá menos propietarios que numOwner.

  • await-initial-state-transfer - Si es verdadero, esto hará que la primera llamada al método CacheManager.getCache() en el nodo que se une se bloquee y espere hasta que la unión haya terminado y la caché haya terminado de recibir el estado de las cachés vecinas (si fetchInMemoryState está habilitado). Esta opción se aplica solo a las cachés distribuidas y replicadas y está habilitada por defecto. Ten en cuenta que establecer esto en falso hará que el objeto de caché esté disponible inmediatamente, pero cualquier acceso a claves que deberían estar disponibles localmente pero que aún no se hayan transferido causará en realidad un acceso remoto (transparente). Aunque esto no tendrá impacto en la lógica de tu aplicación, podría afectar al rendimiento.

  • state-transfer-timeout - El tiempo máximo —en milisegundos— para esperar el estado de las cachés vecinas, antes de lanzar una excepción y abortar el inicio.

  • global-state-persistence-location - La ubicación para persistir el estado global de los nodos dentro de un clúster, relevante solo en despliegues de kubernetes. Tiene un valor por defecto de "java.io.tmpdir". Si la persistencia está habilitada para una caché, este valor DEBE ser siempre la carpeta padre de las ubicaciones de datos e índices de persistencia. Esta ubicación DEBE sobrevivir a un reinicio, por ejemplo un PVC en kubernetes.

Un ejemplo:

ipf.caching.infinispan.settings.cache1.fetch-in-memory-state=true
ipf.caching.infinispan.settings.cache1.await-initial-state-transfer=true
ipf.caching.infinispan.settings.cache1.state-transfer-timeout=6m
ipf.caching.infinispan.settings.cache1.global-state-persistence-location=/cache

Persistencia en Infinispan

Si es necesario, es posible usar almacenamiento en archivos para proporcionar una caché persistente. Para usar esta función, es necesario proporcionar las siguientes configuraciones obligatorias:

ipf.caching.infinispan.settings.cache1.persistence.enabled=true (1)
ipf.caching.infinispan.settings.cache1.persistence.data-location=/cache/data (2)
ipf.caching.infinispan.settings.cache1.persistence.index-location=/cache/index (3)
1 Aquí habilitamos las características de persistencia estableciendo el indicador enabled en true; si no se establece o es false, la persistencia permanecerá inactiva.
2 Aquí definimos la ruta a la carpeta en la que deseamos almacenar los datos que respaldan la caché.
3 Aquí definimos la ruta a la carpeta en la que almacenar los índices de la caché.
Los valores asignados a las ubicaciones de datos e índices. Son subdirectorios de /cache, que es la ubicación asignada a global-state-persistence-location anterior. Si no es así, verás errores en el log como el siguiente.
ISPN000558: "The store location 'foo' is not a child of the global persistent location 'bar'"

Además, existen propiedades de configuración opcionales adicionales para la caché persistida:

ipf.caching.infinispan.settings.cache1.persistence.shared= (1)
ipf.caching.infinispan.settings.cache1.persistence.preload=true (2)
ipf.caching.infinispan.settings.cache1.persistence.purgeOnStartup= (3)
ipf.caching.infinispan.settings.cache1.persistence.maxFileSize=16777216 (4)
ipf.caching.infinispan.settings.cache1.persistence.compactionThreshold=0.5 (5)
1 Esta opción debe establecerse en true cuando múltiples instancias de caché comparten el mismo almacén de caché (por ejemplo, múltiples nodos en un clúster usando un CacheStore basado en JDBC apuntando a la misma base de datos compartida). Establecer esto en true evita que múltiples instancias de caché escriban la misma modificación varias veces. Si está habilitado, solo el nodo donde se originó la modificación escribirá en el almacén. Si está deshabilitado, cada caché individual reacciona a una posible actualización remota almacenando los datos en el almacén.
2 Si es true, cuando la caché se inicia, los datos almacenados en el almacén se precargarán en memoria. Esto es particularmente útil cuando los datos del almacén serán necesarios inmediatamente después del inicio y quieres evitar que las operaciones de caché se retrasen al cargar estos datos de forma perezosa. Puede usarse para proporcionar una "caché caliente" al inicio; sin embargo, hay una penalización de rendimiento ya que el tiempo de arranque se ve afectado por este proceso. Por defecto es true
3 Si es true, purga este almacén de caché cuando se inicia.
4 Tamaño máximo de un archivo único con entradas, en bytes. Por defecto 16777216
5 Si la cantidad de espacio no utilizado en algún archivo de datos supera este umbral, el archivo se compacta: las entradas de ese archivo se copian a un archivo nuevo y el archivo viejo se elimina. Por defecto 0.5
maxFileSize y compactionThreshold son configuraciones útiles si se modifica con frecuencia la misma entrada de caché; reduce el umbral de compactación o reduce el tamaño máximo de archivo para provocar compactaciones más frecuentes del archivo de almacenamiento. Normalmente, se lanza la excepción "Too many records for this key (short overflow)" para indicar que es necesario ajustar cualquiera de estas configuraciones. Esto es particularmente relevante en el caso donde hay una gran cantidad de actualizaciones secuenciales (> 32,767) a una sola clave de caché que tiene un conjunto de datos pequeño, creando así una situación en la que no se produce ninguna compactación. No hay una regla fija aquí aparte de entender que la compactación frecuente es algo bueno. N.B. La excepción anterior provocará que el compactador falle y una acumulación de archivos en el sistema de archivos, por lo que vale la pena dedicar tiempo a comprender la necesidad. Es un problema evitable.
Nunca uses almacenes de caché basados en sistema de archivos en sistemas de archivos compartidos, como NFS o Samba, porque no proporcionan capacidades de bloqueo de archivos y puede producirse corrupción de datos.

Implementación de Infinispan

Obtenemos el bean CacheFactory gratuitamente del módulo ipf-cache-infinispan y habilitando el caché de Infinispan.

    @Bean(name = "infinispanCacheFactory")
    CacheFactory<?, ?> infinispanCacheFactory(InfinispanCacheProvider infinispanCacheProvider, CacheLogger<Object, Object> cacheLogger) {
        return new InfinispanCacheFactory(infinispanCacheProvider, cacheLogger);
    }

Luego, solo necesitas usar el CacheFactory para crear un AsyncCacheAdapter:

        @Bean(name = "paymentInfinispanDataCacheAdapter1")
        AsyncCacheAdapter<Object, Object> paymentInfinispanDataCacheAdapter1(CacheFactory<Object, Object> infinispanCacheFactory) {
            return infinispanCacheFactory.asyncCreateCacheAdapter("cache1");
        }

Clúster incrustado de Infinispan en Kubernetes

Para crear un clúster incrustado de Infinispan basado en kubernetes, DEBES habilitar kubernetes configurando la siguiente propiedad

ipf.caching.infinispan.settings.cache1.kubernetes-stack=true

esto es importante para asegurar que la configuración correcta del CacheManager esté en su lugar.

La mayoría de las cargas de trabajo desplegadas en kubernetes (la misma lógica se aplica a openshift) usan un manifiesto de Deployment. Este enfoque es perfectamente válido si la caché de Infinispan no se persiste en almacenamiento local. Si hay un requisito de persistencia, el enfoque recomendado es usar un StatefulSet. Para ser claros, esto no es porque los despliegues de tipo Deployment no puedan tener persistencia subyacente. Más bien, es porque los StatefulSet aprovisionan volúmenes únicos por instancia, que es lo que se requeriría para un clúster de caché. Un pod de Deployments, por el contrario, compartiría un único volumen en el pod.

Un ejemplo de StatefulSet con una sección de plantilla de reclamación de volumen para configurar la persistencia. Ten en cuenta la inclusión de la definición de puerto 7800, esto es obligatorio para facilitar JGROUPS, que se comenta después del ejemplo.

apiVersion: apps/v1
kind: StatefulSet
metadata:
  name: ipf-cache-infinispan
spec:
  selector:
    matchLabels:
      app: ipf-cache-infinispan
  serviceName: "ipf-cache-infinispan"
  replicas: 3
  minReadySeconds: 10
  template:
    metadata:
      labels:
        app: ipf-cache-infinispan
    spec:
      terminationGracePeriodSeconds: 10
      containers:
      - name: ipf-cache-infinispan
        image: registry.k8s.io/ipf-cache-infinispan:0.1.0
        env:
          - name: IPF_JAVA_ARGS
            value: "-Dconfig.override_with_env_vars=true -Djgroups.dns.query=ipf-cache-infinispan.mynamespace.svc.cluster.local"
          - name:
        ports:
        - containerPort: 7800
          name: dns-ping
        - containerPort: 808
          name: html
        - containerPort: 5005
          name: app
        volumeMounts:
        - name: ipf-cache-infinispan
          mountPath: /tmp/data
        - name: ipf-cache-infinispan
          mountPath: /tmp/index
  volumeClaimTemplates:
  - metadata:
      name: ipf-cache-infinispan
    spec:
      accessModes: [ "ReadWriteMany" ]
      storageClassName: "my-storage-class"
      resources:
        requests:
          storage: 10Gi

Observa la referencia a un servicio llamado "ipf-cache". En el contexto de Infinispan es importante que el servicio sea headless, lo que significa que NO se asignará clusterIP. El motivo se relaciona con el uso de JGROUPS dentro de Infinispan y, más específicamente, con el protocolo de descubrimiento de clústeres DNS_PING, que se prefiere para el descubrimiento de clústeres en entornos kubernetes. Observa también la propiedad del sistema que se pasa a la variable de entorno "IPF_JAVA_ARGS". La propiedad del sistema jgroups.dns.query está documentada como una anulación aceptable para cualquier configuración predeterminada proporcionada a jGROUPS. El valor asignado es el DNS interno habitual de kubernetes asignado a un servicio en el formato "myservicename.mynamespace.svc.cluster.local"

un ejemplo de configuración de servicio headless para el StatefulSet anterior es el siguiente; la definición del puerto 7800 es una adición obligatoria a todas las definiciones de servicio. Esto habilita JGROUPS. También se recomienda incluir el ajuste adicional publishNotReadyAddresses: true. Esto permite que JGROUPS encuentre miembros adicionales del clúster y forme el clúster de caché antes de que el servicio esté en estado "ready".

Se recomienda ENCARECIDAMENTE que el servicio de JGROUPS en kubernetes se defina en su propio manifiesto que gestione ÚNICAMENTE el puerto 7800. Esto es particularmente relevante cuando se involucran clústeres de akka.
apiVersion: v1
kind: Service
metadata:
  name: ipf-cache-infinispan
  labels:
    app: ipf-cache-infinispan
spec:
  ports:
    - name: jgroup
      port: 7800
      protocol: TCP
      targetPort: 7800
  clusterIP: None
  publishNotReadyAddresses: true
  selector:
    app: ipf-cache-infinispan

Estado global del clúster de Infinispan

Al establecer kubernetes-stack=true, el estado global del CacheManager se habilita en Infinispan. El estado global de un nodo es vital para cualquier clúster de caché. Cuando un nodo del clúster se detiene de forma ordenada, el estado del nodo se persiste al apagarse, como es evidente en los logs por lo siguiente

[INFO] [org.infinispan.CLUSTER] [] [SpringApplicationShutdownHook] - ISPN000080: Disconnecting JGroups channel `ISPN` MDC: {}
[INFO] [org.infinispan.CONTAINER] [] [SpringApplicationShutdownHook] - ISPN000390: Persisted state, version=14.0.11.Final timestamp=2023-07-13T08:45:17.267058Z MDC: {}
[DEBUG] [org.infinispan.manager.DefaultCacheManager] [] [SpringApplicationShutdownHook] - Stopped cache manager

Sin embargo, si un nodo se termina por la fuerza por cualquier motivo y lo anterior no se observa en los logs, es muy probable que el estado del nodo esté corrupto. En este caso, se aconseja purgar los datos de Infinispan para el nodo fallido antes de volver a iniciarlo. Esto permitirá que el nodo se reincorpore al clúster como un NEW NODE en lugar de intentar regresar como un nodo existente con un estado corrupto. Hay un ticket abierto en Redhat aquí ISPN-14418 sobre una posible solución automática para la versión 15. Si el estado de un nodo está corrupto y luego se reincorpora al clúster, es probable que veas errores repetidos en todos los nodos del clúster similares a los siguientes

[ERROR] [org.infinispan.interceptors.impl.InvocationContextInterceptor] [] [timeout-thread--p4-t1] - ISPN000136: Error executing command RemoveCommand on Cache 'paxi001', writing keys [WrappedByteArray[{"\v\a\l\u\e":"[\"\c\o\m\.\n... (114 bytes)]] MDC: {}
org.infinispan.util.concurrent.TimeoutException: ISPN000476: Timed out waiting for responses for request 6284 from achme-archiving-service-2-15893 after 15 seconds
    at org.infinispan.remoting.transport.impl.SingleTargetRequest.onTimeout(SingleTargetRequest.java:86)
    at org.infinispan.remoting.transport.AbstractRequest.call(AbstractRequest.java:88)
    at org.infinispan.remoting.transport.AbstractRequest.call(AbstractRequest.java:22)
    at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
    at java.base/java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:304)
    at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1128)
    at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:628)
    at java.base/java.lang.Thread.run(Thread.java:829)

Para evitar que esta situación ocurra, la recomendación es implementar una comprobación en un initContainer para detectar un archivo de bloqueo colgante.

Cuando un nodo se detiene de forma ordenada, el archivo de bloqueo (archivo \___globalState.lck en el ipf.caching.infinispan.settings.cache1.global-state-persistence-location) se elimina al apagarse. Una comprobación simple de la existencia de este archivo, fallando si se encuentra, es suficiente para asegurar que un nodo no arranque en un estado corrupto. Los siguientes fragmentos de manifiesto proporcionan un ejemplo que, además, introduce una ventana para una intervención antes de fallar el arranque del nodo si se encuentra un archivo de bloqueo colgante. El script se proporciona al initContainer a través de un configmap.

[snip]
      initContainers:
        - name: preflight-checks
          image: busybox
          env:
            - name: SLEEPTIME
              valueFrom:
                configMapKeyRef:
                  name: ipf-test-service-cm
                  key: preflightcheck.sleep
          args:
            - /bin/sh
            - -ec
            - /ipf-test-service-app/conf/preflightcheck.sh
          volumeMounts:
            - name: cache-disk
              mountPath: /cache
            - mountPath: /ipf-test-service-app/conf/preflightcheck.sh
              name: config-volume
              subPath: preflightcheck.sh
[snip]
      volumes:
        - name: config-volume
          configMap:
            name: ipf-test-service-cm
            defaultMode: 511
[snip]
apiVersion: v1
kind: ConfigMap
metadata:
  name: ipf-test-service-cm
data:
  preflightcheck.sleep: 60
  preflightcheck.sh: |
    echo "Checking the global state of the cache"
    LOCK=/cache/___global.lck
    STATE=/cache/___global.state
    RDSDOMAINDATA=/cache/rds-domain-data

    if [ -f "$LOCK" ]; then
        ls -latr /cache
    cat <<EOF
    ***ERROR***
    File $LOCK exists, this would strongly suggest the pod was forcibly terminated.
    The result is a dangling lock file and probable corruption of the local cache state,
    it is not advisable to allow this pod to start without an intervention.
    If the remainder of the cluster nodes are in a good state you may purge the persisted cache on this node.

    The command to purge is as follows:
    kubectl exec ${HOSTNAME} -c preflight-checks -- rm -rf ${LOCK} ${STATE} ${RDSDOMAINDATA}
    Please be 100% sure before proceeding, this is a destructive process

    To confirm the purge has worked you can run the following
    kubectl exec ${HOSTNAME} -c preflight-checks -- ls -latr /cache

    This container will remain up for ${SLEEPTIME} seconds after which it will exit 1 forcing the pod to restart
    EOF
        sleep $SLEEPTIME
        exit 1
    else
        echo "Global cache state check completed successfully"
    fi

Métricas de la caché Infinispan

La caché de Infinispan soporta la exposición de métricas de caché (por ejemplo, conteo de aciertos, conteo de fallos, latencia de put, latencia de get, etc.).

Las métricas se pueden habilitar/deshabilitar mediante el parámetro de configuración:

ipf.caching.infinispan.enable-metrics=true

El parámetro de configuración tiene como valor predeterminado true.

Las métricas de caché tienen dependencia de Micrometer.