๐ฑ MySQL ์ธ๋ฑ์ค ์ค์ต - ์ต๋ 83๋ฐฐ ์ฑ๋ฅ ๊ฐ์ ๊ฒฝํ๊ธฐ
๐ค Intro
์ธ๋ฑ์ค, ์๊ณ ๋ ์์์ง๋งโฆ
โ์ธ๋ฑ์ค ์ฐ๋ฉด ๋นจ๋ผ์ง๋คโ๋ ๊ฑด ๋๊ตฌ๋ ์๋ค. ๋ฉด์ ์์๋ ๋จ๊ณจ ์ง๋ฌธ์ด๊ณ , ์ค๋ฌด์์๋ ๋น์ฐํ ์จ์ผ ํ๋ค๊ณ ๋ค ํ์ง๋งโฆ ์์งํ ๋งํ๋ฉด ์ผ๋ง๋ ๋นจ๋ผ์ง๋์ง๋ ์ง์ ๊ฒฝํํด๋ณธ ์ ์ด ์์๋ค.
์ญ๋ง ๊ฑด? ๋ฐฑ๋ง ๊ฑด? ๊ทธ ์ ๋ ๋ฐ์ดํฐ๋ฅผ ์ค์ ๋ก ๋ค๋ค๋ณธ ์ ๋ ์๊ณ , ์ธ๋ฑ์ค ์์ด ์ฟผ๋ฆฌ ๋๋ ค๋ณด๊ณ โ์ ๋๋ฌด ๋๋ฆฌ๋คโ ํ๋ฉด์ ์ธ๋ฑ์ค ๊ฑธ์ด๋ณด๊ณ โ์ค ๋นจ๋ผ์ก๋ค!โ ์ด๋ฐ ๊ฒฝํ๋ ์์๋ค.
๊ทธ๋ฅ ๋ง์ฐํ๊ฒ โ์ธ๋ฑ์ค๋ ์ข์ ๊ฑฐ๋คโ, โB-Tree ๊ตฌ์กฐ๋ก ๋์ด ์๋คโ, โ์นด๋๋๋ฆฌํฐ๊ฐ ๋์์ผ ํ๋คโ ์ด๋ฐ ์ด๋ก ๋ง ์๊ณ ์์์ ๋ฟ์ด๋ค.
๊ทธ๋์ ์ง์ ํด๋ณด๊ธฐ๋ก ํ๋ค!!
๋ง์นจ RealMySQL์ ๊ณต๋ถํ๋ฉด์ ๋ง์นจ ๋ฑ B-Tree ์ธ๋ฑ์ค ํํธ ๊ณต๋ถ๋ฅผ ๋ง์น ์ฐธ์ด๋ผ, ์ง์ ์ค์ตํด๋ณด๊ธฐ๋ก ํ๋ค. ๋ ๊ถ๊ธํ๊ฑด ๋ชป์ฐธ์!!!!!!
์ญ๋ง ๊ฑด์ ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ์ง์ ์์ฑํ๊ณ ์ธ๋ฑ์ค ์์ด ์ฟผ๋ฆฌ ์ฑ๋ฅ์ ์ธก์ ํด๋ณด๊ณ ๋จ์ผ ์ธ๋ฑ์ค, ๋ณตํฉ ์ธ๋ฑ์ค๋ฅผ ๊ฐ๊ฐ ์ ์ฉํด์
์งํํ๋ค..
๊ฒฐ๊ณผ๋ฅผ ๋ฏธ๋ฆฌ ๋งํ์๋ฉด, ์ต๋ 83๋ฐฐ ๋นจ๋ผ์ง๋ ์ด๋ง๋ฌด์ํ..๊ฒฐ๊ณผ๊ฐ ๋์๋ค.
๋ํ๋ฏผ์ด ํํ ํฐ์ง๋ ๊ฒฐ๊ณผ๋ผ ์ง๊ธ๋ ์ค๋ ๋ ๋ง์์ด ๊ทธ๋ํ๋ค
๊ทธ๋ผ ์ด๋ค ๊ณผ์ ์ผ๋ก ์ค์ต์ ์งํํ๋์ง๋ฅผ ๋ ๊ตฌ์ฒด์ ์ผ๋ก ์ดํด๋ณด์.
๐ฉถ Start
์ค์ต์ ์์ํ์ง~
์ค์ต์ ๋จ๊ณ๋ ๋ค์๊ณผ ๊ฐ๋ค.
- ๊ธฐ๋ณธ ์ค์ ๋ฐ ์ฑ๋ฅ ๋ถ์์ ์ํ ๋๋ฏธ ๋ฐ์ดํฐ ์ถ๊ฐ
- ๊ธฐ๋ณธ crud ๊ตฌ์ฑ ๋ฐ ๊ธฐ๋ฅ test
- ๋ณธ๊ฒฉ์ ์ธ ์ฑ๋ฅ ํ ์คํธ ์์
๊ธฐ๋ณธ ์ค์ ๋ฐ ์ฑ๋ฅ ๋ถ์์ ์ํ ๋๋ฏธ ๋ฐ์ดํฐ ์ถ๊ฐ
์คํค๋ง ์์ฑ ๋ฐ ๊ถํ ๋ถ์ฌ
1
CREATE SCHEMA `indextest` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci ;
์ง๊ธ ๋จ๊ณ์์๋ MySQL workbench๋ฅผ ์ฌ์ฉํ๋ ๊ฑด ์๋ต๋์ด ์์ง๋งโฆ ์ฐ์ ์คํค๋ง๋ฅผ ์์ฑํ๊ณ ๋ค์์ ์กฐ๊ฑด์ ๋ฃ์ด์ฃผ์๋ค.
- utf8mb4์ ์ด์ฉํด์ ์ด๋ชจ์ง์ ๊ฐ์ ํน์๋ฌธ์๋ ์๋ฒฝํ๊ฒ ์ ์ฅํด์ ์ง์ง utf8์ ์ฌ์ฉํ ์ ์๋๋ก ํ๋ค.
- utf8mb4_unicode_ci๋ฅผ ์ด์ฉํด์ ์ ํํ ์ ๋ ฌ์ด ๊ฐ๋ฅํ๊ฒ ํ๊ณ , ๋์๋ฌธ์ ๊ตฌ๋ถ์ ์ ํ๋๋ก ํ๋ค.
1
2
3
4
5
6
-- 1. ์ฌ์ฉ์ ์์ฑ
CREATE USER 'indextest_user'@'%' IDENTIFIED BY 'indextest1234';
-- 2. ์คํค๋ง ๊ถํ ๋ถ์ฌ
GRANT ALL PRIVILEGES ON indextest.* TO 'indextest_user'@'%';
-- 3. ๊ถํ ์ ์ฉ
FLUSH PRIVILEGES;
๋ง์ง๋ง์ผ๋ก, ์คํค๋ง๋ฅผ ์ฌ์ฉํ ์ฌ์ฉ์๋ฅผ ์์ฑํ๊ณ ๊ถํ์ ๋ถ์ฌํด์ฃผ๋ฉด ์ค์ต์ ์ฌ์ฉํ ์คํค๋ง ๊ตฌ์ฑ์ ๋์ด๋ค.
yml ํ์ผ ์์ฑ
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
# ํฌํธ ์ค์
server:
port: 8085
spring:
datasource:
url: jdbc:mysql://${DB_HOST:localhost}:${DB_PORT:3306}/${DB_NAME}?characterEncoding=UTF-8&serverTimezone=Asia/Seoul
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
driver-class-name: com.mysql.cj.jdbc.Driver
jpa:
hibernate:
ddl-auto: update
properties:
hibernate:
show_sql: true
format_sql: true
dialect: org.hibernate.dialect.MySQL8Dialect
cache:
use_second_level_cache: false # ์ ํํ ์ธ๋ฑ์ฑ ์ธก์ ์ ์ํ ์บ์ ๋๊ธฐ
use_query_cache: false # default๊ฐ false๋ก ๋์ด ์์
yml ์ค์ ํ์ผ์ด๋ค.
cache๋จ์ด์ผโฆ ์๋ ๊ธฐ๋ณธ๊ฐ์ด false์ด๊ธด ํ์ง๋ง, ๋ช ํํ๊ฒ ๋ช ์ํ๊ธฐ ์ํด false๋ก ์ก์์ค๋ค.
username, password ๊ฐ์ ๋ฏผ๊ฐ ์ ๋ณด๋ ์ ๋ถ .env๋ก ๋นผ์ฃผ์๋ค.
๋๋ฏธ ๋ฐ์ดํฐ ์์ฑ!
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
/**
* ๋ก๊ทธ ๋๋ฏธ ๋ฐ์ดํฐ ์ ์ฅ์ ์ํ ํด๋์ค
*
* ApplicationRunner๋ฅผ ์ด์ฉํด์ ์คํ๋ง ๋ถํธ๊ฐ ์์ ํ ์์๋ ์งํ ์๋์ผ๋ก ์์.
* ๋ฐฐ์น ๋ฐฉ์์ผ๋ก ๋ฌถ์ด์ ํ ๋ฒ์ ์ ์ฅ
**/
@Component
@RequiredArgsConstructor
@Slf4j
public class DataInitializer implements ApplicationRunner {
private final LogRepository logRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
// ์ ํ๋ฆฌ์ผ์ด์
์ ์์ํ๋ฉด์ ์๋์ผ๋ก ์คํ๋๋ค.
log.info("๋๋ฏธ๋ฐ์ดํฐ ์์ฑ ์์");
// ์ด๋ฏธ ํด๋นํ๋ ๋ฐ์ดํฐ๊ฐ ์์ ๊ฒฝ์ฐ, ์คํ๋ ๊ฒ์ด๋ฏ๋ก ์คํตํ๋ค.
if(logRepository.count() > 0){
log.info("๋ฐ์ดํฐ๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ฏ๋ก, ์คํตํฉ๋๋ค.");
return;
}
// 10๋ง๊ฐ์ ๋๋ฏธ ๋ฐ์ดํฐ ๋ก๊ทธ๋ฅผ ์์ฑ
List<Logs> logs = new ArrayList<>();
Random random = new Random();
// Level enum์ ๋ชจ๋ ๊ฐ์ ๋ฐฐ์ด๋ก ๊ฐ์ ธ์ด
Level[] levels = Level.values(); // [ERROR, WARN, INFO, DEBUG]
for(int i = 1; i <= 100000; i++){
Logs logEntity = Logs.builder()
.dateTime(LocalDateTime.now().minusDays(random.nextInt(30))) //์ต๊ทผ 30์ผ์ ๋๋คํ๊ฒ ๋ฃ๋๋ค.
.level(levels[random.nextInt(levels.length)]) //๊ธธ์ด ๋ด์์ ๋๋ค ์ ์๋ฅผ ์์ฑ
.userId((long) random.nextInt(10000)) //10000 ์ค์์ ๋๋คํ ์ซ์๋ฅผ ์์ฑ
.message("๋ก๊ทธ ๋ฉ์ธ์ง : " + i)
.build();
logs.add(logEntity); //๋ฐฐ์ด์ ๋ํ๊ธฐ//๋ฐฐ์น ๋ฐฉ์์ผ๋ก, 1000 ๋จ์๋ก ์ ์ฅ
if(i % 1000 == 0){
logRepository.saveAll(logs);
logs.clear();
log.info("{}๊ฐ ์งธ ์ ์ฅ ์๋ฃ", i);
}
}
log.info("๋๋ฏธ ๋ฐ์ดํฐ ์์ฑ ์๋ฃ.");
}
}
์ฐ์ ๋๋ฏธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํ๊ธฐ ์ํ ๋ฉ์๋๋ฅผ ๋ง๋ค์ด์ผ ํ๋ค.
์ด ํ ์ด ํ๋ก์ ํธ์ ๋ชฉ์ ์ ๋ก๊ทธ ๋ฐ์ดํฐ๋ฅผ ์์ฑํด์ select๋ก ๋น๊ตํ๊ธฐ ์ํจ์ด๋ค.
ApplicationRunner๋ ์คํ๋ง ํ๋ก์ ํธ ์์ ์์ ์ ์คํ๋๋ค๋ ํน์ง์ด ์๋ค. ์ด๋ฅผ ์ด์ฉํ๋ฉด ์ ํ๋ฆฌ์ผ์ด์ ์์ ์์ ์ ๋ก๊ทธ ๋ฐ์ดํฐ๋ค์ ์์ฑํ ์ ์๋ค.
๋ฐ์ดํฐ๊ฐ ์ด๋ฏธ ์กด์ฌํ๋ ์ํ์์ ๋ฐ์ดํฐ๋ฅผ ๋ฃ์ด์ฃผ๋ ๊ฑด ๋ญ๋น์ด๋ฏ๋ก, ๋ฐ์ดํฐ๊ฐ ์กด์ฌํ๋ฉด ํด๋น ๋ฉ์๋๋ฅผ ์คํตํ๋๋ก ํ๋ค.
ํจ์จ์ฑ์ ์ํด, ๋ฐฐ์น ๋ฐฉ์์ผ๋ก 1000๊ฐ ๋จ์๋ก ๋ฌถ์ด์ saveAll๋ก ํ ๋ฒ์ ์ ์ฅ๋๋๋ก ํ๋ค.
๋๋ฏธ ๋ฐ์ดํฐ๊ฐ ์ ๋ค์ด๊ฐ ๊ฒ์ ๋ณผ ์ ์๋ค.
Controller
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
@Controller
@Slf4j
@RequiredArgsConstructor
@RequestMapping("/api/logs")
public class LogController {
private final LogService logService;
/**
* GET /api/logs : ์ ์ฒด ๋ก๊ทธ ์กฐํ
*/
@GetMapping
public ResponseEntity<List<LogResponse>> findAll(){
List<LogResponse> logs = logService.findAll();
return ResponseEntity.ok(logs);
}
/**
* GET /api/logs/search : ์กฐ๊ฑด๋ณ ๊ฒ์
*
* ๋ก๊ทธ ์กฐํ์ ํต์ฌ์ผ๋ก, ์ด๋ฅผ ํตํด ํด๋น ์์๋ฅผ ์ธ๋ฑ์ค๋ก ์ค์ ํ์๋, ์ฑ๋ฅ์ด ์ผ๋ง๋ ๋นจ๋ผ์ง๋์ง๋ฅผ ์ ์ ์๋ค.
*/
@GetMapping("/search")
public ResponseEntity<List<LogResponse>> search(
@DateTimeFormat(iso = DateTimeFormat.ISO.DATE_TIME) LocalDateTime dateTime, //dateTime์ controller์์ ๋ณํํด์ผ ๊น๋ํ๋ค.
@RequestParam(required = false) Level level, //ENUM ํ์
์๋ ๋ฐํ
@RequestParam(required = false) String message,
@RequestParam(required = false) Long userId
){
List<LogResponse> logs = logService.searchByConditions(dateTime, level, message, userId);
return ResponseEntity.ok(logs);
}
/**
* GET /api/logs/performance : ์ธ๋ฑ์ค ์ฑ๋ฅ ํ
์คํธ
*
* ์ธ๋ฑ์ค ์ฑ๋ฅ ํ
์คํธ๋ฅผ ์ํ api
* ์ด๋ฅผ ํตํด ์ธ๋ฑ์ค๋ฅผ ์์ฑํ์๋, ์ด๋ป๊ฒ ์์ฑํ๋์ง์ ๋ฐ๋ฅธ ์๊ฐ์ ์ธก์ ํ๊ธฐ ์ํจ์ด๋ค.
*/
@GetMapping("/performance")
public ResponseEntity<PerformanceTestResponse> performance(){
PerformanceTestResponse log = logService.performanceTest();
return ResponseEntity.ok(log);
}
}
REST API๋ฅผ ๋ถ์ํ๋ ๋จ๊ณ๊ฐ ์๋๋ฏ๋ก controller-service-repository์ ๋ํ ๊น์ ์ค๋ช ์ ํ์ง ์๊ฒ ๋ค. controller์ ๊ฒฝ์ฐ๋ ์ ์ฒด ๋ก๊ทธ ๊ฒ์ & ์กฐ๊ฑด๋ณ ๊ฒ์(์ฟผ๋ฆฌ) & ์ฑ๋ฅ ํ ์คํธ ์ธ ๊ฐ์ง๋ก ๋๋ ์ ๊ตฌ์ฑํ์๋ค. ์ฐธ๊ณ ๋ก, ์ฑ๋ฅ ํ ์คํธ์ ๊ฒฝ์ฐ ์์ ์์ ๊ณผ ์ฟผ๋ฆฌ ์ข ๋ฃ ์์ ์ ms๋ฅผ ๋น๊ตํด์ ์ธ๋ฑ์ค ์ฌ์ฉ ์ ํ์ ์๊ฐ ์ฐจ์ด๋ฅผ ๋น๊ตํ๊ธฐ ์ํด ์์ฑํ ๊ฒ์ด๋ค.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
/**
* index test๋ฅผ ์ํ ์๋ต์ ๋ด์ ๊ฐ๋จํ dto
*/
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class PerformanceTestResponse {
// ์ ์ฒด ๋ก๊ทธ ๊ฐ์
private Long totalRecords;
// ๋จ์ผ ์ปฌ๋ผ ์ธ๋ฑ์ค ํ
์คํธ
private Long levelSearchTime;
private Long userIdSearchTime;
private Long messageSearchTime;
// ์ถ๊ฐ) containing์์ ์ธ๋ฑ์ค ์คํ
private Long messageContainingSearchTime;
// ๋ณตํฉ ์ธ๋ฑ์ค ํ
์คํธ
private Long levelAndUserIdSearchTime;
private Long levelAndMessageSearchTime;
// ์กฐํ๋ ๊ฒฐ๊ณผ ๊ฐ์ (์ต์
)
private Integer levelSearchCount;
private Integer userIdSearchCount;
private Integer levelAndUserIdSearchCount;
// ์ธ๋ฑ์ค ์กด์ฌ ์ฌ๋ถ (๋์ค์ ๋น๊ต์ฉ)
private String testDescription; // "์ธ๋ฑ์ค ์ " or "์ธ๋ฑ์ค ํ"
}
์ฐธ๊ณ ๋ก ๊ทธ๋์ ์ด ์ฑ๋ฅ ์ธก์ dto๋ ์ด๋ ๊ฒ ๊ตฌ์ฑ๋์ด ์๋ค.
Service
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
@Service
@RequiredArgsConstructor
@Slf4j
public class LogService {
private final LogRepository logRepository;
private final EntityManager entityManager; // LogService - ์ง์ ์ฌ์ฉ ์ฉ (์บ์ ํด๋ฆฌ์ด, ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ ๋ฑ)
/**
* ์ ์ฒด ๋ก๊ทธ๋ฅผ ์กฐํํ๊ธฐ ์ํ method
* @return
*/
public List<LogResponse> findAll(){
List<Logs> logEntityList = logRepository.findAll();
List<LogResponse> logResponseList = getLogResponses(logEntityList);
return logResponseList;
}
/**
* ์กฐ๊ฑด๋ณ๋ก ๋ก๊ทธ๋ฅผ ์กฐํํ๊ธฐ ์ํ method
*/
public List<LogResponse> searchByConditions(
LocalDateTime dateTime,
Level level,
String message,
Long userId){
// ํ๋ผ๋ฏธํฐ๋ค์ด ๋ชจ๋ ์ ํ ์ฟผ๋ฆฌ์ด๊ธฐ ๋๋ฌธ์, ๋์ ์ฟผ๋ฆฌ๊ฐ ํ์.
List<Logs> logEntityList = logRepository.searchByConditions(dateTime, level, message, userId);
List<LogResponse> logResponseList = getLogResponses(logEntityList);
return logResponseList;
}
/**
* ์ธ๋ฑ์ค์ ์ ํํ ์ฑ๋ฅ์ ๊ฒ์ํ๊ธฐ ์ํ method
*/
public PerformanceTestResponse performanceTest(){
long startTime; //์ฑ๋ฅ ์ธก์ ์ ์ํ ์์ ์๊ฐ ๋ช
์
// 1. level ๊ฒ์
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> errorLogs = logRepository.findByLevel(Level.ERROR);
long levelSearchTime = System.currentTimeMillis() - startTime;
// 2. userId ๊ฒ์
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> userLogs = logRepository.findByUserId(1L);
long userIdSearchTime = System.currentTimeMillis() - startTime;
// 3. message ๊ฒ์
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> messageLogs = logRepository.findByMessageStartingWith("๊ฐ๋๋"); // LIKE '๊ฐ๋๋%' ์ฒดํฌ๋ฅผ ์ํด StartingWith์ผ๋ก ํ๋ค.
long messageSearchTime = System.currentTimeMillis() - startTime;
// 3-2. message ๊ฒ์ 2
// ์ด๋ฒ์๋, ์ธ๋ฑ์ค๊ฐ ํตํ์ง ์๋ contain์ผ๋ก ์ธ๋ฑ์ค์ ํจ๊ณผ๋ฅผ ์ธก์ ํด๋ณด์.
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> messageContainingLogs = logRepository.findByMessageContaining("๊ฐ๋๋"); // LIKE '%๊ฐ๋๋%' ์ฒดํฌ๋ฅผ ์ํด Containing์ผ๋ก ํ๋ค.
long messageContainingSearchTime = System.currentTimeMillis() - startTime;
// ๋ณตํฉ ๊ฒ์ (level + userId)
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> levelAndUserIdLogs = logRepository.findByLevelAndUserId(Level.ERROR, 1L);
long levelAndUserIdSearchTime = System.currentTimeMillis() - startTime;
// ๋ณตํฉ ๊ฒ์ (level + message)
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> levelAndMessageLogs = logRepository.findByLevelAndMessageStartingWith(Level.ERROR, "๊ฐ๋๋");
long levelAndMessageSearchTime = System.currentTimeMillis() - startTime;
return PerformanceTestResponse.builder()
.totalRecords(logRepository.count())
.levelSearchTime(levelSearchTime)
.userIdSearchTime(userIdSearchTime)
.messageSearchTime(messageSearchTime)
.messageContainingSearchTime(messageContainingSearchTime) //์ธ๋ฑ์ค๊ฐ ์๋จนํ๋์ง ๋ด์ผ ํ๋ค.
.levelAndUserIdSearchTime(levelAndUserIdSearchTime)
.levelAndMessageSearchTime(levelAndMessageSearchTime)
.levelSearchCount(errorLogs.size())
.userIdSearchCount(userLogs.size())
.levelAndUserIdSearchCount(levelAndUserIdLogs.size())
.testDescription("ํ์ฌ ์ธ๋ฑ์ค ์ํ")
.build();
}
// entity to dto๋ฅผ ์ํ method
private static List<LogResponse> getLogResponses(List<Logs> logEntityList) {
List<LogResponse> logResponseList = logEntityList.stream()
.map(logs -> LogResponse.from(logs))
.collect(Collectors.toList());
return logResponseList;
}
}
์๋น์ค์์ repository์์ ์ฟผ๋ฆฌ ์กฐํ๋ฅผ ํ ๊ฒฐ๊ณผ๋ค์ entity > dto ํด์ฃผ๋ ๊ธฐ๋ณธ์ ์ธ ์ญํ ์ ์ํํ๋ค. (๋ง์ฐฌ๊ฐ์ง๋ก REST API์ ๋ํ ๊ฒ ๋ชฉ์ ์ด ์๋๋ฏ๋ก ์ด ๋ถ๋ถ ์ค๋ช ์๋ตํ๋ค..)
์ฌ๊ธฐ์ ์ค์ํ ๊ฒ์, ์ฑ๋ฅ ์ธก์ ์ ์ํ ๋ฉ์๋์ธ performanceTest()
๋ถ๋ถ์ด๋ค.
์ฌ๊ธฐ์ JPA์ ์์์ฑ ์ปจํ
์คํธ(1์ฐจ ์บ์) ๊ธฐ๋ฅ์ ๋ฌดํจํํด์ ๋ ์ ํํ ๊ฒฐ๊ณผ ์ธก์ ์ ์ํด entityManager.clear();
๋ฅผ ๊ฐ ์ฟผ๋ฆฌ ์คํ ์ ์ ํ์๋ก ์งํํ์๋ค.
๐ฏ ์๋ EntityManager๋ฅผ ๋ ๋ฒ ์ ์ธํด๋ ๋๋ ๊ฑฐ์ผ?
์ฌ๊ธฐ์ ์๋ฌธ์ด ๋ค์๋ ๋ถ๋ถ์ ์ด๊ฒ์ด๋ค.
๋์ ๊ฒฝ์ฐ๋ ์ด๋ฏธ QuerydslConfig์ private final EntityManager entityManager;
๊ฐ ์์ฑ๋์ด ์๋๋ฐ, ์ฌ๊ธฐ์ ํ ๋ฒ ๋ ์ฌ์ฉํ๋ค๋ฉด ๋ญ๊ฐ ๋ฌธ์ ๊ฐ ์๊ธธ ๊ฒ ๊ฐ์๊ธฐ ๋๋ฌธ!
1
2
3
4
5
// QuerydslConfig์์
private final EntityManager entityManager;
// LogService์์
private final EntityManager entityManager;
๊ฒฐ๋ก ๋ถํฐ ๋งํ์๋ฉด ์๊ด์๋ค. ์๋๋ฉด, Spring์ EntityManager๋ ํ๋ก์ ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ด๋ค!
์ด ๋์ ์ค์ ์ฐ EntityManager๊ฐ ์๋๊ณ , EntityManager๋ฅผ ๊ฐ๋ฆฌํค๋ ํ๋ก์ ๊ฐ์ฒด์ด๊ธฐ ๋๋ฌธ์ Thread-safeํ๊ฒ ๊ด๋ฆฌ๊ฐ ๋๊ณ ์๊ณ , ๊ฐ ํธ๋์ญ์ ๋ง๋ค ์ค์ EntityManager๋ฅผ ๋ฐ์ธ๋ฉํด์ฃผ๊ธฐ ๋๋ฌธ์ ๊ฐ ์์ฒญ๋ง๋ค ๋ ๋ฆฝ์ ์ธ EntityManager ์ฌ์ฉ์ด ๊ฐ๋ฅํ๊ณ , ์ด๋ก ์ธํด ๊ฐ๋ฐ์๋ ์ ๊ฒฝ ์ธ ํ์ ์์ด ์ฃผ์ ๋ง ๋ฐ์ผ๋ฉด ๋ ๋ฆฝ์ ์ผ๋ก ์ฌ์ฉ์ด ๊ฐ๋ฅํ ๊ฒ์ด๋ค!
LIKE์ ์ฐจ์ด์
๊ทธ๋ฆฌ๊ณ ์ฌ๊ธฐ์ ๋ ์ค์ํ ๊ฒ์ ์ด๊ฒ์ด๋ค.
๋ฐ๋ก LIKE ์ฟผ๋ฆฌ์ ํน์ด์ ๋๋ฌธ์ธ๋ฐ, ์ด ๋ ๊ฐ์ง๋ฅผ ๋น๊ตํด๋ณด์.
1
2
3
4
5
6
7
8
9
10
11
12
13
// 3. message ๊ฒ์
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> messageLogs = logRepository.findByMessageStartingWith("๊ฐ๋๋"); // LIKE '๊ฐ๋๋%' ์ฒดํฌ๋ฅผ ์ํด StartingWith์ผ๋ก ํ๋ค.
long messageSearchTime = System.currentTimeMillis() - startTime;
// 3-2. message ๊ฒ์ 2
// ์ด๋ฒ์๋, ์ธ๋ฑ์ค๊ฐ ํตํ์ง ์๋ contain์ผ๋ก ์ธ๋ฑ์ค์ ํจ๊ณผ๋ฅผ ์ธก์ ํด๋ณด์.
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> messageContainingLogs = logRepository.findByMessageContaining("๊ฐ๋๋"); // LIKE '%๊ฐ๋๋%' ์ฒดํฌ๋ฅผ ์ํด Containing์ผ๋ก ํ๋ค.
long messageContainingSearchTime = System.currentTimeMillis() - startTime;
findByMessageStartingWith("๊ฐ๋๋");
์ ๊ฒฝ์ฐ๋ ๊ฐ๋๋%
๋ก ์นํ๋๊ณ , findByMessageContaining("๊ฐ๋๋");
์ ๊ฒฝ์ฐ๋ %๊ฐ๋๋%
๋ก ์นํ๋๋ค.
์์์ ์ธ๋ฑ์ค์ ๋ํ ๊ณต๋ถ๋ฅผ ํ ๋ ์ธ๊ธํ์ง๋ง, ์ธ๋ฑ์ค์ ๊ฒฝ์ฐ๋ ์ผ์ชฝ์ ๊ธฐ์ค์ผ๋ก ํ๊ธฐ ๋๋ฌธ์ StartingWith๋ ์ธ๋ฑ์ค์ ํจ๊ณผ๋ฅผ ๋ณผ ์ ์์ง๋ง, Containing์ ์ธ๋ฑ์ค์ ํจ๊ณผ๋ฅผ ๋ณผ ์ ์์ ๊ฒ์ด๋ค.
์ฆ, ์ด ์คํ์์ ์ฐ๋ฆฌ๊ฐ ์ค์ํ๊ฒ ์ดํด๋ด์ผ ํ ๋ถ๋ถ์ ๋ํ โ๊ฐ์คโ์ ๋ค์๊ณผ ๊ฐ๋ค.
message ์ปฌ๋ผ์ ์ธ๋ฑ์ค๊ฐ ์ ์ฉ๋ ์ํ์์:
StartingWith
(LIKE โ๊ฐ๋๋%โ): ์ธ๋ฑ์ค ํ์ฉ โ ๋น ๋ฅธ ์กฐํContaining
(LIKE โ%๊ฐ๋๋%โ): ์ธ๋ฑ์ค ๋ฏธํ์ฉ โ ๋๋ฆฐ ์กฐํ (Full Scan)๋ฐ๋ผ์ ๋ ๋ฉ์๋ ๊ฐ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ํตํด ์ธ๋ฑ์ค์ ํจ๊ณผ๋ฅผ ์ ๋์ ์ผ๋ก ์ธก์ ํ ์ ์์ ๊ฒ์ด๋ค
performance test ๊ฒฐ๊ณผ (no index)
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"totalRecords": 100000,
"levelSearchTime": 691,
"userIdSearchTime": 145,
"messageSearchTime": 167,
"messageContainingSearchTime": 218,
"levelAndUserIdSearchTime": 132,
"levelAndMessageSearchTime": 189,
"levelSearchCount": 24975,
"userIdSearchCount": 8,
"levelAndUserIdSearchCount": 2,
"testDescription": "ํ์ฌ ์ธ๋ฑ์ค ์ํ"
}
์ธ๋ฑ์ค๋ฅผ ์์ฑํ๊ธฐ ์ ์ postman์ ํตํด ์ฑ๋ฅ response๋ฅผ ๋ฐ์ ๊ฒฐ๊ณผ์ด๋ค. ๋ณด๋ฉด ์๊ฒ ์ง๋ง, ์ญ๋ง ๊ฐ๋ผ๋ ๋ง์ง ์์ ์์ ๋ฐ์ดํฐ์์๋ ๋จ์ผํ ์กฐ๊ฑด์ ๊ฒ์ํ์ ๋์กฐ์ฐจ ์๋นํ ์ค๋ ๊ฑธ๋ฆฌ๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
๋จ์ผ ์ธ๋ฑ์ค ์์ฑํด์ ์ฟผ๋ฆฌ ์๋ ๋น๊ตํ๊ธฐ~
๋ค์์ผ๋ก ์ฐ์ , ๋จ์ผ ์ธ๋ฑ์ค๋ง ์์ฑํด์ ์ฑ๋ฅ์ ๋น๊ตํด๋ณด๋๋ก ํ๋ค.
1
2
3
4
5
6
7
-- level ์ธ๋ฑ์ค
CREATE INDEX idx_logs_level ON logs(level);
-- userId ์ธ๋ฑ์ค
CREATE INDEX idx_logs_user_id ON logs(user_id);
-- message ์ธ๋ฑ์ค
-- text๋ blob์ ๊ฒฝ์ฐ๋ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ ๋ ๊ธธ์ด๋ฅผ ์์ฑํด์ผ ํ๋ค.
CREATE INDEX idx_logs_message ON logs(message(255));
์, ๋จ์ผ ์ธ๋ฑ์ค ์์ฑ์ ์ฑ๊ณตํ๋ค. ์ด์ postman์ ๋ค์ ์คํํด๋ณด์.
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"totalRecords": 100000,
"levelSearchTime": 374,
"userIdSearchTime": 5,
"messageSearchTime": 4,
"messageContainingSearchTime": 218,
"levelAndUserIdSearchTime": 3,
"levelAndMessageSearchTime": 3,
"levelSearchCount": 24975,
"userIdSearchCount": 8,
"levelAndUserIdSearchCount": 2,
"testDescription": "ํ์ฌ ์ธ๋ฑ์ค ์ํ"
}
์ด ์ด๋ง๋ฌด์ํ ์ฐจ์ด๊ฐ ๋์ ๋ณด์ด๋๊ฐ?
์ด๋ฅผ ์ง์ ๋น๊ตํด์ ํ๋ก ๋ํ๋ด๋ณด๋ฉด ๋ค์๊ณผ ๊ฐ๋ค
์ธก์ ํญ๋ชฉ | ์ธ๋ฑ์ค ์ ์ฉ ์ (ms) | ์ธ๋ฑ์ค ์ ์ฉ ํ (ms) | ๊ฐ์ ์จ | ์ฑ๋ฅ ํฅ์ |
---|---|---|---|---|
levelSearchTime | 691 | 374 | -45.9% | 1.85๋ฐฐ ๋นจ๋ผ์ง |
userIdSearchTime | 145 | 5 | -96.6% | 29๋ฐฐ ๋นจ๋ผ์ง |
messageSearchTime | 167 | 4 | -97.6% | 41.75๋ฐฐ ๋นจ๋ผ์ง |
levelAndUserIdSearchTime | 132 | 3 | -97.7% | 44๋ฐฐ ๋นจ๋ผ์ง |
levelAndMessageSearchTime | 189 | 3 | -98.4% | 63๋ฐฐ ๋นจ๋ผ์ง |
๋จ์ผ ์ธ๋ฑ์ค ๋ง์ผ๋ก ์ด์ ๋ ์ฑ๋ฅ์ ๋ด๋ ๊ฒ์ด ๊ฐ๋ฅํ๋ค.
๊ทธ๋ ๋ค๋ฉด ๋ณตํฉ ์ธ๋ฑ์ค๋ฅผ ์์ฑํ๋ฉด ์ด๋ป๊ฒ ๋ ๊น?
๋ณตํฉ ์ธ๋ฑ์ค ์์ฑํด์ ์ฟผ๋ฆฌ ์๋ ๋น๊ตํ๊ธฐ~
1
2
3
4
-- level, userId ๋ณตํฉ ์ธ๋ฑ์ค
CREATE INDEX idx_logs_level_user_id ON logs(level, user_id);
-- level, message ๋ณตํฉ ์ธ๋ฑ์ค
CREATE INDEX idx_logs_level_message ON logs(level, message(255));
1
2
3
4
5
6
7
8
9
10
11
12
{
"totalRecords": 100000,
"levelSearchTime": 397,
"userIdSearchTime": 3,
"messageSearchTime": 2,
"levelAndUserIdSearchTime": 5,
"levelAndMessageSearchTime": 4,
"levelSearchCount": 24975,
"userIdSearchCount": 8,
"levelAndUserIdSearchCount": 2,
"testDescription": "ํ์ฌ ์ธ๋ฑ์ค ์ํ"
}
์ธก์ ํญ๋ชฉ | ์ธ๋ฑ์ค ์ ์ฉ ์ (ms) | ์ธ๋ฑ์ค ์ ์ฉ ํ (ms) | ๊ฐ์ ์จ | ์ฑ๋ฅ ํฅ์ |
---|---|---|---|---|
levelSearch | 691 | 397 | -42.5% | 1.7๋ฐฐ ๋นจ๋ผ์ง |
userIdSearch | 145 | 3 | -97.9% | ๐ฅ 48๋ฐฐ ๋นจ๋ผ์ง |
messageSearch | 167 | 2 | -98.8% | ๐ฅ 83๋ฐฐ ๋นจ๋ผ์ง |
levelAndUserIdSearch | 132 | 5 | -96.2% | ๐ฅ 26๋ฐฐ ๋นจ๋ผ์ง |
levelAndMessageSearch | 189 | 4 | -97.9% | ๐ฅ 47๋ฐฐ ๋นจ๋ผ์ง |
์ฌ์ค ํต๊ณ์ ์ผ๋ก ์๋ฏธ ์๋ ์ฐจ์ด๊ฐ ๋์ง๋ ์๋๋ค. ์ง๊ธ์ ๋ก๊ทธ ๋ฐ์ดํฐ๊ฐ ์ญ๋ง๊ฐ๋ฐ์ ์์ด์โฆ๊ทธ๋ ๋ค๊ณ ํ๋ค. ์ด๋ฏธ ๋จ์ผ ์ธ๋ฑ์ค ํ๋ ๋ง์ผ๋ก ์ด๋์ ๋ ์ปค๋ฒ๋ง์ด ๋๋ ์ํ๋ผ.
๋ฐ์ดํฐ๊ฐ ์์ฒ ์๋ง์ ์ด๋ฅผ ๊ฒฝ์ฐ, ๋ณตํฉ ์ธ๋ฑ์ค๊ฐ ํ์ ๋ฐํํ๋ค๊ณ ํ๋ค!!
๊ฐ์ค ๊ฒ์ฆ : messageSearchTime vs messageContainingSearchTime ๋น๊ต
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"totalRecords": 100000,
"levelSearchTime": 397,
"userIdSearchTime": 3,
"messageSearchTime": 2,
"messageContainingSearchTime": 218,
"levelAndUserIdSearchTime": 5,
"levelAndMessageSearchTime": 4,
"levelSearchCount": 24975,
"userIdSearchCount": 8,
"levelAndUserIdSearchCount": 2,
"testDescription": "ํ์ฌ ์ธ๋ฑ์ค ์ํ"
}
์ฐธ๊ณ ๋ก ๋งํ์๋ฉด, ์ด๊ฑด ์ธ๋ฑ์ค ์์ฑ ํ์ ๊ฒฐ๊ณผ์์๋ "messageContainingSearchTime": 218
์ ๊ฒฝ์ฐ, ์ ํ ์ธ๋ฑ์ค์ ํจ๊ณผ๋ฅผ ๋ชป ๋ณด๊ณ ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ฆ ์ฐ๋ฆฌ๊ฐ ์ธ์ ๋ ๊ฐ์ค๋๋ก ํ์คํ startingWith๋ ์ธ๋ฑ์ค ํจ๊ณผ๊ฐ ์์ง๋ง, containing์ ์ธ๋ฑ์ค ํจ๊ณผ๊ฐ ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค.
์ถ๊ฐ) ์นด๋๋๋ฆฌํฐ
์ฐธ๊ณ ๋ก ์ธ๋ฑ์ค์๋ ์นด๋๋๋ฆฌํฐ๋ผ๋ ๊ฒ์ด ์กด์ฌํ๋ค.
์นด๋๋๋ฆฌํฐ๊ฐ ํด์๋ก ์ ํ๋๊ฐ ๋๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
์ ํ๋๊ฐ ๋ฌด์์ ์๋ฏธํ๋์ง๋ ์ด๋ฏธ ๊ฐ๋ ์์ ๋ค๋ค์ ์๊ณ ์๋ค์ํผ, ์ ํ๋๊ฐ ๋์์๋ก ์ธ๋ฑ์ค ํ๋๊ฐ ์ ์ผ์ฑ์ด ๋๋ค๋ ๊ฒ์ ์๋ฏธํ๋ค.
๊ทธ๋ฌ๋ฏ๋ก ์ค์ ๋ก ์นด๋๋๋ฆฌํฐ๊ฐ ๋์์๋ก, ์ธ๋ฑ์ค์ ํจ๊ณผ๊ฐ ํฌ๋ค.
์ถ๊ฐ) ์คํ ๊ณํ ๋น๊ต
1
EXPLAIN SELECT * FROM logs WHERE level = 'ERROR' AND user_id = 1;
์คํ ๊ณํ์ ๋น๊ตํด๋ณด๋ฉด ์ค์ ๋ก ๋ณตํฉ ์ธ๋ฑ์ค๊ฐ ์ ์ฐ์ฌ์ง๊ณ ์๋์ง๋ฅผ ์ ์ ์๋ค.
์คํ ๊ณํ์ ์คํํด์ ์ดํด๋ณด๋ฉด, key๊ฐ idx_logs_level_user_id๋ก ๋์ด ์๊ธฐ ๋๋ฌธ์, ๋ณตํฉ ์ธ๋ฑ์ค๊ฐ ์ ์ฐ์ด๊ณ ์๋ ๊ฒ์ ๋ณผ ์ ์๋ค. ๊ทธ๋ฅ ์ง์ง๋ก ๋ฐ์ดํฐ ์๊ฐ ์์์ ์ ์๋ฏธํ ๊ฒฐ๊ณผ๊ฐ ์๋์จ ๊ฒ์ผ ๋ฟ..
ํ์ฌ ๋ฒ์ ์ ๊นํ๋ธ์ tag๋ก ๊ธฐ๋กํ๊ธฐ!
sspur@sunj-PC MINGW64 /c/developer/GitHub/study_repo/RealMySQL (main)
$ git tag v1.0-basic-index -m "๊ธฐ๋ณธ ์ธ๋ฑ์ค ์ต์ ํ : ์ต๋ 83๋ฐฐ ์ฑ๋ฅ ๊ฐ์ "
sspur@sunj-PC MINGW64 /c/developer/GitHub/study_repo/RealMySQL (main)
$ git show v1.0-basic-index
tag v1.0-basic-index
Tagger: sunJ0120 <sspure1214@gmail.com>
Date: Mon Oct 6 19:58:16 2025 +0900
//......
sspur@sunj-PC MINGW64 /c/developer/GitHub/study_repo/RealMySQL (main)
$ git push origin v1.0-basic-index
์ฐธ๊ณ ๋ก ์ด๊ฑด ๋ณด๋์ค์ธ๋ฐ, ์ธ๋ฑ์ค์ ๊ฒฝ์ฐ๋ ๊ณ์ ๋ฒ์ ์ ์ ํ ์์ ์ด๋ผ ํ๊ทธ๋ฅผ ๋ฌ์๋์๋ค.
์ง๊ธ์ ๋ฒ ์ด์ง ์ธ๋ฑ์ค๋ผ v1์ด๊ณ , ์ฐจํ ์ปค๋ฒ๋ง ์ธ๋ฑ์ค ๋ฑ์ ์ฌํ๋ v2, v3..์์ ๋ค๋ฃฐ ์์ ์ด๋ค.
์ด๋ ๊ฒ ํ๊ทธ๋ฅผ ๋ฌ์์, ํน์ ์์ ์ ๋ง์ผ ์คํค์ ์ ์ฅํด์ฃผ๋ฉด ๋๋ค.