[OneTime] ๐Ÿš€ ์„ฑ๋Šฅ ๊ฐœ์„ ์ผ์ง€ 1 (feat. N+1, Index)

Sangho Hanยท2025๋…„ 5์›” 21์ผ
8

โฐ OneTime

๋ชฉ๋ก ๋ณด๊ธฐ
5/7
post-thumbnail

โฐ OneTime?

์›ํƒ€์ž„์— ๋Œ€ํ•ด์„œ ๊ถ๊ธˆํ•˜๋‹ค๋ฉด ์•„๋ž˜๋ฅผ ์ฐธ๊ณ ํ•ด์ฃผ์„ธ์š”!

โฐ OneTime ์„œ๋น„์Šค ๋ฐ”๋กœ๊ฐ€๊ธฐ
๐Ÿ“ OneTime ์†Œ๊ฐœ๊ธ€
๐Ÿง‘๐Ÿปโ€๐Ÿ’ป GitHub
๐Ÿ“ธ Instagram


๐ŸŽฌ ์„œ๋ก 

์ฃผ๋ณ€์—์„œ OneTime์„ ์‚ฌ์šฉํ•˜๋Š” ์นœ๊ตฌ๋“ค์ด ์š”์ฆ˜ ๋“ค์–ด ์›ํƒ€์ž„์ด ๋А๋ ค์กŒ๋‹ค ๋Š” ๋ง์„ ๋งŽ์ด ํ–ˆ๋‹ค ๐Ÿฅฒ
์ฒ˜๋ฆฌ ์†๋„๋ฅผ ๋ณด๋‹ˆ ์ด๋Š” ์„œ๋ฒ„์—์„œ์˜ ๋ฌธ์ œ๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๊ณ  ์ด๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด์„œ DB ์„ฑ๋Šฅ ์ตœ์ ํ™”๋ฅผ ํ•ด๋ณด๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

์ด๋ฒˆ ๊ธ€์€ ์ด์— ๋Œ€ํ•œ ๋‚ด์šฉ์œผ๋กœ ์ด์–ด์ง„๋‹ค.


์ „์ฒด ์Šค์ผ€์ค„ ์กฐํšŒ API

์ด์ „์— ๋กœ๊น…์„ ๊ฐœ์„ ํ•˜๋ฉด์„œ ๊ฐ API์— ๋Œ€ํ•œ ์ฒ˜๋ฆฌ ์†๋„๋ฅผ ์ธก์ •ํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋˜์—ˆ๋‹ค.

โœ… [GET] {์—”๋“œํฌ์ธํŠธ} request completed - 7786ms | status=200

์›ํƒ€์ž„์—์„œ ํŠน์ • ์ด๋ฒคํŠธ์— ๋“ค์–ด๊ฐ€๊ฒŒ ๋˜๋ฉด์€, ์ฐธ์—ฌํ•œ ๋ชจ๋“  ์ฐธ์—ฌ์ž๋“ค์˜ ๋“ฑ๋ก ์Šค์ผ€์ค„์„ ๋ถˆ๋Ÿฌ์˜ค๊ฒŒ ๋œ๋‹ค.

์œ„๋Š” ์ฒด๊ฐ์ƒ ๋กœ๋”ฉ์ด ๊ฐ€์žฅ ์˜ค๋ž˜ ๊ฑธ๋ ธ๋˜ ์ด๋ฒคํŠธ๋กœ, 7786ms ์ฆ‰ 7์ดˆ ์ด์ƒ์˜ ์ฒ˜๋ฆฌ ์†๋„๊ฐ€ ๊ฑธ๋ฆฐ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.
์ด๋Š” ์‚ฌ์šฉ์„ฑ์— ์žˆ์–ด์„œ ์‹ฌ๊ฐํ•œ ๋ฌธ์ œ๊ฐ€ ๋œ๋‹ค. ์œ ์ €๊ฐ€ ์ด๋ฒคํŠธ์— ์ž…์žฅํ•  ๋•Œ 7์ดˆ ์ด์ƒ์„ ๊ธฐ๋‹ค๋ ค์•ผํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

์ดˆ๊ธฐ ์„ฑ๋Šฅ ์ธก์ •

์„ฑ๋Šฅ ์ธก์ • ํˆด์€ Grafana K6๋ฅผ ์‚ฌ์šฉํ•˜์˜€๋‹ค.

