refactor mongo caches - closes #5813
parent
9352ebc6fb
commit
effe244b0d
|
@ -8,7 +8,7 @@ final class Stat(env: Env) extends LilaController(env) {
|
|||
def ratingDistribution(perfKey: lila.rating.Perf.Key) = Open { implicit ctx =>
|
||||
lila.rating.PerfType(perfKey).filter(lila.rating.PerfType.leaderboardable.has) match {
|
||||
case Some(perfType) =>
|
||||
env.user.cached.ratingDistribution(perfType) map { data =>
|
||||
env.user.rankingApi.weeklyRatingDistribution(perfType) dmap { data =>
|
||||
Ok(html.stat.ratingDistribution(perfType, data))
|
||||
}
|
||||
case _ => notFound
|
||||
|
|
|
@ -227,12 +227,11 @@ final class User(
|
|||
}
|
||||
|
||||
def list = Open { implicit ctx =>
|
||||
val nb = 10
|
||||
val leaderboards = env.user.cached.top10.get
|
||||
negotiate(
|
||||
html =
|
||||
for {
|
||||
nbAllTime <- env.user.cached topNbGame nb
|
||||
nbAllTime <- env.user.cached.top10NbGame.get({})
|
||||
tourneyWinners <- env.tournament.winners.all.map(_.top)
|
||||
_ <- env.user.lightUserApi preloadMany tourneyWinners.map(_.userId)
|
||||
} yield Ok(
|
||||
|
@ -269,7 +268,7 @@ final class User(
|
|||
|
||||
def topNb(nb: Int, perfKey: String) = Open { implicit ctx =>
|
||||
PerfType(perfKey) ?? { perfType =>
|
||||
env.user.cached top200Perf perfType.id map { _ take (nb atLeast 1 atMost 200) } flatMap { users =>
|
||||
env.user.cached.top200Perf get perfType.id dmap { _ take (nb atLeast 1 atMost 200) } flatMap { users =>
|
||||
negotiate(
|
||||
html = Ok(html.user.top(perfType, users)).fuccess,
|
||||
api = _ =>
|
||||
|
@ -286,7 +285,7 @@ final class User(
|
|||
negotiate(
|
||||
html = notFound,
|
||||
api = _ =>
|
||||
env.user.cached.topWeek(()).map { users =>
|
||||
env.user.cached.topWeek.map { users =>
|
||||
Ok(Json toJson users.map(env.user.jsonView.lightPerfIsOnline))
|
||||
}
|
||||
)
|
||||
|
@ -431,7 +430,7 @@ final class User(
|
|||
perfStat = oldPerfStat.copy(playStreak = oldPerfStat.playStreak.checkCurrent)
|
||||
ranks = env.user.cached rankingsOf u.id
|
||||
distribution <- u.perfs(perfType).established ?? {
|
||||
env.user.cached.ratingDistribution(perfType) map some
|
||||
env.user.rankingApi.weeklyRatingDistribution(perfType) dmap some
|
||||
}
|
||||
percentile = distribution.map { distrib =>
|
||||
lila.user.Stat.percentile(distrib, u.perfs(perfType).intRating) match {
|
||||
|
|
|
@ -46,7 +46,7 @@ final class Preload(
|
|||
simuls.mon(_.lobby segment "simuls") zip
|
||||
tv.getBestGame.mon(_.lobby segment "tvBestGame") zip
|
||||
(ctx.userId ?? timelineApi.userEntries).mon(_.lobby segment "timeline") zip
|
||||
userCached.topWeek(()).mon(_.lobby segment "userTopWeek") zip
|
||||
userCached.topWeek.mon(_.lobby segment "userTopWeek") zip
|
||||
tourWinners.all.dmap(_.top).mon(_.lobby segment "tourWinners") zip
|
||||
(ctx.noBot ?? dailyPuzzle()).mon(_.lobby segment "puzzle") zip
|
||||
liveStreamApi.all.dmap(_.autoFeatured withTitles lightUserApi).mon(_.lobby segment "streams") zip
|
||||
|
|
|
@ -366,7 +366,6 @@ tournament {
|
|||
sri.timeout = 7 seconds # small to avoid missed events
|
||||
api_actor.name = tournament-api
|
||||
pairing.delay = 3.1 seconds
|
||||
leaderboard.cache.ttl = 1 hour
|
||||
}
|
||||
simul {
|
||||
collection.simul = simul
|
||||
|
|
|
@ -62,6 +62,16 @@ object mon {
|
|||
gauge("caffeine.eviction.count").withTag("name", name).update(stats.evictionCount)
|
||||
gauge("caffeine.entry.count").withTag("name", name).update(cache.estimatedSize)
|
||||
}
|
||||
object mongoCache {
|
||||
def request(name: String, hit: Boolean) =
|
||||
counter("mongocache.request").withTags(
|
||||
Map(
|
||||
"name" -> name,
|
||||
"hit" -> hit
|
||||
)
|
||||
)
|
||||
def compute(name: String) = timer("mongocache.compute").withTag("name", name)
|
||||
}
|
||||
object evalCache {
|
||||
private val r = counter("evalCache.request")
|
||||
def request(ply: Int, isHit: Boolean) =
|
||||
|
|
|
@ -9,35 +9,46 @@ import lila.user.User
|
|||
final class Cached(
|
||||
gameRepo: GameRepo,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
mongoCache: MongoCache.Builder
|
||||
mongoCache: MongoCache.Api
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
def nbImportedBy(userId: User.ID): Fu[Int] = nbImportedCache(userId)
|
||||
def clearNbImportedByCache = nbImportedCache remove _
|
||||
def nbImportedBy(userId: User.ID): Fu[Int] = nbImportedCache.get(userId)
|
||||
def clearNbImportedByCache = nbImportedCache invalidate _
|
||||
|
||||
def nbTotal: Fu[Int] = countCache($empty)
|
||||
def nbTotal: Fu[Int] = nbTotalCache.get({})
|
||||
|
||||
def nbPlaying = nbPlayingCache.get _
|
||||
|
||||
private val nbPlayingCache = cacheApi[User.ID, Int](256, "game.nbPlaying") {
|
||||
_.expireAfterAccess(15 seconds)
|
||||
_.expireAfterWrite(15 seconds)
|
||||
.buildAsyncFuture { userId =>
|
||||
gameRepo.coll.countSel(Query nowPlaying userId)
|
||||
}
|
||||
}
|
||||
|
||||
private val nbImportedCache = mongoCache[User.ID, Int](
|
||||
prefix = "game:imported",
|
||||
f = userId => gameRepo.coll countSel Query.imported(userId),
|
||||
timeToLive = 1 hour,
|
||||
timeToLiveMongo = 30.days.some,
|
||||
keyToString = identity
|
||||
)
|
||||
1024,
|
||||
"game:imported",
|
||||
30 days,
|
||||
_.toString
|
||||
) { loader =>
|
||||
_.expireAfterAccess(10 minutes)
|
||||
.buildAsyncFuture {
|
||||
loader { userId =>
|
||||
gameRepo.coll countSel Query.imported(userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val countCache = mongoCache[Bdoc, Int](
|
||||
prefix = "game:count",
|
||||
f = gameRepo.coll.countSel(_),
|
||||
timeToLive = 1 hour,
|
||||
keyToString = lila.db.BSON.hashDoc
|
||||
)
|
||||
private val nbTotalCache = mongoCache.unit[Int](
|
||||
"game:total",
|
||||
29 minutes
|
||||
) { loader =>
|
||||
_.refreshAfterWrite(30 minutes)
|
||||
.buildAsyncFuture {
|
||||
loader { _ =>
|
||||
gameRepo.coll.countSel($empty)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -27,7 +27,7 @@ final class Env(
|
|||
db: lila.db.Db,
|
||||
baseUrl: BaseUrl,
|
||||
userRepo: lila.user.UserRepo,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
getLightUser: lila.common.LightUser.Getter,
|
||||
cacheApi: lila.memo.CacheApi
|
||||
)(implicit ec: scala.concurrent.ExecutionContext, system: ActorSystem, scheduler: Scheduler) {
|
||||
|
|
|
@ -1,20 +1,17 @@
|
|||
package lila.history
|
||||
|
||||
import com.softwaremill.macwire._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import lila.common.config.CollName
|
||||
|
||||
@Module
|
||||
final class Env(
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
userRepo: lila.user.UserRepo,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
db: lila.db.Db
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
private val cacheTtl = 30 minutes
|
||||
|
||||
private lazy val coll = db(CollName("history3"))
|
||||
|
||||
lazy val api = wire[HistoryApi]
|
||||
|
|
|
@ -9,11 +9,10 @@ import lila.user.User
|
|||
|
||||
final class RatingChartApi(
|
||||
historyApi: HistoryApi,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
cacheTtl: FiniteDuration
|
||||
mongoCache: lila.memo.MongoCache.Api
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
def apply(user: User): Fu[Option[String]] = cache(user) dmap { chart =>
|
||||
def apply(user: User): Fu[Option[String]] = cache.get(user) dmap { chart =>
|
||||
chart.nonEmpty option chart
|
||||
}
|
||||
|
||||
|
@ -23,12 +22,19 @@ final class RatingChartApi(
|
|||
} map JsArray.apply
|
||||
|
||||
private val cache = mongoCache[User, String](
|
||||
prefix = "history:rating",
|
||||
f = user => build(user) dmap (~_),
|
||||
maxCapacity = 1024,
|
||||
timeToLive = cacheTtl,
|
||||
keyToString = _.id
|
||||
)
|
||||
1024,
|
||||
"history:rating",
|
||||
60 minutes,
|
||||
_.id
|
||||
) { loader =>
|
||||
_.expireAfterAccess(10 minutes)
|
||||
.maximumSize(2048)
|
||||
.buildAsyncFuture {
|
||||
loader { user =>
|
||||
build(user) dmap (~_)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def ratingsMapToJson(user: User, ratingsMap: RatingsMap) = ratingsMap.map {
|
||||
case (days, rating) =>
|
||||
|
|
|
@ -7,27 +7,29 @@ import play.api.Mode
|
|||
import scala.concurrent.duration._
|
||||
import scala.concurrent.ExecutionContext
|
||||
|
||||
final class CacheApi(mode: Mode)(implicit ec: ExecutionContext, system: ActorSystem) {
|
||||
final class CacheApi(
|
||||
mode: Mode
|
||||
)(implicit ec: ExecutionContext, system: ActorSystem) {
|
||||
|
||||
import CacheApi._
|
||||
|
||||
// AsyncLoadingCache with monitoring
|
||||
def apply[K, V](initialCapacity: Int, name: String)(
|
||||
build: Builder => AsyncLoadingCache[K, V]
|
||||
): AsyncLoadingCache[K, V] = {
|
||||
val actualCapacity =
|
||||
if (mode != Mode.Prod) math.sqrt(initialCapacity).toInt atLeast 1
|
||||
else initialCapacity
|
||||
val cache = build {
|
||||
scaffeine.recordStats.initialCapacity(actualCapacity)
|
||||
scaffeine.recordStats.initialCapacity(actualCapacity(initialCapacity))
|
||||
}
|
||||
monitor(name, cache)
|
||||
cache
|
||||
}
|
||||
|
||||
// AsyncLoadingCache for a single entry
|
||||
def unit[V](build: Builder => AsyncLoadingCache[Unit, V]): AsyncLoadingCache[Unit, V] = {
|
||||
build(scaffeine initialCapacity 1)
|
||||
}
|
||||
|
||||
// AsyncLoadingCache with monitoring and a synchronous getter
|
||||
def sync[K, V](
|
||||
name: String,
|
||||
initialCapacity: Int,
|
||||
|
@ -52,11 +54,15 @@ final class CacheApi(mode: Mode)(implicit ec: ExecutionContext, system: ActorSys
|
|||
|
||||
def monitor(name: String, cache: caffeine.cache.Cache[_, _]): Unit =
|
||||
startMonitor(name, cache)
|
||||
|
||||
def actualCapacity(c: Int) =
|
||||
if (mode != Mode.Prod) math.sqrt(c).toInt atLeast 1
|
||||
else c
|
||||
}
|
||||
|
||||
object CacheApi {
|
||||
|
||||
private type Builder = Scaffeine[Any, Any]
|
||||
private[memo] type Builder = Scaffeine[Any, Any]
|
||||
|
||||
def scaffeine: Builder = Scaffeine().scheduler(caffeine.cache.Scheduler.systemScheduler)
|
||||
|
||||
|
|
|
@ -20,11 +20,11 @@ final class Env(
|
|||
|
||||
private val config = appConfig.get[MemoConfig]("memo")(AutoConfig.loader)
|
||||
|
||||
lazy val mongoCache = wire[MongoCache.Builder]
|
||||
|
||||
lazy val configStore = wire[ConfigStore.Builder]
|
||||
|
||||
lazy val settingStore = wire[SettingStore.Builder]
|
||||
|
||||
lazy val cacheApi = wire[CacheApi]
|
||||
|
||||
lazy val mongoCacheApi = wire[MongoCache.Api]
|
||||
}
|
||||
|
|
|
@ -1,108 +1,104 @@
|
|||
package lila.memo
|
||||
|
||||
import com.github.blemale.scaffeine.Cache
|
||||
import com.github.blemale.scaffeine.AsyncLoadingCache
|
||||
import org.joda.time.DateTime
|
||||
import reactivemongo.api.bson._
|
||||
import scala.concurrent.duration._
|
||||
|
||||
import CacheApi._
|
||||
import lila.db.BSON.BSONJodaDateTimeHandler
|
||||
import lila.db.dsl._
|
||||
|
||||
/**
|
||||
* To avoid recomputing very expensive values after deploy
|
||||
*/
|
||||
final class MongoCache[K, V: BSONHandler] private (
|
||||
prefix: String,
|
||||
cache: Cache[K, Fu[V]],
|
||||
mongoExpiresAt: () => DateTime,
|
||||
val coll: Coll,
|
||||
f: K => Fu[V],
|
||||
keyToString: K => String
|
||||
name: String,
|
||||
dbTtl: FiniteDuration,
|
||||
keyToString: K => String,
|
||||
build: MongoCache.LoaderWrapper[K, V] => AsyncLoadingCache[K, V],
|
||||
val coll: Coll
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
private case class Entry(_id: String, v: V, e: DateTime)
|
||||
|
||||
implicit private val entryBSONHandler = Macros.handler[Entry]
|
||||
|
||||
def apply(k: K): Fu[V] =
|
||||
cache.get(
|
||||
k,
|
||||
k =>
|
||||
coll.find($id(makeKey(k)), none[Bdoc]).one[Entry] flatMap {
|
||||
case None =>
|
||||
f(k) flatMap { v =>
|
||||
persist(k, v) inject v
|
||||
}
|
||||
case Some(entry) => fuccess(entry.v)
|
||||
}
|
||||
)
|
||||
|
||||
def remove(k: K): Funit = {
|
||||
val fut = f(k)
|
||||
cache.put(k, fut)
|
||||
fut flatMap { v =>
|
||||
persist(k, v).void
|
||||
private val cache = build { loader => k =>
|
||||
val dbKey = makeDbKey(k)
|
||||
coll.one[Entry]($id(dbKey)) flatMap {
|
||||
case None =>
|
||||
lila.mon.mongoCache.request(name, false)
|
||||
loader(k)
|
||||
.flatMap { v =>
|
||||
coll.update.one(
|
||||
$id(dbKey),
|
||||
Entry(dbKey, v, DateTime.now.plusSeconds(dbTtl.toSeconds.toInt)),
|
||||
upsert = true
|
||||
) inject v
|
||||
}
|
||||
.mon(_.mongoCache.compute(name))
|
||||
case Some(entry) =>
|
||||
lila.mon.mongoCache.request(name, false)
|
||||
fuccess(entry.v)
|
||||
}
|
||||
}
|
||||
|
||||
private def makeKey(k: K) = s"$prefix:${keyToString(k)}"
|
||||
def get = cache.get _
|
||||
|
||||
private def persist(k: K, v: V): Funit = {
|
||||
val mongoKey = makeKey(k)
|
||||
coll.update
|
||||
.one(
|
||||
$id(mongoKey),
|
||||
Entry(mongoKey, v, mongoExpiresAt()),
|
||||
upsert = true
|
||||
)
|
||||
.void
|
||||
}
|
||||
def invalidate(key: K): Funit =
|
||||
coll.delete.one($id(makeDbKey(key))).void >>-
|
||||
cache.invalidate(key)
|
||||
|
||||
private def makeDbKey(key: K) = s"$name:${keyToString(key)}"
|
||||
}
|
||||
|
||||
object MongoCache {
|
||||
|
||||
// expire in mongo 3 seconds before in heap,
|
||||
// to make sure the mongo cache is cleared
|
||||
// when the heap value expires
|
||||
private def mongoExpiresAt(ttl: FiniteDuration): () => DateTime = {
|
||||
val seconds = ttl.toSeconds.toInt - 3
|
||||
() => DateTime.now plusSeconds seconds
|
||||
}
|
||||
type Loader[K, V] = K => Fu[V]
|
||||
type LoaderWrapper[K, V] = Loader[K, V] => Loader[K, V]
|
||||
|
||||
final class Builder(db: lila.db.Db, config: MemoConfig)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
final class Api(
|
||||
db: lila.db.Db,
|
||||
config: MemoConfig,
|
||||
cacheApi: CacheApi
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
val coll = db(config.cacheColl)
|
||||
private val coll = db(config.cacheColl)
|
||||
|
||||
// AsyncLoadingCache with monitoring and DB persistence
|
||||
def apply[K, V: BSONHandler](
|
||||
prefix: String,
|
||||
f: K => Fu[V],
|
||||
maxCapacity: Int = 1024,
|
||||
timeToLive: FiniteDuration,
|
||||
timeToLiveMongo: Option[FiniteDuration] = None,
|
||||
initialCapacity: Int,
|
||||
name: String,
|
||||
dbTtl: FiniteDuration,
|
||||
keyToString: K => String
|
||||
): MongoCache[K, V] = new MongoCache[K, V](
|
||||
prefix = prefix,
|
||||
cache = CacheApi.scaffeine
|
||||
.expireAfterWrite(timeToLive)
|
||||
.maximumSize(maxCapacity)
|
||||
.build[K, Fu[V]],
|
||||
mongoExpiresAt = mongoExpiresAt(timeToLiveMongo | timeToLive),
|
||||
coll = coll,
|
||||
f = f,
|
||||
keyToString = keyToString
|
||||
)
|
||||
)(
|
||||
build: LoaderWrapper[K, V] => Builder => AsyncLoadingCache[K, V]
|
||||
): MongoCache[K, V] = {
|
||||
val cache = new MongoCache(
|
||||
name,
|
||||
dbTtl,
|
||||
keyToString,
|
||||
(wrapper: LoaderWrapper[K, V]) =>
|
||||
build(wrapper)(scaffeine.recordStats.initialCapacity(cacheApi.actualCapacity(initialCapacity))),
|
||||
coll
|
||||
)
|
||||
cacheApi.monitor(name, cache.cache)
|
||||
cache
|
||||
}
|
||||
|
||||
def single[V: BSONHandler](
|
||||
prefix: String,
|
||||
f: => Fu[V],
|
||||
timeToLive: FiniteDuration
|
||||
) = new MongoCache[Unit, V](
|
||||
prefix = prefix,
|
||||
cache = CacheApi.scaffeine
|
||||
.expireAfterWrite(timeToLive)
|
||||
.maximumSize(1)
|
||||
.build[Unit, Fu[V]],
|
||||
mongoExpiresAt = mongoExpiresAt(timeToLive),
|
||||
coll = coll,
|
||||
f = _ => f,
|
||||
keyToString = _.toString
|
||||
// AsyncLoadingCache for single entry with DB persistence
|
||||
def unit[V: BSONHandler](
|
||||
name: String,
|
||||
dbTtl: FiniteDuration
|
||||
)(
|
||||
build: LoaderWrapper[Unit, V] => Builder => AsyncLoadingCache[Unit, V]
|
||||
): MongoCache[Unit, V] = new MongoCache(
|
||||
name,
|
||||
dbTtl,
|
||||
_ => "",
|
||||
wrapper => build(wrapper)(scaffeine.initialCapacity(1)),
|
||||
coll
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,13 +13,14 @@ import lila.db.dsl._
|
|||
final private class CheckMail(
|
||||
ws: WSClient,
|
||||
config: SecurityConfig.CheckMail,
|
||||
mongoCache: lila.memo.MongoCache.Builder
|
||||
mongoCache: lila.memo.MongoCache.Api
|
||||
)(implicit ec: scala.concurrent.ExecutionContext, system: akka.actor.ActorSystem) {
|
||||
|
||||
def apply(domain: Domain.Lower): Fu[Boolean] =
|
||||
if (config.key.value.isEmpty) fuccess(true)
|
||||
else
|
||||
cache(domain)
|
||||
cache
|
||||
.get(domain)
|
||||
.withTimeoutDefault(2.seconds, true)
|
||||
.recover {
|
||||
case e: Exception =>
|
||||
|
@ -42,11 +43,14 @@ final private class CheckMail(
|
|||
private val prefix = "security:check_mail"
|
||||
|
||||
private val cache = mongoCache[Domain.Lower, Boolean](
|
||||
prefix = prefix,
|
||||
f = fetch,
|
||||
timeToLive = 1000 days,
|
||||
keyToString = _.toString
|
||||
)
|
||||
512,
|
||||
prefix,
|
||||
1000 days,
|
||||
_.toString
|
||||
) { loader =>
|
||||
_.maximumSize(512)
|
||||
.buildAsyncFuture(loader(fetch))
|
||||
}
|
||||
|
||||
private def fetch(domain: Domain.Lower): Fu[Boolean] =
|
||||
ws.url(config.url)
|
||||
|
|
|
@ -24,7 +24,7 @@ final class Env(
|
|||
cacheApi: lila.memo.CacheApi,
|
||||
settingStore: lila.memo.SettingStore.Builder,
|
||||
tryOAuthServer: OAuthServer.Try,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
db: lila.db.Db
|
||||
)(implicit ec: scala.concurrent.ExecutionContext, system: ActorSystem, scheduler: Scheduler) {
|
||||
|
||||
|
|
|
@ -16,7 +16,6 @@ private class TournamentConfig(
|
|||
@ConfigName("collection.player") val playerColl: CollName,
|
||||
@ConfigName("collection.pairing") val pairingColl: CollName,
|
||||
@ConfigName("collection.leaderboard") val leaderboardColl: CollName,
|
||||
@ConfigName("leaderboard.cache.ttl") val leaderboardCacheTtl: FiniteDuration,
|
||||
@ConfigName("api_actor.name") val apiActorName: String
|
||||
)
|
||||
|
||||
|
@ -24,7 +23,7 @@ private class TournamentConfig(
|
|||
final class Env(
|
||||
appConfig: Configuration,
|
||||
db: lila.db.Db,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
gameRepo: lila.game.GameRepo,
|
||||
userRepo: lila.user.UserRepo,
|
||||
|
|
|
@ -9,21 +9,24 @@ import lila.db.dsl._
|
|||
final class TournamentStatsApi(
|
||||
playerRepo: PlayerRepo,
|
||||
pairingRepo: PairingRepo,
|
||||
mongoCache: lila.memo.MongoCache.Builder
|
||||
mongoCache: lila.memo.MongoCache.Api
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
def apply(tournament: Tournament): Fu[Option[TournamentStats]] =
|
||||
tournament.isFinished ?? cache(tournament.id).dmap(some)
|
||||
tournament.isFinished ?? cache.get(tournament.id).dmap(some)
|
||||
|
||||
implicit private val statsBSONHandler = Macros.handler[TournamentStats]
|
||||
|
||||
private val cache = mongoCache[String, TournamentStats](
|
||||
prefix = "tournament:stats",
|
||||
keyToString = identity,
|
||||
f = fetch,
|
||||
timeToLive = 10 minutes,
|
||||
timeToLiveMongo = 90.days.some
|
||||
)
|
||||
private val cache = mongoCache[Tournament.ID, TournamentStats](
|
||||
32,
|
||||
"tournament:stats",
|
||||
30 days,
|
||||
identity
|
||||
) { loader =>
|
||||
_.expireAfterAccess(10 minutes)
|
||||
.maximumSize(256)
|
||||
.buildAsyncFuture(loader(fetch))
|
||||
}
|
||||
|
||||
private def fetch(tournamentId: Tournament.ID): Fu[TournamentStats] =
|
||||
for {
|
||||
|
|
|
@ -58,8 +58,7 @@ case class AllWinners(
|
|||
|
||||
final class WinnersApi(
|
||||
tournamentRepo: TournamentRepo,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
ttl: FiniteDuration,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
scheduler: akka.actor.Scheduler
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
|
@ -124,18 +123,20 @@ final class WinnersApi(
|
|||
)
|
||||
}
|
||||
|
||||
private val allCache = mongoCache.single[AllWinners](
|
||||
prefix = "tournament:winner:all",
|
||||
f = fetchAll,
|
||||
timeToLive = ttl
|
||||
)
|
||||
private val allCache = mongoCache.unit[AllWinners](
|
||||
"tournament:winner:all",
|
||||
59 minutes
|
||||
) { loader =>
|
||||
_.refreshAfterWrite(1 hour)
|
||||
.buildAsyncFuture(loader(_ => fetchAll))
|
||||
}
|
||||
|
||||
def all: Fu[AllWinners] = allCache(())
|
||||
def all: Fu[AllWinners] = allCache.get({})
|
||||
|
||||
// because we read on secondaries, delay cache clear
|
||||
def clearCache(tour: Tournament) =
|
||||
if (tour.schedule.exists(_.freq.isDailyOrBetter))
|
||||
scheduler.scheduleOnce(5.seconds) { allCache.remove(()) }
|
||||
scheduler.scheduleOnce(5.seconds) { allCache.invalidate({}) }
|
||||
|
||||
}
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@ import User.{ LightCount, LightPerf }
|
|||
final class Cached(
|
||||
userRepo: UserRepo,
|
||||
onlineUserIds: () => Set[User.ID],
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
rankingApi: RankingApi
|
||||
)(implicit ec: scala.concurrent.ExecutionContext, system: akka.actor.ActorSystem) {
|
||||
|
@ -29,31 +29,49 @@ final class Cached(
|
|||
)
|
||||
|
||||
val top200Perf = mongoCache[Perf.ID, List[User.LightPerf]](
|
||||
prefix = "user:top200:perf",
|
||||
f = (perf: Perf.ID) => rankingApi.topPerf(perf, 200),
|
||||
timeToLive = 16 minutes,
|
||||
keyToString = _.toString
|
||||
)
|
||||
|
||||
private val topWeekCache = mongoCache.single[List[User.LightPerf]](
|
||||
prefix = "user:top:week",
|
||||
f = PerfType.leaderboardable
|
||||
.map { perf =>
|
||||
rankingApi.topPerf(perf.id, 1)
|
||||
PerfType.leaderboardable.size,
|
||||
"user:top200:perf",
|
||||
19 minutes,
|
||||
_.toString
|
||||
) { loader =>
|
||||
_.refreshAfterWrite(20 minutes)
|
||||
.buildAsyncFuture {
|
||||
loader {
|
||||
rankingApi.topPerf(_, 200)
|
||||
}
|
||||
}
|
||||
.sequenceFu
|
||||
.map(_.flatten),
|
||||
timeToLive = 9 minutes
|
||||
)
|
||||
}
|
||||
|
||||
def topWeek = topWeekCache.apply _
|
||||
private val topWeekCache = mongoCache.unit[List[User.LightPerf]](
|
||||
"user:top:week",
|
||||
9 minutes
|
||||
) { loader =>
|
||||
_.refreshAfterWrite(10 minutes)
|
||||
.buildAsyncFuture {
|
||||
loader { _ =>
|
||||
PerfType.leaderboardable
|
||||
.map { perf =>
|
||||
rankingApi.topPerf(perf.id, 1)
|
||||
}
|
||||
.sequenceFu
|
||||
.dmap(_.flatten)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val topNbGame = mongoCache[Int, List[User.LightCount]](
|
||||
prefix = "user:top:nbGame",
|
||||
f = nb => userRepo topNbGame nb map { _ map (_.lightCount) },
|
||||
timeToLive = 74 minutes,
|
||||
keyToString = _.toString
|
||||
)
|
||||
def topWeek = topWeekCache.get({})
|
||||
|
||||
val top10NbGame = mongoCache.unit[List[User.LightCount]](
|
||||
"user:top:nbGame",
|
||||
74 minutes
|
||||
) { loader =>
|
||||
_.refreshAfterWrite(75 minutes)
|
||||
.buildAsyncFuture {
|
||||
loader { _ =>
|
||||
userRepo topNbGame 10 dmap (_.map(_.lightCount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val top50OnlineCache = new lila.memo.PeriodicRefreshCache[List[User]](
|
||||
every = Every(30 seconds),
|
||||
|
@ -66,10 +84,6 @@ final class Cached(
|
|||
|
||||
def rankingsOf(userId: User.ID): Map[PerfType, Int] = rankingApi.weeklyStableRanking of userId
|
||||
|
||||
object ratingDistribution {
|
||||
def apply(perf: PerfType) = rankingApi.weeklyRatingDistribution(perf)
|
||||
}
|
||||
|
||||
private[user] val botIds = cacheApi.unit[Set[User.ID]] {
|
||||
_.refreshAfterWrite(10 minutes)
|
||||
.buildAsyncFuture(_ => userRepo.botIds)
|
||||
|
|
|
@ -25,7 +25,7 @@ private class UserConfig(
|
|||
final class Env(
|
||||
appConfig: Configuration,
|
||||
db: lila.db.Db,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
cacheApi: lila.memo.CacheApi,
|
||||
timeline: lila.hub.actors.Timeline,
|
||||
isOnline: lila.socket.IsOnline,
|
||||
|
|
|
@ -13,7 +13,7 @@ import lila.rating.{ Glicko, Perf, PerfType }
|
|||
final class RankingApi(
|
||||
userRepo: UserRepo,
|
||||
coll: Coll,
|
||||
mongoCache: lila.memo.MongoCache.Builder,
|
||||
mongoCache: lila.memo.MongoCache.Api,
|
||||
lightUser: lila.common.LightUser.Getter
|
||||
)(implicit ec: scala.concurrent.ExecutionContext, system: akka.actor.ActorSystem) {
|
||||
|
||||
|
@ -160,14 +160,19 @@ final class RankingApi(
|
|||
|
||||
private type NbUsers = Int
|
||||
|
||||
def apply(perf: PerfType) = cache(perf.id)
|
||||
def apply(pt: PerfType) = cache.get(pt.id)
|
||||
|
||||
private val cache = mongoCache[Perf.ID, List[NbUsers]](
|
||||
prefix = "user:rating:distribution",
|
||||
f = compute,
|
||||
timeToLive = 3 hour,
|
||||
keyToString = _.toString
|
||||
)
|
||||
PerfType.leaderboardable.size,
|
||||
"user:rating:distribution",
|
||||
179 minutes,
|
||||
_.toString
|
||||
) { loader =>
|
||||
_.refreshAfterWrite(180 minutes)
|
||||
.buildAsyncFuture {
|
||||
loader(compute)
|
||||
}
|
||||
}
|
||||
|
||||
// from 600 to 2800 by Stat.group
|
||||
private def compute(perfId: Perf.ID): Fu[List[NbUsers]] =
|
||||
|
|
|
@ -11,7 +11,7 @@ final class TrophyApi(
|
|||
coll: Coll,
|
||||
kindColl: Coll,
|
||||
cacheApi: CacheApi
|
||||
)(implicit ec: scala.concurrent.ExecutionContext, system: akka.actor.ActorSystem) {
|
||||
)(implicit ec: scala.concurrent.ExecutionContext) {
|
||||
|
||||
private val trophyKindObjectBSONHandler = Macros.handler[TrophyKind]
|
||||
|
||||
|
|
Loading…
Reference in New Issue