Redis キャッシュ

Spring Data Redis は、org.springframework.data.redis.cache パッケージで Spring Framework のキャッシュの抽象化の実装を提供します。Redis をバッキング実装として使用するには、次のように構成に RedisCacheManager (Javadoc) を追加します。

@Bean
public RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {
    return RedisCacheManager.create(connectionFactory);
}

RedisCacheManager の動作は RedisCacheManager.RedisCacheManagerBuilder (Javadoc) を使用して構成でき、デフォルトの RedisCacheManager (Javadoc) 、トランザクション動作、定義済みキャッシュを設定できます。

RedisCacheManager cacheManager = RedisCacheManager.builder(connectionFactory)
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    .transactionAware()
    .withInitialCacheConfigurations(Collections.singletonMap("predefined",
        RedisCacheConfiguration.defaultCacheConfig().disableCachingNullValues()))
    .build();

前の例で示したように、RedisCacheManager ではキャッシュごとにカスタム構成が可能です。

RedisCacheManager (Javadoc) によって作成された RedisCache (Javadoc) の動作は、RedisCacheConfiguration で定義されます。次の例に示すように、構成により、キーの有効期限、プレフィックス、バイナリストレージ形式との間の変換に使用する RedisSerializer 実装を設定できます。

RedisCacheConfiguration cacheConfiguration = RedisCacheConfiguration.defaultCacheConfig()
    .entryTtl(Duration.ofSeconds(1))
    .disableCachingNullValues();

RedisCacheManager (Javadoc) は、バイナリ値の読み取りと書き込みにデフォルトでロックフリーの RedisCacheWriter (Javadoc) を使用します。ロックフリーのキャッシュにより、スループットが向上します。エントリロックがないと、Cache putIfAbsentclean 操作で複数のコマンドを Redis に送信する必要があるため、コマンドが重複して非アトミックになる可能性があります。ロック対応では、明示的なロックキーを設定し、このキーの存在をチェックすることでコマンドの重複を防止します。これにより、追加のリクエストが発生し、コマンドの待機時間が発生する可能性があります。

ロックは、キャッシュエントリごとではなく、キャッシュレベルに適用されます。

次のようにロック動作にオプトインすることができます。

RedisCacheManager cacheManager = RedisCacheManager
    .builder(RedisCacheWriter.lockingRedisCacheWriter(connectionFactory))
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    ...

デフォルトでは、キャッシュエントリの key には、実際のキャッシュ名とそれに続く 2 つのコロン (::) が接頭辞として付けられます。この動作は、計算されたプレフィックスだけでなく静的なプレフィックスにも変更できます。

次の例は、静的プレフィックスを設定する方法を示しています。

// static key prefix
RedisCacheConfiguration.defaultCacheConfig().prefixCacheNameWith("(͡° ᴥ ͡°)");

The following example shows how to set a computed prefix:

// computed key prefix
RedisCacheConfiguration.defaultCacheConfig()
    .computePrefixWith(cacheName -> "¯\_(ツ)_/¯" + cacheName);

キャッシュ実装では、デフォルトで KEYS および DEL を使用してキャッシュをクリアします。KEYS は、大きなキースペースでパフォーマンスの課題を引き起こす可能性があります。BatchStrategy を使用してデフォルトの RedisCacheWriter を作成し、SCAN ベースのバッチ戦略に切り替えることができます。SCAN 戦略では、過剰な Redis コマンドの往復を避けるためにバッチサイズが必要です。

RedisCacheManager cacheManager = RedisCacheManager
    .builder(RedisCacheWriter.nonLockingRedisCacheWriter(connectionFactory, BatchStrategies.scan(1000)))
    .cacheDefaults(RedisCacheConfiguration.defaultCacheConfig())
    ...

KEYS バッチ戦略は、任意のドライバーと Redis 操作モード(スタンドアロン、クラスター化)を使用して完全にサポートされます。Lettuce ドライバーを使用する場合、SCAN は完全にサポートされます。Jedis は、非クラスターモードでのみ SCAN をサポートします。

次の表に、RedisCacheManager のデフォルト設定を示します。

表 1: RedisCacheManager のデフォルト
設定

キャッシュライター

ノンロック、KEYS バッチ戦略

キャッシュ構成

RedisCacheConfiguration#defaultConfiguration

