์ผ๊ด„ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ์˜ ๋ฌธ์ œ์ ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•(1๋ถ€)

์ผ๊ด„ ์ฟผ๋ฆฌ ์ฒ˜๋ฆฌ์˜ ๋ฌธ์ œ์ ๊ณผ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•(1๋ถ€)๊ฑฐ์˜ ๋ชจ๋“  ์ตœ์‹  ์†Œํ”„ํŠธ์›จ์–ด ์ œํ’ˆ์€ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค๋กœ ๊ตฌ์„ฑ๋ฉ๋‹ˆ๋‹ค. ์ข…์ข… ์„œ๋น„์Šค ๊ฐ„ ์ฑ„๋„์˜ ๊ธด ์‘๋‹ต ์‹œ๊ฐ„์ด ์„ฑ๋Šฅ ๋ฌธ์ œ์˜ ์›์ธ์ด ๋ฉ๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์ข…๋ฅ˜์˜ ๋ฌธ์ œ์— ๋Œ€ํ•œ ํ‘œ์ค€ ์†”๋ฃจ์…˜์€ ์—ฌ๋Ÿฌ ์„œ๋น„์Šค ๊ฐ„ ์š”์ฒญ์„ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ผ๊ณ  ํ•˜๋Š” ํ•˜๋‚˜์˜ ํŒจํ‚ค์ง€๋กœ ๋ฌถ๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ผ๊ด„ ์ฒ˜๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒฝ์šฐ ์„ฑ๋Šฅ์ด๋‚˜ ์ฝ”๋“œ ๋ช…ํ™•์„ฑ ์ธก๋ฉด์—์„œ ๊ฒฐ๊ณผ๊ฐ€ ๋งŒ์กฑ์Šค๋Ÿฝ์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๋ฐฉ๋ฒ•์€ ๋ฐœ์‹ ์ž์—๊ฒŒ ์ƒ๊ฐ๋งŒํผ ์‰ฝ์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋‹ค์–‘ํ•œ ๋ชฉ์ ๊ณผ ์ƒํ™ฉ์— ๋”ฐ๋ผ ์†”๋ฃจ์…˜์ด ํฌ๊ฒŒ ๋‹ฌ๋ผ์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ตฌ์ฒด์ ์ธ ์˜ˆ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์—ฌ๋Ÿฌ ์ ‘๊ทผ ๋ฐฉ์‹์˜ ์žฅ๋‹จ์ ์„ ๋ณด์—ฌ ๋“œ๋ฆฌ๊ฒ ์Šต๋‹ˆ๋‹ค.

์‹ค์ฆ ํ”„๋กœ์ ํŠธ

๋ช…ํ™•์„ฑ์„ ์œ„ํ•ด ํ˜„์žฌ ์ž‘์—… ์ค‘์ธ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์˜ ์„œ๋น„์Šค ์ค‘ ํ•˜๋‚˜์˜ ์˜ˆ๋ฅผ ์‚ดํŽด๋ณด๊ฒ ์Šต๋‹ˆ๋‹ค.

์˜ˆ์‹œ์— ๋Œ€ํ•œ ํ”Œ๋žซํผ ์„ ํƒ ์„ค๋ช…์„ฑ๋Šฅ ์ €ํ•˜ ๋ฌธ์ œ๋Š” ๋งค์šฐ ์ผ๋ฐ˜์ ์ด๋ฉฐ ํŠน์ • ์–ธ์–ด๋‚˜ ํ”Œ๋žซํผ๊ณผ ๊ด€๋ จ์ด ์—†์Šต๋‹ˆ๋‹ค. ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” 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(nInt): List<ChatMessageUI>
}

