4.2. キャッシュの抽象化(Cache Abstraction)

4.2.1. Overview

本ガイドラインでは、データ永続層のスケール性の確保 にて紹介しているキャッシュ方式の実現により、データアクセス処理の負荷低減および高速化する方法を紹介する。 キャッシュ方式の実現方法にはSpring Cache Abstractionを採用する。Spring Cache Abstractionによるキャッシュ実装の抽象化により、キャッシュ実装の置換を容易化したり、コーディング方法を統一化することによる習得コストの低減などが見込める。

セッションを外部管理化するためにキャッシュを利用する方法については、 セッション外部管理 を参照されたい。

Springのガイドについては、 Spring Cache Abstraction を参照されたい。

4.2.1.1. ローカルヒープを使用したキャッシュ

以下で、キャッシュ実装にローカルヒープ領域を使用した場合の処理の流れを示す。

../../_images/cache-abstraction.png
項番 説明
(1)
ControllerまたはServiceは、引数を渡しキャッシュ定義されたDomain LayerのServiceメソッドを呼び出す。
(2)

Cache AOPは、引数を元にキャッシュキーを特定しSimpleCacheManagerを使用してConcurrentHashMapから既に登録済のキャッシュデータを取得する。

キャッシュデータが取得出来た場合はControllerまたはServiceへキャッシュデータを返却し、キャッシュデータが取得出来ない場合は(3)および(4)を実行する。

(3)

Cache AOPは、引数を渡しキャッシュ定義されたDomain LayerのServiceメソッドを実行し戻り値を取得する。

Cache AOPは、(2)で特定したキャッシュキーで、取得した戻り値をSimpleCacheManagerを使用してConcurrentHashMapへキャッシュデータとして格納する。

(4)
Cache AOPは、ControllerまたはServiceへ取得した戻り値を返却する。

4.2.1.2. Redisを使用したキャッシュ

以下で、キャッシュ実装にRedisを使用した場合の処理の流れを示す。 キャッシュを行う仕組みはローカルヒープを使用したキャッシュと同一であるが、CacheManagerとしてRedisCacheManagerを使用する。

../../_images/cache-abstraction-redis.png
項番 説明
(1)
Cache AOPは、特定したキャッシュキーで、取得した戻り値をRedisCacheManagerを使用してRedisへキャッシュデータとして格納する。また、ControllerまたはServiceへ取得した戻り値を返却する。 Redisへのアクセスは、Spring Data Redisを使用して行われる。

4.2.1.3. キャッシュ方式の選択

本ガイドラインでは、ローカルヒープを使用したキャッシュとRedisを使用したキャッシュを紹介するが、各キャッシュ方式によって特徴が異なる。 そのため、アプリケーションの要件に応じて適したキャッシュ方式を選択されたい。

キャッシュ方式 特徴 適したアプリケーション
ローカルヒープを使用したキャッシュ
Redisを使用したキャッシュよりも高速であるが、APサーバインスタンスのローカルヒープにキャッシュを保持するため、サーバインスタンス間のキャッシュを同期することができない。また、キャッシュするデータ量に応じてサーバインスタンスのメモリ消費量が大きくなる。
キャッシュ対象とするデータがマスタデータ等の運用中に更新されることが想定されないデータに限定されるアプリケーションなどに適している。
Redisを使用したキャッシュ
キャッシュをRedisに保持するため、キャッシュデータを複数のサーバインスタンス間で同期することが可能である。また、キャッシュデータ量が大きくなってもサーバインスタンスのメモリ消費量が大きくなることはない。
キャッシュ対象とするデータ量が大きく、運用中の更新が見込まれるアプリケーションなどに適している。

Note

Spring Cache Abstractionを使用したキャッシュの実装では、複数のキャッシュ方式を併用することが可能である。 詳細は、複数のキャッシュマネージャを併用する を参照されたい。

4.2.2. How to use

以下でSpring Cache Abstractionの利用にあたり、事前に必要な設定、およびアプリケーションでキャッシュデータへアクセスする方法を説明する。

4.2.2.1. Spring Cache Abstractionの設定

4.2.2.1.1. ローカルヒープを使用したキャッシュの設定

キャッシュの機能を有効にするには、キャッシュマネージャの設定が必要になる。 以下に、ローカルヒープを使用したキャッシュマネージャの設定例を示す。

