Chapter 6. The Persistence SPI

6.1. The Persistence SPI

In Red Hat JBoss Data Grid, persistence can configure external (persistent) storage engines. These storage engines complement Red Hat JBoss Data Grid’s default in-memory storage.

Persistent external storage provides several benefits:

  • Memory is volatile and a cache store can increase the life span of the information in the cache, which results in improved durability.
  • Using persistent external stores as a caching layer between an application and a custom storage engine provides improved Write-Through functionality.
  • Using a combination of eviction and passivation, only the frequently required information is stored in-memory and other data is stored in the external storage.
Note

Programmatically configuring persistence can only be accomplished in Red Hat JBoss Data Grid’s Library Mode.

6.2. Persistence SPI Benefits

The Red Hat JBoss Data Grid implementation of the Persistence SPI offers the following benefits:

  • Alignment with JSR-107 (http://jcp.org/en/jsr/detail?id=107). JBoss Data Grid’s CacheWriter and CacheLoader interfaces are similar to the JSR-107 writer and reader. As a result, alignment with JSR-107 provides improved portability for stores across JCache-compliant vendors.
  • Simplified transaction integration. JBoss Data Grid handles locking automatically and so implementations do not have to coordinate concurrent access to the store. Depending on the locking mode, concurrent writes on the same key may not occur. However, implementors expect operations on the store to originate from multiple threads and add the implementation code accordingly.
  • Reduced serialization, resulting in reduced CPU usage. The new SPI exposes stored entries in a serialized format. If an entry is fetched from persistent storage to be sent remotely, it does not need to be deserialized (when reading from the store) and then serialized again (when writing to the wire). Instead, the entry is written to the wire in the serialized format as fetched from the storage.

6.3. Programmatically Configure the Persistence SPI

The following is a sample programmatic configuration for a Single File Store using the Persistence SPI:

Configure the Single File Store via the Persistence SPI

ConfigurationBuilder builder = new ConfigurationBuilder();
    builder.persistence()
        .passivation(false)
        .addSingleFileStore()
            .preload(true)
            .shared(false)
            .fetchPersistentState(true)
            .ignoreModifications(false)
            .purgeOnStartup(false)
            .location(System.getProperty("java.io.tmpdir"))
            .async()
                .enabled(true)
                .threadPoolSize(5)
            .singleton()
                .enabled(true)
                .pushStateWhenCoordinator(true)
                .pushStateTimeout(20000);

6.4. Persistence Examples

6.4.1. Persistence Examples

The following examples demonstrate how to configure various cache stores implementations programmatically. For a comparison of these stores, along with additional information on each, refer to the Administration and Configuration Guide.

6.4.2. Configure the Cache Store Programmatically

The following example demonstrates how to configure the cache store programmatically:

ConfigurationBuilder builder = new ConfigurationBuilder();
builder.persistence()
    .passivation(false)
    .addSingleFileStore()
        .shared(false)
        .preload(true)
        .fetchPersistentState(true)
        .purgeOnStartup(false)
        .location(System.getProperty("java.io.tmpdir"))
        .async()
           .enabled(true)
           .threadPoolSize(5)
        .singleton()
           .enabled(true)
           .pushStateWhenCoordinator(true)
           .pushStateTimeout(20000);
Note

This configuration is for a single-file cache store. Some attributes, such as location are specific to the single-file cache store and are not used for other types of cache stores.

Configure the Cache store Programatically

  1. Use the ConfigurationBuilder to create a new configuration object.
  2. The passivation elements affects the way Red Hat JBoss Data Grid interacts with stores. Passivation removes an object from an in-memory cache and writes it to a secondary data store, such as a system or database. If no secondary data store exists, then the object will only be removed from the in-memory cache. Passivation is false by default.
  3. The addSingleFileStore() elements adds the SingleFileStore as the cache store for this configuration. It is possible to create other stores, such as a JDBC Cache Store, which can be added using the addStore method.
  4. The shared parameter indicates that the cache store is shared by different cache instances. For example, where all instances in a cluster use the same JDBC settings to talk to the same remote, shared database. shared is false by default. When set to true, it prevents duplicate data being written to the cache store by different cache instances.
  5. The preload element is set to false by default. When set to true the data stored in the cache store is preloaded into the memory when the cache starts. This allows data in the cache store to be available immediately after startup and avoids cache operations delays as a result of loading data lazily. Preloaded data is only stored locally on the node, and there is no replication or distribution of the preloaded data. JBoss Data Grid will only preload up to the maximum configured number of entries in eviction.
  6. The fetchPersistentState element determines whether or not to fetch the persistent state of a cache and apply it to the local cache store when joining the cluster. If the cache store is shared the fetch persistent state is ignored, as caches access the same cache store. A configuration exception will be thrown when starting the cache service if more than one cache store has this property set to true. The fetchPersistentState property is false by default.
  7. The purgeOnStartup element controls whether cache store is purged when it starts up and is false by default.
  8. The location element configuration element sets a location on disk where the store can write.
  9. These attributes configure aspects specific to each cache store. For example, the location attribute points to where the SingleFileStore will keep files containing data. Other stores may require more complex configuration.
  10. The singleton element enables modifications to be stored by only one node in the cluster. This node is called the coordinator. The coordinator pushes the caches in-memory states to disk. This function is activated by setting the enabled attribute to true in all nodes. The shared parameter cannot be defined with singleton enabled at the same time. The enabled attribute is false by default.
  11. The pushStateWhenCoordinator element is set to true by default. If true, this property will cause a node that has become the coordinator to transfer in-memory state to the underlying cache store. This parameter is useful where the coordinator has crashed and a new coordinator is elected.

6.4.3. LevelDB Cache Store Programmatic Configuration

The following is a sample programmatic configuration of LevelDB Cache Store:

Configuration cacheConfig = new ConfigurationBuilder().persistence()
                .addStore(LevelDBStoreConfigurationBuilder.class)
                .location("/tmp/leveldb/data")
                .expiredLocation("/tmp/leveldb/expired").build();

LevelDB Cache Store programmatic configuration

  1. Use the ConfigurationBuilder to create a new configuration object.
  2. Add the store using LevelDBCacheStoreConfigurationBuilder class to build its configuration.
  3. Set the LevelDB Cache Store location path. The specified path stores the primary cache store data. The directory is automatically created if it does not exist.
  4. Specify the location for expired data using the expiredLocation parameter for the LevelDB Store. The specified path stores expired data before it is purged. The directory is automatically created if it does not exist.

6.4.4. JdbcBinaryStore Programmatic Configuration

The JdbcBinaryStore supports all key types by storeing all keys with the same hash value (hashCode method on the key) in the same table row/blob.

Important

Binary JDBC stores are deprecated in JBoss Data Grid 7.1, and are not recommended for production use. It is recommended to utilize a String Based store instead.

The following is a sample configuration for the JdbcBinaryStore :

ConfigurationBuilder builder = new ConfigurationBuilder();
  builder.persistence()
     .addStore(JdbcBinaryStoreConfigurationBuilder.class)
     .fetchPersistentState(false)
     .ignoreModifications(false)
     .purgeOnStartup(false)
     .table()
        .dropOnExit(true)
        .createOnStart(true)
        .tableNamePrefix("ISPN_BUCKET_TABLE")
        .idColumnName("ID_COLUMN").idColumnType("VARCHAR(255)")
        .dataColumnName("DATA_COLUMN").dataColumnType("BINARY")
        .timestampColumnName("TIMESTAMP_COLUMN").timestampColumnType("BIGINT")
     .connectionPool()
        .connectionUrl("jdbc:h2:mem:infinispan_binary_based;DB_CLOSE_DELAY=-1")
        .username("sa")
        .driverClass("org.h2.Driver");

JdbcBinaryStore Programmatic Configuration (Library Mode)

  1. Use the ConfigurationBuilder to create a new configuration object.
  2. Add the JdbcBinaryStore configuration builder to build a specific configuration related to this store.
  3. The fetchPersistentState element determines whether or not to fetch the persistent state of a cache and apply it to the local cache store when joining the cluster. If the cache store is shared the fetch persistent state is ignored, as caches access the same cache store. A configuration exception will be thrown when starting the cache service if more than one cache loader has this property set to true. The fetchPersistentState property is false by default.
  4. The ignoreModifications element determines whether write methods are pushed to the specific cache loader by allowing write operations to the local file cache loader, but not the shared cache loader. In some cases, transient application data should only reside in a file-based cache loader on the same server as the in-memory cache. For example, this would apply with a further JDBC based cache loader used by all servers in the network. ignoreModifications is false by default.
  5. The purgeOnStartup element specifies whether the cache is purged when initially started.
  6. Configure the table as follows:

    1. dropOnExit determines if the table will be dropped when the cache store is stopped. This is set to false by default.
    2. createOnStart creates the table when starting the cache store if no table currently exists. This method is true by default.
    3. tableNamePrefix sets the prefix for the name of the table in which the data will be stored.
    4. The idColumnName property defines the column where the cache key or bucket ID is stored.
    5. The dataColumnName property specifies the column where the cache entry or bucket is stored.
    6. The timestampColumnName element specifies the column where the time stamp of the cache entry or bucket is stored.
  7. The connectionPool element specifies a connection pool for the JDBC driver using the following parameters:

    1. The connectionUrl parameter specifies the JDBC driver-specific connection URL.
    2. The username parameter contains the user name used to connect via the connectionUrl.
    3. The driverClass parameter specifies the class name of the driver used to connect to the database.

6.4.5. JdbcStringBasedStore Programmatic Configuration

The JdbcStringBasedStore stores each entry in its own row in the table, instead of grouping multiple entries into each row, resulting in increased throughput under a concurrent load.

The following is a sample configuration for the JdbcStringBasedStore :

ConfigurationBuilder builder = new ConfigurationBuilder();
  builder.persistence().addStore(JdbcStringBasedStoreConfigurationBuilder.class)
     .fetchPersistentState(false)
     .ignoreModifications(false)
     .purgeOnStartup(false)
     .table()
        .dropOnExit(true)
        .createOnStart(true)
        .tableNamePrefix("ISPN_STRING_TABLE")
        .idColumnName("ID_COLUMN").idColumnType("VARCHAR(255)")
        .dataColumnName("DATA_COLUMN").dataColumnType("BINARY")
        .timestampColumnName("TIMESTAMP_COLUMN").timestampColumnType("BIGINT")
     .dataSource()
        .jndiUrl("java:jboss/datasources/JdbcDS");

Configure the JdbcStringBasedStore Programmatically

  1. Use the ConfigurationBuilder to create a new configuration object.
  2. Add the JdbcStringBasedStore configuration builder to build a specific configuration related to this store.
  3. The fetchPersistentState parameter determines whether or not to fetch the persistent state of a cache and apply it to the local cache store when joining the cluster. If the cache store is shared the fetch persistent state is ignored, as caches access the same cache store. A configuration exception will be thrown when starting the cache service if more than one cache loader has this property set to true. The fetchPersistentState property is false by default.
  4. The ignoreModifications parameter determines whether write methods are pushed to the specific cache loader by allowing write operations to the local file cache loader, but not the shared cache loader. In some cases, transient application data should only reside in a file-based cache loader on the same server as the in-memory cache. For example, this would apply with a further JDBC based cache loader used by all servers in the network. ignoreModifications is false by default.
  5. The purgeOnStartup parameter specifies whether the cache is purged when initially started.
  6. Configure the Table

    1. dropOnExit determines if the table will be dropped when the cache store is stopped. This is set to false by default.
    2. createOnStart creates the table when starting the cache store if no table currently exists. This method is true by default.
    3. tableNamePrefix sets the prefix for the name of the table in which the data will be stored.
    4. The idColumnName property defines the column where the cache key or bucket ID is stored.
    5. The dataColumnName property specifies the column where the cache entry or bucket is stored.
    6. The timestampColumnName element specifies the column where the time stamp of the cache entry or bucket is stored.
  7. The dataSource element specifies a data source using the following parameters:

    • The jndiUrl specifies the JNDI URL to the existing JDBC.
Note

An IOException Unsupported protocol version 48 error when using JdbcStringBasedStore indicates that your data column type is set to VARCHAR, CLOB or something similar instead of the correct type, BLOB or VARBINARY. Despite its name, JdbcStringBasedStore only requires that the keys are strings while the values can be any data type, so that they can be stored in a binary column.

6.4.6. JdbcMixedStore Programmatic Configuration

The JdbcMixedStore is a hybrid implementation that delegates keys based on their type to either the JdbcBinaryStore or JdbcStringBasedStore.

Important

Mixed JDBC stores are deprecated in JBoss Data Grid 7.1, and are not recommended for production use. It is recommended to utilize a String Based store instead.

The following is a sample configuration for the JdbcMixedStore :

ConfigurationBuilder builder = new ConfigurationBuilder();
  builder.persistence().addStore(JdbcMixedStoreConfigurationBuilder.class)
     .fetchPersistentState(false)
     .ignoreModifications(false)
     .purgeOnStartup(false)
     .stringTable()
        .dropOnExit(true)
        .createOnStart(true)
        .tableNamePrefix("ISPN_MIXED_STR_TABLE")
        .idColumnName("ID_COLUMN").idColumnType("VARCHAR(255)")
        .dataColumnName("DATA_COLUMN").dataColumnType("BINARY")
        .timestampColumnName("TIMESTAMP_COLUMN").timestampColumnType("BIGINT")
     .binaryTable()
        .dropOnExit(true)
        .createOnStart(true)
        .tableNamePrefix("ISPN_MIXED_BINARY_TABLE")
        .idColumnName("ID_COLUMN").idColumnType("VARCHAR(255)")
        .dataColumnName("DATA_COLUMN").dataColumnType("BINARY")
        .timestampColumnName("TIMESTAMP_COLUMN").timestampColumnType("BIGINT")
     .connectionPool()
        .connectionUrl("jdbc:h2:mem:infinispan_binary_based;DB_CLOSE_DELAY=-1")
        .username("sa")
        .driverClass("org.h2.Driver");

Configure JdbcMixedStore Programmatically

  1. Use the ConfigurationBuilder to create a new configuration object.
  2. Add the JdbcMixedStore configuration builder to build a specific configuration related to this store.
  3. The fetchPersistentState parameter determines whether or not to fetch the persistent state of a cache and apply it to the local cache store when joining the cluster. If the cache store is shared the fetch persistent state is ignored, as caches access the same cache store. A configuration exception will be thrown when starting the cache service if more than one cache loader has this property set to true. The fetchPersistentState property is false by default.
  4. The ignoreModifications parameter determines whether write methods are pushed to the specific cache loader by allowing write operations to the local file cache loader, but not the shared cache loader. In some cases, transient application data should only reside in a file-based cache loader on the same server as the in-memory cache. For example, this would apply with a further JDBC based cache loader used by all servers in the network. ignoreModifications is false by default.
  5. The purgeOnStartup parameter specifies whether the cache is purged when initially started.
  6. Configure the table as follows:

    1. dropOnExit determines if the table will be dropped when the cache store is stopped. This is set to false by default.
    2. createOnStart creates the table when starting the cache store if no table currently exists. This method is true by default.
    3. tableNamePrefix sets the prefix for the name of the table in which the data will be stored.
    4. The idColumnName property defines the column where the cache key or bucket ID is stored.
    5. The dataColumnName property specifies the column where the cache entry or bucket is stored.
    6. The timestampColumnName element specifies the column where the time stamp of the cache entry or bucket is stored.
  7. The connectionPool element specifies a connection pool for the JDBC driver using the following parameters:

    1. The connectionUrl parameter specifies the JDBC driver-specific connection URL.
    2. The username parameter contains the username used to connect via the connectionUrl.
    3. The driverClass parameter specifies the class name of the driver used to connect to the database.

6.4.7. JPA Cache Store Sample Programmatic Configuration

To configure JPA Cache Stores programatically in Red Hat JBoss Data Grid, use the following:

Configuration cacheConfig = new ConfigurationBuilder().persistence()
        .addStore(JpaStoreConfigurationBuilder.class)
        .persistenceUnitName("org.infinispan.loaders.jpa.configurationTest")
        .entityClass(User.class)
    .build();

The parameters used in this code sample are as follows:

  • The persistenceUnitName parameter specifies the name of the JPA cache store in the configuration file (persistence.xml ) that contains the JPA entity class.
  • The entityClass parameter specifies the JPA entity class that is stored in this cache. Only one class can be specified for each configuration.

6.4.8. Cassandra Cache Store Sample Programmatic Configuration

The Cassandra cache store is not part of the Red Hat JBoss Data Grid’s core libraries, and must be added to the classpath. For Maven projects this may be added with the following addition to your pom.xml:

<dependency>
    <groupId>org.infinispan</groupId>
    <artifactId>infinispan-cachestore-cassandra</artifactId>
    <version>${version.infinispan}</version>
</dependency>

The following configuration snippet provides an example on how to define a Cassandra Cache Store programmatically:

Configuration cacheConfig = new ConfigurationBuilder()
    .persistence()
    .addStore(CassandraStoreConfigurationBuilder.class)
    .addServer()
        .host("127.0.0.1")
        .port(9042)
    .addServer()
        .host("127.0.0.1")
        .port(9041)
    .autoCreateKeyspace(true)
    .keyspace("TestKeyspace")
    .entryTable("TestEntryTable")
    .consistencyLevel(ConsistencyLevel.LOCAL_ONE)
    .serialConsistencyLevel(ConsistencyLevel.SERIAL)
    .connectionPool()
        .heartbeatIntervalSeconds(30)
        .idleTimeoutSeconds(120)
        .poolTimeoutMillis(5)
    .build();