Skip to Content
๐Ÿš€ ๋Œ€ํ‘œ ํ”„๋กœ์ ํŠธ๐Ÿฆ ๋ชจ์ž„ ๋ฒŒ๊ธˆ ํ†ต์žฅ ๊ฐ“์ƒ๋ฃจํŒ…

๐Ÿฆ ๋ชจ์ž„ ๋ฒŒ๊ธˆ ํ†ต์žฅ ์„œ๋น„์Šค ๊ฐ“์ƒ๋ฃจํŒ…

๐Ÿ“… 2024.09 ~ 2024.10๐Ÿ‘ฅ 6๋ช…๐Ÿ›  Spring Boot, Kafka, Redis

1) ํ”„๋กœ์ ํŠธ ๊ฐœ์š”

  • ๋ชฉํ‘œ: ๋ชจ์ž„ ๊ตฌ์„ฑ์›์˜ ๋ฏธ์…˜ ์ˆ˜ํ–‰์„ ๊ธฐ๋ฐ˜์œผ๋กœ ๋ฒŒ๊ธˆยท์ƒ๊ธˆ์ด ์ž๋™ ์ •์‚ฐ๋˜๋Š” ๊ธˆ์œตํ˜• ํ˜‘์—… ์„œ๋น„์Šค ๋ฐฑ์—”๋“œ ๊ตฌ์ถ•
  • ๋ฐฐ๊ฒฝ: ํ˜ผ์ž ์Šต๊ด€ ํ˜•์„ฑ์— ๋„์ „ํ–ˆ์ง€๋งŒ ์‹คํŒจ ๊ฒฝํ—˜์ด ๋ฐ˜๋ณต๋จ โ†’ ๋ฒŒ๊ธˆยท๋ณด์ƒ์„ ํŒ€ ๋‹จ์œ„๋กœ ๋ฌถ์–ด ์ƒํ˜ธ ๊ฒฌ์ œ๊ฐ€ ๋˜๋ฉด ํšจ๊ณผ์ ์ด๋ผ๋Š” ๊ฒฝํ—˜์—์„œ ์ถœ๋ฐœํ•ด ์„œ๋น„์Šคํ™”
  • ํ•ต์‹ฌ ๊ฐ€์น˜: ์ •ํ™•ํ•œ ์ •์‚ฐ(ACID) ยท ์ค‘๋ณต ์—†๋Š” ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ(idempotency) ยท ์•ˆ์ „ํ•œ ์ธ์ฆ/์ธ๊ฐ€(HTTP-Only Cookie, JWT)
  • ํ”„๋ก ํŠธ ์š”๊ตฌ์‚ฌํ•ญ ์š”์•ฝ(์—ฐ๋™):
    • GPS/์‚ฌ์ง„ ๊ธฐ๋ฐ˜ ๋ฏธ์…˜ ํŒ๋ณ„
    • ์นด์นด์˜คํ†ก ์ดˆ๋Œ€(๋งํฌ) & ์นด์นด์˜ค๋งต ์œ„์น˜ ํ‘œ์‹œ
    • ํŒ€๋ณ„ ๋ฐฐํŒ… โ†’ ๊ฒฐ๊ณผ ๋ฐ˜์˜ โ†’ ์ž๋™ ์ •์‚ฐ/์บ˜๋ฆฐ๋”/๊ฐค๋Ÿฌ๋ฆฌ

2) ์•„ํ‚คํ…์ฒ˜ ๊ฐœ์š”

  • ๊ธฐ์ˆ  ์Šคํƒ:

    Spring Boot ยท Java ยท MySQL ยท Redis(๋ถ„์‚ฐ๋ฝ/์บ์‹œ) ยท Kafka(๋น„๋™๊ธฐ ์ด๋ฒคํŠธ) ยท Docker ยท Jenkins

    (๋ฐฐํฌ: AWS EC2, Nginx ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ)

[Client(Web/App)] โ”‚ REST API โ–ผ [Spring Boot Backend] โ”œโ”€ Auth Service (JWT/์ฟ ํ‚ค) โ”œโ”€ Mission Service (GPS/์‚ฌ์ง„ ๋ฉ”ํƒ€) โ”œโ”€ Betting Service (๋ฐฐํŒ…/์ •์‚ฐ ๋Œ€๊ธฐ) โ”œโ”€ Settlement Service (์ •์‚ฐ/์›์žฅ) โ”œโ”€ Notification Service (Kafka ์ด๋ฒคํŠธ) โ””โ”€ Media Service (S3 ์—…๋กœ๋“œ/์ฒ˜๋ฆฌ)

