๐ฑ MySQL ์ธ๋ฑ์ค ์ค์ต - ์ ๋ฌธ ์ธ๋ฑ์ค๋ก B-tree ์ธ๋ฑ์ค์์ ์ปค๋ฒ ๋ชปํ๋ ๋ถ๋ถ ๋ณด์ํ๊ธฐ
๐ค Intro
์ ๋ฒ ํฌ์คํธ์์๋ B-Tree ์ธ๋ฑ์ค๋ฅผ ์ง์ ์์ฑํด์ ์ด๋์ ๋์ ์ฑ๋ฅ ์ฐจ์ด๋ฅผ ๋ง๋ค์ด ๋ผ ์ ์๋์ง๋ฅผ ์ง์ ์ดํด๋ณด์๋ค. ์ด๋ฒ ์๊ฐ์๋ ์ ๋ฌธ ์ธ๋ฑ์ค (Full Text Index)๋ฅผ ์ง์ ์์ฑํ๊ณ , ์ด๋์ ๋์ ์ฑ๋ฅ ์ฐจ์ด๊ฐ ๋๋์ง๋ฅผ ์ง์ ์ดํด๋ณด๋๋ก ํ์.
๐ฉถ Start
์ ๋ฌธ ์ธ๋ฑ์ค ์ค์ต์ ์ํ ์ธ๋ฑ์ค ์์ฑ
1
ALTER TABLE logs ADD FULLTEXT INDEX ft_idx_message (message) WITH PARSER ngram;
์ ๋ฌธ ๊ฒ์์ ์ํด์๋ โ์ด๋ค ์ปฌ๋ผโ์ FULLTEXT INDEX๋ฅผ ์์ฑํ ์ง๋ฅผ ์ธ๋ฑ์ค ์์ฑํด์ผ ๊ฒ์์ด ๊ฐ๋ฅํ๋ค.
์ฐธ๊ณ ๋ก ์๊ณ ๋ฆฌ์ฆ์ n-gram์ ์ด์ฉํ๋๋ก ํ์.
DTO
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/**
* full index test๋ฅผ ์ํ ์๋ต์ ๋ด์ ๊ฐ๋จํ dto
*/
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class FullIndexPerfomanceTestResponse {
// ๊ฒ์ ์์ ์๊ฐ
private Long likeSearchTime; // LIKE '%keyword%' ๊ฒ์ ์๊ฐ
private Long fullTextSearchTime; // MATCH AGAINST ๊ฒ์ ์๊ฐ
// ๊ฒ์ ๊ฒฐ๊ณผ
private Integer likeResultCount; // LIKE ๊ฒ์ ๊ฒฐ๊ณผ ๊ฐ์
private Integer fullTextResultCount; // ์ ๋ฌธ ๊ฒ์ ๊ฒฐ๊ณผ ๊ฐ์
// ์ฑ๋ฅ ๋น๊ต
private Double performanceImprovement; // ์ฑ๋ฅ ํฅ์ ๋น์จ (LIKE ์๊ฐ / FULL TEXT ์๊ฐ)
// ํ
์คํธ ์ ๋ณด
private String testDescription; // "์ธ๋ฑ์ค ์ " or "์ธ๋ฑ์ค ํ"
private String keyword; // ๊ฒ์์ด
}
๊ฒฐ๊ตญ ์ ๋ฌธ๊ฒ์์ ํ๋ ๋ชฉ์ ์, LIKE๋ก ์ ์ฒด ํ ์ด๋ธ์ FULL SCAN ํ๋ ๊ฒ๊ณผ ์ด๋์ ๋์ ์ฐจ์ด๊ฐ ๋๋์ง๋ฅผ ๋น๊ตํ๊ธฐ ์ํจ์ด๋ค.
์ด๋ฅผ ์ํด ๋ค์๊ณผ ๊ฐ์ด ํ๋ ๊ฐ๋ค์ ์์ฑํด์ฃผ์๋ค.
Repository
1
2
3
4
5
6
// ์ ๋ฌธ ๊ฒ์
// mysql ์ ์ฉ ํจ์๋ผ querydsl๋ก๋ ๊ตฌํ์ด ์ด๋ ค์์, ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๋ก ์์ฑ
@Query(value = "SELECT * FROM logs " +
"WHERE MATCH(message) AGAINST(:keyword IN BOOLEAN MODE)",
nativeQuery = true)
List<Logs> searchByFullText(@Param("keyword") String keyword);
mysql ์ ์ฉ ํจ์๋ ๊ธฐ๋ณธ querydsl ๋ฌธ๋ฒ์ผ๋ก๋ ์์ฑ์ด ๋ถ๊ฐ๋ฅํ์ฌ, ๋ค์ดํฐ๋ธ ์ฟผ๋ฆฌ๋ก ๋ง๋ค์ด์ฃผ์๋ค.
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
/**
* ์ ๋ฌธ ์ธ๋ฑ์ค์ ์ ํํ ์ฑ๋ฅ์ ๊ฒ์ํ๊ธฐ ์ํ method
*/
public FullIndexPerfomanceTestResponse fullIndexPerfomanceTest(String keyword){
long startTime; //์ฑ๋ฅ ์ธก์ ์ ์ํ ์์ ์๊ฐ ๋ช
์
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> likeSearchLogs = logRepository.findByMessageContaining(keyword); // LIKE '%keyword%' ์ฒดํฌ๋ฅผ ์ํด Containing์ผ๋ก ํ๋ค.
long likeSearchTime = System.currentTimeMillis() - startTime;
entityManager.clear(); // ๊ฐ ํ
์คํธ๋ง๋ค ๋
๋ฆฝ์ ์ธก์ ์ ์ํด clear ํด์ค์ผ ํ๋ค.
startTime = System.currentTimeMillis();
List<Logs> fullTextLogs = logRepository.searchByFullText(keyword);
long fullTextSearchTime = System.currentTimeMillis() - startTime;
// (์ด์ ์๊ฐ - ํ์ฌ ์๊ฐ) / ์ด์ ์๊ฐ * 100
double improvement = ((double)(likeSearchTime - fullTextSearchTime)
/ likeSearchTime) * 100;
return FullIndexPerfomanceTestResponse.builder()
.keyword(keyword)
.likeSearchTime(likeSearchTime)
.likeResultCount(likeSearchLogs.size())
.fullTextSearchTime(fullTextSearchTime)
.fullTextResultCount(fullTextLogs.size())
.performanceImprovement(improvement)
.testDescription("ํ์ฌ ์ ๋ฌธ ์ธ๋ฑ์ค ์ํ")
.build();
}
์๋น์ค ๊ตฌ์ฑ์ ์ด์ ์ b-tree ์ฑ๋ฅ ์ธก์ ํ๋๊ฒ๊ณผ ๋น์ทํ๋ค.
๋ง์ฐฌ๊ฐ์ง๋ก, rest api ์ค๋ช ํ๋ ๋จ๊ณ๊ฐ ์๋๋ฏ๋ก ์์ธํ ์ค๋ช ์ ์๋ตํ๋คโฆ
๊ฒฐ๊ณผ ๋ถ์
1
http://localhost:8085/api/logs/performance/full?keyword=894
1
2
3
4
5
6
7
8
9
{
"likeSearchTime": 187,
"fullTextSearchTime": 40,
"likeResultCount": 300,
"fullTextResultCount": 300,
"performanceImprovement": 78.6096256684492,
"testDescription": "ํ์ฌ ์ ๋ฌธ ์ธ๋ฑ์ค ์ํ",
"keyword": "894"
}
894๋ฅผ ํค์๋๋ก ๊ฒ์ํ ๊ฒฐ๊ณผ๋ฅผ postman์ผ๋ก ๋ฝ์๋ธ ๊ฒ์ด๋ค.
likeSearchTime, ์ฆ LIKE ์ฟผ๋ฆฌ๋ฅผ ํ์ฉํ๋ Contains์ ๋นํด full scan์ด ๋ฌด๋ ค 78%(4.7๋ฐฐ)๋ ๋น ๋ฅด๋ค!!
187ms > 40ms ์ ์ฑ๋ฅ ์ฐจ์ด๋ก, ๋ฐ์ดํฐ๊ฐ ๋ ๋ง์์ง๊ณ , ํค์๋๊ฐ ๋ ๋์ด๋๋ค๋ฉด ๋ ํฐ ๊ฒฉ์ฐจ๊ฐ ๋ณด์ผ ๊ฒ์ด๋ค.
๊ณผ์ฐ ์ ์ธ๋ฑ์ค๋ฅผ ์ฐ๋์ง ์๊ฒ ๋ฌ๊นโฆ..
1
http://localhost:8085/api/logs/performance/full?keyword=๋ก๊ทธ ๋ฉ์ธ์ง
1
2
3
4
5
6
7
8
9
{
"likeSearchTime": 2414,
"fullTextSearchTime": 11985,
"likeResultCount": 100000,
"fullTextResultCount": 100000,
"performanceImprovement": -396.4788732394366,
"testDescription": "ํ์ฌ ์ ๋ฌธ ์ธ๋ฑ์ค ์ํ",
"keyword": "๋ก๊ทธ ๋ฉ์ธ์ง"
}
์, ์ฌ๊ธฐ์ ํน์ดํ ์ ์ด ์๋ค.
์ง๊ธ ์ ๋ฌธ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ๋ message ์ปฌ๋ผ์ ๊ฒฝ์ฐ, ๋ชจ๋ 10๋ง๊ฐ์ ์ปฌ๋ผ์ด ์ ๋ถ ์ โ๋ก๊ทธ ๋ฉ์ธ์งโ๋ผ๋ ํค์๋๋ฅผ ์ฌ์ฉํ๊ณ ์๋๋ฐ, ์ด๋ ๊ฒ ๋๋ฌด ๋ง์ ๊ฒฐ๊ณผ๋ฅผ ๊ฐ์ง๊ณ ์์ ๊ฒฝ์ฐ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ๋ ๊ฒ์ด 300% ๋โฆ.๋ ๋๋ฆฌ๋ค.
์์์ ์ธ๋ฑ์ค๋ ์์ฑ ๋น์ฉ์ด ํฌ๊ธฐ ๋๋ฌธ์, ๊ฒ์ ๊ฒฐ๊ณผ๊ฐ ์ ์ฒด์ 25%๊ฐ ๋์ด๊ฐ ๊ฒฝ์ฐ ์ฐจ๋ผ๋ฆฌ full table scan์ด ๋ ๋ซ๋ค๊ณ ๋งํ๋ ๊ฒ์ ๊ธฐ์ตํ๋๊ฐ? ๊ทธ๊ฒ ๋ฐ๋ก ์ด๋ฌํ ์ด์ ๋๋ฌธ์ด๋ค.
๐ ๊ทธ๋์ ์ธ๋ฑ์ค๋ฅผ ์ฌ์ฉํ ๋๋, ์ธ๋ฑ์ค๊ฐ ๊ณผ์ฐ ๊ฒฐ๊ณผ๋ฅผ ํจ์จ์ ์ผ๋ก ๋์ถํ ์ ์๋๊ฐ?๋ฅผ ์ ์๊ฐํด์ผ ํ๋ค!!
