[๐Ÿ”ฅTroubleShooting - MoodBuddy๐Ÿ”ฅ] Redis ์บ์‹œ ์ „๋žต ๋„ˆ๋ฌด ์˜ค๋ฒ„์ŠคํŒฉ...?

._mungยท2025๋…„ 3์›” 13์ผ
0

MoodBuddy

๋ชฉ๋ก ๋ณด๊ธฐ
5/8

๐Ÿ“Œ ํ”„๋กœ์ ํŠธ ์†Œ๊ฐœ

ํŒ€์› : PM(1) / Design(1) / Frontend(2) / Backend(3)
๊ธฐ๊ฐ„ : 2024.03 ~ 2025.03
๋งํฌ : https://github.com/M-ung/MoodBuddy_Server
์„œ๋น„์Šค ๋‚ด์šฉ : ์‚ฌ์šฉ์ž๊ฐ€ ์ž‘์„ฑํ•œ ์ผ๊ธฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๊ฐ์ • ๋ถ„์„ํ•˜๋Š” ์›น ์„œ๋น„์Šค
์†Œํ†ต : GitHub, Slack, Notion, Discord


๐Ÿ”ฅTroubleShooting๐Ÿ”ฅ

Problems

์ผ๊ธฐ ๋ชฉ๋ก ์กฐํšŒ ์†๋„๋ฅผ ๋†’์ด๊ธฐ ์œ„ํ•ด์„œ Redis ์บ์‹œ๋ฅผ ์ ์šฉํ–ˆ๋‹ค. ํ•˜์ง€๋งŒ ๋‹จ์ˆœํžˆ ์‘๋‹ต ์กฐํšŒ๋ฅผ ๋†’์ด๊ธฐ ์œ„ํ•ด Redis๋ฅผ ์ ์šฉํ•˜์—ฌ, ์˜คํžˆ๋ ค ๊ด€๋ฆฌ ์ธก๋ฉด์—์„œ ๋ถ€๋‹ด์„ ๋А๊ผˆ๋‹ค.

๋˜ํ•œ, ์‚ฌ์šฉ์ž id์™€ ํŽ˜์ด์ง€ ๋ฒˆํ˜ธ ์กฐํ•ฉ์œผ๋กœ ์บ์‹œ ํ‚ค๋ฅผ ์ƒ์„ฑํ•˜๋‹ค ๋ณด๋‹ˆ, ์‚ฌ์šฉ์ž ์ˆ˜์™€ ํŽ˜์ด์ง€ ์ˆ˜์— ๋น„๋ก€ํ•ด ์บ์‹œ ํ‚ค๊ฐ€ ๊ธ‰๊ฒฉํžˆ ์ฆ๊ฐ€ํ•˜๊ฒŒ ๋˜์—ˆ๋‹ค. ์ด ์—ญ์‹œ... ๋ฉ”๋ชจ๋ฆฌ ์ž์›์„ ์ง€์†์ ์œผ๋กœ ์†Œ๋น„ํ•˜๋Š” ์›์ธ์ด ๋˜์—ˆ๋‹ค.

๊ฒฐ๊ตญ ์‚ฌ์šฉ์ž๊ฐ€ ๋Œ€๋Ÿ‰์œผ๋กœ ๋ชฐ๋ฆด ๊ฐ€๋Šฅ์„ฑ์ด ๋‚ฎ์€ ๊ธฐ๋Šฅ์— Redis๋ฅผ ์—ฐ๊ฒฐํ•˜๋ฉด์„œ ์˜คํžˆ๋ ค ๊ฐœ๋ฐœ ๋ฐ ์šด์˜ ๋น„์šฉ์„ ๋†’์ด๋Š” ์š”์ธ์ด ๋˜์—ˆ๋‹ค.

์•„๋ž˜ k6 ๊ฒฐ๊ณผ๋Š” Redis ์บ์‹œ๋ฅผ ์ ์šฉํ–ˆ์„ ๋•Œ์˜ ๊ฒฐ๊ณผ์ด๋‹ค.

๐Ÿ“ ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํšŒ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ (Redis Cache ์ ์šฉ)


How

  1. Redis ์บ์‹œ ์„ค์ •์„ ์ œ๊ฑฐํ•˜๊ณ , Spring Cache๋ฅผ ์ ์šฉ.
  2. ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํšŒ์šฉ ํ…Œ์ด๋ธ”์„ ๋”ฐ๋กœ ๋งŒ๋“ค์–ด์„œ ์กฐํšŒ๋ฅผ ํ•  ๋•Œ ํ•ด๋‹น ํ…Œ์ด๋ธ”์—๋งŒ ์ ‘๊ทผํ•˜๋„๋ก ๊ตฌํ˜„.
  3. ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํšŒ์šฉ ํ…Œ์ด๋ธ”์— ์ž์ฃผ ์กฐํšŒ๋˜๋Š” ์ธ๋ฑ์Šค ์ ์šฉ.