3) ํ•ต์‹ฌ ์‹œํ€€์Šค ์„ค๊ณ„

A. ์ดˆ๋Œ€ ๋ฐ ๊ฐ€์ž…

  1. ๋ฐฑ์—”๋“œ โ†’ ์ดˆ๋Œ€ ํ† ํฐ ์ƒ์„ฑ
  2. ์นด์นด์˜คํ†ก ๋งํฌ๋กœ ์ „๋‹ฌ
  3. ์‹ ๊ทœ ์‚ฌ์šฉ์ž๊ฐ€ ๋งํฌ ํด๋ฆญ โ†’ ํšŒ์›๊ฐ€์ž… & ๊ทธ๋ฃน ๋“ฑ๋ก

B. ๋ฏธ์…˜ ํŒ๋ณ„

  1. ๋ฏธ์…˜ ์ƒ์„ฑ (missions)
  2. ์‚ฌ์šฉ์ž๊ฐ€ GPS/์‚ฌ์ง„ ์—…๋กœ๋“œ โ†’ ์„œ๋ฒ„ ์ €์žฅ โ†’ mission_proofs ์ƒ์„ฑ
  3. ๊ด€๋ฆฌ์ž ๊ฒ€์ฆ ๋˜๋Š” ์ž๋™ ๊ฒ€์ฆ โ†’ verified='Y'

C. ๋ฐฐํŒ…

  • ์‚ฌ์šฉ์ž๋“ค์ด ํŠน์ • ํŒ€์›์˜ ๋ฏธ์…˜ ์„ฑ๊ณต/์‹คํŒจ์— ๋ฒ ํŒ… (bets)
  • ๋งˆ๊ฐ ์‹œ์ ๊นŒ์ง€ ์ˆ˜์ • ๊ฐ€๋Šฅ

D. ์ •์‚ฐ

  1. ์ •์‚ฐ ํŠธ๋ฆฌ๊ฑฐ (์Šค์ผ€์ค„๋Ÿฌ/๊ด€๋ฆฌ์ž ์‹คํ–‰)
  2. Redis ๋ถ„์‚ฐ๋ฝ์œผ๋กœ ์ค‘๋ณต ์‹คํ–‰ ๋ฐฉ์ง€

4) ERD

image.png

5) ๋ฌธ์ œ ๋ฐ ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

๋ฌธ์ œ 1: ์ด์ฒด ์‹œ ์ค‘๋ณต ์ฒ˜๋ฆฌ ๋ฐ ๋ฐ์ดํ„ฐ ์ถฉ๋Œ ๋ฐœ์ƒ

์ƒํ™ฉ

์ด์ฒด์‹œ ํ•˜๋‚˜์˜ ์‚ฌ์šฉ์ž๊ฐ€ ๋น ๋ฅด๊ฒŒ ๋ฒ„ํŠผ์„ ์—ฌ๋Ÿฌ ๋ฒˆ ๋ˆ„๋ฅผ ๋•Œ, ์ •์‚ฐํ•˜๊ธฐ ์š”์ฒญ์ด ์—ฌ๋Ÿฌ ๋ฒˆ ๊ฐ€๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์–ด์„œ ํ•˜๋‚˜์˜ ์ด์ฒด ์š”์ฒญ์— ๋Œ€ํ•ด ์ค‘๋ณต ํŠธ๋žœ์žญ์…˜์ด ์ƒ์„ฑ๋˜๋Š” ๋ฌธ์ œ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋กœ ์ธํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ์›ํ•˜์ง€ ์•Š๋Š” ์ด์ฒด๊ฐ€ ๋ฐœ์ƒํ•˜๊ฑฐ๋‚˜, ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค.

์„ ํƒ์ง€

์„ ํƒ์ง€์žฅ์ ๋‹จ์ 
1. ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์š”์ฒญ ์ œํ•œ๋‹จ์ˆœํ•˜๊ณ , ์š”์ฒญ์ด ๋‘ ๋ฒˆ ๋“ค์–ด์˜ฌ ๊ฒฝ์šฐ๋ฅผ ์›์ฒœ์ฐจ๋‹จํƒ€ ํด๋ผ์ด์–ธํŠธ ํ™•์žฅ์‹œ ๊ฐ™์€ ๋ฌธ์ œ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์Œ
2. ๊ฑฐ๋ž˜ ๊ณ ์œ  ID(ref_id) + Unique ์ œ์•ฝ์กฐ๊ฑด๋ฉฑ๋“ฑ์„ฑ ๋ณด์žฅ์˜ˆ์™ธ ์ฒ˜๋ฆฌ ํ•„์š”
3. ๋ ˆ๋””์Šค ์บ์‹œ ํ‚ค๋กœ ์š”์ฒญ ์ถ”์ ๋น ๋ฆ„TTL ์„ค์ • ๊ด€๋ฆฌ ํ•„์š”