初期キャッシュ

なし

トランザクション対応

いいえ

次の表に、RedisCacheConfiguration のデフォルト設定を示します。

表 2: RedisCacheConfiguration のデフォルト
キーの有効期限 なし

キャッシュ null

はい

プレフィックスキー

はい

デフォルト接頭部

実際のキャッシュ名

キーシリアライザー

StringRedisSerializer

バリューシリアライザー

JdkSerializationRedisSerializer

変換サービス

デフォルトのキャッシュキーコンバーターを備えた DefaultFormattingConversionService 

デフォルトでは RedisCache、統計は無効になっています。RedisCacheManagerBuilder.enableStatistics() を使用して、RedisCache#getStatistics() を介してローカルのヒットミスを収集し、収集されたデータのスナップショットを返します。

Redis キャッシュの有効期限

アイドル時間 (TTI) および存続時間 (TTL) の実装は、データストアが異なる場合でも定義と動作が異なります。

一般:

  • 有効期限 (TTL) の有効期限 - TTL は、データアクセスの作成または更新操作によってのみ設定およびリセットされます。作成時を含め、エントリが TTL 有効期限タイムアウト前に書き込まれている限り、エントリのタイムアウトは、設定された TTL 有効期限タイムアウト期間にリセットされます。例: TTL 有効期限タイムアウトが 5 分に設定されている場合、タイムアウトはエントリの作成時に 5 分に設定され、その後、5 分の間隔が期限切れになる前にエントリが更新されるたびに 5 分にリセットされます。5 分以内に更新が発生しない場合、エントリが数回読み取られた場合でも、5 分間に 1 回読み取られただけでも、エントリは期限切れになります。TTL 有効期限ポリシーを宣言するときに、エントリが期限切れにならないようにエントリを書き込む必要があります。

  • アイドル時間 (TTI) の有効期限 - TTI は、エントリの更新だけでなくエントリが読み取られるたびにリセットされ、実質的に TTL 有効期限ポリシーの拡張となります。

一部のデータストアでは、エントリで発生するデータアクセス操作の種類 (読み取り、書き込み、その他) に関係なく、TTL が構成されている場合にエントリが期限切れになります。設定され、構成された TTL 有効期限タイムアウトが経過すると、エントリはデータストアから削除されます。エビクションアクション (例: 破棄、無効化、ディスクへのオーバーフロー (永続ストアの場合) など) はデータストア固有です。

生存時間 (TTL) の有効期限

Spring Data Redis の Cache 実装は、キャッシュエントリの有効期限 (TTL) をサポートします。ユーザーは、新しい RedisCacheWriter.TtlFunction インターフェースの実装を提供することで、固定 Duration またはキャッシュエントリごとに動的に計算される Duration を使用して TTL 有効期限タイムアウトを構成できます。

RedisCacheWriter.TtlFunction インターフェースは、Spring Data Redis 3.2.0 で導入されました。

すべてのキャッシュエントリが設定された期間後に期限切れになる必要がある場合は、次のように、固定 Duration を使用して TTL 期限切れタイムアウトを構成するだけです。

RedisCacheConfiguration fiveMinuteTtlExpirationDefaults =
    RedisCacheConfiguration.defaultCacheConfig().enableTtl(Duration.ofMinutes(5));

ただし、TTL 有効期限タイムアウトがキャッシュエントリによって異なる場合は、RedisCacheWriter.TtlFunction インターフェースのカスタム実装を提供する必要があります。

enum MyCustomTtlFunction implements TtlFunction {

    INSTANCE;

    @Override
    public Duration getTimeToLive(Object key, @Nullable Object value) {
        // compute a TTL expiration timeout (Duration) based on the cache entry key and/or value
    }
}

内部では、固定 Duration TTL 有効期限が、提供された Duration を返す TtlFunction 実装にラップされています。

次に、次を使用して、固定 Duration または動的、キャッシュエントリごとの Duration TTL 有効期限をグローバルベースで構成できます。

グローバル固定期間 TTL 有効期限タイムアウト
RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
    .cacheDefaults(fiveMinuteTtlExpirationDefaults)
    .build();

または、次のようにすることもできます。

グローバルで動的に計算されるキャッシュごとのエントリ期間 TTL 有効期限タイムアウト
RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
        .entryTtl(MyCustomTtlFunction.INSTANCE);

RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
    .cacheDefaults(defaults)
    .build();

もちろん、以下を使用してグローバル構成とキャッシュごとの構成の両方を組み合わせることができます。

グローバル固定期間 TTL 有効期限タイムアウト
RedisCacheConfiguration predefined = RedisCacheConfiguration.defaultCacheConfig()
                                         .entryTtl(MyCustomTtlFunction.INSTANCE);

Map<String, RedisCacheConfiguration> initialCaches = Collections.singletonMap("predefined", predefined);

RedisCacheManager cacheManager = RedisCacheManager.builder(redisConnectionFactory)
    .cacheDefaults(fiveMinuteTtlExpirationDefaults)
    .withInitialCacheConfigurations(initialCaches)
    .build();

アイドル時間 (TTI) の有効期限

Redis 自体は、真のアイドル時間 (TTI) 有効期限の概念をサポートしていません。それでも、Spring Data Redis のキャッシュ実装を使用すると、アイドル時間 (TTI) の有効期限に似た動作を実現できます。

Spring Data Redis のキャッシュ実装における TTI の構成は、明示的に有効にする必要があります。つまり、オプトインです。さらに、固定 Duration または Redis キャッシュの有効期限で説明した TtlFunction インターフェースのカスタム実装を使用して TTL 構成を提供する必要もあります。

例:

@Configuration
@EnableCaching
class RedisConfiguration {

    @Bean
    RedisConnectionFactory redisConnectionFactory() {
        // ...
    }

    @Bean
    RedisCacheManager cacheManager(RedisConnectionFactory connectionFactory) {

        RedisCacheConfiguration defaults = RedisCacheConfiguration.defaultCacheConfig()
            .entryTtl(Duration.ofMinutes(5))
            .enableTimeToIdle();

        return RedisCacheManager.builder(connectionFactory)
            .cacheDefaults(defaults)
            .build();
    }
}

Redis サーバーは TTI の適切な概念を実装していないため、TTI は有効期限オプションを受け入れる Redis コマンドでのみ実現できます。Redis では、「有効期限」は技術的には存続時間 (TTL) ポリシーです。ただし、Spring Data Redis の Cache.get(key) 操作の場合のように、キーの値を読み取るときに TTL 有効期限を過ぎることができ、それによって TTL 有効期限タイムアウトが効果的にリセットされます。

RedisCache.get(key) は、Redis GETEX コマンドを呼び出すことによって実装されます。

Redis GETEX (英語) コマンドは、Redis バージョン 6.2.0 以降でのみ使用できます。Redis 6.2.0 以降を使用していない場合、Spring Data Redis の TTI 有効期限を使用することはできません。互換性のない Redis (サーバー) バージョンに対して TTI を有効にすると、コマンド実行例外がスローされます。Redis サーバーのバージョンが正しく、GETEX コマンドをサポートしているかどうかを判断する試みは行われません。

Spring Data Redis アプリケーションで真のアイドル時間 (TTI) 有効期限に似た動作を実現するには、すべての読み取りまたは書き込み操作で (TTL) 有効期限を設定してエントリに一貫してアクセスする必要があります。この規則には例外はありません。Spring Data Redis アプリケーション全体でさまざまなデータアクセスパターンを混合および一致させている場合 (たとえば、キャッシング、RedisTemplate を使用した操作の呼び出し、場合によっては、または特に Spring Data リポジトリ CRUD 操作を使用する場合)、エントリにアクセスしても、必ずしもエントリの期限切れが防止されるとは限りません。TTL 有効期限が設定されている場合。例: TTL 有効期限付きの @Cacheable サービスメソッド呼び出し (つまり SET <expiration options>) 中にエントリがキャッシュに「置かれ」(書き込まれ)、後で有効期限タイムアウト前に Spring Data Redis リポジトリを使用して読み取られる場合があります (有効期限オプションなしで GET を使用)。有効期限オプションを指定しない単純な GET では、エントリの TTL 有効期限タイムアウトはリセットされません。エントリは、読み取られたばかりであっても、次のデータアクセス操作の前に期限切れになる可能性があります。これは Redis サーバーでは強制できないため、アイドル時間の有効期限が設定されている場合、必要に応じてキャッシュの内外でエントリに一貫してアクセスするのはアプリケーションの責任です。