Process

์•„๋ž˜์™€ ๊ฐ™์ด ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํšŒ์—๋งŒ ํ•„์š”ํ•œ ํ…Œ์ด๋ธ”์„ ๋”ฐ๋กœ ๋งŒ๋“ค์—ˆ๋‹ค.

Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Table(
        name = "diary_query",
        uniqueConstraints = @UniqueConstraint(columnNames = {"userId", "date"}),
        indexes = {
                @Index(name = "idx_id_user_Id_mood_buddy_status", columnList = "diary_id, user_id, mood_buddy_status"),
                @Index(name = "idx_user_Id_mood_buddy_status_date", columnList = "user_id, mood_buddy_status, date")
        }
)
public class DiaryQuery {
    @Id
    @Column(name = "diary_id")
    private Long diaryId;

    @Column(name = "title", nullable = false)
    private String title;

    @Column(name = "date", nullable = false)
    private LocalDate date;

    @Column(name = "content", nullable = false, columnDefinition = "text")
    private String content;

    @Column(name = "user_id", nullable = false, columnDefinition = "bigint")
    private Long userId;
    
    @Column(name = "thumbnail", columnDefinition = "text")
    private String thumbnail;

    @Enumerated(EnumType.STRING)
    @Column(name = "emotion")
    private DiaryEmotion emotion;

    @Enumerated(EnumType.STRING)
    @Column(name = "subject")
    private DiarySubject subject;

    @Enumerated(EnumType.STRING)
    @Column(name = "mood_buddy_status")
    private MoodBuddyStatus moodBuddyStatus;
}

๊ทธ๋ฆฌ๊ณ  yml ์„ค์ •์„ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ณ€๊ฒฝํ•˜์—ฌ, Spring Cache๋กœ ๋ณ€๊ฒฝํ–ˆ๋‹ค.

spring:
  threads:
    virtual:
      enabled: true
  cache:
    type: simple

Test Scenario

๐Ÿ“Œ ๋ถ€ํ•˜ ์ฆ๊ฐ€ ๋‹จ๊ณ„

  • 2๋ถ„ ๋™์•ˆ 500๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ฆ๊ฐ€
  • 2๋ถ„ ๋™์•ˆ 1000๋ช…์˜ ์‚ฌ์šฉ์ž๊ฐ€ ์ฆ๊ฐ€
  • 2๋ถ„ ๋™์•ˆ 1000๋ช… ์œ ์ง€ (์ตœ๋Œ€ ๋ถ€ํ•˜)
  • 2๋ถ„ ๋™์•ˆ 500๋ช…์œผ๋กœ ๊ฐ์†Œ
  • 2๋ถ„ ๋™์•ˆ 0๋ช…์œผ๋กœ ๊ฐ์†Œ
  • ์ด 10๋ถ„ ํ…Œ์ŠคํŠธ ์ง„ํ–‰

๐Ÿ“Œ ํ…Œ์ŠคํŠธ ์š”์ฒญ

  • 1000๋ช…์˜ ์‚ฌ์šฉ์ž ์ค‘ ๋žœ๋ค์œผ๋กœ ๋กœ๊ทธ์ธ ์‹œ๋„
  • ์‘๋‹ต์—์„œ accessToken์„ ์ถ”์ถœ
  • accessToken์„ ๊ฐ€์ง€๊ณ  getDiaries API ์š”์ฒญ
  • ์ด 50๊ฐœ์˜ ์ผ๊ธฐ๋ฅผ ๊ฐ€์ ธ์˜ค๋ฉฐ, ํŽ˜์ด์ง€๋‹น 20๊ฐœ์”ฉ ์š”์ฒญ
  • ๊ฐ ์š”์ฒญ ํ›„ 1์ดˆ ๋™์•ˆ sleep

๐Ÿ“Œ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ ๊ธฐ์ค€

  • ์‘๋‹ต ์†๋„: 95% ์ด์ƒ์˜ ์š”์ฒญ(p(95))์ด 100ms ์ดํ•˜๋กœ ์ฒ˜๋ฆฌ๋˜์•ผ ํ•จ.
  • ์—๋Ÿฌ์œจ: ์ „์ฒด ์š”์ฒญ ์ค‘ 1% ๋ฏธ๋งŒ๋งŒ ์‹คํŒจ

