EHCache

JavaライブラリキャッシュTerracottaエンタープライズ分散

GitHub概要

ehcache/ehcache3

Ehcache 3.x line

スター2,070
ウォッチ154
フォーク584
作成日:2014年2月26日
言語:Java
ライセンス:Apache License 2.0

トピックス

なし

スター履歴

ehcache/ehcache3 Star History
データ取得日時: 2025/10/22 10:04

ライブラリ

EHCache

概要

EHCacheは、Javaアプリケーション向けのオープンソース、標準ベースのキャッシュライブラリです。パフォーマンスを向上させ、データベースの負荷を軽減し、スケーラビリティを簡素化する、最も広く使用されているJavaベースのキャッシュシステムです。

詳細

EHCache 3は、堅牢で実証済みの高機能キャッシュライブラリで、他の人気ライブラリやフレームワークとの統合が容易です。現代的なAPI設計により、型安全なAPIと設定を提供し、EHCache 2.xと比較して大幅に改良されたコーディング体験を実現します。JSR-107(JCache仕様)に完全準拠し、オフヒープストレージをサポートし、多くの追加機能と改良を提供しています。ティアリングモデルにより、より高速なストレージは希少だが「最もホット」なデータを優先的に保存し、より遅いティア(一般的により潤沢)により多くのデータを保存できます。On-Heap、Off-Heap、Clusteredの3つのストレージオプションを提供し、高い並行性環境での優秀な性能を発揮します。Terracottaと組み合わせることで、分散配置アーキテクチャを通じたクラスター化キャッシュまで任意の用途にスケールできます。EHCache 3.0では、数十のCPUコアを持つシステムでの高並行アクセス下でも優れた動作をするよう特別に構築・テストされています。

メリット・デメリット

メリット

  • 優秀なパフォーマンス: 直接的なget(key)操作が500ns以下の高速性能
  • エンタープライズ機能: 監視、管理、セキュリティ機能を含む包括的なソリューション
  • スケーラビリティ: Terracottaによりテラバイト級のデータストレージまで対応
  • JSR-107準拠: JCache仕様の最も完全な実装を提供
  • 柔軟なストレージ: On-Heap、Off-Heap、Clusteredの多層ストレージ
  • Spring統合: Spring Boot自動設定とアノテーションサポート
  • 高い並行性: 数十CPUコア環境での優秀な並行アクセス性能

デメリット

  • 設定の複雑さ: 高度な機能により設定が複雑になる場合がある
  • メモリ使用量: 大規模キャッシュでは相当なメモリリソースが必要
  • Terracotta依存: クラスター機能にはTerracottaサーバーが必要
  • 学習コスト: 豊富な機能により初期学習コストが高い
  • バージョン互換性: EHCache 2.xから3.xへの移行にはAPI変更が必要
  • デバッグの複雑さ: 分散環境でのデバッグが困難な場合がある

主要リンク

書き方の例

基本的な設定と使用方法

import org.ehcache.Cache;
import org.ehcache.CacheManager;
import org.ehcache.config.builders.CacheConfigurationBuilder;
import org.ehcache.config.builders.CacheManagerBuilder;
import org.ehcache.config.builders.ResourcePoolsBuilder;

// キャッシュマネージャーの作成
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .withCache("myCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String.class, String.class,  // Key, Value types
            ResourcePoolsBuilder.heap(100))  // 100エントリのヒープ
    )
    .build();

cacheManager.init();

// キャッシュの取得と使用
Cache<String, String> myCache = cacheManager.getCache("myCache", 
    String.class, String.class);

// データの保存と取得
myCache.put("key1", "value1");
String value = myCache.get("key1");
System.out.println("Retrieved: " + value);

// キャッシュの確認
if (myCache.containsKey("key1")) {
    System.out.println("Key exists in cache");
}

// キャッシュマネージャーのクローズ
cacheManager.close();

多層ストレージ設定

import org.ehcache.config.units.MemoryUnit;
import org.ehcache.config.builders.ResourcePoolsBuilder;

// ヒープ、オフヒープ、ディスクの3層ストレージ
CacheManager cacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .with(CacheManagerBuilder.persistence("/data/ehcache"))
    .withCache("tieredCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String.class, String.class,
            ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(1000)                           // 1000エントリのヒープ
                .offheap(10, MemoryUnit.MB)          // 10MBのオフヒープ
                .disk(100, MemoryUnit.MB, true)      // 100MBの永続ディスク
        )
    )
    .build();

cacheManager.init();

