Second Level Cache

キャッシュ概念データベースORMHibernateJPAパフォーマンス

GitHub概要

hooopo/second_level_cache

Write Through and Read Through caching library inspired by CacheMoney and cache_fu, support ActiveRecord 4, 5 and 6.

スター395
ウォッチ15
フォーク90
作成日:2011年11月7日
言語:Ruby
ライセンス:MIT License

トピックス

activerecordactiverecord5cachecache-moneyrailsrails5rails6

スター履歴

hooopo/second_level_cache Star History
データ取得日時: 2025/10/22 08:07

キャッシュ概念

Second Level Cache(二次レベルキャッシュ)

概要

Second Level Cacheは、ORMフレームワークにおいてSessionFactory スコープで共有されるキャッシュメカニズムで、複数のセッション間でエンティティデータを共有し、データベースアクセスを削減します。

詳細

Second Level Cache(セカンドレベルキャッシュ)は、Hibernateなどのオブジェクトリレーショナルマッピング(ORM)ライブラリで利用可能なデータキャッシングコンポーネントの一つです。ファーストレベルキャッシュとは別で、SessionFactoryスコープでグローバルに使用可能です。複数のセッション間でキャッシュデータを共有するため、異なるセッションで挿入されたデータでも、セッション作成元のSessionFactoryが同じであれば他のセッション/ユーザーがキャッシュデータを利用できます。セッションが閉じてもデータは残り続け、SessionFactoryが閉じられた時点で関連するすべてのキャッシュとキャッシュマネージャーも終了します。

メリット・デメリット

メリット

  • セッション間共有: 複数のセッションでキャッシュデータを共有可能
  • データベース負荷軽減: 頻繁にアクセスされるデータのDB アクセス削減
  • アプリケーション性能向上: 特に高ボリューム Web アプリケーションで効果的
  • クラスタリング対応: 複数のアプリケーションインスタンス間での共有
  • 柔軟な設定: エンティティ単位での有効/無効設定が可能
  • TTL サポート: キャッシュエントリの有効期限設定

デメリット

  • データ整合性リスク: 直接的なデータベース更新では同期されない
  • メモリ使用量増加: 大量のキャッシュデータによるメモリ消費
  • 設定の複雑さ: 適切な並行性戦略の選択が必要
  • デバッグの困難さ: キャッシュ関連の問題の特定が困難
  • パフォーマンス影響: 場合によってはパフォーマンス低下の可能性

主要リンク

書き方の例

Hibernateでのセカンドレベルキャッシュ有効化

<!-- hibernate.cfg.xml -->
<hibernate-configuration>
    <session-factory>
        <!-- セカンドレベルキャッシュを有効化 -->
        <property name="hibernate.cache.use_second_level_cache">true</property>
        
        <!-- クエリキャッシュを有効化 -->
        <property name="hibernate.cache.use_query_cache">true</property>
        
        <!-- キャッシュプロバイダーの指定 (EhCache) -->
        <property name="hibernate.cache.region.factory_class">
            org.hibernate.cache.ehcache.EhCacheRegionFactory
        </property>
        
        <!-- 統計情報の有効化 -->
        <property name="hibernate.generate_statistics">true</property>
    </session-factory>
</hibernate-configuration>

エンティティでのキャッシュ設定

import org.hibernate.annotations.Cache;
import org.hibernate.annotations.CacheConcurrencyStrategy;

@Entity
@Table(name = "users")
@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class User {
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    
    @Column(name = "username")
    private String username;
    
    @Column(name = "email")
    private String email;
    
    // 関連エンティティのキャッシュ
    @OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
    @Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
    private Set<Order> orders = new HashSet<>();
    
    // getter/setter methods
}

プログラムによるキャッシュ操作

import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.cache.spi.CacheImplementor;

public class UserService {
    
    private SessionFactory sessionFactory;
    
    public User findUser(Long id) {
        try (Session session = sessionFactory.openSession()) {
            // 1. ファーストレベルキャッシュをチェック
            // 2. セカンドレベルキャッシュをチェック
            // 3. データベースからロード(必要に応じて)
            return session.get(User.class, id);
        }
    }
    
    public void evictUserFromCache(Long id) {
        // 特定のエンティティをキャッシュから削除
        sessionFactory.getCache().evict(User.class, id);
    }
    
    public void evictAllUsers() {
        // すべてのUserエンティティをキャッシュから削除
        sessionFactory.getCache().evict(User.class);
    }
    
    public void clearAllCaches() {
        // すべてのキャッシュをクリア
        sessionFactory.getCache().evictAllRegions();
    }
}

