Post

๐ŸŽฑ MySQL ์ธ๋ฑ์Šค ์‹ค์Šต - ์ตœ๋Œ€ 83๋ฐฐ ์„ฑ๋Šฅ ๊ฐœ์„  ๊ฒฝํ—˜๊ธฐ

๐ŸŽฑ MySQL ์ธ๋ฑ์Šค ์‹ค์Šต - ์ตœ๋Œ€ 83๋ฐฐ ์„ฑ๋Šฅ ๊ฐœ์„  ๊ฒฝํ—˜๊ธฐ

๐Ÿ–ค Intro

์ธ๋ฑ์Šค, ์•Œ๊ณ ๋Š” ์žˆ์—ˆ์ง€๋งŒโ€ฆ

โ€œ์ธ๋ฑ์Šค ์“ฐ๋ฉด ๋นจ๋ผ์ง„๋‹คโ€๋Š” ๊ฑด ๋ˆ„๊ตฌ๋‚˜ ์•ˆ๋‹ค. ๋ฉด์ ‘์—์„œ๋„ ๋‹จ๊ณจ ์งˆ๋ฌธ์ด๊ณ , ์‹ค๋ฌด์—์„œ๋„ ๋‹น์—ฐํžˆ ์จ์•ผ ํ•œ๋‹ค๊ณ ๋“ค ํ•˜์ง€๋งŒโ€ฆ ์†”์งํžˆ ๋งํ•˜๋ฉด ์–ผ๋งˆ๋‚˜ ๋นจ๋ผ์ง€๋Š”์ง€๋Š” ์ง์ ‘ ๊ฒฝํ—˜ํ•ด๋ณธ ์ ์ด ์—†์—ˆ๋‹ค.

์‹ญ๋งŒ ๊ฑด? ๋ฐฑ๋งŒ ๊ฑด? ๊ทธ ์ •๋„ ๋ฐ์ดํ„ฐ๋ฅผ ์‹ค์ œ๋กœ ๋‹ค๋ค„๋ณธ ์ ๋„ ์—†๊ณ , ์ธ๋ฑ์Šค ์—†์ด ์ฟผ๋ฆฌ ๋Œ๋ ค๋ณด๊ณ  โ€œ์•„ ๋„ˆ๋ฌด ๋А๋ฆฌ๋„คโ€ ํ•˜๋ฉด์„œ ์ธ๋ฑ์Šค ๊ฑธ์–ด๋ณด๊ณ  โ€œ์˜ค ๋นจ๋ผ์กŒ๋‹ค!โ€ ์ด๋Ÿฐ ๊ฒฝํ—˜๋„ ์—†์—ˆ๋‹ค.

๊ทธ๋ƒฅ ๋ง‰์—ฐํ•˜๊ฒŒ โ€œ์ธ๋ฑ์Šค๋Š” ์ข‹์€ ๊ฑฐ๋‹คโ€, โ€œB-Tree ๊ตฌ์กฐ๋กœ ๋˜์–ด ์žˆ๋‹คโ€, โ€œ์นด๋””๋„๋ฆฌํ‹ฐ๊ฐ€ ๋†’์•„์•ผ ํ•œ๋‹คโ€ ์ด๋Ÿฐ ์ด๋ก ๋งŒ ์•Œ๊ณ  ์žˆ์—ˆ์„ ๋ฟ์ด๋‹ค.

๊ทธ๋ž˜์„œ ์ง์ ‘ ํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค!!

๋งˆ์นจ RealMySQL์„ ๊ณต๋ถ€ํ•˜๋ฉด์„œ ๋งˆ์นจ ๋”ฑ B-Tree ์ธ๋ฑ์Šค ํŒŒํŠธ ๊ณต๋ถ€๋ฅผ ๋งˆ์นœ ์ฐธ์ด๋ผ, ์ง์ ‘ ์‹ค์Šตํ•ด๋ณด๊ธฐ๋กœ ํ–ˆ๋‹ค. ๋‚œ ๊ถ๊ธˆํ•œ๊ฑด ๋ชป์ฐธ์•„!!!!!!

์‹ญ๋งŒ ๊ฑด์˜ ๋กœ๊ทธ ๋ฐ์ดํ„ฐ๋ฅผ ์ง์ ‘ ์ƒ์„ฑํ•˜๊ณ  ์ธ๋ฑ์Šค ์—†์ด ์ฟผ๋ฆฌ ์„ฑ๋Šฅ์„ ์ธก์ •ํ•ด๋ณด๊ณ  ๋‹จ์ผ ์ธ๋ฑ์Šค, ๋ณตํ•ฉ ์ธ๋ฑ์Šค๋ฅผ ๊ฐ๊ฐ ์ ์šฉํ•ด์„œ

์ง„ํ–‰ํ–ˆ๋‹ค..

๊ฒฐ๊ณผ๋ฅผ ๋ฏธ๋ฆฌ ๋งํ•˜์ž๋ฉด, ์ตœ๋Œ€ 83๋ฐฐ ๋นจ๋ผ์ง€๋Š” ์–ด๋งˆ๋ฌด์‹œํ•œ..๊ฒฐ๊ณผ๊ฐ€ ๋‚˜์™”๋‹ค.

๋„ํŒŒ๋ฏผ์ด ํํ ํ„ฐ์ง€๋Š” ๊ฒฐ๊ณผ๋ผ ์ง€๊ธˆ๋„ ์„ค๋ ˆ๋Š” ๋งˆ์Œ์ด ๊ทธ๋“ํ•˜๋‹ค