Postfix UI๋Š” ํ”„๋ŸฐํŠธ์—”๋“œ์šฉ DTO ๋ชจ๋ธ, ์ฆ‰ REST๋ฅผ ํ†ตํ•ด ์ œ๊ณตํ•ด์•ผ ํ•˜๋Š” ๋ชจ๋ธ์„ ์˜๋ฏธํ•ฉ๋‹ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๋†€๋ผ์šด ์ ์€ ์ฑ„ํŒ… ์‹๋ณ„์ž๋ฅผ ์ „๋‹ฌํ•˜์ง€ ์•Š๊ณ  ChatMessage/ChatMessageUI ๋ชจ๋ธ์—๋„ ์•„๋ฌด ๊ฒƒ๋„ ์ „๋‹ฌํ•˜์ง€ ์•Š๋Š”๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ๋‚˜๋Š” ์˜ˆ์ œ์˜ ์ฝ”๋“œ๋ฅผ ๋ณต์žกํ•˜๊ฒŒ ํ•˜์ง€ ์•Š๊ธฐ ์œ„ํ•ด ์˜๋„์ ์œผ๋กœ ์ด๋ ‡๊ฒŒ ํ–ˆ์Šต๋‹ˆ๋‹ค(์ฑ„ํŒ…์€ ๋ถ„๋ฆฌ๋˜์–ด ์žˆ์œผ๋ฏ€๋กœ ํ•˜๋‚˜๋งŒ ์žˆ๋‹ค๊ณ  ๊ฐ€์ •ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค).

์ฒ ํ•™์  ์—ฌ๋‹ดChatMessageUI ํด๋ž˜์Šค์™€ ChatRestApi.getLast ๋ฉ”์†Œ๋“œ ๋ชจ๋‘ ์‹ค์ œ๋กœ๋Š” ์ˆœ์„œ๊ฐ€ ์ง€์ •๋œ Set์ธ List ๋ฐ์ดํ„ฐ ์œ ํ˜•์„ ์‚ฌ์šฉํ•ฉ๋‹ˆ๋‹ค. JDK์—์„œ๋Š” ์ด๊ฒƒ์ด ๋ชจ๋‘ ์ข‹์ง€ ์•Š์œผ๋ฏ€๋กœ ์ธํ„ฐํŽ˜์ด์Šค ์ˆ˜์ค€์—์„œ ์š”์†Œ ์ˆœ์„œ๋ฅผ ์„ ์–ธํ•˜๋Š” ๊ฒƒ(์ถ”๊ฐ€ ๋ฐ ๊ฒ€์ƒ‰ ์‹œ ์ˆœ์„œ ์œ ์ง€)์€ ์ž‘๋™ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ˆœ์„œ๊ฐ€ ์ง€์ •๋œ Set์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ List๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ผ๋ฐ˜์ ์ธ ๊ด€ํ–‰์ด ๋˜์—ˆ์Šต๋‹ˆ๋‹ค(LinkedHashSet๋„ ์žˆ์ง€๋งŒ ์ด๋Š” ์ธํ„ฐํŽ˜์ด์Šค๊ฐ€ ์•„๋‹™๋‹ˆ๋‹ค).
์ค‘์š”ํ•œ ์ œํ•œ์‚ฌํ•ญ: ์šฐ๋ฆฌ๋Š” ๊ธด ์‘๋‹ต์ด๋‚˜ ์ „์†ก ์ฒด์ธ์ด ์—†๋‹ค๊ณ  ๊ฐ€์ •ํ•ฉ๋‹ˆ๋‹ค. ์ฆ‰, ์กด์žฌํ•˜์ง€๋งŒ ๊ธธ์ด๊ฐ€ XNUMX๊ฐœ ๋ฉ”์‹œ์ง€๋ฅผ ์ดˆ๊ณผํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค. ์ „์ฒด ๋ฉ”์‹œ์ง€ ์ฒด์ธ์ด ํ”„๋ŸฐํŠธ์—”๋“œ๋กœ ์ „์†ก๋˜์–ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์™ธ๋ถ€ ์„œ๋น„์Šค๋กœ๋ถ€ํ„ฐ ๋ฐ์ดํ„ฐ๋ฅผ ์ˆ˜์‹ ํ•˜๋ ค๋ฉด ๋‹ค์Œ API๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

interface ChatMessageRepository {
  fun findLast(nInt): List<ChatMessage>
}
data class FileHeadRemote(
  val id: FileReference,
  val name: String
)
interface FileRemoteApi {
  fun getHeadById(idFileReference): FileHeadRemote
  fun getHeadsByIds(idSet<FileReference>): Set<FileHeadRemote>
  fun getHeadsByIds(idList<FileReference>): List<FileHeadRemote>
  fun getHeadsByChat(): List<FileHeadRemote>
}
data class UserRemote(
  val id: UserReference,
  val name: String
)
interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote>
  fun getUsersByIds(idList<UserReference>): List<UserRemote>
}

