๊ฑฐ์ ๋ชจ๋ ์ต์ ์ํํธ์จ์ด ์ ํ์ ์ฌ๋ฌ ์๋น์ค๋ก ๊ตฌ์ฑ๋ฉ๋๋ค. ์ข ์ข ์๋น์ค ๊ฐ ์ฑ๋์ ๊ธด ์๋ต ์๊ฐ์ด ์ฑ๋ฅ ๋ฌธ์ ์ ์์ธ์ด ๋ฉ๋๋ค. ์ด๋ฌํ ์ข ๋ฅ์ ๋ฌธ์ ์ ๋ํ ํ์ค ์๋ฃจ์ ์ ์ฌ๋ฌ ์๋น์ค ๊ฐ ์์ฒญ์ ์ผ๊ด ์ฒ๋ฆฌ๋ผ๊ณ ํ๋ ํ๋์ ํจํค์ง๋ก ๋ฌถ๋ ๊ฒ์ ๋๋ค.
์ผ๊ด ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ ์ฑ๋ฅ์ด๋ ์ฝ๋ ๋ช
ํ์ฑ ์ธก๋ฉด์์ ๊ฒฐ๊ณผ๊ฐ ๋ง์กฑ์ค๋ฝ์ง ์์ ์ ์์ต๋๋ค. ์ด ๋ฐฉ๋ฒ์ ๋ฐ์ ์์๊ฒ ์๊ฐ๋งํผ ์ฝ์ง ์์ต๋๋ค. ๋ค์ํ ๋ชฉ์ ๊ณผ ์ํฉ์ ๋ฐ๋ผ ์๋ฃจ์
์ด ํฌ๊ฒ ๋ฌ๋ผ์ง ์ ์์ต๋๋ค. ๊ตฌ์ฒด์ ์ธ ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฌ๋ฌ ์ ๊ทผ ๋ฐฉ์์ ์ฅ๋จ์ ์ ๋ณด์ฌ ๋๋ฆฌ๊ฒ ์ต๋๋ค.
์ค์ฆ ํ๋ก์ ํธ
๋ช ํ์ฑ์ ์ํด ํ์ฌ ์์ ์ค์ธ ์ ํ๋ฆฌ์ผ์ด์ ์ ์๋น์ค ์ค ํ๋์ ์๋ฅผ ์ดํด๋ณด๊ฒ ์ต๋๋ค.
์์์ ๋ํ ํ๋ซํผ ์ ํ ์ค๋ช
์ฑ๋ฅ ์ ํ ๋ฌธ์ ๋ ๋งค์ฐ ์ผ๋ฐ์ ์ด๋ฉฐ ํน์ ์ธ์ด๋ ํ๋ซํผ๊ณผ ๊ด๋ จ์ด ์์ต๋๋ค. ์ด ๊ธฐ์ฌ์์๋ Spring + Kotlin ์ฝ๋ ์์ ๋ฅผ ์ฌ์ฉํ์ฌ ๋ฌธ์ ์ ํด๊ฒฐ ๋ฐฉ๋ฒ์ ๋ณด์ฌ์ค๋๋ค. Kotlin์ Java ๋ฐ C# ๊ฐ๋ฐ์๊ฐ ๋๊ฐ์ด ์ดํดํ๊ธฐ ์ฝ์ต๋๋ค(๋๋ ์ดํดํ๊ธฐ ์ด๋ ต์ต๋๋ค). ๋ํ ์ฝ๋๊ฐ Java๋ณด๋ค ๋ ๊ฐ๊ฒฐํ๊ณ ์ดํดํ๊ธฐ ์ฝ์ต๋๋ค. ์์ Java ๊ฐ๋ฐ์๊ฐ ์ดํดํ๊ธฐ ์ฝ๋๋ก Kotlin์ ํ๋ง์ ์ ํผํ๊ณ (Lombok์ ์ ์ ์ผ๋ก) ๋ฐฑ๋ง๋ฒ๋ง ์ฌ์ฉํ๊ฒ ์ต๋๋ค. ๋ช ๊ฐ์ง ํ์ฅ ๋ฉ์๋๊ฐ ์์ง๋ง ์ค์ ๋ก๋ ๋ชจ๋ Java ํ๋ก๊ทธ๋๋จธ์๊ฒ ์ ์ ๋ฉ์๋๋ก ์น์ํ๋ฏ๋ก ์ด๋ ์๋ฆฌ์ ๋ง์ ๋ง์น์ง ์๋ ์์ ์คํ์ด ๋ ๊ฒ์
๋๋ค.
์๋ฅ์น์ธ ์๋น์ค๊ฐ ์์ต๋๋ค. ๋๊ตฐ๊ฐ ๋ฌธ์๋ฅผ ์์ฑํ์ฌ ํ ๋ก ์ ์ํด ์ ์ถํ๋ ๋์ ํธ์ง์ด ์ด๋ฃจ์ด์ง๊ณ ์ต์ข
์ ์ผ๋ก ๋ฌธ์์ ๋ํ ํฉ์๊ฐ ์ด๋ฃจ์ด์ง๋๋ค. ์น์ธ ์๋น์ค ์์ฒด๋ ๋ฌธ์์ ๋ํด ์๋ฌด๊ฒ๋ ๋ชจ๋ฆ
๋๋ค. ์ฌ๊ธฐ์๋ ๊ณ ๋ คํ์ง ์์ ์์ ์ถ๊ฐ ๊ธฐ๋ฅ์ ๊ฐ์ง ์น์ธ์ ๊ฐ์ ์ฑํ
์ผ ๋ฟ์
๋๋ค.
๋ฐ๋ผ์ ๋ฏธ๋ฆฌ ์ ์๋ ์ฐธ๊ฐ์ ์งํฉ์ด ์๋ ์ฑํ ๋ฐฉ(๋ฌธ์์ ํด๋น)์ด ์์ต๋๋ค. ์ผ๋ฐ ์ฑํ ๊ณผ ๋ง์ฐฌ๊ฐ์ง๋ก ๋ฉ์์ง์๋ ํ ์คํธ์ ํ์ผ์ด ํฌํจ๋์ด ์์ผ๋ฉฐ ๋ต์ฅ์ด๋ ์ ๋ฌ์ด ๊ฐ๋ฅํฉ๋๋ค.
data class ChatMessage(
// nullable ัะฐะบ ะบะฐะบ ะฟะพัะฒะปัะตััั ัะพะปัะบะพ ะฟะพัะปะต persist
val id: Long? = null,
/** ะกััะปะบะฐ ะฝะฐ ะฐะฒัะพัะฐ */
val author: UserReference,
/** ะกะพะพะฑัะตะฝะธะต */
val message: String,
/** ะกััะปะบะธ ะฝะฐ ะฐััะฐัะธ */
// ะธะท-ะทะฐ ะพัะพะฑะตะฝะฝะพััะตะน ัะฒัะทะบะธ JPA+ะกะฃะะ ะฟัะพัะต ะฟะพะดะดะตัะถะธะฒะฐัั ะธ null, ะธ ะฟััััะต ัะฟะธัะบะธ
val files: List<FileReference>? = null,
/** ะัะปะธ ัะฒะปัะตััั ะพัะฒะตัะพะผ, ัะพ ะทะดะตัั ะฑัะดะตั ะพัะธะณะธะฝะฐะป */
val replyTo: ChatMessage? = null,
/** ะัะปะธ ัะฒะปัะตััั ะฟะตัะตััะปะบะพะน, ัะพ ะทะดะตัั ะฑัะดะตั ะพัะธะณะธะฝะฐะป */
val forwardFrom: ChatMessage? = null
)
ํ์ผ ๋ฐ ์ฌ์ฉ์ ๋งํฌ๋ ๋ค๋ฅธ ๋๋ฉ์ธ์ ๋ํ ๋งํฌ์ ๋๋ค. ์ฌ๊ธฐ์ ์ฐ๋ฆฌ๋ ์ด๋ ๊ฒ ์ด๊ณ ์์ต๋๋ค.
typealias FileReference = Long
typealias UserReference = Long
์ฌ์ฉ์ ๋ฐ์ดํฐ๋ Keycloak์ ์ ์ฅ๋๊ณ REST๋ฅผ ํตํด ์์ ๋ฉ๋๋ค. ํ์ผ์๋ ๋ง์ฐฌ๊ฐ์ง์
๋๋ค. ํ์ผ๊ณผ ๊ทธ์ ๋ํ ๋ฉํ์ ๋ณด๋ ๋ณ๋์ ํ์ผ ์ ์ฅ ์๋น์ค์ ์์ต๋๋ค.
์ด ์๋น์ค์ ๋ํ ๋ชจ๋ ํธ์ถ์ ๋ฌด๊ฑฐ์ด ์์ฒญ. ์ด๋ ์ด๋ฌํ ์์ฒญ์ ์ ์กํ๋ ๋ฐ ๋๋ ์ค๋ฒํค๋๊ฐ ํ์ฌ ์๋น์ค์์ ์์ฒญ์ ์ฒ๋ฆฌํ๋ ๋ฐ ๊ฑธ๋ฆฌ๋ ์๊ฐ๋ณด๋ค ํจ์ฌ ํฌ๋ค๋ ๊ฒ์ ์๋ฏธํฉ๋๋ค. ํ ์คํธ ๋ฒค์น์์ ์ด๋ฌํ ์๋น์ค์ ์ผ๋ฐ์ ์ธ ํธ์ถ ์๊ฐ์ 100ms์ด๋ฏ๋ก ์์ผ๋ก ์ด ์ซ์๋ฅผ ์ฌ์ฉํ ๊ฒ์ ๋๋ค.
ํ์ํ ๋ชจ๋ ์ ๋ณด๊ฐ ํฌํจ๋ ๋ง์ง๋ง N๊ฐ ๋ฉ์์ง๋ฅผ ์์ ํ๋ ค๋ฉด ๊ฐ๋จํ REST ์ปจํธ๋กค๋ฌ๋ฅผ ๋ง๋ค์ด์ผ ํฉ๋๋ค. ์ฆ, ์ฐ๋ฆฌ๋ ํ๋ก ํธ์๋์ ๋ฉ์์ง ๋ชจ๋ธ์ด ๊ฑฐ์ ๋์ผํ๋ฉฐ ๋ชจ๋ ๋ฐ์ดํฐ๋ฅผ ์ ์กํด์ผ ํ๋ค๊ณ ๋ฏฟ์ต๋๋ค. ํ๋ฐํธ์๋ ๋ชจ๋ธ์ ์ฐจ์ด์ ์ ํ์ผ๊ณผ ์ฌ์ฉ์๊ฐ ๋งํฌ๋ฅผ ๋ง๋ค๋ ค๋ฉด ์ฝ๊ฐ ํด๋ ๋ ํ์์ผ๋ก ํ์๋์ด์ผ ํ๋ค๋ ๊ฒ์ ๋๋ค.
/** ะ ัะฐะบะพะผ ะฒะธะดะต ะพัะดะฐัััั ัััะปะบะธ ะฝะฐ ัััะฝะพััะธ ะดะปั ััะพะฝัะฐ */
data class ReferenceUI(
/** ะะดะตะฝัะธัะธะบะฐัะพั ะดะปั url */
val ref: String,
/** ะะธะดะธะผะพะต ะฟะพะปัะทะพะฒะฐัะตะปั ะฝะฐะทะฒะฐะฝะธะต ัััะปะบะธ */
val name: String
)
data class ChatMessageUI(
val id: Long,
/** ะกััะปะบะฐ ะฝะฐ ะฐะฒัะพัะฐ */
val author: ReferenceUI,
/** ะกะพะพะฑัะตะฝะธะต */
val message: String,
/** ะกััะปะบะธ ะฝะฐ ะฐััะฐัะธ */
val files: List<ReferenceUI>,
/** ะัะปะธ ัะฒะปัััั ะพัะฒะตัะพะผ, ัะพ ะทะดะตัั ะฑัะดะตั ะพัะธะณะธะฝะฐะป */
val replyTo: ChatMessageUI? = null,
/** ะัะปะธ ัะฒะปัััั ะฟะตัะตััะปะบะพะน, ัะพ ะทะดะตัั ะฑัะดะตั ะพัะธะณะธะฝะฐะป */
val forwardFrom: ChatMessageUI? = null
)
์ฐ๋ฆฌ๋ ๋ค์์ ๊ตฌํํด์ผ ํฉ๋๋ค:
interface ChatRestApi {
fun getLast(n: Int): List<ChatMessageUI>
}
Postfix UI๋ ํ๋ฐํธ์๋์ฉ DTO ๋ชจ๋ธ, ์ฆ REST๋ฅผ ํตํด ์ ๊ณตํด์ผ ํ๋ ๋ชจ๋ธ์ ์๋ฏธํฉ๋๋ค.
์ฌ๊ธฐ์ ๋๋ผ์ด ์ ์ ์ฑํ ์๋ณ์๋ฅผ ์ ๋ฌํ์ง ์๊ณ ChatMessage/ChatMessageUI ๋ชจ๋ธ์๋ ์๋ฌด ๊ฒ๋ ์ ๋ฌํ์ง ์๋๋ค๋ ๊ฒ์ ๋๋ค. ๋๋ ์์ ์ ์ฝ๋๋ฅผ ๋ณต์กํ๊ฒ ํ์ง ์๊ธฐ ์ํด ์๋์ ์ผ๋ก ์ด๋ ๊ฒ ํ์ต๋๋ค(์ฑํ ์ ๋ถ๋ฆฌ๋์ด ์์ผ๋ฏ๋ก ํ๋๋ง ์๋ค๊ณ ๊ฐ์ ํ ์ ์์ต๋๋ค).
์ฒ ํ์ ์ฌ๋ดChatMessageUI ํด๋์ค์ ChatRestApi.getLast ๋ฉ์๋ ๋ชจ๋ ์ค์ ๋ก๋ ์์๊ฐ ์ง์ ๋ Set์ธ List ๋ฐ์ดํฐ ์ ํ์ ์ฌ์ฉํฉ๋๋ค. JDK์์๋ ์ด๊ฒ์ด ๋ชจ๋ ์ข์ง ์์ผ๋ฏ๋ก ์ธํฐํ์ด์ค ์์ค์์ ์์ ์์๋ฅผ ์ ์ธํ๋ ๊ฒ(์ถ๊ฐ ๋ฐ ๊ฒ์ ์ ์์ ์ ์ง)์ ์๋ํ์ง ์์ต๋๋ค. ๋ฐ๋ผ์ ์์๊ฐ ์ง์ ๋ Set์ด ํ์ํ ๊ฒฝ์ฐ List๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ธ ๊ดํ์ด ๋์์ต๋๋ค(LinkedHashSet๋ ์์ง๋ง ์ด๋ ์ธํฐํ์ด์ค๊ฐ ์๋๋๋ค).
์ค์ํ ์ ํ์ฌํญ: ์ฐ๋ฆฌ๋ ๊ธด ์๋ต์ด๋ ์ ์ก ์ฒด์ธ์ด ์๋ค๊ณ ๊ฐ์ ํฉ๋๋ค. ์ฆ, ์กด์ฌํ์ง๋ง ๊ธธ์ด๊ฐ XNUMX๊ฐ ๋ฉ์์ง๋ฅผ ์ด๊ณผํ์ง ์์ต๋๋ค. ์ ์ฒด ๋ฉ์์ง ์ฒด์ธ์ด ํ๋ฐํธ์๋๋ก ์ ์ก๋์ด์ผ ํฉ๋๋ค.
์ธ๋ถ ์๋น์ค๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๋ ค๋ฉด ๋ค์ API๊ฐ ์์ต๋๋ค.
interface ChatMessageRepository {
fun findLast(n: Int): List<ChatMessage>
}
data class FileHeadRemote(
val id: FileReference,
val name: String
)
interface FileRemoteApi {
fun getHeadById(id: FileReference): FileHeadRemote
fun getHeadsByIds(id: Set<FileReference>): Set<FileHeadRemote>
fun getHeadsByIds(id: List<FileReference>): List<FileHeadRemote>
fun getHeadsByChat(): List<FileHeadRemote>
}
data class UserRemote(
val id: UserReference,
val name: String
)
interface UserRemoteApi {
fun getUserById(id: UserReference): UserRemote
fun getUsersByIds(id: Set<UserReference>): Set<UserRemote>
fun getUsersByIds(id: List<UserReference>): List<UserRemote>
}
์ธ๋ถ ์๋น์ค๋ ์ฒ์์ ์ผ๊ด ์ฒ๋ฆฌ๋ฅผ ์ ๊ณตํ๊ณ ๋ ๋ณํ ๋ชจ๋์์ Set(์์ ์์๋ฅผ ์ ์งํ์ง ์๊ณ ๊ณ ์ ํค ์ฌ์ฉ)๊ณผ List(์ค๋ณต์ด ์์ ์ ์์ - ์์๊ฐ ์ ์ง๋จ)๋ฅผ ํตํด ์ ๊ณต๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค.
๊ฐ๋จํ ๊ตฌํ
์์งํ ๊ตฌํ
REST ์ปจํธ๋กค๋ฌ์ ์ฒซ ๋ฒ์งธ ์์งํ ๊ตฌํ์ ๋๋ถ๋ถ์ ๊ฒฝ์ฐ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
class ChatRestController(
private val messageRepository: ChatMessageRepository,
private val userRepository: UserRemoteApi,
private val fileRepository: FileRemoteApi
) : ChatRestApi {
override fun getLast(n: Int) =
messageRepository.findLast(n)
.map { it.toFrontModel() }
private fun ChatMessage.toFrontModel(): ChatMessageUI =
ChatMessageUI(
id = id ?: throw IllegalStateException("$this must be persisted"),
author = userRepository.getUserById(author).toFrontReference(),
message = message,
files = files?.let { files ->
fileRepository.getHeadsByIds(files)
.map { it.toFrontReference() }
} ?: listOf(),
forwardFrom = forwardFrom?.toFrontModel(),
replyTo = replyTo?.toFrontModel()
)
}
๋ชจ๋ ๊ฒ์ด ๋งค์ฐ ๋ช
ํํ๋ฉฐ ์ด๋ ํฐ ์ฅ์ ์
๋๋ค.
์ฐ๋ฆฌ๋ ์ผ๊ด ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํ๊ณ ์ผ๊ด์ ์ผ๋ก ์ธ๋ถ ์๋น์ค๋ก๋ถํฐ ๋ฐ์ดํฐ๋ฅผ ๋ฐ์ต๋๋ค. ๊ทธ๋ฐ๋ฐ ์ฐ๋ฆฌ์ ์์ฐ์ฑ์๋ ๋ฌด์จ ์ผ์ด ์ผ์ด๋๊ณ ์๋ ๊ฑธ๊น์?
๊ฐ ๋ฉ์์ง์ ๋ํด UserRemoteApi๋ฅผ ํ ๋ฒ ํธ์ถํ์ฌ ์์ฑ์ ํ๋์ ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ FileRemoteApi๋ฅผ ํ ๋ฒ ํธ์ถํ์ฌ ๋ชจ๋ ์ฒจ๋ถ ํ์ผ์ ๊ฐ์ ธ์ต๋๋ค. ๊ทธ๋ฐ ๊ฒ ๊ฐ์ต๋๋ค. ๋ถํ์ํ ํธ์ถ์ด ํ์ํ์ง ์์ ๋ฐฉ์์ผ๋ก ChatMessage์ ์ ๋ฌ ๋ฐ ์๋ต ํ๋๊ฐ ํ๋๋์๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ๊ทธ๋ฌ๋ ์ด๋ฅผ ChatMessageUI๋ก ๋ฐ๊พธ๋ฉด ์ฌ๊ท๊ฐ ๋ฐ์ํฉ๋๋ค. ์ฆ, ํตํ ์นด์ดํฐ๊ฐ ํฌ๊ฒ ๋์ด๋ ์ ์์ต๋๋ค. ์์ ์ธ๊ธํ๋ฏ์ด ์ค์ฒฉ์ด ๋ง์ง ์๊ณ ์ฒด์ธ์ด ๋ฉ์์ง XNUMX๊ฐ๋ก ์ ํ๋์ด ์๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค.
๊ฒฐ๊ณผ์ ์ผ๋ก ๋ฉ์์ง๋น ์ธ๋ถ ์๋น์ค์ ๋ํ 2~1๊ฐ์ ํธ์ถ๊ณผ ์ ์ฒด ๋ฉ์์ง ํจํค์ง์ ๋ํ 6๊ฐ์ JPA ํธ์ถ์ ๋ฐ๊ฒ ๋ฉ๋๋ค. ์ด ํธ์ถ ์๋ 1*N+20์์ 4*N+10๊น์ง ๋ค์ํฉ๋๋ค. ์ค์ ๋จ์๋ก ์ผ๋ง์ธ๊ฐ์? ํ ํ์ด์ง๋ฅผ ๋ ๋๋งํ๋ ๋ฐ 500๊ฐ์ ๋ฉ์์ง๊ฐ ํ์ํ๋ค๊ณ ๊ฐ์ ํด ๋ณด๊ฒ ์ต๋๋ค. ๊ทธ๊ฒ์ ์ป์ผ๋ ค๋ฉด XNUMX ์ด์์ XNUMX ์ด๊ฐ ํ์ํฉ๋๋ค. ๋์ฐํ! XNUMXms ์ด๋ด๋ก ์ ์งํ๊ณ ์ถ์ต๋๋ค. ๊ทธ๋ฆฌ๊ณ ํ๋ฐํธ์๋์์ ์ํํ ์คํฌ๋กค์ ์ํ๊ธฐ ๋๋ฌธ์ ์ด ์๋ํฌ์ธํธ์ ๋ํ ์ฑ๋ฅ ์๊ตฌ ์ฌํญ์ด ๋ ๋ฐฐ๋ก ๋์ด๋ ์ ์์ต๋๋ค.
์ฅ์ :
- ์ฝ๋๋ ๊ฐ๊ฒฐํ๊ณ ์์ฒด ๋ฌธ์ํ๋ฉ๋๋ค(์ง์ ํ์ ๊ฟ).
- ์ฝ๋๊ฐ ๊ฐ๋จํด์ ๋ฐ์ ์ด์ ์ ๊ธฐํ๊ฐ ๊ฑฐ์ ์์ต๋๋ค.
- ์ผ๊ด ์ฒ๋ฆฌ๋ ์ด์ง์ ์ธ ๊ฒ์ฒ๋ผ ๋ณด์ด์ง ์์ผ๋ฉฐ ๋ ผ๋ฆฌ์ ์ ๊ธฐ์ ์ผ๋ก ํตํฉ๋์ด ์์ต๋๋ค.
- ๋ ผ๋ฆฌ ๋ณ๊ฒฝ์ ์ฝ๊ฒ ์ํํ ์ ์์ผ๋ฉฐ ๋ก์ปฌ์์ ์ํ๋ฉ๋๋ค.
๋นผ๊ธฐ :
๋งค์ฐ ์์ ํจํท์ผ๋ก ์ธํด ์ฑ๋ฅ์ด ์ ํ๋ฉ๋๋ค.
์ด๋ฌํ ์ ๊ทผ ๋ฐฉ์์ ๊ฐ๋จํ ์๋น์ค๋ ํ๋กํ ํ์ ์์ ์์ฃผ ๋ณผ ์ ์์ต๋๋ค. ๋ณ๊ฒฝ ์๋๊ฐ ์ค์ํ๋ค๋ฉด ์์คํ ์ ๋ณต์กํ๊ฒ ๋ง๋ค ๊ฐ์น๊ฐ ๊ฑฐ์ ์์ต๋๋ค. ๋์์, ๋งค์ฐ ๊ฐ๋จํ ์๋น์ค์ ๊ฒฝ์ฐ ์ฑ๋ฅ์ด ํํธ์๊ธฐ ๋๋ฌธ์ ์ด ์ ๊ทผ ๋ฐฉ์์ ์ ์ฉ ๋ฒ์๊ฐ ๋งค์ฐ ์ข์ต๋๋ค.
์์งํ ๋ณ๋ ฌ ์ฒ๋ฆฌ
๋ชจ๋ ๋ฉ์์ง ์ฒ๋ฆฌ๋ฅผ ๋ณ๋ ฌ๋ก ์์ํ ์ ์์ต๋๋ค. ์ด๋ ๊ฒ ํ๋ฉด ๋ฉ์์ง ์์ ๋ฐ๋ฅธ ์ ํ์ ์ธ ์๊ฐ ์ฆ๊ฐ๋ฅผ ์์จ ์ ์์ต๋๋ค. ์ด๋ ์ธ๋ถ ์๋น์ค์ ํฐ ์ต๋ ๋ถํ๋ฅผ ์ด๋ํ๋ฏ๋ก ํน๋ณํ ์ข์ ๊ฒฝ๋ก๋ ์๋๋๋ค.
๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ๊ตฌํํ๋ ๊ฒ์ ๋งค์ฐ ๊ฐ๋จํฉ๋๋ค.
override fun getLast(n: Int) =
messageRepository.findLast(n).parallelStream()
.map { it.toFrontModel() }
.collect(toList())
๋ณ๋ ฌ ๋ฉ์์ง ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด ์ด์์ ์ผ๋ก๋ 300~700ms๋ฅผ ์ป์ ์ ์์ต๋๋ค. ์ด๋ ์์งํ ๊ตฌํ๋ณด๋ค ํจ์ฌ ๋ซ์ง๋ง ์ฌ์ ํ ์ถฉ๋ถํ ๋น ๋ฅด์ง๋ ์์ต๋๋ค.
์ด ์ ๊ทผ ๋ฐฉ์์ ์ฌ์ฉํ๋ฉด userRepository ๋ฐ fileRepository์ ๋ํ ์์ฒญ์ด ๋๊ธฐ์ ์ผ๋ก ์คํ๋๋ฏ๋ก ๊ทธ๋ค์ง ํจ์จ์ ์ด์ง ์์ต๋๋ค. ์ด ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ค๋ฉด ํธ์ถ ๋ ผ๋ฆฌ๋ฅผ ์๋นํ ๋ง์ด ๋ณ๊ฒฝํด์ผ ํฉ๋๋ค. ์๋ฅผ ๋ค์ด CompletionStage(CompletableFuture๋ผ๊ณ ๋ ํจ)๋ฅผ ํตํด ๋ค์์ ์ํํฉ๋๋ค.
private fun ChatMessage.toFrontModel(): ChatMessageUI =
CompletableFuture.supplyAsync {
userRepository.getUserById(author).toFrontReference()
}.thenCombine(
files?.let {
CompletableFuture.supplyAsync {
fileRepository.getHeadsByIds(files).map { it.toFrontReference() }
}
} ?: CompletableFuture.completedFuture(listOf())
) { author, files ->
ChatMessageUI(
id = id ?: throw IllegalStateException("$this must be persisted"),
author = author,
message = message,
files = files,
forwardFrom = forwardFrom?.toFrontModel(),
replyTo = replyTo?.toFrontModel()
)
}.get()!!
์ฒ์์๋ ๋จ์ํ๋ ๋งคํ ์ฝ๋๊ฐ ์ดํดํ๊ธฐ ์ด๋ ค์์ง ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค. ๊ฒฐ๊ณผ๊ฐ ์ฌ์ฉ๋๋ ๊ณณ๊ณผ ์ธ๋ถ ์๋น์ค์ ๋ํ ํธ์ถ์ ๋ถ๋ฆฌํด์ผ ํ๊ธฐ ๋๋ฌธ์ ๋๋ค. ์ด๊ฒ ์์ฒด๋ ๋์์ง ์์ต๋๋ค. ๊ทธ๋ฌ๋ ํตํ ๊ฒฐํฉ์ ํน๋ณํ ์ฐ์ํด ๋ณด์ด์ง ์์ผ๋ฉฐ ์ผ๋ฐ์ ์ธ ๋ฐ์ํ "๊ตญ์"์ ์ ์ฌํฉ๋๋ค.
์ฝ๋ฃจํด์ ์ฌ์ฉํ๋ฉด ๋ชจ๋ ๊ฒ์ด ๋ ๊ด์ฐฎ์ ๋ณด์ผ ๊ฒ์ ๋๋ค.
private fun ChatMessage.toFrontModel(): ChatMessageUI =
join(
{ userRepository.getUserById(author).toFrontReference() },
{ files?.let { fileRepository.getHeadsByIds(files)
.map { it.toFrontReference() } } ?: listOf() }
).let { (author, files) ->
ChatMessageUI(
id = id ?: throw IllegalStateException("$this must be persisted"),
author = author,
message = message,
files = files,
forwardFrom = forwardFrom?.toFrontModel(),
replyTo = replyTo?.toFrontModel()
)
}
์ฅ์ :
fun <A, B> join(a: () -> A, b: () -> B) =
runBlocking(IO) {
awaitAll(async { a() }, async { b() })
}.let {
it[0] as A to it[1] as B
}
์ด๋ก ์ ์ผ๋ก ์ด๋ฌํ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ฉด 200~400ms๋ฅผ ์ป์ ์ ์์ผ๋ฉฐ ์ด๋ ์ด๋ฏธ ๊ธฐ๋์น์ ๊ฐ๊น์ต๋๋ค.
๋ถํํ๊ฒ๋ ์ด๋ฌํ ์ข์ ๋ณ๋ ฌํ๋ ์ด๋ฃจ์ด์ง์ง ์์ผ๋ฉฐ ์ง๋ถํด์ผ ํ ๋๊ฐ๋ ๋งค์ฐ ์์ธํฉ๋๋ค. ๋์์ ์์ ํ๋ ์ฌ์ฉ์๊ฐ ์์์ ๋ถ๊ณผํ๋ฉด ์๋น์ค์ ์์ฒญ์ด ๋๋ฌด ๋ง์์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌ๋์ง ์์ ์ ์์ต๋๋ค. ์ฐ๋ฆฌ์ ์ฌํ 4s๋ก ๋์๊ฐ ๊ฒ์ด๋ค.
์ด๋ฌํ ์๋น์ค๋ฅผ ์ฌ์ฉํ ๋์ ๊ฒฐ๊ณผ๋ 1300๊ฐ์ ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ๋ ๋ฐ 1700~20ms์ ๋๋ค. ์ด๋ ์ฒซ ๋ฒ์งธ ๊ตฌํ๋ณด๋ค ๋น ๋ฅด์ง๋ง ์ฌ์ ํ ๋ฌธ์ ๊ฐ ํด๊ฒฐ๋์ง๋ ์์ต๋๋ค.
๋ณ๋ ฌ ์ฟผ๋ฆฌ์ ๋์ฒด ์ฉ๋ํ์ฌ ์๋น์ค๊ฐ ์ผ๊ด ์ฒ๋ฆฌ๋ฅผ ์ ๊ณตํ์ง ์์ผ๋ฉด ์ด๋ป๊ฒ ๋๋์? ์๋ฅผ ๋ค์ด, ์ธํฐํ์ด์ค ๋ฉ์๋ ๋ด์์ ์ผ๊ด ์ฒ๋ฆฌ ๊ตฌํ ๋ถ์กฑ์ ์จ๊ธธ ์ ์์ต๋๋ค.
interface UserRemoteApi {
fun getUserById(id: UserReference): UserRemote
fun getUsersByIds(id: Set<UserReference>): Set<UserRemote> =
id.parallelStream()
.map { getUserById(it) }.collect(toSet())
fun getUsersByIds(id: List<UserReference>): List<UserRemote> =
id.parallelStream()
.map { getUserById(it) }.collect(toList())
}
ํฅํ ๋ฒ์ ์์ ์ผ๊ด ์ฒ๋ฆฌ๋ฅผ ์ํ ๊ฒฝ์ฐ ์ด๋ ์๋ฏธ๊ฐ ์์ต๋๋ค.
์ฅ์ :
- ๋ฉ์์ง ๊ธฐ๋ฐ ๋ณ๋ ฌ ์ฒ๋ฆฌ๋ฅผ ์ฝ๊ฒ ๊ตฌํํฉ๋๋ค.
- ํ์ฅ์ฑ์ด ์ข์ต๋๋ค.
๋จ์ :
- ๋ค์ํ ์๋น์ค์ ๋ํ ์์ฒญ์ ๋ณ๋ ฌ๋ก ์ฒ๋ฆฌํ ๋ ๋ฐ์ดํฐ ์์ง๊ณผ ์ฒ๋ฆฌ๋ฅผ ๋ถ๋ฆฌํด์ผ ํฉ๋๋ค.
- ํ์ฌ ์๋น์ค์ ๋ํ ๋ถํ๊ฐ ์ฆ๊ฐํ์ต๋๋ค.
์ ์ฉ ๋ฒ์๋ naive ์ ๊ทผ ๋ฐฉ์๊ณผ ๊ฑฐ์ ๋์ผํจ์ ์ ์ ์๋ค. ํ์ธ์ ๋ฌด์๋นํ ์ฐฉ์ทจ๋ก ์ธํด ์๋น์ค ์ฑ๋ฅ์ ์ฌ๋ฌ ๋ฒ ๋์ด๋ ค๋ฉด ๋ณ๋ ฌ ์์ฒญ ๋ฐฉ๋ฒ์ ์ฌ์ฉํ๋ ๊ฒ์ด ์ข์ต๋๋ค. ์ด ์์์๋ ์์ฐ์ฑ์ด 2,5๋ฐฐ ์ฆ๊ฐํ์ง๋ง ์ด๊ฒ๋ง์ผ๋ก๋ ์ถฉ๋ถํ์ง ์์ต๋๋ค.
์บ์ฑ
์ธ๋ถ ์๋น์ค์ ๋ํด JPA ์ ์ ์ผ๋ก ์บ์ฑ์ ์ํํ ์ ์์ต๋๋ค. ์ฆ, ์์ ๋ ๊ฐ์ฒด๋ฅผ ๋ค์ ์์ ํ์ง ์๋๋ก(์ผ๊ด ์ฒ๋ฆฌ ์ค ํฌํจ) ์ธ์ ๋ด์ ์ ์ฅํ๋ ๊ฒ์ ๋๋ค. ์ด๋ฌํ ์บ์๋ฅผ ์ง์ ๋ง๋ค ์ ์๊ณ @Cacheable๊ณผ ํจ๊ป Spring์ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ EhCache์ ๊ฐ์ ๋ฏธ๋ฆฌ ๋ง๋ค์ด์ง ์บ์๋ฅผ ํญ์ ์๋์ผ๋ก ์ฌ์ฉํ ์ ์์ต๋๋ค.
์ผ๋ฐ์ ์ธ ๋ฌธ์ ๋ ์บ์๊ฐ ํํธ๊ฐ ์๋ ๊ฒฝ์ฐ์๋ง ์ ์ฉํ๋ค๋ ๊ฒ์ ๋๋ค. ์ฐ๋ฆฌ์ ๊ฒฝ์ฐ ์์ฑ์ ํ๋์ ๋ํ ์กฐํ๊ฐ ๋ฐ์ํ ๊ฐ๋ฅ์ฑ์ด ๋งค์ฐ ๋์ง๋ง(๊ฐ๋ น 50%) ํ์ผ์ ๋ํ ์กฐํ๋ ์ ํ ์์ต๋๋ค. ์ด ์ ๊ทผ ๋ฐฉ์์ ์ผ๋ถ ๊ฐ์ ์ ์ ๊ณตํ์ง๋ง ์์ฐ์ฑ์ ๊ทผ๋ณธ์ ์ผ๋ก ๋ณํ์ํค์ง๋ ์์ต๋๋ค(๊ทธ๋ฆฌ๊ณ ํ๊ธฐ์ ์ธ ๋ฐ์ ์ด ํ์ํฉ๋๋ค).
์ธ์ ๊ฐ(๊ธด) ์บ์์๋ ๋ณต์กํ ๋ฌดํจํ ๋ ผ๋ฆฌ๊ฐ ํ์ํฉ๋๋ค. ์ผ๋ฐ์ ์ผ๋ก ์ธ์ ๊ฐ ์บ์๋ฅผ ์ฌ์ฉํ์ฌ ์ฑ๋ฅ ๋ฌธ์ ๋ฅผ ๋์ค์ ํด๊ฒฐํ ์๋ก ์ข์ต๋๋ค.
์ฅ์ :
- ์ฝ๋๋ฅผ ๋ณ๊ฒฝํ์ง ์๊ณ ์บ์ฑ์ ๊ตฌํํฉ๋๋ค.
- ์์ฐ์ฑ์ด ์ฌ๋ฌ ๋ฒ ํฅ์๋์์ต๋๋ค(๊ฒฝ์ฐ์ ๋ฐ๋ผ).
๋จ์ :
- ์๋ชป ์ฌ์ฉํ๋ฉด ์ฑ๋ฅ์ด ์ ํ๋ ๊ฐ๋ฅ์ฑ์ด ์์ต๋๋ค.
- ํนํ ๊ธด ์บ์์ ๊ฒฝ์ฐ ๋ฉ๋ชจ๋ฆฌ ์ค๋ฒํค๋๊ฐ ํฝ๋๋ค.
- ๋ณต์กํ ๋ฌดํจํ, ๋ฐํ์ ์ ์ฌํํ๊ธฐ ์ด๋ ค์ด ๋ฌธ์ ๋ก ์ด์ด์ง๋ ์ค๋ฅ์ ๋๋ค.
์บ์๋ ์ค๊ณ ๋ฌธ์ ๋ฅผ ์ ์ํ๊ฒ ํด๊ฒฐํ๋ ๋ฐ์๋ง ์ฌ์ฉ๋๋ ๊ฒฝ์ฐ๊ฐ ๋ง์ต๋๋ค. ๊ทธ๋ ๋ค๊ณ ํด์ ์ฌ์ฉํด์๋ ์๋๋ค๋ ์๋ฏธ๋ ์๋๋๋ค. ๊ทธ๋ฌ๋ ํญ์ ์ฃผ์ํด์ ๋ค๋ฃจ์ด์ผ ํ๋ฉฐ ๋จผ์ ๊ฒฐ๊ณผ์ ์ธ ์ฑ๋ฅ ํฅ์์ ํ๊ฐํ ๋ค์ ๊ฒฐ์ ์ ๋ด๋ ค์ผ ํฉ๋๋ค.
์ด ์์์ ์บ์๋ ์ฝ 25%์ ์ฑ๋ฅ ํฅ์์ ์ ๊ณตํฉ๋๋ค. ๋์์ ์บ์์๋ ๋จ์ ์ด ๋ง์ด ์์ผ๋ฏ๋ก ์ฌ๊ธฐ์๋ ์ฌ์ฉํ์ง ์๊ฒ ์ต๋๋ค.
๊ฒฐ๊ณผ
๊ทธ๋์ ์ฐ๋ฆฌ๋ ์ผ๊ด ์ฒ๋ฆฌ๋ฅผ ์ฌ์ฉํ๋ ์๋น์ค์ ๋จ์ํ ๊ตฌํ๊ณผ ์๋๋ฅผ ๋์ด๋ ๋ช ๊ฐ์ง ๊ฐ๋จํ ๋ฐฉ๋ฒ์ ์ดํด๋ณด์์ต๋๋ค.
์ด ๋ชจ๋ ๋ฐฉ๋ฒ์ ๊ฐ์ฅ ํฐ ์ฅ์ ์ ๋จ์์ฑ์ด๋ฉฐ ์ด๋ก ์ธํด ๋ง์ ์ฆ๊ฑฐ์ด ๊ฒฐ๊ณผ๊ฐ ๋ฐ์ํฉ๋๋ค.
์ด๋ฌํ ๋ฐฉ๋ฒ์ ์ผ๋ฐ์ ์ธ ๋ฌธ์ ๋ ์ฃผ๋ก ํจํท ํฌ๊ธฐ์ ๊ด๋ จ๋ ์ฑ๋ฅ ์ ํ์ ๋๋ค. ๋ฐ๋ผ์ ์ด๋ฌํ ์๋ฃจ์ ์ด ๊ทํ์๊ฒ ์ ํฉํ์ง ์๋ค๋ฉด๋ณด๋ค ๊ทผ๋ณธ์ ์ธ ๋ฐฉ๋ฒ์ ๊ณ ๋ คํด ๋ณผ ๊ฐ์น๊ฐ ์์ต๋๋ค.
์๋ฃจ์ ์ ์ฐพ์ ์ ์๋ ๋ ๊ฐ์ง ์ฃผ์ ๋ฐฉํฅ์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
- ๋ฐ์ดํฐ๋ฅผ ์ฌ์ฉํ ๋น๋๊ธฐ ์์ (ํจ๋ฌ๋ค์ ์ ํ์ด ํ์ํ๋ฏ๋ก ์ด ๊ธฐ์ฌ์์๋ ๋ ผ์ํ์ง ์์)
- ๋๊ธฐ ์ฒ๋ฆฌ๋ฅผ ์ ์งํ๋ฉด์ ๋ฐฐ์น๋ฅผ ํ๋ํฉ๋๋ค.
๋ฐฐ์น๋ฅผ ํ๋ํ๋ฉด ์ธ๋ถ ํธ์ถ ์๊ฐ ํฌ๊ฒ ์ค์ด๋ค๊ณ ๋์์ ์ฝ๋ ๋๊ธฐ๊ฐ ์ ์ง๋ฉ๋๋ค. ๊ธฐ์ฌ์ ๋ค์ ๋ถ๋ถ์ ์ด ์ฃผ์ ์ ๋ํด ๋ค๋ฃฐ ๊ฒ์
๋๋ค.
์ถ์ฒ : habr.com