4.2. キャッシュの抽象化(Cache Abstraction)¶
目次
4.2.1. Overview¶
本ガイドラインでは、データ永続層のスケール性の確保 にて紹介しているキャッシュ方式の実現により、データアクセス処理の負荷低減および高速化する方法を紹介する。 キャッシュ方式の実現方法にはSpring Cache Abstractionを採用する。Spring Cache Abstractionによるキャッシュ実装の抽象化により、キャッシュ実装の置換を容易化したり、コーディング方法を統一化することによる習得コストの低減などが見込める。
セッションを外部管理化するためにキャッシュを利用する方法については、 セッション外部管理 を参照されたい。
Springのガイドについては、 Spring Cache Abstraction を参照されたい。
4.2.1.1. ローカルヒープを使用したキャッシュ¶
以下で、キャッシュ実装にローカルヒープ領域を使用した場合の処理の流れを示す。
項番 説明 (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
を使用する。
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
プロパティには@CacheConfig
のcacheNames
に指定するキャッシュ名を設定する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.host
とspring.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は代替手段として、インタフェースしか実装しない場合(Proxyとなるインタフェース、例えばDynamoDBのリポジトリ等)にメソッドの
@Cacheable
を付与する際は、メソッド引数のインデックスを指定することで引数へのアクセスを実現することが可能である。以下に、インデックス指定の例を示す。
@CacheConfig(cacheNames = "shardids") @EnableScan public interface AccountShardKeyRepository extends CrudRepository<ShardingAccount, String> { @Override // (1) @Cacheable(key = "'shardid/' + #a0") Optional<ShardingAccount> findById(String id); }
項番 説明 (1)
Cacheable
アノテーションの属性key
で設定している、#a0
がメソッドfindById
の引数0番目(id)を指定している。詳細は Springのリファレンス Available caching SpEL evaluation context を参照されたい。
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定義を行う。コンストラクタにRedisClusterConfigurationとJedisPoolConfigを指定し、接続先の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 を参照されたい。