์™ธ๋ถ€ ์„œ๋น„์Šค๋Š” ์ฒ˜์Œ์— ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜๊ณ  ๋‘ ๋ณ€ํ˜• ๋ชจ๋‘์—์„œ Set(์š”์†Œ ์ˆœ์„œ๋ฅผ ์œ ์ง€ํ•˜์ง€ ์•Š๊ณ  ๊ณ ์œ  ํ‚ค ์‚ฌ์šฉ)๊ณผ List(์ค‘๋ณต์ด ์žˆ์„ ์ˆ˜ ์žˆ์Œ - ์ˆœ์„œ๊ฐ€ ์œ ์ง€๋จ)๋ฅผ ํ†ตํ•ด ์ œ๊ณต๋˜๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

๊ฐ„๋‹จํ•œ ๊ตฌํ˜„

์ˆœ์ง„ํ•œ ๊ตฌํ˜„

REST ์ปจํŠธ๋กค๋Ÿฌ์˜ ์ฒซ ๋ฒˆ์งธ ์ˆœ์ง„ํ•œ ๊ตฌํ˜„์€ ๋Œ€๋ถ€๋ถ„์˜ ๊ฒฝ์šฐ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

class ChatRestController(
  private val messageRepository: ChatMessageRepository,
  private val userRepository: UserRemoteApi,
  private val fileRepository: FileRemoteApi
) : ChatRestApi {
  override fun getLast(nInt) =
    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 ์ด๋‚ด๋กœ ์œ ์ง€ํ•˜๊ณ  ์‹ถ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  ํ”„๋ŸฐํŠธ์—”๋“œ์—์„œ ์›ํ™œํ•œ ์Šคํฌ๋กค์„ ์›ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— ์ด ์—”๋“œํฌ์ธํŠธ์— ๋Œ€ํ•œ ์„ฑ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ์ด ๋‘ ๋ฐฐ๋กœ ๋Š˜์–ด๋‚  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์žฅ์  :

  1. ์ฝ”๋“œ๋Š” ๊ฐ„๊ฒฐํ•˜๊ณ  ์ž์ฒด ๋ฌธ์„œํ™”๋ฉ๋‹ˆ๋‹ค(์ง€์› ํŒ€์˜ ๊ฟˆ).
  2. ์ฝ”๋“œ๊ฐ€ ๊ฐ„๋‹จํ•ด์„œ ๋ฐœ์— ์ด์„ ์  ๊ธฐํšŒ๊ฐ€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค.
  3. ์ผ๊ด„ ์ฒ˜๋ฆฌ๋Š” ์ด์งˆ์ ์ธ ๊ฒƒ์ฒ˜๋Ÿผ ๋ณด์ด์ง€ ์•Š์œผ๋ฉฐ ๋…ผ๋ฆฌ์— ์œ ๊ธฐ์ ์œผ๋กœ ํ†ตํ•ฉ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค.
  4. ๋…ผ๋ฆฌ ๋ณ€๊ฒฝ์€ ์‰ฝ๊ฒŒ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ๋กœ์ปฌ์—์„œ ์ˆ˜ํ–‰๋ฉ๋‹ˆ๋‹ค.

๋นผ๊ธฐ :

๋งค์šฐ ์ž‘์€ ํŒจํ‚ท์œผ๋กœ ์ธํ•ด ์„ฑ๋Šฅ์ด ์ €ํ•˜๋ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ์ ‘๊ทผ ๋ฐฉ์‹์€ ๊ฐ„๋‹จํ•œ ์„œ๋น„์Šค๋‚˜ ํ”„๋กœํ† ํƒ€์ž…์—์„œ ์ž์ฃผ ๋ณผ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋ณ€๊ฒฝ ์†๋„๊ฐ€ ์ค‘์š”ํ•˜๋‹ค๋ฉด ์‹œ์Šคํ…œ์„ ๋ณต์žกํ•˜๊ฒŒ ๋งŒ๋“ค ๊ฐ€์น˜๊ฐ€ ๊ฑฐ์˜ ์—†์Šต๋‹ˆ๋‹ค. ๋™์‹œ์—, ๋งค์šฐ ๊ฐ„๋‹จํ•œ ์„œ๋น„์Šค์˜ ๊ฒฝ์šฐ ์„ฑ๋Šฅ์ด ํ˜•ํŽธ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ด ์ ‘๊ทผ ๋ฐฉ์‹์˜ ์ ์šฉ ๋ฒ”์œ„๊ฐ€ ๋งค์šฐ ์ข์Šต๋‹ˆ๋‹ค.

์ˆœ์ง„ํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ

๋ชจ๋“  ๋ฉ”์‹œ์ง€ ์ฒ˜๋ฆฌ๋ฅผ ๋ณ‘๋ ฌ๋กœ ์‹œ์ž‘ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ๋ฉ”์‹œ์ง€ ์ˆ˜์— ๋”ฐ๋ฅธ ์„ ํ˜•์ ์ธ ์‹œ๊ฐ„ ์ฆ๊ฐ€๋ฅผ ์—†์•จ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ์™ธ๋ถ€ ์„œ๋น„์Šค์— ํฐ ์ตœ๋Œ€ ๋ถ€ํ•˜๋ฅผ ์ดˆ๋ž˜ํ•˜๋ฏ€๋กœ ํŠน๋ณ„ํžˆ ์ข‹์€ ๊ฒฝ๋กœ๋Š” ์•„๋‹™๋‹ˆ๋‹ค.

๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์€ ๋งค์šฐ ๊ฐ„๋‹จํ•ฉ๋‹ˆ๋‹ค.

override fun getLast(nInt) =
  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())
  ) authorfiles ->
    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 <ABjoin(a: () -> Ab: () -> B) =
  runBlocking(IO{
    awaitAll(async a() }async b() })
  }.let {
    it[0as to it[1as B
  }

์ด๋ก ์ ์œผ๋กœ ์ด๋Ÿฌํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด 200~400ms๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ด๋Š” ์ด๋ฏธ ๊ธฐ๋Œ€์น˜์— ๊ฐ€๊น์Šต๋‹ˆ๋‹ค.

๋ถˆํ–‰ํ•˜๊ฒŒ๋„ ์ด๋Ÿฌํ•œ ์ข‹์€ ๋ณ‘๋ ฌํ™”๋Š” ์ด๋ฃจ์–ด์ง€์ง€ ์•Š์œผ๋ฉฐ ์ง€๋ถˆํ•ด์•ผ ํ•  ๋Œ€๊ฐ€๋Š” ๋งค์šฐ ์ž”์ธํ•ฉ๋‹ˆ๋‹ค. ๋™์‹œ์— ์ž‘์—…ํ•˜๋Š” ์‚ฌ์šฉ์ž๊ฐ€ ์†Œ์ˆ˜์— ๋ถˆ๊ณผํ•˜๋ฉด ์„œ๋น„์Šค์— ์š”์ฒญ์ด ๋„ˆ๋ฌด ๋งŽ์•„์„œ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌ๋˜์ง€ ์•Š์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ์Šฌํ”ˆ 4s๋กœ ๋Œ์•„๊ฐˆ ๊ฒƒ์ด๋‹ค.

์ด๋Ÿฌํ•œ ์„œ๋น„์Šค๋ฅผ ์‚ฌ์šฉํ•  ๋•Œ์˜ ๊ฒฐ๊ณผ๋Š” 1300๊ฐœ์˜ ๋ฉ”์‹œ์ง€๋ฅผ ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐ 1700~20ms์ž…๋‹ˆ๋‹ค. ์ด๋Š” ์ฒซ ๋ฒˆ์งธ ๊ตฌํ˜„๋ณด๋‹ค ๋น ๋ฅด์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ฌธ์ œ๊ฐ€ ํ•ด๊ฒฐ๋˜์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค.

๋ณ‘๋ ฌ ์ฟผ๋ฆฌ์˜ ๋Œ€์ฒด ์šฉ๋„ํƒ€์‚ฌ ์„œ๋น„์Šค๊ฐ€ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ฅผ ์ œ๊ณตํ•˜์ง€ ์•Š์œผ๋ฉด ์–ด๋–ป๊ฒŒ ๋˜๋‚˜์š”? ์˜ˆ๋ฅผ ๋“ค์–ด, ์ธํ„ฐํŽ˜์ด์Šค ๋ฉ”์†Œ๋“œ ๋‚ด์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌ ๊ตฌํ˜„ ๋ถ€์กฑ์„ ์ˆจ๊ธธ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

interface UserRemoteApi {
  fun getUserById(idUserReference): UserRemote
  fun getUsersByIds(idSet<UserReference>): Set<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toSet())
  fun getUsersByIds(idList<UserReference>): List<UserRemote> =
    id.parallelStream()
      .map getUserById(it}.collect(toList())
}

ํ–ฅํ›„ ๋ฒ„์ „์—์„œ ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ฅผ ์›ํ•  ๊ฒฝ์šฐ ์ด๋Š” ์˜๋ฏธ๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.
์žฅ์  :

  1. ๋ฉ”์‹œ์ง€ ๊ธฐ๋ฐ˜ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ๋ฅผ ์‰ฝ๊ฒŒ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  2. ํ™•์žฅ์„ฑ์ด ์ข‹์Šต๋‹ˆ๋‹ค.

๋‹จ์  :

  1. ๋‹ค์–‘ํ•œ ์„œ๋น„์Šค์— ๋Œ€ํ•œ ์š”์ฒญ์„ ๋ณ‘๋ ฌ๋กœ ์ฒ˜๋ฆฌํ•  ๋•Œ ๋ฐ์ดํ„ฐ ์ˆ˜์ง‘๊ณผ ์ฒ˜๋ฆฌ๋ฅผ ๋ถ„๋ฆฌํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
  2. ํƒ€์‚ฌ ์„œ๋น„์Šค์— ๋Œ€ํ•œ ๋ถ€ํ•˜๊ฐ€ ์ฆ๊ฐ€ํ–ˆ์Šต๋‹ˆ๋‹ค.

์ ์šฉ ๋ฒ”์œ„๋Š” naive ์ ‘๊ทผ ๋ฐฉ์‹๊ณผ ๊ฑฐ์˜ ๋™์ผํ•จ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค. ํƒ€์ธ์˜ ๋ฌด์ž๋น„ํ•œ ์ฐฉ์ทจ๋กœ ์ธํ•ด ์„œ๋น„์Šค ์„ฑ๋Šฅ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋†’์ด๋ ค๋ฉด ๋ณ‘๋ ฌ ์š”์ฒญ ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ์ข‹์Šต๋‹ˆ๋‹ค. ์ด ์˜ˆ์—์„œ๋Š” ์ƒ์‚ฐ์„ฑ์ด 2,5๋ฐฐ ์ฆ๊ฐ€ํ–ˆ์ง€๋งŒ ์ด๊ฒƒ๋งŒ์œผ๋กœ๋Š” ์ถฉ๋ถ„ํ•˜์ง€ ์•Š์Šต๋‹ˆ๋‹ค.

์บ์‹ฑ

์™ธ๋ถ€ ์„œ๋น„์Šค์— ๋Œ€ํ•ด JPA ์ •์‹ ์œผ๋กœ ์บ์‹ฑ์„ ์ˆ˜ํ–‰ํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ฆ‰, ์ˆ˜์‹ ๋œ ๊ฐ์ฒด๋ฅผ ๋‹ค์‹œ ์ˆ˜์‹ ํ•˜์ง€ ์•Š๋„๋ก(์ผ๊ด„ ์ฒ˜๋ฆฌ ์ค‘ ํฌํ•จ) ์„ธ์…˜ ๋‚ด์— ์ €์žฅํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์ด๋Ÿฌํ•œ ์บ์‹œ๋ฅผ ์ง์ ‘ ๋งŒ๋“ค ์ˆ˜ ์žˆ๊ณ  @Cacheable๊ณผ ํ•จ๊ป˜ Spring์„ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ EhCache์™€ ๊ฐ™์€ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด์ง„ ์บ์‹œ๋ฅผ ํ•ญ์ƒ ์ˆ˜๋™์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ๋Š” ์บ์‹œ๊ฐ€ ํžˆํŠธ๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ์—๋งŒ ์œ ์šฉํ•˜๋‹ค๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค. ์šฐ๋ฆฌ์˜ ๊ฒฝ์šฐ ์ž‘์„ฑ์ž ํ•„๋“œ์— ๋Œ€ํ•œ ์กฐํšŒ๊ฐ€ ๋ฐœ์ƒํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋งค์šฐ ๋†’์ง€๋งŒ(๊ฐ€๋ น 50%) ํŒŒ์ผ์— ๋Œ€ํ•œ ์กฐํšŒ๋Š” ์ „ํ˜€ ์—†์Šต๋‹ˆ๋‹ค. ์ด ์ ‘๊ทผ ๋ฐฉ์‹์€ ์ผ๋ถ€ ๊ฐœ์„ ์„ ์ œ๊ณตํ•˜์ง€๋งŒ ์ƒ์‚ฐ์„ฑ์„ ๊ทผ๋ณธ์ ์œผ๋กœ ๋ณ€ํ™”์‹œํ‚ค์ง€๋Š” ์•Š์Šต๋‹ˆ๋‹ค(๊ทธ๋ฆฌ๊ณ  ํš๊ธฐ์ ์ธ ๋ฐœ์ „์ด ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค).

์„ธ์…˜ ๊ฐ„(๊ธด) ์บ์‹œ์—๋Š” ๋ณต์žกํ•œ ๋ฌดํšจํ™” ๋…ผ๋ฆฌ๊ฐ€ ํ•„์š”ํ•ฉ๋‹ˆ๋‹ค. ์ผ๋ฐ˜์ ์œผ๋กœ ์„ธ์…˜ ๊ฐ„ ์บ์‹œ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ๋‚˜์ค‘์— ํ•ด๊ฒฐํ• ์ˆ˜๋ก ์ข‹์Šต๋‹ˆ๋‹ค.

์žฅ์  :

  1. ์ฝ”๋“œ๋ฅผ ๋ณ€๊ฒฝํ•˜์ง€ ์•Š๊ณ  ์บ์‹ฑ์„ ๊ตฌํ˜„ํ•ฉ๋‹ˆ๋‹ค.
  2. ์ƒ์‚ฐ์„ฑ์ด ์—ฌ๋Ÿฌ ๋ฒˆ ํ–ฅ์ƒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค(๊ฒฝ์šฐ์— ๋”ฐ๋ผ).

๋‹จ์  :

  1. ์ž˜๋ชป ์‚ฌ์šฉํ•˜๋ฉด ์„ฑ๋Šฅ์ด ์ €ํ•˜๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ์Šต๋‹ˆ๋‹ค.
  2. ํŠนํžˆ ๊ธด ์บ์‹œ์˜ ๊ฒฝ์šฐ ๋ฉ”๋ชจ๋ฆฌ ์˜ค๋ฒ„ํ—ค๋“œ๊ฐ€ ํฝ๋‹ˆ๋‹ค.
  3. ๋ณต์žกํ•œ ๋ฌดํšจํ™”, ๋Ÿฐํƒ€์ž„ ์‹œ ์žฌํ˜„ํ•˜๊ธฐ ์–ด๋ ค์šด ๋ฌธ์ œ๋กœ ์ด์–ด์ง€๋Š” ์˜ค๋ฅ˜์ž…๋‹ˆ๋‹ค.

์บ์‹œ๋Š” ์„ค๊ณ„ ๋ฌธ์ œ๋ฅผ ์‹ ์†ํ•˜๊ฒŒ ํ•ด๊ฒฐํ•˜๋Š” ๋ฐ์—๋งŒ ์‚ฌ์šฉ๋˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋งŽ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ ‡๋‹ค๊ณ ํ•ด์„œ ์‚ฌ์šฉํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค๋Š” ์˜๋ฏธ๋Š” ์•„๋‹™๋‹ˆ๋‹ค. ๊ทธ๋Ÿฌ๋‚˜ ํ•ญ์ƒ ์ฃผ์˜ํ•ด์„œ ๋‹ค๋ฃจ์–ด์•ผ ํ•˜๋ฉฐ ๋จผ์ € ๊ฒฐ๊ณผ์ ์ธ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ํ‰๊ฐ€ํ•œ ๋‹ค์Œ ๊ฒฐ์ •์„ ๋‚ด๋ ค์•ผ ํ•ฉ๋‹ˆ๋‹ค.

์ด ์˜ˆ์—์„œ ์บ์‹œ๋Š” ์•ฝ 25%์˜ ์„ฑ๋Šฅ ํ–ฅ์ƒ์„ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. ๋™์‹œ์— ์บ์‹œ์—๋Š” ๋‹จ์ ์ด ๋งŽ์ด ์žˆ์œผ๋ฏ€๋กœ ์—ฌ๊ธฐ์„œ๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ฒ ์Šต๋‹ˆ๋‹ค.

๊ฒฐ๊ณผ

๊ทธ๋ž˜์„œ ์šฐ๋ฆฌ๋Š” ์ผ๊ด„ ์ฒ˜๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์„œ๋น„์Šค์˜ ๋‹จ์ˆœํ•œ ๊ตฌํ˜„๊ณผ ์†๋„๋ฅผ ๋†’์ด๋Š” ๋ช‡ ๊ฐ€์ง€ ๊ฐ„๋‹จํ•œ ๋ฐฉ๋ฒ•์„ ์‚ดํŽด๋ณด์•˜์Šต๋‹ˆ๋‹ค.

์ด ๋ชจ๋“  ๋ฐฉ๋ฒ•์˜ ๊ฐ€์žฅ ํฐ ์žฅ์ ์€ ๋‹จ์ˆœ์„ฑ์ด๋ฉฐ ์ด๋กœ ์ธํ•ด ๋งŽ์€ ์ฆ๊ฑฐ์šด ๊ฒฐ๊ณผ๊ฐ€ ๋ฐœ์ƒํ•ฉ๋‹ˆ๋‹ค.

์ด๋Ÿฌํ•œ ๋ฐฉ๋ฒ•์˜ ์ผ๋ฐ˜์ ์ธ ๋ฌธ์ œ๋Š” ์ฃผ๋กœ ํŒจํ‚ท ํฌ๊ธฐ์™€ ๊ด€๋ จ๋œ ์„ฑ๋Šฅ ์ €ํ•˜์ž…๋‹ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋Ÿฌํ•œ ์†”๋ฃจ์…˜์ด ๊ท€ํ•˜์—๊ฒŒ ์ ํ•ฉํ•˜์ง€ ์•Š๋‹ค๋ฉด๋ณด๋‹ค ๊ทผ๋ณธ์ ์ธ ๋ฐฉ๋ฒ•์„ ๊ณ ๋ คํ•ด ๋ณผ ๊ฐ€์น˜๊ฐ€ ์žˆ์Šต๋‹ˆ๋‹ค.

์†”๋ฃจ์…˜์„ ์ฐพ์„ ์ˆ˜ ์žˆ๋Š” ๋‘ ๊ฐ€์ง€ ์ฃผ์š” ๋ฐฉํ–ฅ์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

  • ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•œ ๋น„๋™๊ธฐ ์ž‘์—…(ํŒจ๋Ÿฌ๋‹ค์ž„ ์ „ํ™˜์ด ํ•„์š”ํ•˜๋ฏ€๋กœ ์ด ๊ธฐ์‚ฌ์—์„œ๋Š” ๋…ผ์˜ํ•˜์ง€ ์•Š์Œ)
  • ๋™๊ธฐ ์ฒ˜๋ฆฌ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ๋ฐฐ์น˜๋ฅผ ํ™•๋Œ€ํ•ฉ๋‹ˆ๋‹ค.

๋ฐฐ์น˜๋ฅผ ํ™•๋Œ€ํ•˜๋ฉด ์™ธ๋ถ€ ํ˜ธ์ถœ ์ˆ˜๊ฐ€ ํฌ๊ฒŒ ์ค„์–ด๋“ค๊ณ  ๋™์‹œ์— ์ฝ”๋“œ ๋™๊ธฐ๊ฐ€ ์œ ์ง€๋ฉ๋‹ˆ๋‹ค. ๊ธฐ์‚ฌ์˜ ๋‹ค์Œ ๋ถ€๋ถ„์€ ์ด ์ฃผ์ œ์— ๋Œ€ํ•ด ๋‹ค๋ฃฐ ๊ฒƒ์ž…๋‹ˆ๋‹ค.

์ถœ์ฒ˜ : habr.com

์ฝ”๋ฉ˜ํŠธ๋ฅผ ์ถ”๊ฐ€