ํŒ๋‹จ ๊ทผ๊ฑฐ

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

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

  • ํด๋ผ์ด์–ธํŠธ ์š”์ฒญ๋งˆ๋‹ค UUID ๊ธฐ๋ฐ˜ ref_id ์ƒ์„ฑ
  • ๋™์ผ ref_id ์žฌ์š”์ฒญ ์‹œ, โ€œ์ด๋ฏธ ์ฒ˜๋ฆฌ๋œ ์š”์ฒญ์ž…๋‹ˆ๋‹ค.โ€๋ฅผ ์ตœ์ข…์ ์œผ๋กœ ๋ฐ˜ํ™˜

๊ฒฐ๊ณผ

  • ์ค‘๋ณต ์ด์ฒด 0๊ฑด
  • ๋„คํŠธ์›Œํฌ ์žฌ์ „์†ก ์ƒํ™ฉ์—์„œ๋„ ์•ˆ์ •์ ์ธ ์ฒ˜๋ฆฌ

๋ฌธ์ œ 2 : ์ž”์•ก ์—…๋ฐ์ดํŠธ ๋™์‹œ์„ฑ ์ถฉ๋Œ

์ƒํ™ฉ

๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์ด์ฒด์™€ ๋™์‹œ์— ๊ฐ™์€ ๊ณ„์ขŒ์— ์ ‘๊ทผํ•˜๋ฉด ์ž”์•ก ๊ณ„์‚ฐ์ด ๊ผฌ์ด๋Š” ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•˜์˜€์Šต๋‹ˆ๋‹ค.

์„ ํƒ์ง€์žฅ์ ๋‹จ์ 
1. ๋‚™๊ด€์  ๋ฝ์„ฑ๋Šฅ ์šฐ์ˆ˜, ๋ฝ ๊ฒฝํ•ฉ ์—†์Œ์ถฉ๋Œ ์‹œ ์žฌ์‹œ๋„ ํ•„์š”
2. ๋น„๊ด€์  ๋ฝ์™„๋ฒฝํ•œ ์ผ๊ด€์„ฑ ๋ณด์žฅ๋ฝ ๋Œ€๊ธฐ์‹œ๊ฐ„ ์ฆ๊ฐ€
3. Redis ๋ถ„์‚ฐ๋ฝํ™•์žฅ์„ฑ ์ข‹์Œ์ธํ”„๋ผ ๊ด€๋ฆฌ ๋ถ€๋‹ด

ํŒ๋‹จ ๊ทผ๊ฑฐ

์„œ๋น„์Šค ํŠธ๋ž˜ํ”ฝ์ด ๋‚ฎ๊ณ  ๋‹จ์ผ ์ธ์Šคํ„ด์Šค ํ™˜๊ฒฝ์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์—

โ‘ก ๋น„๊ด€์  ๋ฝ ์„ ์ ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค.

๋™์‹œ์— ํ•˜๋‚˜์˜ ๊ณ„์ขŒ์— ์ ‘๊ทผํ•˜๋Š” ์š”์ฒญ์€ ๋Œ€๊ธฐ์‹œ์ผœ ์ผ๊ด€์„ฑ ํ™•๋ณด.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

@Lock(LockModeType.PESSIMISTIC_WRITE) Account findByIdForUpdate(Long id);

๊ฒฐ๊ณผ

  • ๋™์‹œ ์ด์ฒด ์š”์ฒญ 50๊ฑด ํ…Œ์ŠคํŠธ์—์„œ๋„ ์ •ํ™•ํ•œ ์ตœ์ข… ์ž”์•ก ์œ ์ง€
  • ํ‰๊ท  ์ด์ฒด ์ฒ˜๋ฆฌ์‹œ๊ฐ„ 1.0s โ†’ 1.3s (์ผ๋ถ€ ์ฆ๊ฐ€ํ–ˆ์œผ๋‚˜ ์•ˆ์ •์„ฑ ํ™•๋ณด)

๋ฌธ์ œ 3: ์ด์ฒด ์ค‘ ์˜ค๋ฅ˜ ๋ฐœ์ƒ ์‹œ ์ž”์•ก ๋ถˆ์ผ์น˜ ๋ฌธ์ œ