๋˜ํ•œ ์•„๋ž˜์˜ ์กฐ๊ฑด์„ ๊ณ ์ •์œผ๋กœ ๋‘์–ด ์ธก์ •ํ•˜์˜€๋‹ค.

1) 20๋ช…์˜ ๋™์‹œ ์‚ฌ์šฉ์ž๊ฐ€ ํ˜ธ์ถœ : vus: 20
2) 50๋ฒˆ ํ˜ธ์ถœ ์‹œ ์ข…๋ฃŒ : iterations: 50

์ „์ฒด ์Šค์ผ€์ค„ ์กฐํšŒ๋Š” ์ฒซ ์ž…์žฅ ์‹œ 1๋ฒˆ๋งŒ ํ˜ธ์ถœ ๋˜๊ธฐ์— 50๋ฒˆ๋งŒ ํ˜ธ์ถœํ•ด๋ณด๋Š” ๊ฒƒ์œผ๋กœ ์ œํ•œํ•˜์˜€๋‹ค.

์ดˆ๊ธฐ์—๋Š” ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„์ด 18.38s ๋ผ๋Š” ๋งค์šฐ ์ข‹์ง€ ์•Š์€ ์„ฑ๋Šฅ์„ ๋ณด์˜€๋‹ค.

๋•Œ๋ฌธ์— ํ•ด๋‹น API๊ฐ€ ๊ฐ€์žฅ ๋จผ์ € ํ•ด๊ฒฐํ•ด์•ผ ํ•  ๋ถ€๋ถ„์ด๋ผ๊ณ  ์ƒ๊ฐ์ด ๋“ค์—ˆ๊ณ , ์šฐ์„  ๋ฌธ์ œ๋ฅผ ํŒŒ์•…ํ•ด๋ณด์•˜๋‹ค.


๐Ÿšจ ๋ฌธ์ œ 1 : N+1 ๋ฌธ์ œ ๋ฐœ์ƒ

2025-05-21T19:00:06.192+09:00 DEBUG 49903 --- [nio-8090-exec-1] org.hibernate.SQL                        : 
    select
        s1_0.schedules_id,
        s1_0.created_date,
        s1_0.date,
        s1_0.day,
        s1_0.events_id,
        s1_0.time,
        s1_0.updated_date 
    from
        schedules s1_0 
    where
        s1_0.schedules_id=?
Hibernate: 
    select
        s1_0.schedules_id,
        s1_0.created_date,
        s1_0.date,
        s1_0.day,
        s1_0.events_id,
        s1_0.time,
        s1_0.updated_date 
    from
        schedules s1_0 
    where
        s1_0.schedules_id=?
2025-05-21T19:00:06.192+09:00 TRACE 49903 --- [nio-8090-exec-1] org.hibernate.orm.jdbc.bind              : binding parameter (1:BIGINT) <- [387712]
2025-05-21T19:00:06.199+09:00 DEBUG 49903 --- [nio-8090-exec-1] org.hibernate.SQL                        : 
    select
        s1_0.schedules_id,
        s1_0.created_date,
        s1_0.date,
        s1_0.day,
        s1_0.events_id,
        s1_0.time,
        s1_0.updated_date 
    from
        schedules s1_0 
    where
        s1_0.schedules_id=?
Hibernate: 
    select
        s1_0.schedules_id,
        s1_0.created_date,
        s1_0.date,
        s1_0.day,
        s1_0.events_id,
        s1_0.time,
        s1_0.updated_date 
    from
        schedules s1_0 
    where
        s1_0.schedules_id=?

์œ„ ๋กœ๊ทธ๋ฅผ ๋ณด๋ฉด, schedules ํ…Œ์ด๋ธ”์—์„œ ๋™์ผํ•œ ํŒจํ„ด์˜ ์ฟผ๋ฆฌ๊ฐ€ ๋ฐ˜๋ณต์ ์œผ๋กœ ๋ฐœ์ƒํ•˜๊ณ  ์žˆ์Œ์„ ์•Œ ์ˆ˜ ์žˆ๋‹ค.
์ด๋Š” N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Œ์„ ๋‚˜ํƒ€๋‚ธ๋‹ค.

