PlayのCache APIでMemcachedやRedisの代わりにRDBを使う
Play frameworkはキャッシュのためのAPIを play.api.cahe
というパッケージで提供しています。play.api.cache
はそのバックエンドとして、CaffineやEhcacheが選択でき、またサードバーティのライブラリを使えばMemcached, Redisなどをバックエンドにすることができます。キャッシュと言うと高速化のためのものですが、Playのキャッシュは高速化の他に、単なるデータの一時保存のため、つまり単なるKey-Value Storeとして使われていたりもします。
Caffeineを使うとインメモリのキャッシュが簡単に実現できますが、アプリケーションサーバーのインスタンスが複数ある場合、各サーバーでキャッシュした内容を共有することができません。MemcachedやRedisを使えばインスタンス間でキャッシュした内容を共有できますが、MemcachedやRedisを管理する手間が発生します。
PlayのキャッシュAPIは使いたいけれど、MemcachedやRedisなどの新たなミドルウェアを導入したくない。という状況で、ならばRDBをバックエンドにしてしまおうと作ったのがdbcacheというライブラリです。実際のところ、MemcachedやRedisを使わなくてもRDBで中小規模のサービスでは事足りてしまいます。
https://github.com/tototoshi/dbcache
設定
テーブル追加
MySQLをバックエンドとして使う場合の例です。まずデータベースにcache_entriesテーブルを作成します。
CREATE TABLE `cache_entries` ( `cache_key` varchar(191) PRIMARY KEY, `cache_value` mediumblob NOT NULL, `expired_at` datetime, `created_at` datetime NOT NULL, `updated_at` datetime NOT NULL, INDEX (`expired_at`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4
Play2.7.xの場合
Play 2.7.xを使っている場合は dbcache-mysql
と dbcache-play
をbuild.sbtで追加します。
libraryDependencies ++= Seq( "com.github.tototoshi" %% "dbcache-mysql" % "0.3.0", "com.github.tototoshi" %% "dbcache-play" % "0.3.0" )
Play2.6.xの場合
dbcache-play
は最近追加したモジュールなので、Play 2.6.xには対応していません。Play2.6.xを使っている場合は dbcache-mysql
だけを追加し、以下のコードをアプリケーションコードに追加します。
libraryDependencies ++= Seq( "com.github.tototoshi" %% "dbcache-mysql" % "0.2.0" )
// Create adapter for play.api.cache class DBCacheApi(myCache: DBCache) extends CacheApi { def set(key: String, value: Any, expiration: Duration): Unit = myCache.set(key, value, expiration) def get[A](key: String)(implicit ct: ClassTag[A]): Option[A] = myCache.get[A](key) def getOrElse[A: ClassTag](key: String, expiration: Duration)(orElse: => A) = myCache.getOrElse(key, expiration)(orElse) def remove(key: String) = myCache.remove(key) }
DIの設定
あとはProviderを作ってDIしてあげるだけです。Scalikejdbcを使っている場合はこんな感じになります。
import java.sql.Connection import com.github.tototoshi.dbcache.ConnectionFactory import com.github.tototoshi.dbcache.mysql.MySQLCache import com.github.tototoshi.dbcache.play.DBCacheApi import javax.inject.{Inject, Provider} import play.api.Environment import play.api.cache.SyncCacheApi import scalikejdbc.DB class MySQLCacheApiProvider @Inject()(environment: Environment) extends Provider[SyncCacheApi] { private val connectionFactory = new ConnectionFactory { override def get(): Connection = { DB.autoCommitSession().connection } } override def get(): SyncCacheApi = { val mysqlCache = new MySQLCache(connectionFactory, environment.classLoader) new DBCacheApi(mysqlCache) } }
EhCacheモジュールは無効にしておきます。
play.modules.disabled += "play.api.cache.ehcache.EhCacheModule"
あとは普通にPlayのキャッシュAPIを使えばデータベースに保存されるようになります。
運用について
期限切れのキャッシュデータがDBに残るのでバッチか何か動かしてたまに掃除してあげてください。
DELETE FROM cache_entries where expired_at < ${n日前}