์ƒํ™ฉ

๋ฒŒ๊ธˆ ๋‚ฉ๋ถ€๋‚˜ ์ƒ๊ธˆ ๋ฐฐ๋ถ„ ๊ณผ์ •์—์„œ, ํ•œ ์‚ฌ์šฉ์ž์˜ ์ž”์•ก ์ฐจ๊ฐ์ด ์„ฑ๊ณตํ•˜๊ณ  ๋‹ค๋ฅธ ์‚ฌ์šฉ์ž์˜ ์ž…๊ธˆ์ด ์‹คํŒจํ•˜๋ฉด ์ด ๊ณ„์ขŒํ•ฉ ์ด ๋งž์ง€ ์•Š๋Š” ๋ถˆ์ผ์น˜ ๋ฌธ์ œ๊ฐ€ ์ƒ๊ฒผ์Šต๋‹ˆ๋‹ค.

์„ ํƒ์ง€

์„ ํƒ์ง€์žฅ์ ๋‹จ์ 
โ‘  ๋‹จ์ˆœ ๋‘ ๋ฒˆ์˜ DB UPDATE๊ตฌํ˜„ ๊ฐ„๋‹จ์›์ž์„ฑ ๋ณด์žฅ ๋ถˆ๊ฐ€ (ํ•œ์ชฝ๋งŒ ์„ฑ๊ณต ๊ฐ€๋Šฅ)
โ‘ก Spring @Transactional (๋‹จ์ผ ํŠธ๋žœ์žญ์…˜)์›์ž์„ฑ ๋ณด์žฅ์„œ๋น„์Šค ๊ณ„์ธต ๋ณต์žก๋„ ์ฆ๊ฐ€
โ‘ข SAGA ํŒจํ„ด (๋ณด์ƒ ํŠธ๋žœ์žญ์…˜)๋ถ„์‚ฐ ํŠธ๋žœ์žญ์…˜ ๋Œ€์ฒด ๊ฐ€๋Šฅ๊ตฌํ˜„ ๋ณต์žก, ์„œ๋น„์Šค ๊ทœ๋ชจ ๋Œ€๋น„ ๊ณผํ•จ

ํŒ๋‹จ ๊ทผ๊ฑฐ

์„œ๋น„์Šค๊ฐ€ ๋‹จ์ผ DB ๋‚ด์—์„œ ๋™์ž‘ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์— โ‘ก @Transactional ๊ธฐ๋ฐ˜์˜ ๋‹จ์ผ ํŠธ๋žœ์žญ์…˜์ด ๊ฐ€์žฅ ์ ์ ˆํ–ˆ์Šต๋‹ˆ๋‹ค. ๋‹จ, ํŠธ๋žœ์žญ์…˜ ์•ˆ์—์„œ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋ณด์ƒ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜์—ฌ, ๋ชจ๋“  ์ด์ฒด๊ฐ€ ์‹คํŒจ ์‹œ ์ด์ „ ์ƒํƒœ๋กœ ์™„์ „ํžˆ ๋ณต๊ตฌ๋˜๋„๋ก ์„ค๊ณ„ํ–ˆ์Šต๋‹ˆ๋‹ค.

ํ•ด๊ฒฐ ๋ฐฉ๋ฒ•

@Transactional public void transfer(Long fromId, Long toId, BigDecimal amount) { Account from = accountRepository.findById(fromId) .orElseThrow(() -> new AccountNotFoundException()); Account to = accountRepository.findById(toId) .orElseThrow(() -> new AccountNotFoundException()); if (from.getBalance().compareTo(amount) < 0) { throw new InsufficientBalanceException(); } from.withdraw(amount); to.deposit(amount); transactionRepository.save(new Transaction(from, to, amount, LocalDateTime.now())); }

๊ฒฐ๊ณผ

  • ์ด์ฒด ์ค‘ ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ ๋ชจ๋“  ๋ณ€๊ฒฝ ์ž๋™ ๋กค๋ฐฑ
  • ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค 100ํšŒ ์ค‘ ๋ฐ์ดํ„ฐ ๋ถˆ์ผ์น˜ 0๊ฑด
  • ์ „์ฒด ์ด์ฒด ํŠธ๋žœ์žญ์…˜ ํ‰๊ท  ์ฒ˜๋ฆฌ์‹œ๊ฐ„ 0.9s