Map<String, List<Selection>> groupedSelectionsByDate = member.getSelections().stream()
	.filter(s -> s.getSchedule() != null && s.getSchedule().getDate() != null)
    .collect(Collectors.groupingBy(
    	s -> s.getSchedule().getDate(),
        LinkedHashMap::new,
        Collectors.toList()
   	 ));
  1. member.getSelections() ํ˜ธ์ถœ ์‹œ์—๋Š” ์•„์ง ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์กฐํšŒ๊ฐ€ ์ผ์–ด๋‚˜์ง€ ์•Š๋Š”๋‹ค. ์ด ์‹œ์ ์—์„œ๋Š” JPA์˜ Lazy ํ”„๋ก์‹œ ๊ฐ์ฒด๋งŒ ๋ฐ˜ํ™˜๋œ๋‹ค.
  2. ํ•˜์ง€๋งŒ ์ดํ›„ .stream().filter(...) ๋“ฑ์˜ ๊ณผ์ •์—์„œ s.getSchedule()์„ ํ˜ธ์ถœํ•˜๊ฒŒ ๋˜๋ฉด, Hibernate๋Š” ๊ฐ Selection์— ๋Œ€ํ•ด Schedule์„ ๊ฐœ๋ณ„ ์ฟผ๋ฆฌ๋กœ ์กฐํšŒํ•˜๊ฒŒ ๋œ๋‹ค.
  3. ๊ฒฐ๊ณผ์ ์œผ๋กœ Selection์ด 100๊ฐœ๋ผ๋ฉด Schedule๋„ 100๋ฒˆ ์กฐํšŒ๋˜๋ฉฐ, ์ฟผ๋ฆฌ ์ˆ˜๊ฐ€ Selection ์ˆ˜๋งŒํผ ์ฆ๊ฐ€ํ•˜๋Š” N+1 ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ : Fetch Join

    @Query("""
        SELECT s FROM Selection s
        JOIN FETCH s.schedule sc
        WHERE s.member = :member
    """)
    List<Selection> findAllByMemberWithSchedule(@Param("member") Member member);

    @Query("""
        SELECT s FROM Selection s
        JOIN FETCH s.schedule sc
        JOIN FETCH sc.event
        WHERE s.user = :user
    """)
    List<Selection> findAllByUserWithScheduleAndEvent(@Param("user") User user);

๊ฐ๊ฐ ๋ฉค๋ฒ„์™€ ์œ ์ €์— ๋Œ€ํ•ด์„œ selections๊ณผ ์—ฐ๊ด€๋œ schedules ํ…Œ์ด๋ธ”์„ ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ค๊ธฐ ์œ„ํ•œ fetch join ์ฟผ๋ฆฌ๋ฅผ ์ž‘์„ฑํ•˜์˜€๋‹ค.
์œ ์ €์˜ ๊ฒฝ์šฐ์—๋Š” events ํ…Œ์ด๋ธ”๋„ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์ด ๋˜ํ•œ ํ•จ๊ป˜ ๊ฐ€์ ธ์˜ค๋„๋ก ํ•˜์˜€๋‹ค.

๐Ÿ“Š ๊ฒฐ๊ณผ

select
    s1_0.selections_id,
    ...
    s2_0.schedules_id,
    ...
    e1_0.events_id,
    ...
from
    selections s1_0
join
    schedules s2_0
    on s2_0.schedules_id = s1_0.schedules_id
join
    events e1_0
    on e1_0.events_id = s2_0.events_id
where
    s1_0.users_id = ?

์ฟผ๋ฆฌ ๋‚ด์—ญ์„ ๋ณด๋ฉด, ํ•œ ๋ฒˆ์˜ ์ฟผ๋ฆฌ๋กœ selections / schedules / events ํ…Œ์ด๋ธ”์„ ๋ชจ๋‘ ๊ฐ€์ ธ์˜ค๊ณ  ์žˆ๋‹ค.

๋†€๋ž๊ฒŒ๋„ N+1๋ฌธ์ œ๋งŒ ํ•ด๊ฒฐํ•˜์ž ํ‰๊ท  ์‘๋‹ต ์‹œ๊ฐ„์ด 0.35s๋กœ ๊ฐœ์„ ๋˜์—ˆ๋‹ค. ๋งค๋ฒˆ ์กฐ๊ธˆ์”ฉ ์ฐจ์ด๊ฐ€ ์žˆ์ง€๋งŒ ํ‰๊ท ์ ์œผ๋กœ 0.3 ~ 0.5์ดˆ ์ •๋„๋งŒ ์†Œ์š”๋˜์—ˆ๋‹ค.

