GridGain Developers Hub

Cache Storage

GridGain Caches are designed as temporary storage for rapid response "cache" of data that may be required for local operation.

Cache data is always stored in a special cache store. By default, the cache maintains a weak consistency with the remote storage.

Difference Between Caches and Tables

Unlike tables, in GridGain caches do not have persistence or store transaction history.

This means that caches also do not support read-only transactions and continuous queries, as those features rely on transaction history to ensure consistency.

Additionally, fields in caches cannot be nullable.

Requirements

To use caches in your project, you need to add a gridgain-jdbc-cache-store dependency:

<dependency>
    <groupId>org.gridgain</groupId>
    <artifactId>gridgain-jdbc-cache-store</artifactId>
    <version>9.0.9</version>
</dependency>

Creating a Cache

Caches are created by using the DDL CREATE CACHE command, for example:

CREATE ZONE CACHES WITH STORAGE_PROFILES='in-memory'

CREATE CACHE Accounts (
    accountNumber INT PRIMARY KEY,
    firstName VARCHAR,
    lastName VARCHAR,
    balance DOUBLE
) WITH PRIMARY_ZONE = 'CACHES';

Cache-Only Transactions

Transactions cannot interact with both caches and tables at the same time. When you create a transaction, it must be specified as a cache-specific transaction by setting the cacheOnly option to true:

Transaction tx = client.transactions().begin(new TransactionOptions().cacheOnly(true));

Caches as External Storage

Caches need to be connected to an external storage to store data permanently. When connected, you can read data directly from the external storage if required data is not available in the cache, or write data that was sent to cache into the persistent storage.

Currently, GridGain supports the JDBC cache store, which allows to connect a cache and a relational database contents. Support for more storages will be added later.

You can use tuples or POJOs to work with the database. In the examples below we will use the following POJOs for POJO examples:

static class AccountKey {
    int accountNumber;

    /**
     * Default constructor (required for deserialization).
     */
    @SuppressWarnings("unused")
    public AccountKey() {
    }

    public AccountKey(int accountNumber) {
        this.accountNumber = accountNumber;
    }
}

/**
 * POJO class that represents value.
 */
static class Account {
    String firstName;
    String lastName;
    double balance;

    /**
     * Default constructor (required for deserialization).
     */
    @SuppressWarnings("unused")
    public Account() {
    }

    public Account(String firstName, String lastName, double balance) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.balance = balance;
    }
}
// Tuple objects are created in code samples.

