์ค๋ ์งํํ ๋ด์ฉ๋ค ๐ง
์ฐ๋ น๋ ๋ณ ์ธ๊ธฐ ํค์๋ ์์ 10๊ฐ ์กฐํ ์ฑ๋ฅ ๊ฐ์ ํ๊ธฐ
์ฑ๋ฅ ๊ฐ์ ๊ณํ
- ์ธ๋ฑ์ค๋ก ์ฑ๋ฅ์ ๊ฐ์ ํ๊ธฐ
- ์๋ธ ์ฟผ๋ฆฌ๋ก ๋จ์ผ ์ฟผ๋ฆฌ๋ก ๋ณ๊ฒฝํ์ฌ ์ฑ๋ฅ์ ๊ฐ์ ํ๊ธฐ
๋ ๊ฐ์ง ์ฑ๋ฅ ๊ฐ์ ๊ณํ์ด ์์๋ค.
๊ทธ๋์ ํ์ฌ ์ฟผ๋ฆฌ์ ์ํฉ์ด ์ด๋ค์ง ๋จผ์ ํ์
ํด ๋ณด๊ธฐ๋ก ํ๋ค.
ํ์ฌ ์ฟผ๋ฆฌ์ ์ํฉ
์ฐ๋ น๋๋ฅผ 25์ธ~40์ธ๋ก ๊ณ ์ ํ๋ค. (๊ฐ์ฅ ๋ง์ ์ทจ์ ์ฐ๋ น์ธต์ด๋ผ๊ณ ์๊ฐํ๊ณ ๊ณ ์ ํ๋ค.)
๊ทธ๋ค์ Postman์ผ๋ก ๋ฐฐํฌ ์ค์ธ ์๋ฒ์ ๋ก์ปฌ์์ ์ง์ ์กฐํ๋ฅผ ์งํํด ๋ณด์๋ค.
[ Local ]
ํ๋ฒ ์กฐํํ๋๋ฐ 31.49s๊ฐ ๊ฑธ๋ ธ๋ค…
๋งค์ฐ ๋งค์ฐ.. ์์ฒญ๋๊ฒ ๋๋ฆฐ ์๋์๋ค..
[ ๋ฐฐํฌ ์ค์ธ ์๋ฒ ]
์ด๋ด ์๊ฐ… ์ฌ์ง์ด ์๋ฒ์์ ์กฐํํ์ ๋๋ ์๊ฐ์ด ๋๋ฌด ์ค๋ ๊ฑธ๋ ค์ 504 Gateway Time-out์ด ๋ฐ์ํ๋ค..
ํน์๋ ํ๋ ๋ง์์ ๋ถํ ํ
์คํธ๋ ํจ๊ป ์งํํด ๋ณด์๋ค.
100 ์ค๋ ๋ 10์ด๋ก ์ค์ ํ๊ณ JMeter์์ ํ
์คํธํ์ ๋ ์๋ฌ์จ์ด 90%๊ฐ ๋์๋ค..
๋ถํ ์ฒ๋ฆฌ๊ฐ ๋ถ๊ฐ๋ฅํ ์ํ์๋ค.
“ํน์๋ ์๊ฐ ๋๋ฌธ์ธ๊ฐ..?”๋ผ๋ ์๊ฐ์ด ๋ค์ด์ Ramp-up period๋ฅผ 10์ด์์ 120์ด๋ก ๋๋ํ๊ฒ ์ค์ ํ๋ค.
๊ทธ๋ฌ๋๋ ์ฌ์ ํ ์๋ฌ์จ์ด 83%๋ก ๋๊ฒ ๋์๋ค.
์๋ธ ์ฟผ๋ฆฌ๋ฅผ ํ์ฉํ์ฌ ์ธ๊ธฐ ํค์๋๋ฅผ ์กฐํ ์ ์ฑ๋ฅ์ด ๋งค์ฐ ๋งค์ฐ ๋๋ฆฌ๊ณ ์ข์ง ์์๋ค..
๋ถํํ
์คํธ๋ฅผ ์งํํ๋ ์๋ฏธ๋ ์์๋ค..
์ธ๋ฑ์ค๋ก ์ฑ๋ฅ์ ๊ฐ์ ํ๋ , ์ฟผ๋ฆฌ๋ฅผ ๋ณ๊ฒฝํ์ฌ ์ฑ๋ฅ์ ์ต์ ํํ๋ ๊ฐ์ ์ด ๋น์ฅ ํ์ํ ์ํฉ์ด์๋ค.
๊ทธ๋์ ๊ณ ๋ฏผ์ด ์๊ฒผ๋ค..
๊ณผ์ฐ ์ด๋ ๊ฒ ๋๋ฆฐ ์ฑ๋ฅ์ ๊ฐ์ง ์ฟผ๋ฆฌ๋ฅผ ์ธ๋ฑ์ค ๋ง์ผ๋ก ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์์๊น..??
์ง๊ณ๋ฅผ ์ํํ๊ณ ์๋ ์๋ธ ์ฟผ๋ฆฌ๋ฅผ ํด๊ฒฐํด์ผ ๋๋ ๋ฌธ์ ๊ฐ ์๋๊น??
๊ทธ๋์ ์ฟผ๋ฆฌ๋ฅผ ์ต์ ํํ์ฌ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ ์ ์๋์ง ์ฐพ์๋ณด์๋ค.
์๋ธ ์ฟผ๋ฆฌ๋ฅผ ํด๊ฒฐํ ์ ์๋ ๋ฐฉ๋ฒ๋ค์ ์ฐพ๋ค ๋ณด๋ QueryDSL ๋ผ์ด๋ธ๋ฌ๋ฆฌ์์ ์ ๊ณตํ๋ numberTemplate ๋ฉ์๋๋ฅผ ํ์ฉํ์ฌ ์ง๊ณ๊ฐ ๊ฐ๋ฅํ๋ค!!
์๋ธ ์ฟผ๋ฆฌ๋ฅผ ์ฌ์ฉํ์ง ์๊ณ ๋จ์ผ ์ฟผ๋ฆฌ์์ ๋ฐ๋ก count ๊ณ์ฐ์ด ๊ฐ๋ฅํด์ง ๊ฒ์ด๋ค!
์๋ธ ์ฟผ๋ฆฌ๋ฅผ ๋จ์ผ ์ฟผ๋ฆฌ๋ก ๋ณ๊ฒฝํ๋ค.
์์ ์ฝ๋์ฒ๋ผ ์๋ธ ์ฟผ๋ฆฌ๋ฅผ ์ญ์ ํ๊ณ ๋จ์ผ ์ฟผ๋ฆฌ ๋ด์์ count๋ฅผ ๋ฐ๋ก ๊ณ์ฐํ ์ ์๋๋ก ๋ณ๊ฒฝํ๋ค.
์๋ธ ์ฟผ๋ฆฌ๊ฐ ์ฌ๋ผ์ง๋ฉด์ ๋ฐ์ดํฐ๋ฒ ์ด์ค์์ ์คํํด์ผ ํ ์ฟผ๋ฆฌ๊ฐ 1ํ๋ก ์ค์ด๋ค์ด, ํ์ํ์ง ์์ ์ฟผ๋ฆฌ ์คํ์ด ์ฌ๋ผ์ง๋ค.
๊ทธ๋ ๋ค๋ฉด ์ฑ๋ฅ์ด ์ผ๋ง๋ ๊ฐ์ ๋์์๊น?!
๋ก์ปฌ์์ Postman์ ํ์ฉํ์ฌ ์ฑ๋ฅ์ ํ์ธํด ๋ดค๋ค.
์… 31.49s๊ฐ ๊ฑธ๋ฆฌ๋ ์ฟผ๋ฆฌ๊ฐ 146ms๋ก ํ์ฐํ๊ฒ ์ค์ด๋ ๊ฒ์ ํ์ธํ ์ ์์๋ค..
JMeter๋ฅผ ํ์ฉํ์ฌ ๋ถํํ
์คํธ๋ฅผ ๋ค์ ํ๋ฒ ์งํํด ๋ดค๋ค.
100 ์ค๋ ๋ 10์ด๋ก ์ค์ ํ๊ณ ํ
์คํธ๋ฅผ ์งํํ ๊ฒฐ๊ณผ..
์๋ฌ์จ์ด 0%์ ์๊ฐ๋ 50ms๋ก ์์ฒญ๋๊ฒ ๊ฐ์ ์ด ๋ ๊ฒ์ ๋ณผ ์ ์๋ค!!
100 ์ค๋ ๋ 120์ด๋ก ์ค์ ํ๊ณ ํ ์คํธ๋ฅผ ์งํํ ๊ฒฐ๊ณผ๋
์ญ์๋ ์๋ฌ์จ์ด 0%์๋ค!!
์ด๋ก์จ ์๋ธ ์ฟผ๋ฆฌ๋ฅผ ํด๊ฒฐํ ๊ฒ๋ง์ผ๋ก๋ ์ฑ๋ฅ ๊ฐ์ ์ด ์์ฒญ๋๋ค๋ ๊ฒ์ ์ ์ ์์๋ค.
๊ทธ๋ ๋ค๋ฉด ์ฌ๊ธฐ์ ํ ์ ์๋ ์๊ฐ!
user ํ ์ด๋ธ์ age ์ปฌ๋ผ์ ์ธ๋ฑ์ค๋ฅผ ์ ์ฉํ๋ค๋ฉด ์ฑ๋ฅ์ด ๋ ๊ฐ์ ๋์ง ์์๊น??
๊ทธ๋์ ๋ฐ๋ก ์ธ๋ฑ์ค๋ฅผ ์ ์ฉ ์ ํ ์ฑ๋ฅ์ ๋น๊ตํด ๋ดค๋ค.
์ธ๋ฑ์ค ์ ์ฉ ์ ์ฑ๋ฅ
-> Limit: 10 row(s) (actual time=62.5..62.5 rows=10 loops=1)
-> Sort: `count(uk1_0.id)` DESC, limit input to 10 row(s) per chunk (actual time=62.5..62.5 rows=10 loops=1)
-> Table scan on <temporary> (actual time=62.4..62.4 rows=45 loops=1)
-> Aggregate using temporary table (actual time=62.4..62.4 rows=45 loops=1)
-> Nested loop inner join (cost=2547 rows=5073) (actual time=0.372..40.2 rows=26004 loops=1)
-> Nested loop inner join (cost=771 rows=5073) (actual time=0.311..18.2 rows=26004 loops=1)
-> Filter: (u1_0.age between 25 and 40) (cost=205 rows=222) (actual time=0.237..1.2 rows=1118 loops=1)
-> Table scan on u1_0 (cost=205 rows=2000) (actual time=0.235..1.03 rows=2000 loops=1)
-> Filter: (uk1_0.keyword_id is not null) (cost=0.278 rows=22.8) (actual time=0.0081..0.0139 rows=23.3 loops=1118)
-> Covering index lookup on uk1_0 using UKobffw3p1x4w47vq32puin9a0o (user_id=u1_0.id) (cost=0.278 rows=22.8) (actual time=0.00799..0.0125 rows=23.3 loops=1118)
-> Single-row index lookup on k1_0 using PRIMARY (id=uk1_0.keyword_id) (cost=0.25 rows=1) (actual time=686e-6..712e-6 rows=1 loops=26004)
CREATE INDEX idx_user_age ON user (age);
์ ์ฝ๋๋ฅผ ํ์ฉํด์ ์ธ๋ฑ์ค๋ฅผ ์์ฑํด ์ฃผ๊ณ ๋ค์ ์ฑ๋ฅ์ ํ์ธํด ๋ณด์๋ค!
์ธ๋ฑ์ค ์ ์ฉ ํ ์ฑ๋ฅ
-> Limit: 10 row(s) (actual time=52.4..52.4 rows=10 loops=1)
-> Sort: `count(uk1_0.id)` DESC, limit input to 10 row(s) per chunk (actual time=52.4..52.4 rows=10 loops=1)
-> Table scan on <temporary> (actual time=52.3..52.3 rows=45 loops=1)
-> Aggregate using temporary table (actual time=52.3..52.3 rows=45 loops=1)
-> Nested loop inner join (cost=12010 rows=25524) (actual time=0.742..36 rows=26004 loops=1)
-> Nested loop inner join (cost=3077 rows=25524) (actual time=0.698..16.1 rows=26004 loops=1)
-> Filter: (u1_0.age between 25 and 40) (cost=226 rows=1118) (actual time=0.602..1.18 rows=1118 loops=1)
-> Covering index range scan on u1_0 using idx_user_age over (25 <= age <= 40) (cost=226 rows=1118) (actual time=0.599..1.04 rows=1118 loops=1)
-> Filter: (uk1_0.keyword_id is not null) (cost=0.269 rows=22.8) (actual time=0.00678..0.012 rows=23.3 loops=1118)
-> Covering index lookup on uk1_0 using UKobffw3p1x4w47vq32puin9a0o (user_id=u1_0.id) (cost=0.269 rows=22.8) (actual time=0.00667..0.0107 rows=23.3 loops=1118)
-> Single-row index lookup on k1_0 using PRIMARY (id=uk1_0.keyword_id) (cost=0.25 rows=1) (actual time=607e-6..633e-6 rows=1 loops=26004)
ํญ๋ชฉ | ์ธ๋ฑ์ค ์ ์ฉ ์ | ์ธ๋ฑ์ค ์ ์ฉ ํ | ๊ฐ์ ์ฌํญ |
user ํ ์ด๋ธ ์กฐํ ๋ฐฉ์ | ALL (Full Table Scan) | range (Index Range Scan) | ์ธ๋ฑ์ค ์ ์ฉ์ผ๋ก ์ต์ ํ |
JOIN ๋ฐฉ์ (userKeyword ํ ์ด๋ธ) |
ref | ref | ๋์ผ |
keyword ํ ์ด๋ธ ์กฐํ ๋ฐฉ์ | eq_ref | eq_ref | ์ต์ ํ ์ ์ง |
Extra (์ถ๊ฐ ์ฐ์ฐ) | Using temporary; Using filesort |
Using where; Using index; Using temporary |
ํ์ผ ์ ๋ ฌ ์ ๊ฑฐ & ์ธ๋ฑ์ค ํ์ฉ ์ฆ๊ฐ |
์คํ ์๊ฐ | 62.5ms | 52.4ms | ์ฝ 16% ์๋ ๊ฐ์ |
์ฑ๋ฅ์ด ์ข์์ง๊ธด ํ์ผ๋ ๋์ ๋๊ฒ ๊ฐ์ ๋ ๊ฒ์ ์๋ ๊ฒ ๊ฐ๋ค.
์ค์ ๋ก Postman์ ์คํํ์ ๋์ ๊ฒฐ๊ณผ๊ฐ ์ธ๋ฑ์ค ์ ์ฉ ์ ๋ณด๋ค ์๋ต ์๋๊ฐ ๋ ๋์ด๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
JMeter๋ฅผ ํ์ฉํ์ฌ ๋๊ฐ์ ์กฐ๊ฑด์์ ๋ถํํ ์คํธ๋ฅผ ์งํํด ๋ดค๋ค.
์ฑ๋ฅ์ด ๋ ์ข์์ง ๋ชจ์ต์ ๋ณด๊ธฐ ํ๋ค์๋ค.
๊ทธ๋ ๋ค๋ฉด ์ฑ๋ฅ ๊ฐ์ ์ด ํฌ์ง ์์ ์ธ๋ฑ์ค๋ฅผ ์ ์ฉํด์ผ ํ๋ ๊ฑด๊ฐ??
๊ณ ๋ฏผํ ๋์ ๋์จ ๊ฒฐ๋ก ์ ์ธ๋ฑ์ค๋ฅผ ์ ์ฉํ์ง ์๋ ๊ฒ์ด์๋ค.
user ํ
์ด๋ธ์๋ ์ฌ์ฉ์๊ฐ ํ์๊ฐ์
์ ์งํํ ๋๋ง๋ค ๋ฐ์ดํฐ๊ฐ INSERT ๋์ด ์ถ๊ฐ๋๋ค. ๊ทธ๋ด ๋๋ง๋ค ์ธ๋ฑ์ค๊ฐ ์ ์ฉ๋์ด ์์ผ๋ฉด ๋ค์ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ ๋ ฌ์ด ํ์ํ๊ณ ์ฐ๊ธฐ ์ฐ์ฐ์ ์ฑ๋ฅ์ด ์ ํ๋ ๊ฐ๋ฅ์ฑ์ด ์กด์ฌํ๋ค.
๋ํ ๋ฐ์ดํฐ๊ฐ ๋ง์์ง์๋ก ์ธ๋ฑ์ค ๊ด๋ฆฌ ๋น์ฉ์ด ์ฆ๊ฐํ๊ฒ ๋๋ค.
๋ค์๊ณผ ๊ฐ์ ์ด์ ๋ก ์ฟผ๋ฆฌ ์ต์ ํ๊น์ง ์งํํ๊ณ ์ธ๋ฑ์ค๋ ์ ์ฉํ์ง ์๊ธฐ๋ก ๊ฒฐ์ ํ๋ค.
์ฟผ๋ฆฌ ์ต์ ํ๋ฅผ ์งํํ๋ฉด์ ์ถฉ๋ถํ ์๋ต ์๋๊ฐ ๊ฐ์ ๋์๋ค๊ณ ์๊ฐํ๋ค!
ํ ์คํธ ์ฝ๋ ์์ฑํ๊ธฐ
@Test
@DisplayName("์์ธ ๋ฐ์: ์กด์ฌํ์ง ์๋ jobOpeningId๋ฅผ ์กฐํํ ๊ฒฝ์ฐ NotFoundException ๋ฐ์")
void ์ฑ์ฉ๊ณต๊ณ _๋จ๊ฑด_์กฐํ_์คํจ() {
// given
Long jobOpeningId = 999L;
when(jobOpeningRepository.findById(jobOpeningId)).thenReturn(Optional.empty());
// when & then
NotFoundException exception = assertThrows(
NotFoundException.class,
() -> jobOpeningFindByService.findById(jobOpeningId)
);
assertThat(exception.getMessage()).contains(DataErrorCode.JOB_OPENING_NOT_FOUND.getMessage());
verify(jobOpeningRepository, times(1)).findById(jobOpeningId);
}
@ExtendWith(MockitoExtension.class)
public class SearchHistoryServiceTest {
@InjectMocks
private SearchHistoryService searchHistoryService;
@Mock
private RedisTemplate<String, String> redisTemplate;
@Mock
private ZSetOperations<String, String> zSetOperations;
@Mock
private SearchHistoryRepository searchHistoryRepository;
private static final long EXPIRATION_TIME = 3600;
@BeforeEach
void setUp() {
when(redisTemplate.opsForZSet()).thenReturn(zSetOperations);
}
@Test
@DisplayName("๊ฒ์์ด๊ฐ Redis์ ์ ์์ ์ผ๋ก ์ ์ฅ")
void Redis์_๊ฒ์์ด_์ ์์ ์ผ๋ก_์ ์ฅ() {
// given
Long userId = 1L;
String searchTerm = "Spring";
String key = "user:" + userId + ":search_history";
// when
searchHistoryService.saveSearchTerm(userId, searchTerm);
// then
verify(zSetOperations, times(1)).add(eq(key), eq(searchTerm), anyDouble());
verify(redisTemplate, times(1)).expire(eq(key), eq(EXPIRATION_TIME), eq(TimeUnit.SECONDS));
}
@Test
@DisplayName("๊ฒ์์ด ๊ฐ์๊ฐ MAX_SEARCH_HISOTRY_SIZE๋ฅผ ์ด๊ณผํ๋ฉด ๊ฐ์ฅ ์ค๋๋ ๊ฒ์์ด ์ญ์ ")
void Redis์์_๊ฐ์ฅ_์ค๋๋_๊ฒ์์ด_์ญ์ () {
//given
Long userId = 2L;
String searchTerm = "Redis";
String key = "user:" + userId + ":search_history";
when(zSetOperations.zCard(key)).thenReturn(11L);
// when
searchHistoryService.saveSearchTerm(userId, searchTerm);
// then
verify(zSetOperations, times(1)).add(eq(key), eq(searchTerm), anyDouble());
verify(zSetOperations, times(1)).removeRange(eq(key),eq(0L), eq(0L));
verify(redisTemplate, times(1)).expire(eq(key), eq(EXPIRATION_TIME), eq(TimeUnit.SECONDS));
}
@Test
@DisplayName("Redis์ ๊ฒ์ ๊ธฐ๋ก์ด ์กด์ฌํ๋ฉด ํด๋น ๋ฐ์ดํฐ๋ฅผ ๋ฐํ")
void Redis์_๊ฒ์_๊ธฐ๋ก_์กด์ฌํ๋ฉด_๋ฐ์ดํฐ_๋ฐํ() {
// given
Long userId = 1L;
String key = "user:" + userId + ":search_history";
Set<String> searchTermSet = new LinkedHashSet<>(List.of("Java", "Spring"));
when(zSetOperations.reverseRange(key, 0, 9)).thenReturn(searchTermSet);
// when
List<String> searchTermList = searchHistoryService.getRecentSearchTerms(userId);
// then
assertThat(searchTermList).containsExactly("Java", "Spring");
verify(searchHistoryRepository, never()).findTop10ByUserIdOrderByCreatedAtDesc(anyLong());
}
@Test
@DisplayName("Redis์ ๊ฒ์ ๊ธฐ๋ก์ด ์์ผ๋ฉด DB์์ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ํ Redis์ ์ ์ฅํ๊ณ ๋ฐํ")
void Redis์_๊ฒ์_๊ธฐ๋ก์ด_์์ผ๋ฉด_DB์์_๋ฐ์ดํฐ_์กฐํํ๊ณ _Redis์_์ ์ฅ_ํ_๋ฐํ() {
// given
Long userId = 2L;
String key = "user:" + userId + ":search_history";
User mockUser = User.toEntity("test@gmail.com", "์ฌ์ฉ์1", 27, 1, "password123");
when(zSetOperations.reverseRange(key, 0, 9)).thenReturn(Collections.emptySet());
SearchHistory history1 = SearchHistory.toEntity(mockUser, "Elasticsearch");
SearchHistory history2 = SearchHistory.toEntity(mockUser, "Kafka");
ReflectionTestUtils.setField(history1, "createdAt", LocalDateTime.now().minusMinutes(10));
ReflectionTestUtils.setField(history2, "createdAt", LocalDateTime.now().minusMinutes(5));
List<SearchHistory> searchTermListInDatabase = List.of(history1, history2);
when(searchHistoryRepository.findTop10ByUserIdOrderByCreatedAtDesc(userId)).thenReturn(searchTermListInDatabase);
// when
List<String> resultList = searchHistoryService.getRecentSearchTerms(userId);
//then
assertThat(resultList).containsExactly("Kafka", "Elasticsearch");
verify(searchHistoryRepository, times(1)).findTop10ByUserIdOrderByCreatedAtDesc(userId);
verify(zSetOperations, times(1)).add(eq(key), eq("Elasticsearch"), anyDouble());
verify(zSetOperations, times(1)).add(eq(key), eq("Kafka"), anyDouble());
}
}
๋ด์ผ ๊ณํ โฐ
- ํ ์คํธ ์ฝ๋ ์์ฑ
- ์ปค๋ฆฌ์ด ์ฝ์นญ ์๋ด
- ํผํผํฐ ์๊ฐ ์์ฑ
- ๋ธ๋ก์ ์์ฑ
- ๋ฌธ์ํ ์์ ํ๊ธฐ
+ ์ถ๊ฐ ๊ณํ์ด ๋ ์๊ธธ ์๋ ์์ต๋๋ค~_~
'๐ฅ๏ธ ์ต์ข ํ๋ก์ ํธ > โ๏ธ TIL' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[ TIL ] ์ต์ข ํ๋ก์ ํธ_Day 33 (2) | 2025.03.15 |
---|---|
[ TIL ] ์ต์ข ํ๋ก์ ํธ_Day 30 (2) | 2025.03.11 |
[ TIL ] ์ต์ข ํ๋ก์ ํธ_Day26 (1) | 2025.03.08 |
[ TIL ] ์ต์ข ํ๋ก์ ํธ_Day 25 (1) | 2025.03.06 |
[ TIL ] ์ต์ข ํ๋ก์ ํธ_Day 24 (0) | 2025.03.05 |