SpringBootとInfinispanの組み込みモードでローカルなキャッシュ機構を作る

エキサイト株式会社エンジニアの佐々木です。エキサイトホールディングス 2023 advent calendar 1日目を担当させていただきます。

qiita.com

今回は、SpringBoot/Javaで、サーバローカルなキャッシュをRedHatが開発しているInfinispanの組み込みモードを使用し実装していきます。

前提

## Java

openjdk version "21.0.1" 2023-10-17 LTS
OpenJDK Runtime Environment Corretto-21.0.1.12.1 (build 21.0.1+12-LTS)
OpenJDK 64-Bit Server VM Corretto-21.0.1.12.1 (build 21.0.1+12-LTS, mixed mode, sharing)
## Gradle

------------------------------------------------------------
Gradle 8.4
------------------------------------------------------------

Build time:   2023-10-04 20:52:13 UTC
Revision:     e9251e572c9bd1d01e503a0dfdf43aedaeecdc3f
## SpringBoot

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::                (v3.2.0)

設定

build.gradleはこのように設定する。

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-cache'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    implementation 'org.infinispan:infinispan-spring-boot3-starter-embedded:14.0.13.Final'  // SpringBoot3に対応したinfinispanの組み込みモードのライブラリ
    implementation 'org.infinispan:infinispan-component-annotations:10.1.8.Final'   // infinispan共通で使用するライブラリ
    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

SpringBootの設定用のコードは下記のように実装する。

@Configuration
public class CacheConfig {

    /**
     * キャッシュの設定を行う.
    */
    @Bean
    public EmbeddedCacheManager embeddedCacheManager(EmbeddedCacheManager embeddedCacheManager) {
        Arrays.stream(CacheType.values()).forEach(e -> {
                    ConfigurationBuilder configurationBuilder = new ConfigurationBuilder();
                    org.infinispan.configuration.cache.Configuration configuration = configurationBuilder
                            .expiration()
                            .lifespan(e.getTtl(), TimeUnit.SECONDS) // キャッシュのTTLを設定する
                            .build();

                    embeddedCacheManager.defineConfiguration(e.getName(), configuration);  // 上記で設定した内容を CacheNameと共に、設定を行う
                }
        );
        return embeddedCacheManager;
    }
    
}

enum CacheType {  // キーごとにTTLを設定する用の enumを作成する

    MIN_1(CacheType.MIN_1_KEY, 60)  // 1分のキャッシュ設定
    , SECOND_30(CacheType.SECOND_30_KEY, 30); // 30秒のキャッシュ設定

    public static final String MIN_1_KEY = "MIN_1";
    public static final String SECOND_30_KEY = "SECOND_30";

    @Getter
    private final String name;
    @Getter
    private final long ttl;
    CacheType(String name, long ttl) {
        this.name = name;
        this.ttl = ttl;
    }
}

SpringBootのController/Serviceの実装は下記のようになる。

interface DemoService {

    Object get5SecondsExpirationData() throws InterruptedException;

    Object get10SecondsExpirationData() throws InterruptedException;
}

@RestController
@RequestMapping("")
@RequiredArgsConstructor
public class DemoController {

    private final DemoService demoService;

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

    @GetMapping("cacheExpiration5Seconds")
    public Object cacheExpiration5Seconds() throws InterruptedException {
        return LocalTime.now().format(formatter) + ":" + demoService.get5SecondsExpirationData();
    }

    @GetMapping("cacheExpiration10Seconds")
    public Object cacheExpiration10Seconds() throws InterruptedException {
        return LocalTime.now().format(formatter) + ":" + demoService.get10SecondsExpirationData();
    }
}

@Service
class DemoServiceImpl implements DemoService {

    DateTimeFormatter formatter = DateTimeFormatter.ofPattern("HH:mm:ss");

    @Override
    @Cacheable(CacheType.SECOND_10_KEY)  // 10秒キャッシュ指定
    public Object get10SecondsExpirationData() throws InterruptedException {
        Thread.sleep(10000);   // スリープ10秒
        return "expiration 10 seconds \n";
    }

    @Override
    @Cacheable(CacheType.SECOND_5_KEY)  // 5秒キャッシュ指定
    public Object get5SecondsExpirationData() throws InterruptedException {
        Thread.sleep(5000);  // スリープ5秒
        return "expiration 5 seconds \n";
    }
}

Service層のメソッドにアノテーションでそれぞれのキャッシュ時間を指定します。タイプセーフにキャッシュ指定できるようにしています。 検証用にService層内のメソッドが実行される場合は、それぞれ5秒、10秒待たされる実装になっています。

検証

5秒キャッシュの方のAPIを実行してみます。

5秒キャッシュ

キャッシュの有効期限が過ぎたタイミングで、5秒待たされるような挙動になっています。

まとめ

今回はInfinispanの組み込みモードを使用して、サーバーローカルなキャッシュ機構を作成してみました。SpringBootではこのようなライブラリを使用しなければ、HashMapを使用したキャッシュが作られますが、有効期限などの設定は自前で実装することになるので、これだけの設定で作れるは便利かと思います。サーバーローカルなキャッシュを設定とリモートサーバーキャッシュの両方を使用する設定は次回ブログに書きます。

最後に

エキサイトではフロントエンジニア、バックエンドエンジニア、アプリエンジニアを随時募集しております。長期インターンも歓迎していますので、興味があれば連絡いただければと思います。

募集職種一覧はこちらになります!(カジュアルからもOK) www.wantedly.com