クエリキャッシュの使用

public class UserRepository {
    
    public List<User> findActiveUsers() {
        try (Session session = sessionFactory.openSession()) {
            return session.createQuery(
                "FROM User u WHERE u.active = true", User.class)
                .setCacheable(true)  // クエリキャッシュを有効化
                .setCacheRegion("activeUsers")  // キャッシュリージョン指定
                .getResultList();
        }
    }
    
    public List<User> findUsersByRole(String role) {
        try (Session session = sessionFactory.openSession()) {
            return session.createQuery(
                "FROM User u WHERE u.role = :role", User.class)
                .setParameter("role", role)
                .setCacheable(true)
                .getResultList();
        }
    }
}

EhCache設定ファイル

<!-- ehcache.xml -->
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="ehcache.xsd">

    <!-- デフォルトキャッシュ設定 -->
    <defaultCache
        maxElementsInMemory="10000"
        eternal="false"
        timeToIdleSeconds="120"
        timeToLiveSeconds="120"
        overflowToDisk="true"
        diskPersistent="false"
        diskExpiryThreadIntervalSeconds="120"/>

    <!-- Userエンティティ用キャッシュ -->
    <cache name="com.example.model.User"
           maxElementsInMemory="5000"
           eternal="false"
           timeToIdleSeconds="300"
           timeToLiveSeconds="600"
           overflowToDisk="false"/>

    <!-- クエリ結果キャッシュ -->
    <cache name="org.hibernate.cache.internal.StandardQueryCache"
           maxElementsInMemory="1000"
           eternal="false"
           timeToLiveSeconds="120"/>

    <!-- クエリキャッシュのメタデータ -->
    <cache name="org.hibernate.cache.spi.UpdateTimestampsCache"
           maxElementsInMemory="5000"
           eternal="true"/>
</ehcache>

Spring Boot での設定

@Configuration
@EnableCaching
public class CacheConfig {
    
    @Bean
    public CacheManager cacheManager() {
        EhCacheCacheManager cacheManager = new EhCacheCacheManager();
        cacheManager.setCacheManager(ehCacheManagerFactory().getObject());
        return cacheManager;
    }
    
    @Bean
    public EhCacheManagerFactoryBean ehCacheManagerFactory() {
        EhCacheManagerFactoryBean factory = new EhCacheManagerFactoryBean();
        factory.setConfigLocation(new ClassPathResource("ehcache.xml"));
        factory.setShared(true);
        return factory;
    }
}

@Service
public class UserService {
    
    @Cacheable(value = "users", key = "#id")
    public User findById(Long id) {
        // データベースからのロード処理
        return userRepository.findById(id);
    }
    
    @CacheEvict(value = "users", key = "#user.id")
    public User updateUser(User user) {
        return userRepository.save(user);
    }
    
    @CacheEvict(value = "users", allEntries = true)
    public void clearUserCache() {
        // すべてのユーザーキャッシュをクリア
    }
}

キャッシュ統計とモニタリング

public class CacheMonitoringService {
    
    private SessionFactory sessionFactory;
    
    public void printCacheStatistics() {
        Statistics stats = sessionFactory.getStatistics();
        
        System.out.println("=== Cache Statistics ===");
        System.out.println("Second level cache hits: " + 
            stats.getSecondLevelCacheHitCount());
        System.out.println("Second level cache misses: " + 
            stats.getSecondLevelCacheMissCount());
        System.out.println("Second level cache puts: " + 
            stats.getSecondLevelCachePutCount());
        
        System.out.println("Query cache hits: " + 
            stats.getQueryCacheHitCount());
        System.out.println("Query cache misses: " + 
            stats.getQueryCacheMissCount());
        
        // リージョン別の統計
        String[] regionNames = stats.getSecondLevelCacheRegionNames();
        for (String regionName : regionNames) {
            SecondLevelCacheStatistics regionStats = 
                stats.getSecondLevelCacheStatistics(regionName);
            System.out.println("Region " + regionName + ":");
            System.out.println("  Hits: " + regionStats.getHitCount());
            System.out.println("  Misses: " + regionStats.getMissCount());
            System.out.println("  Elements: " + regionStats.getElementCountInMemory());
        }
    }
    
    public double getCacheHitRatio() {
        Statistics stats = sessionFactory.getStatistics();
        long hits = stats.getSecondLevelCacheHitCount();
        long misses = stats.getSecondLevelCacheMissCount();
        
        if (hits + misses == 0) {
            return 0.0;
        }
        
        return (double) hits / (hits + misses);
    }
}