[-] Remove ranking cache

This commit is contained in:
Azalea
2026-08-04 05:01:38 +00:00
parent 30139cae8d
commit 3c6262a823

View File

@@ -7,12 +7,8 @@ import icu.samnyan.aqua.net.utils.SUCCESS
import icu.samnyan.aqua.sega.allnet.UserKeychipRepo
import icu.samnyan.aqua.sega.general.model.Card
import icu.samnyan.aqua.sega.general.service.CardService
import jakarta.annotation.PostConstruct
import org.slf4j.LoggerFactory
import org.springframework.beans.factory.annotation.Autowired
import org.springframework.scheduling.annotation.Scheduled
import java.util.concurrent.TimeUnit
import java.util.concurrent.locks.ReentrantLock
import kotlin.jvm.optionals.getOrNull
import kotlin.reflect.KClass
@@ -45,33 +41,8 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
playlogRepo.findByUserCardExtId(card.extId)
}
// List<Pair<should_hide, player>>>
private var rankingCache: List<GenericRankingPlayer> = emptyList()
private var rankingCacheLock = ReentrantLock()
// Sorted index List<Rating> = Rank
private var rankingLookupCache: Map<Long, GenericRankingPlayer> = emptyMap()
private val pageSize = 100
@API("ranking")
fun ranking(@RP token: String?, @RP page: Int?): List<GenericRankingPlayer> {
val time = millis()
// Check cache validity
if (rankingCache.isEmpty()) (500 - "Rank is empty or is currently computing.")
return page?.let {
if (it < 0) (400 - "Invalid page number")
rankingCache.drop(it * pageSize).take(pageSize)
} ?: rankingCache
}
@PostConstruct
fun rankingCacheInit() = thread { rankingCacheRun() }
// Every 20 minutes
@Scheduled(fixedRate = 20, initialDelay = 20, timeUnit = TimeUnit.MINUTES)
fun rankingCacheRun() = rankingCacheLock.maybeLock { rankingCacheCompute() }
private val tableName = when (name) { "mai2" -> "maimai2"; "chu3" -> "chusan"; else -> name }
private val userDataTable = when (name) {
"mai2" -> "maimai2_user_detail"
@@ -80,47 +51,66 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
else -> "wacca_user"
}
private val userCardColumn = if (name == "chu3") "card_id" else "aime_card_id"
private val ratingColumn = if (name == "ongeki") "u.new_player_rating" else "u.player_rating"
fun rankingCacheCompute() {
val time = millis()
rankingCache = us.em.createNativeQuery(
"""
SELECT
c.id,
u.user_name,
${if (name == "ongeki") "u.new_player_rating" else "u.player_rating"} AS rating,
u.last_play_date,
r.achievement_sum / NULLIF(r.play_count, 0) / 10000.0 AS acc,
r.full_combo_count AS fc,
r.all_perfect_count AS ap,
c.ranking_banned or COALESCE(a.opt_out_of_leaderboard, 0) or c.status = 12 AS hide,
a.username
FROM ${tableName}_user_ranking_cache r
JOIN $userDataTable u ON r.user_id = u.id
JOIN sega_card c ON u.$userCardColumn = c.id
LEFT JOIN aqua_net_user a ON c.net_user_id = a.au_id
WHERE r.play_count > 0
AND NOT (c.ranking_banned or COALESCE(a.opt_out_of_leaderboard, 0) or c.status = 12)
${if (name == "ongeki") "AND u.new_player_rating > 0" else "" /* Hide users on Ongeki 1.45 and below */}
ORDER BY rating DESC;
"""
).setHint("jakarta.persistence.query.timeout", 60_000).exec.mapIndexed { i, it ->
private val rankingSelect = """
SELECT
ROW_NUMBER() OVER (ORDER BY $ratingColumn DESC, c.id) AS player_rank,
c.id AS card_id,
u.user_name,
$ratingColumn AS rating,
u.last_play_date,
r.achievement_sum / NULLIF(r.play_count, 0) / 10000.0 AS acc,
r.full_combo_count AS fc,
r.all_perfect_count AS ap,
a.username
FROM ${tableName}_user_ranking_cache r
JOIN $userDataTable u ON r.user_id = u.id
JOIN sega_card c ON u.$userCardColumn = c.id
LEFT JOIN aqua_net_user a ON c.net_user_id = a.au_id
WHERE r.play_count > 0
AND NOT (c.ranking_banned or COALESCE(a.opt_out_of_leaderboard, 0) or c.status = 12)
${if (name == "ongeki") "AND u.new_player_rating > 0" else "" /* Hide users on Ongeki 1.45 and below */}
""".trimIndent()
@API("ranking")
fun ranking(@RP token: String?, @RP page: Int?): List<GenericRankingPlayer> {
if (page != null && page < 0) (400 - "Invalid page number")
val offset = page?.toLong()?.times(pageSize)
if (offset != null && offset > Int.MAX_VALUE) return emptyList()
val query = us.em.createNativeQuery("$rankingSelect ORDER BY player_rank")
.setHint("jakarta.persistence.query.timeout", 60_000)
if (offset != null) {
query.setFirstResult(offset.toInt()).setMaxResults(pageSize)
}
return query.exec.map { row ->
GenericRankingPlayer(
rank = i + 1,
id = it[0]!!.long,
name = it[1].toString(),
rating = it[2]!!.int,
lastSeen = it[3].toString(),
accuracy = it[4]!!.double,
fullCombo = it[5]!!.int,
allPerfect = it[6]!!.int,
username = it[8]?.toString() ?: "user${it[0]}"
rank = row[0]!!.int,
id = row[1]!!.long,
name = row[2].toString(),
rating = row[3]!!.int,
lastSeen = row[4].toString(),
accuracy = row[5]!!.double,
fullCombo = row[6]!!.int,
allPerfect = row[7]!!.int,
username = row[8]?.toString() ?: "user${row[1]}"
)
}
rankingLookupCache = rankingCache.associateBy { it.id }
logger.info("Ranking for $name computed in ${millis() - time}ms")
}
private fun rankingPosition(cardId: Long): String = us.em.createNativeQuery(
"""
SELECT player_rank
FROM ($rankingSelect) ranking
WHERE card_id = :cardId
""".trimIndent()
).setParameter("cardId", cardId)
.setHint("jakarta.persistence.query.timeout", 60_000)
.resultList.singleOrNull()?.let { (it as Number).toInt().str } ?: "-"
@API("playlog")
fun playlog(@RP id: Long): IGenericGamePlaylog = playlogRepo.findById(id)() ?: (404 - "Playlog not found")
@@ -188,7 +178,7 @@ abstract class GameApiController<T : IUserData>(val name: String, userDataClass:
return GenericGameSummary(
name = user.userName,
aquaUser = card.aquaUser?.publicFields,
serverRank = rankingLookupCache[user.card!!.id]?.rank?.str ?: "-",
serverRank = rankingPosition(user.card!!.id),
accuracy = plays.acc(),
rating = getRating(user, false),
ratingHighest = getRating(user, true),