・・・
<!-- (1) -->
<cache:annotation-driven order="-1" />
・・・
<!-- (2) -->
<bean id="cacheManager" class="org.springframework.cache.support.SimpleCacheManager">
    <!-- (3) -->
    <property name="caches">
        <set>
            <bean
                class="org.springframework.cache.concurrent.ConcurrentMapCacheFactoryBean">
                <property name="name" value="members" />
            </bean>
            ・・・
        </set>
    </property>
</bean>
項番 説明
(1)

アノテーションでのキャッシュを有効にする。

キャッシュデータの管理をするためorder="-1"を設定し、キャッシュインターセプタがトランザクションインターセプタより先に動作する設定とする。これにより、キャッシュデータの参照はトランザクション開始前に、登録と削除はトランザクションの終了後に行う。

(2)
キャッシュデータの格納場所にローカルヒープ領域を使用する場合は、Springが提供するSimpleCacheManagerをキャッシュマネージャとして使用する。
(3)
cachesプロパティに、実際にキャッシュデータを格納する「入れ物(Cache)」をBean定義する。SimpleCacheManagerを使用する場合は、後述する@CacheConfigアノテーションに対応した数だけBean定義が必要になる。「入れ物」の実装にJDK標準のConcurrentHashMapを使用する場合はConcurrentMapCacheFactoryBeanを使用する。@CacheConfigとの関連付けのため、nameプロパティには@CacheConfigcacheNamesに指定するキャッシュ名を設定する

Note

ローカルヒープ領域における「入れ物」の実装は、ConcurrentMapCacheFactoryBean以外のものもSpringに用意されている。 詳細は Springのリファレンス Configuring the cache storage を参照されたい。

4.2.2.1.2. Redisを使用したキャッシュの設定

以下に、Redisを使用したキャッシュマネージャの設定例を示す。

  • pom.xml
<dependencies>
        <!-- (1) -->
        <dependency>
                <groupId>org.springframework.data</groupId>
                <artifactId>spring-data-redis</artifactId>
        </dependency>
        <!-- (2) -->
        <dependency>
                <groupId>redis.clients</groupId>
                <artifactId>jedis</artifactId>
        </dependency>
</dependencies>
項番 説明
(1)
依存ライブラリにspring-data-redisを追加する。
(2)
依存ライブラリにjedisを追加する。

  • xxx-env.xml

    ・・・
    <!-- (1) -->
    <cache:annotation-driven order="-1" />
    ・・・
    <!-- (2) -->
    <bean id="cacheManager"
        class="org.springframework.data.redis.cache.RedisCacheManager"
        factory-method="create"
        c:connection-factory-ref="redisConnectionFactory"
        p:transaction-aware="true" />
    ・・・
    <!-- (3) -->
    <bean id="redisConnectionFactory"
        class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory"
        p:host-name="${spring.redis.host}" p:port="${spring.redis.port}" />
    
    項番 説明
    (1)
    アノテーションでのキャッシュを有効にする。ローカルヒープを使用したキャッシュと同様にorder="-1"を設定する。
    (2)
    キャッシュデータの格納場所にRedisを使用する場合は、Spring Data Redisが提供するRedisCacheManagerをキャッシュマネージャとして使用する。 RedisCacheManagerの設定方法は Support for the Spring Cache Abstraction を参照されたい。
    (3)
    キャッシュマネージャで利用するredisConnectionFactoryを設定する。

  • application.yml
spring:
  redis:
    # (1)
    host: 127.0.0.1
    port: 6379
項番 説明
(1)
spring.redis.hostspring.redis.portに接続するredisのホストとポートを設定する。

4.2.2.2. キャッシュするデータの選択

以下にキャッシュしたいデータを定義する方法を説明する。 Spring Cache Abstractionでは、メソッドにアノテーションを定義することでキャッシュ対象データを選択する方式をとる。 対象メソッドの戻り値がキャッシュ対象のデータとなる。

// omitted...
// (1)
@CacheConfig(cacheNames = "members")
public class MemberUpdateServiceImpl implements MemberUpdateService {
  // omitted...
  @Transactional(readOnly = true)
  // (2)
  @Cacheable(key = "'member/' + #customerNo")
  public Member findMember(String customerNo) throws IOException {
    // omitted...
  }
  // omitted...
}
項番 説明
(1)

CacheConfigアノテーションをクラスへ付与する。

このクラス内のキャッシュアノテーションの属性cacheNamesを設定する。 ここで設定したcacheNamesはredisに格納する際のキープレフィックス(この例では「members::」)となる。

(2)

Cacheableアノテーションをキャッシュ対象の参照メソッドへ付与する。

