add caches

This commit is contained in:
InsanusMokrassar 2023-01-18 23:22:49 +06:00
parent 8206131425
commit b603fa8822
11 changed files with 220 additions and 13 deletions

View File

@ -11,7 +11,9 @@ kotlin {
dependencies { dependencies {
api libs.tgbotapi api libs.tgbotapi
api libs.microutils.repos.common api libs.microutils.repos.common
api libs.microutils.repos.cache
api libs.kslog api libs.kslog
api libs.microutils.koin
} }
} }
jvmMain { jvmMain {

View File

@ -0,0 +1,16 @@
package dev.inmo.plaguposter.common
import org.koin.core.Koin
import org.koin.core.module.Module
import org.koin.core.qualifier.named
import org.koin.core.scope.Scope
val Scope.useCache: Boolean
get() = getOrNull(named("useCache")) ?: false
val Koin.useCache: Boolean
get() = getOrNull(named("useCache")) ?: false
fun Module.useCache(useCache: Boolean) {
single(named("useCache")) { useCache }
}

View File

@ -10,6 +10,8 @@ import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.serialization.json.JsonObject import kotlinx.serialization.json.JsonObject
import kotlinx.serialization.json.JsonPrimitive
import kotlinx.serialization.json.booleanOrNull
import org.jetbrains.exposed.sql.Database import org.jetbrains.exposed.sql.Database
import org.koin.core.Koin import org.koin.core.Koin
import org.koin.core.module.Module import org.koin.core.module.Module
@ -18,6 +20,8 @@ object CommonPlugin : Plugin {
private val Log = logger private val Log = logger
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
single { CoroutineScope(Dispatchers.Default + SupervisorJob()) } single { CoroutineScope(Dispatchers.Default + SupervisorJob()) }
val useCache = (params["useCache"] as? JsonPrimitive) ?.booleanOrNull ?: true
useCache(useCache)
} }
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {

View File

@ -4,6 +4,9 @@ import com.benasher44.uuid.uuid4
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.koin.getAllDistinct import dev.inmo.micro_utils.koin.getAllDistinct
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.cached
import dev.inmo.micro_utils.repos.cache.full.cached
import dev.inmo.micro_utils.repos.deleteById import dev.inmo.micro_utils.repos.deleteById
import dev.inmo.micro_utils.repos.id import dev.inmo.micro_utils.repos.id
import dev.inmo.micro_utils.repos.set import dev.inmo.micro_utils.repos.set
@ -12,6 +15,7 @@ import dev.inmo.micro_utils.repos.value
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.common.ChatConfig import dev.inmo.plaguposter.common.ChatConfig
import dev.inmo.plaguposter.common.UnsuccessfulSymbol import dev.inmo.plaguposter.common.UnsuccessfulSymbol
import dev.inmo.plaguposter.common.useCache
import dev.inmo.plaguposter.posts.models.PostId import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.panel.repos.PostsMessages import dev.inmo.plaguposter.posts.panel.repos.PostsMessages
import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.repo.PostsRepo
@ -93,7 +97,12 @@ object Plugin : Plugin {
val chatsConfig = koin.get<ChatConfig>() val chatsConfig = koin.get<ChatConfig>()
val config = koin.getOrNull<Config>() ?: Config() val config = koin.getOrNull<Config>() ?: Config()
val api = koin.get<PanelButtonsAPI>() val api = koin.get<PanelButtonsAPI>()
val postsMessages = PostsMessages(koin.get(), koin.get()) val basePostsMessages = PostsMessages(koin.get(), koin.get())
val postsMessages = if (koin.useCache) {
basePostsMessages.cached(FullKVCache(), koin.get())
} else {
basePostsMessages
}
postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) { postsRepo.newObjectsFlow.subscribeSafelyWithoutExceptions(this) {
val firstContent = it.content.first() val firstContent = it.content.first()

View File

@ -4,6 +4,7 @@ import dev.inmo.kslog.common.logger
import dev.inmo.kslog.common.w import dev.inmo.kslog.common.w
import dev.inmo.micro_utils.coroutines.runCatchingSafely import dev.inmo.micro_utils.coroutines.runCatchingSafely
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.koin.singleWithBinds
import dev.inmo.micro_utils.repos.deleteById import dev.inmo.micro_utils.repos.deleteById
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.common.SuccessfulSymbol import dev.inmo.plaguposter.common.SuccessfulSymbol
@ -13,6 +14,8 @@ import dev.inmo.plaguposter.common.ChatConfig
import dev.inmo.plagubot.plugins.inline.queries.models.Format import dev.inmo.plagubot.plugins.inline.queries.models.Format
import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate import dev.inmo.plagubot.plugins.inline.queries.models.OfferTemplate
import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo import dev.inmo.plagubot.plugins.inline.queries.repos.InlineTemplatesRepo
import dev.inmo.plaguposter.common.useCache
import dev.inmo.plaguposter.posts.cached.CachedPostsRepo
import dev.inmo.plaguposter.posts.repo.* import dev.inmo.plaguposter.posts.repo.*
import dev.inmo.plaguposter.posts.sending.PostPublisher import dev.inmo.plaguposter.posts.sending.PostPublisher
import dev.inmo.tgbotapi.extensions.api.delete import dev.inmo.tgbotapi.extensions.api.delete
@ -44,11 +47,16 @@ object Plugin : Plugin {
} }
single { get<Json>().decodeFromJsonElement(Config.serializer(), configJson) } single { get<Json>().decodeFromJsonElement(Config.serializer(), configJson) }
single { get<Config>().chats } single { get<Config>().chats }
single { ExposedPostsRepo(database) } binds arrayOf( single { ExposedPostsRepo(database) }
PostsRepo::class, singleWithBinds<PostsRepo> {
ReadPostsRepo::class, val base = get<ExposedPostsRepo>()
WritePostsRepo::class,
) if (useCache) {
CachedPostsRepo(base, get())
} else {
base
}
}
single { single {
val config = get<Config>() val config = get<Config>()
PostPublisher(get(), get(), config.chats.cacheChatId, config.chats.targetChatId, config.deleteAfterPublishing) PostPublisher(get(), get(), config.chats.cacheChatId, config.chats.targetChatId, config.deleteAfterPublishing)

View File

@ -0,0 +1,49 @@
package dev.inmo.plaguposter.posts.cached
import com.soywiz.klock.DateTime
import dev.inmo.micro_utils.pagination.FirstPagePagination
import dev.inmo.micro_utils.pagination.firstPageWithOneElementPagination
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.CRUDRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.full.FullCRUDCacheRepo
import dev.inmo.plaguposter.posts.models.NewPost
import dev.inmo.plaguposter.posts.models.PostContentInfo
import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.posts.models.RegisteredPost
import dev.inmo.plaguposter.posts.repo.PostsRepo
import dev.inmo.tgbotapi.types.IdChatIdentifier
import dev.inmo.tgbotapi.types.MessageIdentifier
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.flow.Flow
class CachedPostsRepo(
private val parentRepo: PostsRepo,
private val scope: CoroutineScope,
private val kvCache: FullKVCache<PostId, RegisteredPost> = FullKVCache()
) : PostsRepo, CRUDRepo<RegisteredPost, PostId, NewPost> by FullCRUDCacheRepo(
parentRepo,
kvCache,
scope,
{ it.id }
) {
override val removedPostsFlow: Flow<RegisteredPost> by parentRepo::removedPostsFlow
override suspend fun getIdByChatAndMessage(chatId: IdChatIdentifier, messageId: MessageIdentifier): PostId? {
doForAllWithNextPaging(firstPageWithOneElementPagination) {
kvCache.values(it).also {
it.results.forEach {
return it.takeIf {
it.content.any { it.chatId == chatId && it.messageId == messageId }
} ?.id ?: return@forEach
}
}
}
return null
}
override suspend fun getPostCreationTime(postId: PostId): DateTime? = kvCache.get(postId) ?.created
override suspend fun getFirstMessageInfo(postId: PostId): PostContentInfo? = kvCache.get(postId) ?.content ?.firstOrNull()
}

View File

@ -0,0 +1,15 @@
package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.cache.KeyValueCacheRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.full.cached
import dev.inmo.plaguposter.common.ShortMessageInfo
import dev.inmo.tgbotapi.types.PollIdentifier
import kotlinx.coroutines.CoroutineScope
class CachedPollsToMessagesInfoRepo(
private val repo: PollsToMessagesInfoRepo,
private val scope: CoroutineScope,
private val kvCache: FullKVCache<PollIdentifier, ShortMessageInfo> = FullKVCache()
) : PollsToMessagesInfoRepo, KeyValueRepo<PollIdentifier, ShortMessageInfo> by repo.cached(kvCache, scope)

View File

@ -0,0 +1,15 @@
package dev.inmo.plaguposter.ratings.source.repos
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.full.cached
import dev.inmo.plaguposter.common.ShortMessageInfo
import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.tgbotapi.types.PollIdentifier
import kotlinx.coroutines.CoroutineScope
class CachedPollsToPostsIdsRepo(
private val repo: PollsToPostsIdsRepo,
private val scope: CoroutineScope,
private val kvCache: FullKVCache<PollIdentifier, PostId> = FullKVCache()
) : PollsToPostsIdsRepo, KeyValueRepo<PollIdentifier, PostId> by repo.cached(kvCache, scope)

View File

@ -67,8 +67,29 @@ object Plugin : Plugin {
get<Json>().decodeFromJsonElement(Config.serializer(), params["ratingsPolls"] ?: error("Unable to load config for rating polls in $params")) get<Json>().decodeFromJsonElement(Config.serializer(), params["ratingsPolls"] ?: error("Unable to load config for rating polls in $params"))
} }
single<RatingsVariants>(ratingVariantsQualifier) { get<Config>().variants } single<RatingsVariants>(ratingVariantsQualifier) { get<Config>().variants }
single<PollsToPostsIdsRepo> { ExposedPollsToPostsIdsRepo(database) }
single<PollsToMessagesInfoRepo> { ExposedPollsToMessagesInfoRepo(database) } single { ExposedPollsToPostsIdsRepo(database) }
single<PollsToPostsIdsRepo> {
val base = get<ExposedPollsToPostsIdsRepo>()
if (useCache) {
CachedPollsToPostsIdsRepo(base, get())
} else {
base
}
}
single { ExposedPollsToMessagesInfoRepo(database) }
single<PollsToMessagesInfoRepo> {
val base = get<ExposedPollsToMessagesInfoRepo>()
if (useCache) {
CachedPollsToMessagesInfoRepo(base, get())
} else {
base
}
}
single<VariantTransformer> { single<VariantTransformer> {
val ratingsSettings = get<RatingsVariants>(ratingVariantsQualifier) val ratingsSettings = get<RatingsVariants>(ratingVariantsQualifier)
VariantTransformer { VariantTransformer {

View File

@ -0,0 +1,61 @@
package dev.inmo.plaguposter.ratings.repo
import dev.inmo.micro_utils.pagination.utils.doForAllWithNextPaging
import dev.inmo.micro_utils.repos.KeyValueRepo
import dev.inmo.micro_utils.repos.cache.cache.FullKVCache
import dev.inmo.micro_utils.repos.cache.full.FullKeyValueCacheRepo
import dev.inmo.plaguposter.posts.models.PostId
import dev.inmo.plaguposter.ratings.models.Rating
import kotlinx.coroutines.CoroutineScope
class CachedRatingsRepo(
private val base: RatingsRepo,
private val scope: CoroutineScope,
private val kvCache: FullKVCache<PostId, Rating> = FullKVCache()
) : RatingsRepo, KeyValueRepo<PostId, Rating> by FullKeyValueCacheRepo(base, kvCache, scope) {
override suspend fun getPosts(
range: ClosedRange<Rating>,
reversed: Boolean,
count: Int?,
exclude: List<PostId>
): Map<PostId, Rating> {
val result = mutableMapOf<PostId, Rating>()
doForAllWithNextPaging {
kvCache.keys(it).also {
it.results.forEach {
val rating = kvCache.get(it) ?: return@forEach
if (it !in exclude && rating in range) {
result[it] = rating
}
}
}
}
return result.toMap()
}
override suspend fun getPostsWithRatingGreaterEq(
then: Rating,
reversed: Boolean,
count: Int?,
exclude: List<PostId>
): Map<PostId, Rating> = getPosts(
then .. Rating(Double.MAX_VALUE),
reversed,
count,
exclude
)
override suspend fun getPostsWithRatingLessEq(
then: Rating,
reversed: Boolean,
count: Int?,
exclude: List<PostId>
): Map<PostId, Rating> = getPosts(
Rating(Double.MIN_VALUE) .. then,
reversed,
count,
exclude
)
}

View File

@ -1,8 +1,10 @@
package dev.inmo.plaguposter.ratings package dev.inmo.plaguposter.ratings
import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions import dev.inmo.micro_utils.coroutines.subscribeSafelyWithoutExceptions
import dev.inmo.micro_utils.koin.singleWithBinds
import dev.inmo.micro_utils.repos.unset import dev.inmo.micro_utils.repos.unset
import dev.inmo.plagubot.Plugin import dev.inmo.plagubot.Plugin
import dev.inmo.plaguposter.common.useCache
import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo import dev.inmo.plaguposter.posts.exposed.ExposedPostsRepo
import dev.inmo.plaguposter.posts.repo.PostsRepo import dev.inmo.plaguposter.posts.repo.PostsRepo
import dev.inmo.plaguposter.ratings.exposed.ExposedRatingsRepo import dev.inmo.plaguposter.ratings.exposed.ExposedRatingsRepo
@ -16,11 +18,16 @@ import org.koin.dsl.binds
object Plugin : Plugin { object Plugin : Plugin {
override fun Module.setupDI(database: Database, params: JsonObject) { override fun Module.setupDI(database: Database, params: JsonObject) {
single { ExposedRatingsRepo(database) } binds arrayOf( single { ExposedRatingsRepo(database) }
RatingsRepo::class, singleWithBinds<RatingsRepo> {
ReadRatingsRepo::class, val base = get<ExposedRatingsRepo>()
WriteRatingsRepo::class,
) if (useCache) {
CachedRatingsRepo(base, get())
} else {
base
}
}
} }
override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) { override suspend fun BehaviourContext.setupBotPlugin(koin: Koin) {