Cache<String, String> tieredCache = cacheManager.getCache("tieredCache", 
    String.class, String.class);

// 大量データの保存テスト
for (int i = 0; i < 5000; i++) {
    tieredCache.put("key" + i, "Large data value " + i);
}

// データ取得パフォーマンステスト
long startTime = System.nanoTime();
String result = tieredCache.get("key2500");
long endTime = System.nanoTime();
System.out.printf("Get operation took: %d ns%n", endTime - startTime);

cacheManager.close();

Spring Boot統合

// Spring Boot設定クラス
@Configuration
@EnableCaching
public class CacheConfig {

    @Bean
    public CacheManager cacheManager() {
        return CacheManagerBuilder.newCacheManagerBuilder()
            .withCache("userCache",
                CacheConfigurationBuilder.newCacheConfigurationBuilder(
                    Long.class, User.class,
                    ResourcePoolsBuilder.heap(1000)
                        .offheap(5, MemoryUnit.MB)
                )
                .withExpiry(ExpiryPolicyBuilder.timeToLiveExpiration(
                    Duration.ofMinutes(30)))
            )
            .withCache("productCache",
                CacheConfigurationBuilder.newCacheConfigurationBuilder(
                    String.class, Product.class,
                    ResourcePoolsBuilder.heap(500)
                        .offheap(2, MemoryUnit.MB)
                )
                .withExpiry(ExpiryPolicyBuilder.timeToIdleExpiration(
                    Duration.ofMinutes(15)))
            )
            .build(true);  // 自動初期化
    }
}

// サービスクラスでのキャッシュ使用
@Service
public class UserService {

    @Cacheable(value = "userCache", key = "#userId")
    public User getUserById(Long userId) {
        // データベースからユーザー取得(重い処理)
        System.out.println("Fetching user from database: " + userId);
        return userRepository.findById(userId)
            .orElse(null);
    }

    @CachePut(value = "userCache", key = "#user.id")
    public User updateUser(User user) {
        // ユーザー更新とキャッシュ更新
        User updatedUser = userRepository.save(user);
        System.out.println("User updated and cached: " + user.getId());
        return updatedUser;
    }

    @CacheEvict(value = "userCache", key = "#userId")
    public void deleteUser(Long userId) {
        // ユーザー削除とキャッシュクリア
        userRepository.deleteById(userId);
        System.out.println("User deleted and cache evicted: " + userId);
    }

    @CacheEvict(value = "userCache", allEntries = true)
    public void clearAllUsers() {
        // 全ユーザーキャッシュをクリア
        System.out.println("All user cache cleared");
    }
}

クラスター構成(Terracotta使用)

import org.ehcache.clustered.client.config.builders.ClusteredResourcePoolBuilder;
import org.ehcache.clustered.client.config.builders.ClusteringServiceConfigurationBuilder;
import org.ehcache.config.units.MemoryUnit;

// Terracottaクラスター設定
CacheManager clusteredCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .with(ClusteringServiceConfigurationBuilder.cluster(
        URI.create("terracotta://server1:9410,server2:9410/my-server-entity"))
        .autoCreate(server -> server
            .defaultServerResource("primary-server-resource")
            .resourcePool("resource-pool-a", 10, MemoryUnit.MB)
            .resourcePool("resource-pool-b", 20, MemoryUnit.MB)
        )
    )
    .withCache("clusteredCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String.class, String.class,
            ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(1000)
                .offheap(5, MemoryUnit.MB)
                .with(ClusteredResourcePoolBuilder.clusteredDedicated(
                    "primary-server-resource", 8, MemoryUnit.MB))
        )
    )
    .withCache("sharedCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String.class, String.class,
            ResourcePoolsBuilder.newResourcePoolsBuilder()
                .heap(500)
                .with(ClusteredResourcePoolBuilder.clusteredShared(
                    "resource-pool-a"))
        )
    )
    .build(true);

// クラスターキャッシュの使用
Cache<String, String> clusteredCache = clusteredCacheManager
    .getCache("clusteredCache", String.class, String.class);

// 複数ノード間でデータ共有
clusteredCache.put("shared-key", "This data is shared across cluster");

// 他のノードからもアクセス可能
String sharedValue = clusteredCache.get("shared-key");
System.out.println("Cluster shared value: " + sharedValue);

clusteredCacheManager.close();

カスタムイベントリスナー

import org.ehcache.event.CacheEvent;
import org.ehcache.event.CacheEventListener;
import org.ehcache.event.EventType;