属性key(キャッシュキー)を設定する。この例では、文字列引数(customerNo)の値にプレフィックス’member/’を付けてキーにしている。例えば customerNo=000001 の場合、キャッシュキーは「members::member/000001」となり、キャッシュされる値はメソッドの戻り値となる。

Warning

Spring Cache Abstractionのアノテーションを使用する場合は、@Cacheable@CachePut@CacheEvictアノテーションの属性 value (または cacheNames )の値は、Spring frameworkがキャッシュオペレーション作成時に必須の値となるため設定すること。

また、インタフェースにSpring Cache Abstractionのアノテーションを付与することは基本的に推奨していない。理由としては、インタフェースのメソッドの引数名はデフォルトでは取得できないためである。

詳細な内容については Spring Cache AbstractionのリファレンスのTipsを参照されたい。

4.2.2.3. キャッシュしたデータの削除

キャッシュデータは、対象データが更新または、削除された場合にキャッシュデータの削除が必要になる。

以下にキャッシュしたデータを削除する定義方法を説明する。 Spring Cache Abstractionでは、メソッドにアノテーションを定義することでキャッシュ対象データを削除する方式をとる。 メソッドで定義したアノテーションキーが削除対象データのキーとなる。

// omitted...
// (1)
@CacheConfig(cacheNames = "members")
public class MemberUpdateServiceImpl implements MemberUpdateService {
  // omitted...
  @Transactional(readOnly = true)
  // (2)
  @Cacheable(key = "'member/' + #customerNo")
  public Member findMember(String customerNo) throws IOException {
    // omitted...
  }
  // (3)
  // omitted...
  @CacheEvict(key = "'member/' + #member.customerNo")
  public void updateMember(Member member) {
    // omitted...
  }
}
項番 説明
(1)
CacheConfigアノテーションをクラスへ付与する。 ここで設定したcacheNamesはredisに格納する際のキープレフィックス(この例では「members::」)となる。
(2)
キャッシュするデータの選択で説明したキャッシュデータを登録または参照するメソッド定義。
(3)

CacheEvictアノテーションをキャッシュ対象の更新メソッドへ付与する。

属性key(キャッシュキー)を設定する。この例では、引数であるMemberオブジェクトのフィールド(customerNo)の値にプレフィックス’member/’を付けてキーにしている。例えば customerNo=000001 の場合、キャッシュキーは「members::member/000001」となり、(2)でキャッシュされたキーを同じになるため、(2)でキャッシュされた値を削除する。

Warning

トランザクショナルなDBの値をキャッシュデータにしている場合は、DBの値更新時に完全なデータの同期が出来ない事に注意が必要である。

DBの値が更新され、コミットされてからキャッシュデータが削除されるまでの間のデータ参照は古いキャッシュデータが参照される。

4.2.2.4. 注意事項

  • ローカルヒープ領域を利用した場合は、キャッシュが共有される範囲は同一のDIコンテナ内のみである。
  • 特にローカルヒープ領域をキャッシュ格納場所に使用する場合は、キャッシュ対象データのサイズに注意すること。ヒープサイズに見合わない量のデータをキャッシュした場合、パフォーマンスが低下したりメモリ不足に陥る可能性がある。そのような場合には、ローカルヒープ領域外を格納場所として使用するなどを検討すること。
  • 本ガイドラインで説明しているセッションの外部管理の方法と、Redisを使用したキャッシュの設定 にて説明した方法でRedisを使用したキャッシュを併用する場合、接続先のRedisは同一のRedisとなる。高負荷での運用が想定されるアプリケーションについては、セッション情報とキャッシュを格納するRedisを別にすることでコネクション数の枯渇を回避することが出来る。キャッシュ格納用のRedisの設定方法については、 目的別に接続先Redisを指定する を参照。

4.2.3. How to extend

4.2.3.1. 目的別に接続先Redisを指定する

高負荷での運用が想定される場合等、セッションの外部管理やキャッシュの格納等の複数の目的でRedisを使用する際に、利用目的別に接続先のRedisを別にすることが望ましいケースがある。

以下に、キャッシュ格納用のRedisを目的別に設定する方法を示す。

・・・
<cache:annotation-driven order="-1" />
・・・
<!-- (1) -->
<bean id="cacheManager"
    class="org.springframework.data.redis.cache.RedisCacheManager"
    factory-method="create"
    c:connection-factory-ref="jedisConnectionFactoryForCache"
    p:transaction-aware="true" />