๊ทธ๋Ÿผ ์–ด๋–ค ๊ณผ์ •์œผ๋กœ ์‹ค์Šต์„ ์ง„ํ–‰ํ–ˆ๋Š”์ง€๋ฅผ ๋” ๊ตฌ์ฒด์ ์œผ๋กœ ์‚ดํŽด๋ณด์ž.

๐Ÿฉถ Start

์‹ค์Šต์„ ์‹œ์ž‘ํ•˜์ง€~

์‹ค์Šต์˜ ๋‹จ๊ณ„๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™๋‹ค.

  1. ๊ธฐ๋ณธ ์„ค์ • ๋ฐ ์„ฑ๋Šฅ ๋ถ„์„์„ ์œ„ํ•œ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€
  2. ๊ธฐ๋ณธ crud ๊ตฌ์„ฑ ๋ฐ ๊ธฐ๋Šฅ test
  3. ๋ณธ๊ฒฉ์ ์ธ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ ์‹œ์ž‘

๊ธฐ๋ณธ ์„ค์ • ๋ฐ ์„ฑ๋Šฅ ๋ถ„์„์„ ์œ„ํ•œ ๋”๋ฏธ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€

์Šคํ‚ค๋งˆ ์ƒ์„ฑ ๋ฐ ๊ถŒํ•œ ๋ถ€์—ฌ

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๋กœ ํ•œ ๋ฒˆ์— ์ €์žฅ๋˜๋„๋ก ํ•œ๋‹ค.

๋กœ๊ทธ๋ฐ์ดํ„ฐ.png

๋”๋ฏธ ๋ฐ์ดํ„ฐ๊ฐ€ ์ž˜ ๋“ค์–ด๊ฐ„ ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

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)๊ฐœ์„ ์œจ์„ฑ๋Šฅ ํ–ฅ์ƒ
levelSearchTime691374-45.9%1.85๋ฐฐ ๋นจ๋ผ์ง
userIdSearchTime1455-96.6%29๋ฐฐ ๋นจ๋ผ์ง
messageSearchTime1674-97.6%41.75๋ฐฐ ๋นจ๋ผ์ง
levelAndUserIdSearchTime1323-97.7%44๋ฐฐ ๋นจ๋ผ์ง
levelAndMessageSearchTime1893-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)๊ฐœ์„ ์œจ์„ฑ๋Šฅ ํ–ฅ์ƒ
levelSearch691397-42.5%1.7๋ฐฐ ๋นจ๋ผ์ง
userIdSearch1453-97.9%๐Ÿ”ฅ 48๋ฐฐ ๋นจ๋ผ์ง
messageSearch1672-98.8%๐Ÿ”ฅ 83๋ฐฐ ๋นจ๋ผ์ง
levelAndUserIdSearch1325-96.2%๐Ÿ”ฅ 26๋ฐฐ ๋นจ๋ผ์ง
levelAndMessageSearch1894-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์€ ์ธ๋ฑ์Šค ํšจ๊ณผ๊ฐ€ ์—†๋Š” ๊ฒƒ์„ ๋ณผ ์ˆ˜ ์žˆ๋‹ค.

์ถ”๊ฐ€) ์นด๋””๋„๋ฆฌํ‹ฐ

์นด๋””๋„๋ฆฌํ‹ฐ.png

์ฐธ๊ณ ๋กœ ์ธ๋ฑ์Šค์—๋Š” ์นด๋””๋„๋ฆฌํ‹ฐ๋ผ๋Š” ๊ฒƒ์ด ์กด์žฌํ•œ๋‹ค.

์นด๋””๋„๋ฆฌํ‹ฐ๊ฐ€ ํด์ˆ˜๋ก ์„ ํƒ๋„๊ฐ€ ๋†’๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

์„ ํƒ๋„๊ฐ€ ๋ฌด์—‡์„ ์˜๋ฏธํ•˜๋Š”์ง€๋Š” ์ด๋ฏธ ๊ฐœ๋…์—์„œ ๋‹ค๋ค„์„œ ์•Œ๊ณ  ์žˆ๋‹ค์‹œํ”ผ, ์„ ํƒ๋„๊ฐ€ ๋†’์„์ˆ˜๋ก ์ธ๋ฑ์Šค ํ•˜๋‚˜๊ฐ€ ์œ ์ผ์„ฑ์ด ๋†’๋‹ค๋Š” ๊ฒƒ์„ ์˜๋ฏธํ•œ๋‹ค.

๊ทธ๋Ÿฌ๋ฏ€๋กœ ์‹ค์ œ๋กœ ์นด๋””๋„๋ฆฌํ‹ฐ๊ฐ€ ๋†’์„์ˆ˜๋ก, ์ธ๋ฑ์Šค์˜ ํšจ๊ณผ๊ฐ€ ํฌ๋‹ค.

์ถ”๊ฐ€) ์‹คํ–‰ ๊ณ„ํš ๋น„๊ต

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..์—์„œ ๋‹ค๋ฃฐ ์˜ˆ์ •์ด๋‹ค.

์ด๋ ‡๊ฒŒ ํƒœ๊ทธ๋ฅผ ๋‹ฌ์•„์„œ, ํŠน์ • ์‹œ์ ์˜ ๋งˆ์ผ ์Šคํ†ค์„ ์ €์žฅํ•ด์ฃผ๋ฉด ๋œ๋‹ค.

ํƒœ๊ทธ๋‹ฌ๊ธฐ.png

This post is licensed under CC BY 4.0 by the author.