// カスタムイベントリスナー
public class MyCacheEventListener implements CacheEventListener<String, String> {
    
    @Override
    public void onEvent(CacheEvent<String, String> event) {
        System.out.printf("Cache Event: %s - Key: %s, Old Value: %s, New Value: %s%n",
            event.getType(),
            event.getKey(),
            event.getOldValue(),
            event.getNewValue()
        );
        
        // 特定イベントに対する処理
        switch (event.getType()) {
            case CREATED:
                onCacheEntryCreated(event);
                break;
            case UPDATED:
                onCacheEntryUpdated(event);
                break;
            case EXPIRED:
                onCacheEntryExpired(event);
                break;
            case EVICTED:
                onCacheEntryEvicted(event);
                break;
        }
    }
    
    private void onCacheEntryCreated(CacheEvent<String, String> event) {
        // 作成時の処理
        System.out.println("New cache entry created: " + event.getKey());
    }
    
    private void onCacheEntryUpdated(CacheEvent<String, String> event) {
        // 更新時の処理
        System.out.println("Cache entry updated: " + event.getKey());
    }
    
    private void onCacheEntryExpired(CacheEvent<String, String> event) {
        // 期限切れ時の処理
        System.out.println("Cache entry expired: " + event.getKey());
    }
    
    private void onCacheEntryEvicted(CacheEvent<String, String> event) {
        // 削除時の処理
        System.out.println("Cache entry evicted: " + event.getKey());
    }
}

// イベントリスナー付きキャッシュの設定
CacheManager eventCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .withCache("eventCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String.class, String.class,
            ResourcePoolsBuilder.heap(100)
        )
        .withService(CacheEventListenerConfigurationBuilder
            .newEventListenerConfiguration(new MyCacheEventListener(),
                EventType.CREATED, EventType.UPDATED, 
                EventType.EXPIRED, EventType.EVICTED)
            .unordered()  // 順序保証なし(パフォーマンス優先)
            .asynchronous()  // 非同期実行
        )
    )
    .build(true);

Cache<String, String> eventCache = eventCacheManager
    .getCache("eventCache", String.class, String.class);

// イベント発生のテスト
eventCache.put("test-key", "test-value");  // CREATED イベント
eventCache.put("test-key", "updated-value");  // UPDATED イベント
eventCache.remove("test-key");  // REMOVED イベント

eventCacheManager.close();

パフォーマンス測定とモニタリング

import org.ehcache.config.builders.CacheEventListenerConfigurationBuilder;
import org.ehcache.management.registry.DefaultManagementRegistryConfiguration;
import org.ehcache.management.registry.DefaultManagementRegistryService;

// 管理とモニタリング設定
CacheManager monitoringCacheManager = CacheManagerBuilder.newCacheManagerBuilder()
    .using(new DefaultManagementRegistryService(
        new DefaultManagementRegistryConfiguration()
            .setCacheManagerAlias("myCacheManager")
    ))
    .withCache("performanceCache",
        CacheConfigurationBuilder.newCacheConfigurationBuilder(
            String.class, String.class,
            ResourcePoolsBuilder.heap(1000)
                .offheap(10, MemoryUnit.MB)
        )
        .withService(StatisticsServiceConfigurationBuilder
            .newStatisticsServiceConfigurationBuilder()
            .build())
    )
    .build(true);

Cache<String, String> perfCache = monitoringCacheManager
    .getCache("performanceCache", String.class, String.class);

// パフォーマンステスト
long operations = 10000;
long startTime = System.currentTimeMillis();

// 書き込みテスト
for (int i = 0; i < operations; i++) {
    perfCache.put("key" + i, "value" + i);
}

long writeTime = System.currentTimeMillis() - startTime;
System.out.printf("Write %d entries took: %d ms%n", operations, writeTime);

// 読み込みテスト
startTime = System.currentTimeMillis();
for (int i = 0; i < operations; i++) {
    perfCache.get("key" + i);
}

long readTime = System.currentTimeMillis() - startTime;
System.out.printf("Read %d entries took: %d ms%n", operations, readTime);

// 統計情報の取得
CacheStatistics stats = perfCache.getStatistics();
System.out.printf("Cache Statistics:%n");
System.out.printf("  Hit ratio: %.2f%%%n", stats.getCacheHitPercentage());
System.out.printf("  Hit count: %d%n", stats.getCacheHits());
System.out.printf("  Miss count: %d%n", stats.getCacheMisses());
System.out.printf("  Eviction count: %d%n", stats.getCacheEvictions());

monitoringCacheManager.close();