์—ฌ๊ธฐ์„œ ๊ทธ์น˜์ง€ ์•Š๊ณ  ๋”์šฑ ๊ฐœ์„ ํ•  ์ˆ˜ ์žˆ๋„๋ก ๋‹ค๋ฅธ ๋ฐฉ์•ˆ๋“ค๋„ ์ ์šฉํ•ด ๋ณด๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.


๐Ÿšจ ๋ฌธ์ œ 2 : ์ธ๋ฑ์Šค ๋ฏธ์ ์šฉ

DB ์ธ๋ฑ์Šค์— ๋Œ€ํ•ด์„œ๋Š” ์ตœ๊ทผ์— ๊ณต๋ถ€๋ฅผ ํ–ˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ง€๊ธˆ๊นŒ์ง€ ์ ์šฉ์„ ํ•ด ๋ณธ ์ ์€ ์—†์—ˆ๋‹ค.
ํ•˜์ง€๋งŒ ๊ณต๋ถ€๋ฅผ ํ•˜๊ณ  ๋‚˜๋‹ˆ, ์™œ ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ์–ด์•ผ ํ•˜๊ณ  & ํ˜„์žฌ DB์—์„œ ์–ด๋А ๋ถ€๋ถ„์— ๊ฑธ๋ฉด ์ข‹์„์ง€ ์–ด๋А์ •๋„ ๋– ์˜ฌ๋ฆด ์ˆ˜๊ฐ€ ์žˆ์—ˆ๋‹ค.

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ 1 : selections ํ…Œ์ด๋ธ”์— ์ธ๋ฑ์Šค ๊ฑธ๊ธฐ

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

INSERT๋Š” ๋ฐœ์ƒํ•˜์ง€๋งŒ, UPDATE๋‚˜ DELETE๋Š” ๊ฑฐ์˜ ์—†์œผ๋ฉฐ, API ์š”์ฒญ ํ๋ฆ„์ƒ ๋งค์šฐ ๋นˆ๋ฒˆํ•œ ์กฐํšŒ(read ์—ฐ์‚ฐ) ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.

๋”ฐ๋ผ์„œ, users_id, members_id, schedules_id ์ปฌ๋Ÿผ์— ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ๋ฉด ์กฐํšŒ ์„ฑ๋Šฅ์„ ํš๊ธฐ์ ์œผ๋กœ ํ–ฅ์ƒ์‹œํ‚ฌ ์ˆ˜ ์žˆ๊ณ , ์“ฐ๊ธฐ ๋ถ€ํ•˜๊ฐ€ ๊ฑฐ์˜ ์—†๊ธฐ ๋•Œ๋ฌธ์— ์ธ๋ฑ์Šค ์˜ค๋ฒ„ํ—ค๋“œ๋„ ๋ฌด์‹œํ•  ์ˆ˜ ์žˆ๋Š” ์ˆ˜์ค€์ด๋ผ๊ณ  ํŒ๋‹จ๋˜์—ˆ๋‹ค.

CREATE INDEX idx_selections_users_id ON selections(users_id);
CREATE INDEX idx_selections_members_id ON selections(members_id);
CREATE INDEX idx_selections_schedules_id ON selections(schedules_id);

์œ„ DDL์„ ํ†ตํ•ด์„œ ์ธ๋ฑ์Šค๋ฅผ ์ง์ ‘ ๊ฑธ์–ด์ค„ ์ˆ˜๊ฐ€ ์žˆ๋‹ค.
๊ฑธ๊ธฐ ์ „์— ์•„๋ž˜ ๋ช…๋ น์–ด๋ฅผ ํ†ตํ•ด์„œ ํ˜„์žฌ ์ธ๋ฑ์Šค๋ฅผ ํ™•์ธํ•ด ๋ณด์•˜๋‹ค.

SHOW INDEX FROM selections;

๊ทธ๋Ÿฐ๋ฐ PK ์™ธ์˜ ๋ชจ๋“  FK๋“ค์—๋„ ์ด๋ฏธ ์ธ๋ฑ์Šค๊ฐ€ ๊ฑธ๋ ค์žˆ๋Š” ๊ฒƒ์„ ์•Œ ์ˆ˜ ์žˆ์—ˆ๋‹ค.

์ด์— ๋Œ€ํ•ด ์ฐพ์•„๋ณด๋‹ˆ MySQL์€ FK์— ๋Œ€ํ•ด์„œ๋„ ์ž๋™์œผ๋กœ ์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ•ด ์ค€๋‹ค๊ณ  ํ•œ๋‹ค.
์ฐธ๊ณ  ๋ธ”๋กœ๊ทธ