<!-- (2) -->
<bean id="jedisConnectionFactoryForCache"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <constructor-arg index="0">
        <!-- (3) -->
        <bean
            class="org.springframework.data.redis.connection.RedisClusterConfiguration">
            <constructor-arg>
                <list>
                    <value>${redis.cache.cluster.node1}</value>
                    <value>${redis.cache.cluster.node2}</value>
                    <value>${redis.cache.cluster.node3}</value>
                </list>
            </constructor-arg>
        </bean>
    </constructor-arg>
    <constructor-arg index="1">
        <!-- (4) -->
        <bean class="redis.clients.jedis.JedisPoolConfig">
            <property name="maxTotal" value="${redis.cache.maxTotal}" />
            <property name="maxIdle" value="${redis.cache.maxIdle}" />
            <property name="maxWaitMillis" value="${redis.cache.maxWaitMillis}" />
            <property name="minIdle" value="${redis.cache.minIdle}" />
        </bean>
    </constructor-arg>
</bean>

<!-- (5) -->
<bean id="jedisConnectionFactoryForSession" primary="true"
    class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
    <constructor-arg index="0">
        <bean
            class="org.springframework.data.redis.connection.RedisClusterConfiguration">
            <constructor-arg>
                <list>
                    <value>${redis.session.cluster.node1}</value>
                </list>
            </constructor-arg>
        </bean>
    </constructor-arg>
    <constructor-arg index="1">
        <bean class="redis.clients.jedis.JedisPoolConfig">
            <property name="maxTotal" value="${redis.session.maxTotal}" />
            <property name="maxIdle" value="${redis.session.maxIdle}" />
            <property name="maxWaitMillis" value="${redis.session.maxWaitMillis}" />
            <property name="minIdle" value="${redis.session.minIdle}" />
        </bean>
    </constructor-arg>
</bean>
項番 説明
(1)
キャッシュマネージャとしてRedisCacheManagerを使用する。
(2)

JedisConnectionFactoryを使用したコネクションファクトリのBean定義を行う。コンストラクタにRedisClusterConfigurationJedisPoolConfigを指定し、接続先のRedisクラスタの指定および接続設定を行う。AutoConfigurationによるRedisConnectionFactoryのBean定義との重複を避けるため、idを”jedisConnectionFactory”とは異なる文字列とする。

Note

JedisConnectionFactoryのコンストラクタにRedisClusterConfigurationを指定する場合、接続先のRedisはクラスタ構成であることが必須となる。スタンドアローン構成のRedisに接続する場合はJedisConnectionFactoryに直接接続先のServerとPortを指定すること。 指定方法については、Spring Data Redisのリファレンス を参照されたい。

(3)
RedisClusterConfigurationのBean定義を行う。コンストラクタの引数に接続先のノードを指定する。
(4)
JedisPoolConfigのBean定義を行う。
(5)
RedisConnectionFactoryインタフェースの実装クラスをBean定義すると、AutoConfigurationによるRedisConnectionFactoryのBean定義が無効となる。そのため、Spring Sessionがセッション情報をRedisに格納するために使用するJedisConnectionFactoryのBean定義を行う。primary=”true”を指定し、Spring SessionがRedisTemplateを生成する際に優先的に使用させる。

プロパティキーに対応する値の設定を行う。

redis:
  cache:
    cluster:
      # (1)
      node1:
        127.0.0.1:30001
      node2:
        127.0.0.1:30002
      node3:
        127.0.0.1:30003
    # (2)
    maxTotal: 8
    maxIdle: 8
    maxWaitMillis: -1
    minIdle: 0
  session:
    cluster:
      # (3)
      node1:
        127.0.0.1:30004
      node2:
        127.0.0.1:30005
      node3:
        127.0.0.1:30006
    # (4)
    maxTotal: 8
    maxIdle: 8
    maxWaitMillis: -1
    minIdle: 0
項番 説明
(1)
キャッシュを格納するRedisの接続先ノードを指定する。
(2)
キャッシュを格納するRedisのJedisPoolConfigに設定するプロパティを指定する。
(3)
セッションを格納するRedisの接続先ノードを指定する。
(4)
セッションを格納するRedisのJedisPoolConfigに設定するプロパティを指定する。

4.2.3.2. 複数のキャッシュマネージャを併用する

Spring Cache Abstractionでは、複数のキャッシュマネージャを別のBean名で定義しておき、@CacheableアノテーションのcacheManager属性に指定することで、キャッシュ対象データ毎に使用するキャッシュマネージャを指定することが可能である。 詳細は、Custom cache resolution <Custom cache resolution を参照されたい。