- ๋น๋์ค ์กฐํ
- ๋น๋์ค ์ข์์
- ์ฑ๋ ๊ตฌ๋
- ์๋ก์ด ๋น๋์ค ์๋ฆผ ์ ์ก
- ๋๊ธ ์์ฑ
- ๋๊ธ ์ฐจ๋จ
- ๋์ ๊ฒ์
- ์ฟ ํฐ ๋ฐ๊ธ
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ์ฌํญ | ์ธ๊ธฐ ๋น๋์ค ์กฐํ API ์์ฒญ์ด ๋งค๋ฒ MySQL์ ์ง์ ๋๋ฌํ์ฌ ํธ๋ํฝ ์ง์ค ์ DB ๋ถํ๊ฐ ๊ธ์ฆํ๊ณ ์๋ต ์๋๊ฐ ์ ํ๋จ |
| ์์ธํ์ | ์บ์ฑ ๋ ์ด์ด ์์ด ๋ชจ๋ ์ฝ๊ธฐ ์์ฒญ์ด MySQL SELECT๋ก ์ฒ๋ฆฌ๋์๊ณ , ๋์ผ ๋น๋์ค์ ๋ํ ์ค๋ณต ์กฐํ๊ฐ ๋ฐ๋ณต์ ์ผ๋ก ๋ฐ์ |
| ํด๊ฒฐ๊ณผ์ | Redis๋ฅผ ์บ์ ๋ ์ด์ด๋ก ๋์
. @Cacheable์ ํ์ฉํด ๋น๋์คยท์ฑ๋ ์ ๋ณด๋ฅผ ์บ์ฑํ๊ณ TTL์ ์ค์ ํ์ฌ ๋ฐ์ดํฐ ์ผ๊ด์ฑ ์ ์ง |
| ๊ฒฐ๊ณผ | DB ์กฐํ ์ฟผ๋ฆฌ ์ ์ฝ 85% ๊ฐ์ / ์๋ต์๊ฐ 200ms โ 15ms (92% ๊ฐ์ ) |
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ์ฌํญ | ๋น๋์ค ์กฐํ๋ง๋ค MySQL์ view_count ์ปฌ๋ผ์ UPDATE ์ฟผ๋ฆฌ๊ฐ ๋ฐ์ํ์ฌ, ์ธ๊ธฐ ๋น๋์ค์ ์ด๋น ์๋ฐฑ ๊ฑด์ Write๊ฐ ์ง์ค๋๋ ๋ณ๋ชฉ ๋ฐ์ |
| ์์ธํ์ | ์ค์๊ฐ ์นด์ดํฐ๋ฅผ RDB์ ์ง์ ์ ์ฉํ๋ ๊ตฌ์กฐ๋ ํ(Row) ๋จ์ ์ ๊ธ์ผ๋ก ์ธํ ๊ฒฝํฉ์ด ๋ถ๊ฐํผํ๋ฉฐ, Write ์ง์ค์ด ์ ์ฒด DB ์ฑ๋ฅ ์ ํ๋ก ์ด์ด์ง |
| ํด๊ฒฐ๊ณผ์ | Redis INCR๋ก ์กฐํ์๋ฅผ ์ธ๋ฉ๋ชจ๋ฆฌ์ ๋์ ํ ๋ค, Spring Batch ์ค์ผ์ค๋ฌ๊ฐ ์ฃผ๊ธฐ์ ์ผ๋ก ์ง๊ณํ์ฌ MySQL์ ๋ฒํฌ UPDATE๋ก ๋๊ธฐํ |
| ๊ฒฐ๊ณผ | DB Write ์ฟผ๋ฆฌ ์ ์ฝ 95% ๊ฐ์ (์กฐํ๋ง๋ค 1๊ฑด โ ๋ฐฐ์น ์ฃผ๊ธฐ๋ง๋ค 1๊ฑด ๋ฒํฌ UPDATE) |
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ์ฌํญ | ์ ์ฐฉ์ ํ์ ์ฟ ํฐ ๋ฐ๊ธ ์ ๋ค์์ ๋์ ์์ฒญ์ด ๋ชฐ๋ ค ์ฌ๊ณ ์๋์ ์ด๊ณผํ๋ ์ค๋ณต ๋ฐ๊ธ์ด ๋ฐ์ |
| ์์ธํ์ | ๋ค์ค ์๋ฒ ํ๊ฒฝ์์ SELECT โ ๊ฒ์ฆ โ INSERT ํ๋ฆ ์ฌ์ด์ Race Condition. JVM ๋ ๋ฒจ์ synchronized๋ ๋ถ์ฐ ํ๊ฒฝ์์ ํจ๋ ฅ ์์ |
| ํด๊ฒฐ๊ณผ์ | Redisson์ ๋ถ์ฐ ๋ฝ(RLock)์ ์ฟ ํฐ ๋ฐ๊ธ ์ ์ฆ์ผ์ด์ค์ ์ ์ฉํ์ฌ, ๋จ์ผ ์๋ฒ๊ฐ ์๊ณ ๊ตฌ์ญ์ ์ ์ ํ๋ ๋์ ๋๋จธ์ง ์์ฒญ์ ๋๊ธฐํ๋๋ก ์ฒ๋ฆฌ |
| ๊ฒฐ๊ณผ | JMeter 500 ๋์ ์์ฒญ ํ ์คํธ ๊ธฐ์ค โ ์ด๊ณผ ๋ฐ๊ธ ๊ฑด์ 0๊ฑด, ์ ํฉ์ฑ 100% ๋ณด์ฅ |
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ์ฌํญ | ๋น๋์ค ์์ฑ ํธ๋์ญ์ ์์์ ๊ตฌ๋ ์ ์๋ฆผ์ ๋๊ธฐ ์ ์กํ์ฌ, ์๋ฆผ ์๋น์ค ์ง์ฐยท์ฅ์ ๋ฐ์ ์ ๋น๋์ค ์์ฑ API์ ์๋ต์๊ฐ์ด ํจ๊ป ์ฆ๊ฐ |
| ์์ธํ์ | ๊ตฌ๋ ์ ์์ ๋น๋กํ์ฌ ์๋ฆผ ๋ฃจํ ์ฒ๋ฆฌ ์๊ฐ์ด ์ ํ ์ฆ๊ฐํ๊ณ , ์ธ๋ถ ์ฅ์ ๊ฐ ํต์ฌ ๋น์ฆ๋์ค ํธ๋์ญ์ ์ ์ง์ ์ํฅ์ ์ค |
| ํด๊ฒฐ๊ณผ์ | ๋น๋์ค ์์ฑ ์๋ฃ ํ Kafka ํ ํฝ์ ์ด๋ฒคํธ๋ฅผ ๋ฐํํ๊ณ , ์๋ฆผ ์ปจ์๋จธ๊ฐ ๋ณ๋๋ก ๊ตฌ๋ ์์๊ฒ ์ ์กํ๋๋ก ๋ถ๋ฆฌ. ์ปจ์๋จธ ํํฐ์ ์๋ฅผ ๋๋ ค ์ํ ํ์ฅ ๊ฐ๋ฅํ ๊ตฌ์กฐ๋ก ์ค๊ณ |
| ๊ฒฐ๊ณผ | ๋น๋์ค ์์ฑ API ์๋ต์๊ฐ ์ฝ 2,500ms โ 150ms (94% ๊ฐ์ ) / ์๋ฆผ ์ฅ์ ๊ฐ ๋น๋์ค ์์ฑ ์ฑ๊ณต ์ฌ๋ถ์ ๋ฌด์ํฅ |
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ์ฌํญ | Kafka consumer๊ฐ ๋ฉ์์ง๋ฅผ ์ฒ๋ฆฌํ ๋ค offset commit ์ ์ ์ฅ์ ๊ฐ ๋ฐ์ํ๋ฉด ๊ฐ์ ์๋ฆผ ์ด๋ฒคํธ๊ฐ ์ฌ์ ๋ฌ๋์ด ๊ตฌ๋ ์์๊ฒ ์ค๋ณต ์๋ฆผ์ ๋ณด๋ผ ์ ์์ |
| ์์ธํ์ | ๋ฉ์์ง์ idempotency key๊ฐ ์๊ณ consumer offset commit๊ณผ DB ํธ๋์ญ์ ์ด ๋ถ๋ฆฌ๋์ด ์์ด, ์ฌ์ฒ๋ฆฌ ์ฌ๋ถ๋ฅผ ํ๋จํ ์์ ์ํ๊ฐ ์์์ |
| ํด๊ฒฐ๊ณผ์ | NewVideoMessage์ eventId๋ฅผ ์ถ๊ฐํ๊ณ , consumer๊ฐ processed_event ํ
์ด๋ธ์ event_id๋ฅผ ๋จผ์ ์ ์ฅํ ๋ค ์๋ฆผ์ ์ฒ๋ฆฌํ๋๋ก ๋ณ๊ฒฝ. Kafka offset์ DB ํธ๋์ญ์
commit ์ฑ๊ณต ํ manual ack๋ก ์ฒ๋ฆฌ |
| ๊ฒฐ๊ณผ | ๋์ผ eventId ์ฌ์ ๋ฌ ์ ์๋ฆผ ๋ก์ง์ ๊ฑด๋๋ฐ๊ณ ack๋ง ์ํ. ์ฒ๋ฆฌ ์ค ์์ธ๊ฐ ๋ฐ์ํ๋ฉด processed_event ์ ์ฅ๋ rollback๋๊ณ ackํ์ง ์์ Kafka ์ฌ์ฒ๋ฆฌ ๊ฐ๋ฅ |
๋ณ๊ฒฝ ํ์ผ ๋ชฉ๋ก.
video-adapters/src/main/java/com/videoservice/manager/mq/dto/NewVideoMessage.javavideo-adapters/src/main/java/com/videoservice/manager/mq/NewVideMessageProducer.javavideo-adapters/src/main/java/com/videoservice/manager/mq/NewVideMessageConsumer.javavideo-adapters/src/main/java/com/videoservice/manager/mq/config/KafkaConsumerConfig.javavideo-adapters/src/main/java/com/videoservice/manager/jpa/event/ProcessedEventJpaEntity.javavideo-adapters/src/main/java/com/videoservice/manager/jpa/event/ProcessedEventJpaRepository.javavideo-adapters/src/test/java/com/videoservice/manager/mq/NewVideMessageConsumerTest.javavideo-adapters/src/test/java/com/videoservice/manager/mq/NewVideMessageProducerTest.java
์ค๊ณ ์ด์ .
processed_event.event_id๋ฅผ PK์ unique key๋ก ์ฌ์ฉํด ์ด๋ฏธ ์ฒ๋ฆฌ๋ ์ด๋ฒคํธ์ ๋์ ์ค๋ณต ์ฒ๋ฆฌ ๊ฒฝ์์ DB ์ ์ฝ์ผ๋ก ๋ฐฉ์ดํฉ๋๋ค.- consumer๋
enable.auto.commit=false์AckMode.MANUAL์ ์ฌ์ฉํด DB ํธ๋์ญ์ ์ด ์ฑ๊ณตํ ๋ค์๋ง offset์ ackํฉ๋๋ค. topic,partition,offset,processed_at์ ํจ๊ป ์ ์ฅํด ์ด์ ์ค ์ค๋ณต ์ฒ๋ฆฌ ํ๋จ๊ณผ ๋ฉ์์ง ์ถ์ ์ด ๊ฐ๋ฅํ๊ฒ ํฉ๋๋ค.
ํ ์คํธ ๋ฐฉ๋ฒ.
- ์ค๋ณต ์์ ๊ฒ์ฆ์
NewVideMessageConsumerTest.consumesSameEventOnlyOnceAndAcknowledgesBothDeliveries์์ ๊ฐ์eventId๋ฅผ ๋ ๋ฒ ์๋นํด ๊ตฌ๋ ์ ์กฐํ๋ 1ํ, ack๋ 2ํ ์ํ๋๋์ง ํ์ธํฉ๋๋ค. - ์์ธ ์ฌ์ฒ๋ฆฌ ๊ฒ์ฆ์
NewVideMessageConsumerTest.doesNotAcknowledgeWhenNotificationProcessingFails์์ ์๋ฆผ ์ฒ๋ฆฌ ์์ธ ์ rollback๋๊ณ ackํ์ง ์๋์ง ํ์ธํฉ๋๋ค. - unique ์ ์ฝ ๊ฒ์ฆ์
NewVideMessageConsumerTest.acknowledgesConcurrentDuplicateWithoutNotification์์DataIntegrityViolationException์ ๋ชจ์ฌํด ์๋ฆผ ์์ด ackํ๋์ง ํ์ธํฉ๋๋ค. - producer eventId ๊ฒ์ฆ์
NewVideMessageProducerTest.sendsMessageWithGeneratedEventId์์ UUID ํ์์eventId๊ฐ ์ฑ์์ง๋์ง ํ์ธํฉ๋๋ค. - ์ ์ฒด adapter ๊ฒ์ฆ ๋ช
๋ น์
./gradlew :video-adapters:test์ ๋๋ค.
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ์ฌํญ | ๋ถ๋ชจ-์์ ๊ณ์ธต ๊ตฌ์กฐ์ ๋๊ธ(๋๋๊ธ)์ MySQL์์ ์ฒ๋ฆฌํ ๋ ์ฌ๊ท CTE ๋๋ ๋ค๋จ๊ณ JOIN์ด ํ์ํ์ฌ ์ฟผ๋ฆฌ ๋ณต์ก๋๊ฐ ๋๊ณ ์๋ต์ด ๋๋ฆผ |
| ์์ธํ์ | RDB๋ ๊ณ์ธตํ ํธ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ํํํ๋ ๋ฐ ์ ํฉํ์ง ์์ผ๋ฉฐ, depth๊ฐ ๊น์ด์ง์๋ก ์ฟผ๋ฆฌ ๋น์ฉ์ด ๊ธฐํ๊ธ์์ ์ผ๋ก ์ฆ๊ฐ |
| ํด๊ฒฐ๊ณผ์ | ๋๊ธ ๋๋ฉ์ธ์ MongoDB๋ก ๋ถ๋ฆฌ. ๋๊ธ ๋ฌธ์ ์์ ์์ ๋๊ธ์ ์ค์ฒฉ ๋ฐฐ์ด๋ก ์ ์ฅํ๊ฑฐ๋ parentId ์ฐธ์กฐ ๋ฐฉ์์ ํ์ฉํ์ฌ ๋จ์ผ ์ฟผ๋ฆฌ๋ก ๊ณ์ธต ์กฐํ ๊ฐ๋ฅํ๊ฒ ์ค๊ณ |
| ๊ฒฐ๊ณผ | 3-depth ๋๊ธ ์กฐํ ์๋ต์๊ฐ ์ฝ 180ms โ 30ms (83% ๊ฐ์ ) / ์คํค๋ง ๋ณ๊ฒฝ ์์ด ๋๊ธ ๋ฉํ๋ฐ์ดํฐ ํ๋ ํ์ฅ ๊ฐ๋ฅ |
| ํญ๋ชฉ | ๋ด์ฉ |
|---|---|
| ๋ฌธ์ ์ฌํญ | Naver ๋์ ๊ฒ์ API๊ฐ ์ผ์์ ์ผ๋ก ์ฅ์ ์ํ์ผ ๋ ์์ฒญ์ด ๊ณ์ ๋์ ๋์ด ์ค๋ ๋ ๊ณ ๊ฐ์ด ๋ฐ์ํ๊ณ , ๋์ ๊ฒ์ ๊ธฐ๋ฅ ์ ์ฒด๊ฐ ์๋ต ๋ถ๊ฐ ์ํ๊ฐ ๋จ |
| ์์ธํ์ | ๋จ์ผ ์ธ๋ถ API ์์กด ๊ตฌ์กฐ์์ Timeout ์ค์ ๋ง์ผ๋ก๋ ์ฅ์ ์ ํ๋ฅผ ๋ง๊ธฐ ์ด๋ ต๊ณ , ์ฅ์ ์ง์ ์ค์๋ ๋ฐ๋ณต ์์ฒญ์ด ๋ฐ์ํ์ฌ ๋ณต๊ตฌ๋ฅผ ๋ฐฉํด |
| ํด๊ฒฐ๊ณผ์ | Resilience4j Circuit Breaker๋ฅผ Naver API ํธ์ถ๋ถ์ ์ ์ฉ. ์ค๋ฅ์จ 50% ์ด๊ณผ ์ ํ๋ก๋ฅผ OPENํ์ฌ ์ฆ์ Kakao API๋ก Fallback ์ ํ. HALF_OPEN ์ํ์์ ํ๋ณต ์ฌ๋ถ๋ฅผ ์๋ ๊ฐ์ง |
| ๊ฒฐ๊ณผ | Naver API ์ฅ์ ์ Fallback ์ ํ ์๊ฐ 500ms ์ด๋ด / ๋์ ๊ฒ์ ์๋น์ค ๊ฐ์ฉ์ฑ 99% ์ด์ ์ ์ง |
- Java
- Spring Boot
- Gradle
- JPA
- QueryDSL
- MySQL
- mongodb
- redis
- kafka
ํฅ์ฌ๊ณ ๋ ์ํคํ ์ณ๋ก ๊ตฌ์ฑํ๊ธฐ ์ํด ํ๋ก์ ํธ๋ฅผ ๋ฉํฐ ๋ชจ๋๋ก ๊ตฌ์ฑํฉ๋๋ค.
์ฃผ์ ๋ชจ๋:
video-core: ๋น์ฆ๋์ค ๋ก์ง๊ณผ ๋๋ฉ์ธ ๋ชจ๋ธ์ ๊ด๋ฆฌํ๋ ๊ธฐ๋ณธ ๋ชจ๋video-apps: ํด๋ผ์ด์ธํธ๊ฐ ํธ์ถํ ์ ์๋ REST API ์ ๋ฐฐ์น์ก์ ๋ชจ์๋ ๋ชจ๋video-adapters: ์ธ๋ถ ์ธํ๋ผ์ ํต์ ํ๊ธฐ ์ํ ๋ชจ๋video-commons: ๊ณตํต์ผ๋ก ์ฌ์ฉ๋๋ ์ ํธ๋ฆฌํฐ๋ฅผ ๋ชจ์๋ ๋ชจ๋
docker compose up
./gradlew bootRun
๋น์ฆ๋์ค ๋ก์ง(core-service, core-usecase)์ ์ธ๋ถ ๊ธฐ์ (JPA, Redis, Kafka ๋ฑ)๋ก๋ถํฐ ์์ ํ ๋ถ๋ฆฌํ๊ธฐ ์ํด ํฅ์ฌ๊ณ ๋ ์ํคํ
์ฒ๋ฅผ ์ฑํํ์ต๋๋ค.
- ์์กด์ฑ ์ญ์ (DIP): Port ์ธํฐํ์ด์ค๋ฅผ ํตํด ๋๋ฉ์ธ์ด ์ธํ๋ผ๋ฅผ ์ง์ ์ฐธ์กฐํ์ง ์์ผ๋ฉฐ, ๊ธฐ์ ๊ต์ฒด ์ Adapter๋ง ์์ ํ๋ฉด ๋ฉ๋๋ค.
- ๋ ๋ฆฝ์ ํ ์คํธ: ๊ฐ ๋ ์ด์ด๋ฅผ Port ๋ชฉํน ๊ธฐ๋ฐ์ผ๋ก ๋จ์ ํ ์คํธํ ์ ์์ด, ์ธ๋ถ ์ธํ๋ผ ์์ด๋ ๋น์ฆ๋์ค ๋ก์ง ๊ฒ์ฆ์ด ๊ฐ๋ฅํฉ๋๋ค.
- ์ปดํ์ผ ํ์ ์์กด์ฑ ๊ฐ์ : ๋ฉํฐ๋ชจ๋ ๊ตฌ์กฐ๋ก ๋ ์ด์ด ๊ฐ ์๋ชป๋ ์์กด์ฑ์ ์ปดํ์ผ ํ์์ ์ฐจ๋จํฉ๋๋ค.
| ๋ชจ๋ | ์๋ |
|---|---|
video-core |
์์ Java๋ก ์์ฑํ์ฌ ์ธ๋ถ ๋ผ์ด๋ธ๋ฌ๋ฆฌ ์์กด ์์ โ ๊ธฐ์ ์ค๋ฆฝ์ฑ ๋ณด์ฅ |
video-adapters |
JPA, Redis, Kafka, MongoDB ๋ฑ ๋ชจ๋ ์ธ๋ถ ๊ธฐ์ ์์กด์ฑ์ ํ ๊ณณ์ ๊ฒฉ๋ฆฌ |
video-apps |
REST API(video-api)์ ๋ฐฐ์น(video-batch) ์ง์
์ ์ ๋ถ๋ฆฌํ์ฌ ๋ฐฐํฌ ๋จ์ ๋
๋ฆฝ์ฑ ํ๋ณด |
video-commons |
๊ณตํต ์ ํธ๋ฆฌํฐ๋ฅผ ๋ณ๋ ๋ชจ๋๋ก ๋ถ๋ฆฌํ์ฌ ์ค๋ณต ์ ๊ฑฐ |
| ๊ธฐ์ | ์ ํ ์ด์ |
|---|---|
| MySQL | ๋น๋์คยท์ฑ๋ยท์ฌ์ฉ์ยท๊ตฌ๋ ยท์ฟ ํฐ ๋ฑ ๊ด๊ณํ ๋ฐ์ดํฐ์ ํธ๋์ญ์ ๋ฌด๊ฒฐ์ฑ ๋ณด์ฅ์ด ํ์ํ๊ณ , ๊ตฌ๋ ๊ด๊ณ ๋ฑ ๋ณต์กํ JOIN ์ฟผ๋ฆฌ๊ฐ ์๊ตฌ๋๋ ์๊ตฌ ์ ์ฅ์ |
| MongoDB | ๋๊ธ์ ๋ถ๋ชจ-์์ ๊ณ์ธต ๊ตฌ์กฐ์ ์คํค๋ง ์ ์ฐ์ฑ์ด ํ์ํ๋ฉฐ, ๊ณ ๋น๋ ์ฝ๊ธฐ/์ฐ๊ธฐ์ RDB๋ณด๋ค ์ ๋ฆฌ |
| Redis | โ ๋น๋์คยท์ฑ๋ ์บ์๋ก DB ๋ถํ ๊ฐ์, โก์กฐํ์ ๊ณ ๋น๋ Write๋ฅผ ๋ฉ๋ชจ๋ฆฌ์์ ์ฒ๋ฆฌ ํ ๋ฐฐ์น ๋๊ธฐํ, โขRedisson ๋ถ์ฐ ๋ฝ์ผ๋ก ์ฟ ํฐ ๋ฐ๊ธ ๋์์ฑ ์ ์ด, โฃ์ฌ์ฉ์ ์ธ์ ๊ด๋ฆฌ |
| Kafka | ์ ๋น๋์ค ์ ๋ก๋ ์ ๊ตฌ๋ ์ ์๋ฆผ์ ๋น๋๊ธฐ๋ก ๋ถ๋ฆฌํ์ฌ ์๋ฆผ ์ฅ์ ๊ฐ ๋น๋์ค ์์ฑ ํธ๋์ญ์ ์ ์ํฅ ์๋๋ก ์ค๊ณ, ์ปจ์๋จธ ์ํ ํ์ฅ์ผ๋ก ๋๋ ๊ตฌ๋ ์ ์ฒ๋ฆฌ ๊ฐ๋ฅ |
| QueryDSL | ์ฑ๋๋ณ ๋น๋์ค ์กฐํ ๋ฑ ๋์ ์กฐ๊ฑด ์ฟผ๋ฆฌ๋ฅผ ์ปดํ์ผ ํ์์ ํ์ ์์ ํ๊ฒ ์์ฑํ์ฌ ๋ฐํ์ ์ค๋ฅ ๋ฐฉ์ง |
| Resilience4j (Circuit Breaker) | ๋์ ๊ฒ์ ์ธ๋ถ API(Naver)์ ์ฅ์ ๋ฅผ ๊ฐ์งํด ์๋์ผ๋ก Kakao API๋ก Fallback, ์ธ๋ถ ์์กด์ฑ ์ฅ์ ์ ๋ด๋ถ ์ ํ ๋ฐฉ์ง |
| Spring Batch | Redis์ ๋์ ๋ ๋น๋์ค ์กฐํ์๋ฅผ ์ฃผ๊ธฐ์ ์ผ๋ก MySQL์ ๋๊ธฐํํ๋ ๋๋ ๋ฐ์ดํฐ ์ฒ๋ฆฌ ์์ ์ ์ ํฉ |
- ๋น๋์ค ์์ฑ
- ์ฑ๋ ๊ตฌ๋ ๋ฑ๋ก
| ์๋๋ฆฌ์ค | ๊ฒ์ฆ ๋์ | ํต์ฌ ์งํ |
|---|---|---|
| video-load | Redis ์บ์ (๋น๋์ค ์กฐํ), Redis INCR (์กฐํ์) | ์บ์ ํํธ์จ >80%, P95 <100ms |
| comment-load | MongoDB CRUD, ์ปค์ ํ์ด์ง๋ค์ด์ | P95 <200ms (์์ฑ), <150ms (์กฐํ) |
| coupon-load | Redisson ๋ถ์ฐ ๋ฝ, Race Condition ๋ฐฉ์ด | ์ด๊ณผ ๋ฐ๊ธ 0๊ฑด (โค500๊ฑด) |
| stress-test | ์ ์ฒด ์์คํ ํ๊ณ์ | P99 <2000ms |
| spike-test | Circuit Breaker (Resilience4j), ๊ธ๊ฒฉํ ํธ๋ํฝ ๋์ | P99 <3000ms |
MySQL์ ๋ค์ ID๋ก ๋ฐ์ดํฐ๊ฐ ์กด์ฌํด์ผ ํฉ๋๋ค:
- ๋น๋์ค:
video-seed-001~video-seed-005 - ์ฑ๋:
channel-seed-001~channel-seed-003
MongoDB์ ๋ค์ ID๋ก ๋ฐ์ดํฐ๊ฐ ์กด์ฌํด์ผ ํฉ๋๋ค:
- ๋๊ธ:
comment-seed-001~comment-seed-003 - ๋ถ๋ชจ ๋๊ธ:
parent-comment-001~parent-comment-002
๋ค์ ํ ํฐ์ด Redis์ ์ธ์
์ผ๋ก ๋ฑ๋ก๋์ด์ผ ํฉ๋๋ค (x-auth-key ํค๋ ์ธ์ฆ):
test-auth-token-001~test-auth-token-005
์ฟ ํฐ ๋ถ์ฐ ๋ฝ ํ ์คํธ ์ ๋ค์ ์ ์ฑ ์ด ์กด์ฌํด์ผ ํฉ๋๋ค:
- ID:
coupon-policy-concurrent-001 totalQuantity: 500
# Smoke Test (๊ธฐ๋ณธ ๋์ ํ์ธ โ ๋ฐฐํฌ ์งํ ํ์)
k6 run k6-scripts/scenarios/smoke/smoke-test.js
# Video Load Test (Redis ์บ์ ์ฑ๋ฅ)
k6 run k6-scripts/scenarios/load/video-load.js
# Video Cache Stampede Test (ํซํค ์บ์ ๋ง๋ฃ ๋์ ์์ฒญ)
k6 run k6-scripts/scenarios/load/video-cache-stampede.js
# Comment Load Test (MongoDB CRUD)
k6 run k6-scripts/scenarios/load/comment-load.js
# Coupon Load Test (Redisson ๋ถ์ฐ ๋ฝ) โ ํต์ฌ
k6 run k6-scripts/scenarios/load/coupon-load.js
# Stress Test (ํ๊ณ์ ํ์)
k6 run k6-scripts/scenarios/stress/stress-test.js
# Spike Test (ํธ๋ํฝ ํญ์ฆ)
k6 run k6-scripts/scenarios/spike/spike-test.jsBASE_URL=http://your-server:8080 k6 run k6-scripts/scenarios/smoke/smoke-test.js๋์ API๋ ๋น๋์ค ์์ธ ์กฐํ GET /api/v1/videos/{videoId}์
๋๋ค. ๊ฐ์ ํซ ๋น๋์ค๋ฅผ ํ ๋ฒ warm-upํด์ Redis์ ์ ์ฅํ ๋ค, ์งง์ TTL ๋ง๋ฃ ์งํ 100~300๊ฐ VU๊ฐ ๋์์ ๊ฐ์ ํค๋ฅผ ์กฐํํฉ๋๋ค.
๋ฌธ์ ์ฌํ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
# ๊ฐ์ ์ ๋ชจ๋๋ก ์ฑ ์คํ
VIDEO_CACHE_STAMPEDE_PROTECTION_ENABLED=false \
VIDEO_CACHE_DETAIL_TTL=3s \
./gradlew :video-apps:app-api:bootRun
# TTL ๋ง๋ฃ ์งํ 200 ๋์ ์์ฒญ
STAMPEDE_CONCURRENCY=200 \
CACHE_TTL_SECONDS=3 \
HOT_VIDEO_ID=video-seed-001 \
k6 run k6-scripts/scenarios/load/video-cache-stampede.js๊ฐ์ ํ ๋ฐฉ์์ ๋ค์๊ณผ ๊ฐ์ต๋๋ค.
# ๊ฐ์ ํ ๋ชจ๋๋ก ์ฑ ์คํ
VIDEO_CACHE_STAMPEDE_PROTECTION_ENABLED=true \
VIDEO_CACHE_DETAIL_TTL=3s \
./gradlew :video-apps:app-api:bootRun
# ๊ฐ์ ์กฐ๊ฑด์ผ๋ก ์ฌ์คํ
STAMPEDE_CONCURRENCY=200 \
CACHE_TTL_SECONDS=3 \
HOT_VIDEO_ID=video-seed-001 \
k6 run k6-scripts/scenarios/load/video-cache-stampede.js์์ธ์ Cache-Aside ๊ตฌ์กฐ์์ ํซํค TTL์ด ๋ง๋ฃ๋๋ ์๊ฐ ๋ชจ๋ ์์ฒญ์ด ๋์์ cache miss๋ฅผ ๋ง๊ณ MySQL ์กฐํ๋ก ์ง์
ํ๋ ๊ฒ์
๋๋ค. ๊ฐ์ ํ ๋ชจ๋๋ Redisson RLock ๊ธฐ๋ฐ per-key lock์ ์ฌ์ฉํ๊ณ , ๋ฝ ํ๋ ํ DB ์กฐํ ์ ์บ์๋ฅผ ํ ๋ฒ ๋ ํ์ธํฉ๋๋ค. ๋ฝ์ ์ป์ง ๋ชปํ ์์ฒญ์ ์งง๊ฒ ๋๊ธฐํ๋ฉฐ ์บ์๋ฅผ ์ฌ์กฐํํ๊ณ , ์ ํ ์๊ฐ ๋ด ์บ์๊ฐ ์ฑ์์ง๋ฉด DB๋ฅผ ์กฐํํ์ง ์์ต๋๋ค. ๋๊น์ง ์บ์๊ฐ ๋น์ด ์์ผ๋ฉด ๋๊ธฐ ์ ํ ์๊ฐ ์ด๊ณผ๋ฅผ ๊ธฐ๋กํ ๋ค DB fallback์ผ๋ก ์๋ต์ ์ ์งํฉ๋๋ค.
Actuator/Prometheus์์ ํ์ธํ ์ ํ๋ฆฌ์ผ์ด์ ๋ฉํธ๋ฆญ์ ๋๋ค.
| ๋ฉํธ๋ฆญ | ์๋ฏธ |
|---|---|
video.cache.hit |
๋น๋์ค ์์ธ ์บ์ hit |
video.cache.miss |
๋น๋์ค ์์ธ ์ด๊ธฐ cache miss |
video.cache.db.load |
์ค์ MySQL ๋น๋์ค ์์ธ ์กฐํ |
video.cache.lock.acquired |
stampede lock ํ๋ |
video.cache.lock.wait |
lock ํ๋ ์คํจ ํ ์บ์ ๋๊ธฐ |
video.cache.lock.timeout |
๋๊ธฐ ์ ํ ์๊ฐ ์ด๊ณผ ํ DB fallback |
๊ฒฐ๊ณผ ๊ธฐ๋ก ํ์ ๋๋ค. ๋ก์ปฌ ์ธํ๋ผ์ ์๋ ๋ฐ์ดํฐ๊ฐ ์ค๋น๋ ํ๊ฒฝ์์ ์ ๋ช ๋ น์ผ๋ก ์ธก์ ํ ๊ฐ์ ๊ธฐ๋กํฉ๋๋ค.
| ๋ชจ๋ | ๋์ ์์ฒญ | TTL | video.cache.db.load ์ฆ๊ฐ๋ |
video.cache.lock.acquired ์ฆ๊ฐ๋ |
video.cache.lock.timeout ์ฆ๊ฐ๋ |
k6 P95 | ๋น๊ณ |
|---|---|---|---|---|---|---|---|
| ๊ฐ์ ์ | 200 | 3s | ์ฝ 200๊ฑด | 0๊ฑด | 0๊ฑด | ์ฝ 300ms | stampede protection off, ์์๊ฐ |
| ๊ฐ์ ํ | 200 | 3s | ์ฝ 1๊ฑด | 1๊ฑด | 0๊ฑด | ์ฝ 80ms | stampede protection on, ์์๊ฐ |
k6 run --out json=result.json k6-scripts/scenarios/load/coupon-load.jsk6 run --out influxdb=http://localhost:8086/k6 k6-scripts/scenarios/stress/stress-test.js| ์๋๋ฆฌ์ค | ๋ชฉํ ์งํ | ์๊ณ๊ฐ |
|---|---|---|
| Smoke | P95 ์๋ต์๊ฐ | < 500ms |
| Smoke | ์คํจ์จ | < 1% |
| Video Load | P95 ์๋ต์๊ฐ (์กฐํ) | < 100ms |
| Video Load | Redis ์บ์ ํํธ์จ | > 80% |
| Video Load | ์คํจ์จ | < 1% |
| Comment Load | P95 ์๋ต์๊ฐ (์์ฑ) | < 200ms |
| Comment Load | P95 ์๋ต์๊ฐ (๋ชฉ๋ก) | < 150ms |
| Comment Load | ์คํจ์จ | < 2% |
| Coupon Load | ๋ฐ๊ธ ์ฑ๊ณต ๊ฑด์ | โค 500๊ฑด (์ ๋) |
| Coupon Load | ์๋ฒ ์๋ฌ์จ | < 5% |
| Stress | P99 ์๋ต์๊ฐ | < 2000ms |
| Stress | ์คํจ์จ | < 10% |
| Spike | P99 ์๋ต์๊ฐ | < 3000ms |
| Spike | ์คํจ์จ | < 15% |
| ๊ธฐ๋ฅ | ๋ฉ์๋ | URL | ์ธ์ฆ ํ์ |
|---|---|---|---|
| ๋น๋์ค ์กฐํ | GET | /api/v1/videos/{videoId} |
๋ถํ์ |
| ์ฑ๋๋ณ ๋น๋์ค ๋ชฉ๋ก | GET | /api/v1/videos?channelId={id} |
๋ถํ์ |
| ๋น๋์ค ์์ฑ | POST | /api/v1/videos |
๋ถํ์ |
| ์กฐํ์ ์ฆ๊ฐ | POST | /api/v1/videos/{videoId}/view |
๋ถํ์ |
| ์ฑ๋ ์์ฑ | POST | /api/v1/channels |
๋ถํ์ |
| ์ฑ๋ ์์ | PUT | /api/v1/channels/{channelId} |
๋ถํ์ |
| ์ฑ๋ ์กฐํ | GET | /api/v1/channels/{channelId} |
๋ถํ์ |
| ๋๊ธ ์์ฑ | POST | /api/v1/comments |
ํ์ |
| ๋๊ธ ์์ | PUT | /api/v1/comments/{commentId} |
ํ์ |
| ๋๊ธ ์ญ์ | DELETE | /api/v1/comments/{commentId} |
ํ์ |
| ๋๊ธ ๋ชฉ๋ก | GET | /api/v1/comments/list?videoId=&order=&offset=&maxSize= |
ํ์ |
| ๋๋๊ธ ๋ชฉ๋ก | GET | /api/v1/comments/reply?parentId=&offset=&maxSize= |
๋ถํ์ |
| ๊ตฌ๋ | POST | /api/v1/subscribe?channelId={id} |
ํ์ |
| ๊ตฌ๋ ์ทจ์ | DELETE | /api/v1/subscribe?subscribeId={id} |
ํ์ |
| ๋ด ๊ตฌ๋ ๋ชฉ๋ก | GET | /api/v1/subscribe/mine |
ํ์ |
| ์ฟ ํฐ ๋ฐ๊ธ | POST | /api/v1/coupons?couponPolicyId={id} |
ํ์ |
| ๋น๋์ค ์ข์์ | POST | /api/v1/videos/rate?videoId=&rating=like |
ํ์ |
| ๋์ ๊ฒ์ | GET | /api/v1/book/list?query=&page=&size= |
๋ถํ์ |
์ธ์ฆ:
x-auth-keyํค๋์ ํ ํฐ ๊ฐ ์ ๋ฌ (HeaderAttribute.java ์ฐธ์กฐ)
| ๋ฉํธ๋ฆญ | ์ ํ | ์ค๋ช |
|---|---|---|
video_cache_hit_rate |
Rate | 50ms ๋ฏธ๋ง ์๋ต = Redis ์บ์ ํํธ ํ์ |
video_get_duration |
Trend | ๋น๋์ค ๋จ๊ฑด ์กฐํ ์๋ต์๊ฐ |
video_view_duration |
Trend | ์กฐํ์ ์ฆ๊ฐ ์๋ต์๊ฐ |
stampede_video_get_duration |
Trend | cache stampede ์คํ์ ๋น๋์ค ๋จ๊ฑด ์กฐํ ์๋ต์๊ฐ |
stampede_success_total |
Counter | cache stampede ์คํ์ ์ฑ๊ณต ์๋ต ์ |
stampede_failed_total |
Counter | cache stampede ์คํ์ ์คํจ ์๋ต ์ |
comment_create_duration |
Trend | ๋๊ธ ์์ฑ ์๋ต์๊ฐ |
comment_list_duration |
Trend | ๋๊ธ ๋ชฉ๋ก ์กฐํ ์๋ต์๊ฐ |
comment_reply_duration |
Trend | ๋๋๊ธ ๋ชฉ๋ก ์กฐํ ์๋ต์๊ฐ |
coupon_issue_success_total |
Counter | ์ฟ ํฐ ๋ฐ๊ธ ์ฑ๊ณต ๊ฑด์ |
coupon_issue_conflict_total |
Counter | ์ฌ๊ณ ์์ง/์ค๋ณต (์ ์ ์คํจ) |
coupon_issue_failed_total |
Counter | ์๋ฒ ์๋ฌ (๋น์ ์ ์คํจ) |
coupon_success_rate |
Rate | ์ฟ ํฐ ๋ฐ๊ธ ์ฑ๊ณต๋ฅ |
circuit_breaker_open_total |
Counter | Circuit Breaker ์คํ ํ์ |
spike_video_get_duration |
Trend | ์คํ์ดํฌ ๊ตฌ๊ฐ ๋น๋์ค ์กฐํ ์๋ต์๊ฐ |
spike_error_rate |
Rate | ์คํ์ดํฌ ๊ตฌ๊ฐ ์๋ฌ์จ |
๋ค์์ ๋ฃฐ์ ๋ฐ๋ฆ ๋๋ค.
| ์์ ํ๊ทธ | ์ค๋ช |
|---|---|
feat |
์๋ก์ด ๊ธฐ๋ฅ ์ถ๊ฐ / ์ผ๋ถ ์ฝ๋ ์ถ๊ฐ / ์ผ๋ถ ์ฝ๋ ์์ (๋ฆฌํฉํ ๋ง๊ณผ ๊ตฌ๋ถ) / ๋์์ธ ์์ ์์ |
fix |
๋ฒ๊ทธ ์์ |
refactor |
์ฝ๋ ๋ฆฌํฉํ ๋ง |
style |
์ฝ๋ ์๋ฏธ์ ์ํฅ์ ์ฃผ์ง ์๋ ๋ณ๊ฒฝ์ฌํญ (์ฝ๋ ํฌ๋งทํ , ์คํ ์์ , ๋ณ์๋ช ๋ณ๊ฒฝ, ์์ ์ถ๊ฐ) |
chore |
๋น๋ ๋ถ๋ถ ํน์ ํจํค์ง ๋งค๋์ ์์ ์ฌํญ / ํ์ผ ์ด๋ฆ ๋ณ๊ฒฝ ๋ฐ ์์น ๋ณ๊ฒฝ / ํ์ผ ์ญ์ |
docs |
๋ฌธ์ ์ถ๊ฐ ๋ฐ ์์ |
rename |
ํจํค์ง ํน์ ํด๋๋ช , ํด๋์ค๋ช ์์ (๋จ๋ ์ผ๋ก ์ํํ์์ ์) |
remove |
ํจํค์ง ํน์ ํด๋, ํด๋์ค๋ฅผ ์ญ์ ํ์์ ๋ (๋จ๋ ์ผ๋ก ์ํํ์์ ์) |