5) ์ธ์ฆ ๋ฐ ๋ณด์•ˆ

  • ์ธ์ฆ: JWT + HttpOnly Secure Cookie
  • ์ธ๊ฐ€: ๊ทธ๋ฃน/๋ฏธ์…˜ ์ ‘๊ทผ ์‹œ ๊ถŒํ•œ(ROLE) ๊ธฐ๋ฐ˜ ์ ‘๊ทผ
  • ์ž…๋ ฅ ๊ฒ€์ฆ: @Value ์–ด๋…ธํ…Œ์ด์…˜์„ ์‚ฌ์šฉํ•œ DTO ๋‹จ์œ„ ์ž…๋ ฅ๊ฒ€์ฆ
  • ๋น„๋ฐ€๊ด€๋ฆฌ: Jenkins Credential, .env ํŒŒ์ผ ๋ถ„๋ฆฌ

6) ์„ฑ๋Šฅ ๋ฐ ๋ชจ๋‹ˆํ„ฐ๋ง

  • ์บ์‹ฑ: Redis๋กœ ๊ทธ๋ฃน/๋ฏธ์…˜ ์บ์‹œ (TTL 5~15๋ถ„)
  • ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ: ์ด๋ฏธ์ง€ ๋ฆฌ์‚ฌ์ด์ง• ๋น„๋™๊ธฐํ™” โ†’ ์‘๋‹ต์†๋„ 2.0s โ†’ 0.5s
  • ํ…Œ์ŠคํŠธ: JUnit5 + Testcontainers(MySQL/Redis)
  • ๋กœ๊น…: JSON ๊ตฌ์กฐํ™” ๋กœ๊ทธ + Grafana/ํ”„๋กœ๋ฉ”ํ…Œ์šฐ์Šค๋Œ€์‹œ๋ณด๋“œ

7) CI/CD ๋ฐ ๋ฐฐํฌ

  • Jenkins ํŒŒ์ดํ”„๋ผ์ธ
    • GitHub PR โ†’ ๋นŒ๋“œ/ํ…Œ์ŠคํŠธ โ†’ Docker ์ด๋ฏธ์ง€ โ†’ ๋ฐฐํฌ ์ž๋™ํ™”
  • ์šด์˜ ํ™˜๊ฒฝ
    • AWS EC2 + Nginx (HTTPS)
  • ๋กค๋ฐฑ
    • Docker ์ด๋ฏธ์ง€ ํƒœ๊ทธ ๋‹จ์œ„ ์žฌ๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ ์ค€๋น„

8) ๊ฒฐ๊ณผ ๋ฐ ์„ฑ๊ณผ

  • ์ •์‚ฐ ์ค‘๋ณต ์‹คํ–‰ 0๊ฑด (๋ฝ/๋ฉฑ๋“ฑํ‚ค)
  • ์•Œ๋ฆผ ์ด๋ฒคํŠธ ๋ˆ„๋ฝ๋ฅ  90% ์ด์ƒ ๊ฐ์†Œ (Kafka)
  • ๋ณด์•ˆ ๊ฐ•ํ™”: JWT + HttpOnly ์ฟ ํ‚ค๋กœ ํ† ํฐ ํƒˆ์ทจ ๋ฐฉ์ง€
  • ์ฃผ์š” ๊ธฐ๋Šฅ(API/์ •์‚ฐ/๋ฐฐํŒ…) 100% ๊ตฌํ˜„ ์™„๋ฃŒ

9) ํ”„๋กœ์ ํŠธ ์ด๋ฏธ์ง€

  • ๋ฏธ์…˜์ˆ˜ํ–‰ - ์šด๋™ํ•˜๊ธฐ(์žฅ์†Œ๋ฏธ์…˜)

    image.png

  • ๋ฏธ์…˜์ˆ˜ํ–‰ - ๋ฌผ๊ฑด์ดฌ์˜

image.png

  • ๋ฒ ํŒ…ํ•˜๊ธฐ - ํŒ€์›์˜ ๋ฏธ์…˜ ์ˆ˜ํ–‰ ์—ฌ๋ถ€๋ฅผ ๋†“๊ณ  ๋ฐฐํŒ…

image.png

  • ์ •์‚ฐ ๋ฐ ์บ˜๋ฆฐ๋”์™€ ๊ฐค๋Ÿฌ๋ฆฌ - ๋ฏธ์…˜ ๊ฒฐ๊ณผ๋ฅผ ์บ˜๋ฆฐ๋”์™€ ๊ฐค๋Ÿฌ๋ฆฌ์—์„œ ํ™•์ธ ๋ฐ ์ •์‚ฐ์—์„œ ๋‚ธ ๋ฒŒ๊ธˆ ํ™•์ธ๊ฐ€๋Šฅ

image.png

Last updated on