์•Œ๋‹ค์‹œํ”ผ ์ธ๋ฑ์Šค๋ฅผ ์ ์ ˆํ•˜์ง€ ์•Š๊ฒŒ ๊ฑธ๊ฒŒ ๋˜๋ฉด ์˜คํžˆ๋ ค ์„ฑ๋Šฅ์„ ์•…ํ™”์‹œํ‚ฌ ์ˆ˜๊ฐ€ ์žˆ๋‹ค. FK๋ฅผ ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์˜๋„์น˜ ์•Š๊ฒŒ ์ด๋Ÿฌํ•œ ๊ฒฝ์šฐ๊ฐ€ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ ๊ฐ™๋‹ค.

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป FK๋ฅผ ์“ฐ์ง€ ์•Š์•„์•ผ ํ•œ๋‹ค๋Š” ์˜๊ฒฌ๋„ ๋งŽ์ด ์กด์žฌํ•œ๋‹ค. ์•„๋งˆ ์ด๋Ÿฌํ•œ ๋ถ€๋ถ„๋“ค๊ณผ ๋”๋ถˆ์–ด ์ œ์•ฝ์กฐ๊ฑด์œผ๋กœ ์ธํ•ด์„œ ์ œ์–ดํ•˜๊ธฐ ์–ด๋ ค์šด ์ƒํ™ฉ์ด ๋ฐœ์ƒํ•˜๋Š” ๊ฒƒ ๋•Œ๋ฌธ์ธ ๋“ฏ ํ•˜๋‹ค.
๋‚˜๋Š” ์•„์ง๊นŒ์ง€๋Š” FK๋ฅผ ๊ณ„์†ํ•ด์„œ ์‚ฌ์šฉํ•˜๊ณ  ์žˆ๋Š”๋ฐ, ์ถ”ํ›„ ๊ณต๋ถ€๋ฅผ ํ•œ ํ›„ ๋ฐฉํ–ฅ์„ฑ์„ ์ •ํ•ด๋ด์•ผ๊ฒ ๋‹ค!

โœ… ํ•ด๊ฒฐ ๋ฐฉ์•ˆ 2 : schedules ํ…Œ์ด๋ธ”์— ์ธ๋ฑ์Šค ๊ฑธ๊ธฐ

์Šค์ผ€์ค„ ํ…Œ์ด๋ธ”์˜ ๊ฒฝ์šฐ์—๋Š” ์ด๋ฒคํŠธ๊ฐ€ ์ƒ์„ฑ๋จ์— ๋”ฐ๋ผ์„œ, INSERT ์ž‘์—…์ด ์ด๋ฃจ์–ด์ง„๋‹ค.
์ดํ›„ ์ด๋ฒคํŠธ๋ฅผ ์ˆ˜์ •ํ•จ์— ๋”ฐ๋ผ์„œ INSERT or DELETE ์ž‘์—…์ด ๋ฐœ์ƒํ•  ์—ฌ์ง€๊ฐ€ ์žˆ์ง€๋งŒ, SELECT์— ๋น„ํ•ด ๋นˆ๋„๋Š” ๋งค์šฐ ์ ์€ ํŽธ์ด๋‹ค.

๋•Œ๋ฌธ์— FK๋ฅผ ์ œ์™ธํ•œ 1) date 2) day 3) time ์„ ๋Œ€์ƒ์œผ๋กœ ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ๊ธฐ๋กœ ๊ฒฐ์ •ํ–ˆ๋‹ค.

CREATE INDEX idx_schedules_date ON schedules(date);
CREATE INDEX idx_schedules_day ON schedules(day);
CREATE INDEX idx_schedules_time ON schedules(time);

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ ์œ ์˜๋ฏธํ•œ ๊ฒฐ๊ณผ๋Š” ์–ป์ง€ ๋ชปํ•˜์˜€๋‹ค. ๋‚˜๋Š” ๊ทธ ์ด์œ ๋กœ ํ•ด๋‹น ์„ธ ์ปฌ๋Ÿผ์˜ ์นด๋””๋„๋ฆฌํ‹ฐ๊ฐ€ ๋‚ฎ๊ธฐ ๋•Œ๋ฌธ์ด๋ผ๊ณ  ํŒ๋‹จํ–ˆ๋‹ค.