Result Analysis

๐Ÿ“ ์ผ๊ธฐ ๋ชฉ๋ก ์กฐํšŒ ํ…Œ์ŠคํŠธ ๊ฒฐ๊ณผ (Spring Cache ์ ์šฉ)

์ง€ํ‘œRedis Cache ์ ์šฉSpring Cache ์ ์šฉ๊ฐœ์„ ์œจ
์ด ์š”์ฒญ ์ˆ˜ (http_reqs)474,188473,6520.11% ๊ฐ์†Œ
์‘๋‹ต ์‹œ๊ฐ„ ํ‰๊ท  (http_req_duration avg)12.33ms13.2ms7.04% ์ฆ๊ฐ€
์‘๋‹ต ์‹œ๊ฐ„ 90ํผ์„ผํŠธ (p(90))23.89ms27.01ms13.04% ์ฆ๊ฐ€
์‘๋‹ต ์‹œ๊ฐ„ 95ํผ์„ผํŠธ (p(95))33.22ms38.22ms15.05% ์ฆ๊ฐ€
๋Œ€๊ธฐ ์‹œ๊ฐ„ ํ‰๊ท  (http_req_waiting avg)12.26ms13.13ms7.1% ์ฆ๊ฐ€
์ตœ๋Œ€ ์‘๋‹ต ์‹œ๊ฐ„ (http_req_duration max)979.22ms2.08s112.4% ์ฆ๊ฐ€

Thoughts

์ด๋ฒˆ ํ…Œ์ŠคํŠธ๋Š” ํ˜„์‹ค๋ณด๋‹ค ํ›จ์”ฌ ๋†’์€ ๋ถ€ํ•˜์ธ 1000๋ช…์˜ ๋™์‹œ ์‚ฌ์šฉ์ž(VUs)๋ฅผ ๊ฐ€์ •ํ•œ ํ™˜๊ฒฝ์—์„œ ์ง„ํ–‰๋˜์—ˆ๋‹ค.
์ด๋Ÿฌํ•œ ์กฐ๊ฑด์€ ์‹ค์„œ๋น„์Šค์—์„œ ์ž์ฃผ ๋ฐœ์ƒํ•˜์ง„ ์•Š์ง€๋งŒ, ์ตœ์•…์˜ ์ƒํ™ฉ์„ ๊ฐ€์ •ํ•œ ์„ฑ๋Šฅ ์•ˆ์ •์„ฑ ํ‰๊ฐ€๋กœ๋Š” ์˜๋ฏธ๊ฐ€ ์žˆ๋‹ค.

Spring Cache๋Š” Redis Cache์— ๋น„ํ•ด ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„๊ณผ ์ตœ๋Œ€ ์‘๋‹ต ์‹œ๊ฐ„์—์„œ ๋‹ค์†Œ ๋А๋ฆฐ ๊ฒฐ๊ณผ๋ฅผ ๋ณด์˜€์ง€๋งŒ, ๊ทน๋‹จ์ ์ธ ๋ถ€ํ•˜ ํ™˜๊ฒฝ์—์„œ ๋‚˜ํƒ€๋‚œ ์ฐจ์ด์ด๋ฉฐ, ์‹ค์ œ ์šด์˜ ์ƒํ™ฉ์—์„œ๋Š” ๊ทธ ์ฐจ์ด๊ฐ€ ์ฒด๊ฐ๋  ์ˆ˜์ค€์€ ์•„๋‹ˆ๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.

๐Ÿ“ Spring Cache ์ ์šฉ์œผ๋กœ ์–ป์€ ์ด์ 

  • Redis ์ธํ”„๋ผ ์šด์˜ ๋ถ€๋‹ด ์ œ๊ฑฐ
  • ๋„คํŠธ์›Œํฌ I/O/์ง๋ ฌํ™” ๋น„์šฉ ๊ฐ์†Œ
  • ์„ค์ • ๊ฐ„์†Œํ™”

๊ฒฐ๊ณผ์ ์œผ๋กœ ์†๋„๋Š” ์•ฝ๊ฐ„ ๋‚ฎ์•„์กŒ์ง€๋งŒ, ์šด์˜ ๋น„์šฉ์„ ์ค„์˜€๋‹ค๋Š” ์ ์—์„œ ์ถฉ๋ถ„ํžˆ ์ข‹์€ ์„ ํƒ์ด๋ผ๊ณ  ์ƒ๊ฐํ•œ๋‹ค.


profile
๐Ÿ’ป ๐Ÿ’ป ๐Ÿ’ป

0๊ฐœ์˜ ๋Œ“๊ธ€