diff --git a/api/src/main/kotlin/com/backgu/amaker/api/chat/config/ChatServiceConfig.kt b/api/src/main/kotlin/com/backgu/amaker/api/chat/config/ChatServiceConfig.kt index d9f993ac..34d6d3b1 100644 --- a/api/src/main/kotlin/com/backgu/amaker/api/chat/config/ChatServiceConfig.kt +++ b/api/src/main/kotlin/com/backgu/amaker/api/chat/config/ChatServiceConfig.kt @@ -14,6 +14,7 @@ import com.backgu.amaker.infra.jpa.chat.repository.ChatRepository import com.backgu.amaker.infra.jpa.chat.repository.ChatRoomRepository import com.backgu.amaker.infra.jpa.chat.repository.ChatRoomUserRepository import com.backgu.amaker.infra.redis.chat.repository.ChatCacheRepository +import com.backgu.amaker.infra.redis.chat.repository.ChatPipelinedQueryRepository import com.backgu.amaker.infra.redis.chat.repository.ChatRoomUserCacheRepository import org.springframework.context.annotation.Bean import org.springframework.context.annotation.Configuration @@ -43,12 +44,14 @@ class ChatServiceConfig { fun chatUserCacheService( chatCacheService: ChatCacheService, userCacheService: UserCacheService, + chatPipelinedQueryRepository: ChatPipelinedQueryRepository, chatRoomUserCacheService: ChatRoomUserCacheService, userService: UserService, chatService: ChatService, eventAssignedUserService: EventAssignedUserService, ) = ChatUserCacheFacadeService( chatCacheService, + chatPipelinedQueryRepository, userCacheService, chatRoomUserCacheService, userService, diff --git a/api/src/main/kotlin/com/backgu/amaker/api/chat/service/ChatFacadeService.kt b/api/src/main/kotlin/com/backgu/amaker/api/chat/service/ChatFacadeService.kt index 5aa9631d..b1be3e3c 100644 --- a/api/src/main/kotlin/com/backgu/amaker/api/chat/service/ChatFacadeService.kt +++ b/api/src/main/kotlin/com/backgu/amaker/api/chat/service/ChatFacadeService.kt @@ -113,9 +113,6 @@ class ChatFacadeService( ?.let { return ChatListDto.of(chatQuery, it.map { chatWithUser -> ChatWithUserDto.of(chatWithUser) }) } val chatList = chatQueryService.findAfterChatList(chatQuery.chatRoomId, chatQuery.cursor, chatQuery.size) - for (defaultChatWithUser in chatList) { - println(defaultChatWithUser.id) - } val eventMap = eventService.findEventByIdsToMap(chatList.filter { ChatType.isEventChat(it.chatType) }.map { it.id }) diff --git a/infra/src/main/kotlin/com/backgu/amaker/application/chat/service/ChatUserCacheFacadeService.kt b/infra/src/main/kotlin/com/backgu/amaker/application/chat/service/ChatUserCacheFacadeService.kt index 63f7b942..fbcc60b2 100644 --- a/infra/src/main/kotlin/com/backgu/amaker/application/chat/service/ChatUserCacheFacadeService.kt +++ b/infra/src/main/kotlin/com/backgu/amaker/application/chat/service/ChatUserCacheFacadeService.kt @@ -11,12 +11,14 @@ import com.backgu.amaker.domain.user.User import com.backgu.amaker.infra.redis.chat.data.ChatWithUserCache import com.backgu.amaker.infra.redis.chat.data.DefaultChatWithUserCache import com.backgu.amaker.infra.redis.chat.data.EventChatWithUserCache +import com.backgu.amaker.infra.redis.chat.repository.ChatPipelinedQueryRepository import org.springframework.stereotype.Service import org.springframework.transaction.event.TransactionalEventListener @Service class ChatUserCacheFacadeService( private val chatCacheService: ChatCacheService, + private val chatPipelinedQueryRepository: ChatPipelinedQueryRepository, private val userCacheService: UserCacheService, private val chatRoomUserCacheService: ChatRoomUserCacheService, private val userService: UserService, @@ -103,7 +105,21 @@ class ChatUserCacheFacadeService( .map { chat -> mapChatToDto(chatRoomId, chat, cachedUsersMap) } } - fun mapChatToDto( + fun findAfterChatsWithPipelinedQuery( + chatRoomId: Long, + cursor: Long, + count: Int, + ): List>? = + chatPipelinedQueryRepository.findAfterChats(chatRoomId, cursor, count)?.let { chats -> + userCacheService + .findAllByUserIds(chatRoomUserCacheService.findUserIds(chatRoomId).toList()) + .associateBy { it.id } + .let { cachedUsersMap -> + chats.map { chat -> mapChatToDto(chatRoomId, chat, cachedUsersMap) } + } + } + + private fun mapChatToDto( chatRoomId: Long, chat: ChatWithUserCache<*>, cachedUsersMap: Map, diff --git a/infra/src/main/kotlin/com/backgu/amaker/infra/redis/chat/repository/ChatPipelinedQueryRepository.kt b/infra/src/main/kotlin/com/backgu/amaker/infra/redis/chat/repository/ChatPipelinedQueryRepository.kt new file mode 100644 index 00000000..01008ce9 --- /dev/null +++ b/infra/src/main/kotlin/com/backgu/amaker/infra/redis/chat/repository/ChatPipelinedQueryRepository.kt @@ -0,0 +1,64 @@ +package com.backgu.amaker.infra.redis.chat.repository + +import com.backgu.amaker.infra.redis.chat.data.ChatWithUserCache +import com.fasterxml.jackson.databind.ObjectMapper +import org.springframework.data.redis.core.RedisTemplate +import org.springframework.data.redis.core.script.RedisScript +import org.springframework.stereotype.Repository + +@Repository +class ChatPipelinedQueryRepository( + private val redisChatTemplate: RedisTemplate, + private val timeObjectMapper: ObjectMapper, +) { + companion object { + const val PREFIX = "chatRoom:" + + fun key(chatRoomId: Long) = "$PREFIX$chatRoomId" + + const val AFTER_CHAT_SCRIPT = + """ + local key = KEYS[1] + local cursor = tonumber(ARGV[1]) + local count = tonumber(ARGV[2]) + + local firstResult = redis.call('ZRANGE', key, 0, 0, 'WITHSCORES') + + if #firstResult > 0 then + local firstChat = firstResult[1] + local firstScore = tonumber(firstResult[2]) -- 첫 번째 요소의 score 값을 가져옴 + + if firstScore > cursor then + local result = redis.call('ZRANGEBYSCORE', key, cursor + 1, math.huge, 'LIMIT', 0, count) + if #result > 0 then + return table.concat(result, ",") + else + return "" + end + else + return firstChat + end + else + return nil + end + """ + } + + fun findAfterChats( + chatRoomId: Long, + cursor: Long, + count: Int, + ): List>? { + val result: List = + redisChatTemplate.execute( + RedisScript.of(AFTER_CHAT_SCRIPT.trimIndent(), List::class.java as Class>), + listOf(key(chatRoomId)), + cursor.toString(), + count.toString(), + ) + + return result.map { chat -> + timeObjectMapper.readValue(chat, ChatWithUserCache::class.java) + } + } +}