๊ฐ๊ฐ 178, 7, 49๋กœ ๊ณ ์œ ํ•œ ๋ฐ์ดํ„ฐ๋“ค์˜ ๊ฐœ์ˆ˜๊ฐ€ ์ ์€ ํŽธ์ด๋ฉฐ, ๊ทธ๋ ‡๊ฒŒ ๋œ๋‹ค๋ฉด DB ์˜ตํ‹ฐ๋งˆ์ด์ €๊ฐ€ ์ธ๋ฑ์Šค๋ฅผ ์•ˆ ํƒ€๋Š” ๊ฒƒ์ด ๋” ๋น ๋ฅด๊ฒ ๋Š”๋ฐ?๋ผ๋Š” ํŒ๋‹จ์„ ํ•  ๊ฐ€๋Šฅ์„ฑ์ด ๋†’์•„์ง„๋‹ค.

์ธ๋ฑ์Šค๋ฅผ ์ƒ์„ฑํ–ˆ๋Š”๋ฐ ์‚ฌ์šฉํ•˜์ง€ ์•Š๋Š”๋‹ค๋ฉด ์ด๋Š” ์˜คํžˆ๋ ค ๋ถˆํ•„์š”ํ•œ ์ €์žฅ ๊ณต๊ฐ„ ์†Œ๋ชจ ๋ฐ ์“ฐ๊ธฐ ์—ฐ์‚ฐ ์‹œ ์„ฑ๋Šฅ ์ €ํ•˜๋ฅผ ์•ผ๊ธฐํ•  ์ˆ˜๋„ ์žˆ๋‹ค.

๋ณตํ•ฉ ์ธ๋ฑ์Šค ์‚ฌ์šฉ

๊ทธ๋ ‡๋‹ค๋ฉด ๋‹จ์ผ์ด ์•„๋‹ˆ๋ผ ๋ณตํ•ฉ ์ธ๋ฑ์Šค๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ์–ด๋–ป๊ฒŒ ๋ ๊นŒ?

์กฐํšŒ๋ฅผ ํ•  ๋•Œ date + time / day + time ์˜ ์กฐํ•ฉ์œผ๋กœ ์กฐํšŒํ•˜๋Š” ๊ฒฝ์šฐ๊ฐ€ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ณตํ•ฉ ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ์–ด๋ณด์•˜๋‹ค.

CREATE INDEX idx_schedules_date_time ON schedules(date, time);
CREATE INDEX idx_schedules_day_time ON schedules(day, time);

ํ•˜์ง€๋งŒ ์—ฌ๊ธฐ์„œ๋„ ์œ ์˜๋ฏธํ•œ ์„ฑ๋Šฅ ๊ฐœ์„ ์„ ํ•˜์ง€๋Š” ๋ชป ํ•˜์˜€๋‹ค.

EXPLAIN SELECT * FROM schedules
        WHERE date = '2025.05.13' AND time = '09:00';
EXPLAIN SELECT * FROM schedules FORCE INDEX (idx_schedules_date_time)
        WHERE date = '2025.05.13' AND time = '09:00';

์œ„์ฒ˜๋Ÿผ DB ์˜ตํ‹ฐ๋งˆ์ด์ €์˜ ํŒ๋‹จ vs ๊ฐ•์ œ ์ธ๋ฑ์Šค ์‚ฌ์šฉ ํ…Œ์ŠคํŠธ๋„ ์ง„ํ–‰ํ•ด๋ณด์•˜๋Š”๋ฐ, date + time์˜ ๊ฒฝ์šฐ์—๋Š” ์ธ๋ฑ์Šค ์‚ฌ์šฉ์ด ์šฐ์„ธํ•œ ๊ฒฝ์šฐ๊ฐ€ ์žˆ์—ˆ์œผ๋‚˜ ๋Œ€์ฒด๋กœ ํฐ ์˜๋ฏธ๋Š” ์—†์—ˆ๋‹ค.

๐Ÿง‘๐Ÿปโ€๐Ÿ’ป ๋ฏธ๋ฏธํ•œ ํšจ๊ณผ ๋•Œ๋ฌธ์— ์ธ๋ฑ์Šค๋ฅผ ๊ฑธ๊ธฐ์—๋Š”, ๊ทธ๋กœ ์ธํ•œ ์‚ฌ์ด๋“œ ์ดํŽ™ํŠธ์˜ ๋ฆฌ์Šคํฌ๊ฐ€ ๋” ํด ๊ฒƒ์ด๋ผ๋Š” ์ƒ๊ฐ์ด ๋“ค์–ด schedules ํ…Œ์ด๋ธ”์—์„œ๋„ ์ธ๋ฑ์Šค๋Š” ๊ฑธ์ง€ ์•Š๊ธฐ๋กœ ๊ฒฐ์ •ํ•˜์˜€๋‹ค.