To connect to an external database:

  • Configure the connection to the external database:

    JdbcConnectionPool dataSrc = JdbcConnectionPool.create("jdbc:h2:mem:GridGainKeyValueViewExample", "sa", "")
  • Configure a cache store factory:

    // Create a store factory object
    JdbcCacheStoreFactory storeFactory = new JdbcCacheStoreFactory();
    
    // Set the storage dialect
    // Supported dialects: DB2Dialect, H2Dialect, JdbcDialect, MySQLDialect, OracleDialect, SQLServerDialect.
    storeFactory.setDialect(new H2Dialect());
    
    // Create a JDBC type object will be used to store data.
    JdbcType jdbcType = new JdbcType();
    
    // Set the table schema.
    jdbcType.setDatabaseSchema("PUBLIC");
    
    // Set the name of the table to connect the cache to.
    jdbcType.setDatabaseTable("Accounts");
    
    // Set the key configuratin in the object matched to the columns of the underlying database.
    jdbcType.setKeyType(Person.class);
    jdbcType.setKeyFields(new JdbcTypeField(Types.INTEGER, "accountNumber", Integer.class, "accountNumber"));
    
    // Create a mapping for column values of the underlying database.
    jdbcType.setValueType(Account.class);
    jdbcType.setValueFields(
        new JdbcTypeField(Types.VARCHAR, "firstName", String.class, "firstName"),
        new JdbcTypeField(Types.VARCHAR, "lastName", String.class, "lastName"),
        new JdbcTypeField(Types.DOUBLE, "balance", Double.class, "balance")
    );
    
    // Set the created type object as the store factory data type.
    storeFactory.setType(jdbcType);
    // Set the store factory to connect to the database.
    storeFactory.setDataSource(dataSrc);
    // Create a store factory object
    JdbcCacheStoreFactory storeFactory = new JdbcCacheStoreFactory();
    
    // Set the storage dialect
    // Supported dialects: DB2Dialect, H2Dialect, JdbcDialect, MySQLDialect, OracleDialect, SQLServerDialect.
    storeFactory.setDialect(new H2Dialect());
    
    // Create a JDBC type object will be used to store data.
    JdbcType jdbcType = new JdbcType();
    
    // Set the table schema.
    jdbcType.setDatabaseSchema("PUBLIC");
    
    // Set the name of the table to connect the cache to.
    jdbcType.setDatabaseTable("Accounts");
    
    // Set the key configuratin in the object matched to the columns of the underlying database.
    jdbcType.setKeyType(Tuple.class);
    jdbcType.setKeyFields(new JdbcTypeField(Types.INTEGER, "accountNumber", Integer.class, "accountNumber"));
    
    // Create a mapping for column values of the underlying database.
    jdbcType.setValueType(Tuple.class);
    jdbcType.setValueFields(
        new JdbcTypeField(Types.VARCHAR, "firstName", String.class, "firstName"),
        new JdbcTypeField(Types.VARCHAR, "lastName", String.class, "lastName"),
        new JdbcTypeField(Types.DOUBLE, "balance", Double.class, "balance")
    );
    
    // Set the created type object as the store factory data type.
    storeFactory.setType(jdbcType);
    // Set the store factory to connect to the database.
    storeFactory.setDataSource(dataSrc);
  • Create a key-value view for the table in the underlying database:

    KeyValueView<AccountKey, Account> kvView = client.caches().cache("accounts").keyValueView(storeFactory, Mapper.of(AccountKey.class), Mapper.of(Account.class));
    // Get the cache Person
    Cache cache = client.caches().cache("Accounts");
    
    // Make sure there are no null values in the table.
    Objects.requireNonNull(cache);
    
    // Create a key-value view
    KeyValueView<Tuple, Tuple> kvView = cache.keyValueView(storeFactory);

The cache is now connected to the database. Write operations to the cache will be propagated to the database, and data missing in the cache will be retrieved automatically. For example, here is how you can retrieve a key:

// Create a key1 tuple and retrieve a value.
// If it is not present in the cache, it will be read from the database.
AccountKey key1 = new AccountKey(123);
value = kvView.get(null, key);

// Create a key2 tuple and write to the cache and database.
AccountKey key2 = new AccountKey(1234);
Account value = new Account(
    "John",
    "Smith",
    100
);
kvView.put(null, key, value);
// Create a key1 tuple and retrieve a value.
// If it is not present in the cache, it will be read from the database.
Tuple key1 = Tuple.create().set("id", 123);
value = kvView.get(null, key1);

// Create a key2 tuple and write to the cache and database.
Tuple key2 = Tuple.create().set("id", 1234);
Tuple value = Tuple.create()
    .set("firstName", "John")
    .set("lastName", "Smith")
    .set("balance", 100);

kvView.put(null, key2, value);

External Storage Write-Behind

By default, GridGain keeps the cache consistent with the connected database. You can configure your cache to copy data from clients asynchronously by setting the WRITE_MODE parameter to ASYNC.

CREATE ZONE CACHES WITH STORAGE_PROFILES='in-memory'

CREATE CACHE Accounts (
    accountNumber INT PRIMARY KEY,
    firstName VARCHAR,
    lastName VARCHAR,
    balance DOUBLE
) WITH PRIMARY_ZONE = 'CACHES', WRITE_MODE = ASYNC;

When a cache is set to async mode, any data written to it from the client will be propagated to the database asynchronously. For example:

IgniteClient client = IgniteClient.builder().addresses("127.0.0.1:10800")
    .cache(ClientCacheConfiguration.builder().cacheWriteBehindParallelOperations(2048).build())
    .build();

KeyValueView<AccountKey, Account> kvView = client.caches().cache("accounts").keyValueView(storeFactory, Mapper.of(AccountKey.class), Mapper.of(Account.class));

kvView.put(null, key2, value);

The cacheWriteBehindParallelOperations client configuration option defines the maximum number of parallel write behind operations for the cache (1024 by default). If the write-behind queue is full, new tasks will be performed in SYNC mode.

Currently, only operations made from GridGain clients are be propagated in asynchronous mode.