๐Ÿ ๋งˆ๋ฌด๋ฆฌ

์ง€์‹์˜ ํ•œ๊ณ„๋ฅผ ๋А๋ผ๊ณ , ์กฐ๊ธˆ ๋” ํ•™์Šต์„ ํ•œ ํ›„์— ์„ฑ๋Šฅ ๊ฐœ์„ ์„ ๋” ํ•ด๋ณด์•„์•ผ๊ฒ ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋“ค์–ด ์—ฌ๊ธฐ์„œ ๊ธ€์„ ๋งˆ๋ฌด๋ฆฌํ•˜๋ ค๊ณ  ํ•œ๋‹ค.

๐Ÿƒ๐Ÿป ์•ž์œผ๋กœ ํ•ด๋ณผ ๊ฒƒ๋“ค

  1. DTO Projection
  2. Query DSL
  3. ์บ์‹ฑ
  4. ์ •๊ทœํ™” / ๋น„์ •๊ทœํ™” ๊ณ ๋ ค

๐Ÿ’ก ๋А๋‚€ ์  ๋ฐ ๋ฐฐ์šด ์ 

  1. ํ…Œ์ŠคํŠธ ์ƒ์œผ๋กœ 18.32s -> 0.35 ์ฆ‰ 98%์˜ ์„ฑ๋Šฅ ๊ฐœ์„ ์„ ํ•  ์ˆ˜ ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” N+1 ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•จ์œผ๋กœ์จ ๋น„๊ต์  ์‰ฝ๊ฒŒ ์–ป์–ด๋‚ธ ๊ฒฐ๊ณผ์ด๋ฏ€๋กœ, ์•ž์œผ๋กœ ๋‹ค๋ฅธ ๋ถ€๋ถ„์„ ๋” ์ ์šฉํ•ด ๋ณด๊ณ  ์‹ถ๋‹ค๋Š” ์ƒ๊ฐ์ด ๋” ์ปค์กŒ๋‹ค.
  2. ์„ฑ๋Šฅ ๊ฐœ์„ ์„ ์ œ๋Œ€๋กœ ๋„์ „ํ•ด ๋ณธ ๊ฒƒ์€ ์ฒ˜์Œ์ด์—ˆ๋Š”๋ฐ ์ƒ๊ฐ๋ณด๋‹ค ์žฌ๋ฏธ์žˆ์—ˆ๋‹ค. DB๋Š” ๊ณต๋ถ€ํ• ์ˆ˜๋ก ์œ ์ € ์‚ฌ์šฉ์„ฑ์— ๊ธ์ •์ ์ธ ์˜ํ–ฅ์„ ๋ฏธ์น  ์ˆ˜ ์žˆ๋‹ค๋Š” ์ ์—์„œ ์ข‹์€ ๊ฒƒ ๊ฐ™๋‹ค.
  3. DB ์ธ๋ฑ์Šค์— ๋Œ€ํ•ด ๊ณต๋ถ€๋ฅผ ํ•˜๊ณ  ์ ์šฉ์„ ํ•ด ๋ณด๋‹ˆ ์ข€ ๋” ์ดํ•ด & ํŒ๋‹จ์ด ์ž˜ ๋˜์—ˆ๋˜ ๊ฒƒ ๊ฐ™๋‹ค. ๊ทธ๋ ‡์ง€ ์•Š์•˜๋‹ค๋ฉด ๋ฌด๋ถ„๋ณ„ํ•˜๊ฒŒ ์ธ๋ฑ์Šค๋ฅผ ์ ์šฉํ–ˆ์„ ๋“ฏํ•˜๋‹ค.
profile
์•ˆ๋…•ํ•˜์„ธ์š”. ๋น„์ฆˆ๋‹ˆ์Šค๋ฅผ ์ดํ•ดํ•˜๋Š” ๋ฐฑ์—”๋“œ ๊ฐœ๋ฐœ์ž, ํ•œ์ƒํ˜ธ์ž…๋‹ˆ๋‹ค.

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